From d58b7c1f5396ec5030d1185e1d7544c24d65f5cc Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 21 Nov 2019 09:18:28 +0000 Subject: [PATCH 01/15] Work on BTC-ACCT Bump CIYAM AT dependency to v1.2 for MachineState.toCreationBytes() --- lib/org/ciyam/at/1.2/at-1.2.jar | Bin 0 -> 136317 bytes lib/org/ciyam/at/1.2/at-1.2.pom | 9 + lib/org/ciyam/at/maven-metadata-local.xml | 5 +- .../java/org/qora/crosschain/BTCACCT.java | 181 ++++++++++++++++++ src/main/java/org/qortal/crosschain/BTC.java | 68 ++++--- .../java/org/qortal/utils/BitTwiddling.java | 5 + .../java/org/qora/test/btcacct/Initiate1.java | 107 +++++++++++ .../org/qortal/test/apps/BTCACCTTests.java | 18 +- 8 files changed, 354 insertions(+), 39 deletions(-) create mode 100644 lib/org/ciyam/at/1.2/at-1.2.jar create mode 100644 lib/org/ciyam/at/1.2/at-1.2.pom create mode 100644 src/main/java/org/qora/crosschain/BTCACCT.java create mode 100644 src/test/java/org/qora/test/btcacct/Initiate1.java diff --git a/lib/org/ciyam/at/1.2/at-1.2.jar b/lib/org/ciyam/at/1.2/at-1.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..862c37c623f40a62f40c54e5a4c0fb9612c99825 GIT binary patch literal 136317 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

$L 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 + + 4.0.0 + org.ciyam + at + 1.2 + POM was created from install:install-file + diff --git a/lib/org/ciyam/at/maven-metadata-local.xml b/lib/org/ciyam/at/maven-metadata-local.xml index 61f9d592..ccf3a2dd 100644 --- a/lib/org/ciyam/at/maven-metadata-local.xml +++ b/lib/org/ciyam/at/maven-metadata-local.xml @@ -3,10 +3,11 @@ org.ciyam at - 1.0 + 1.2 1.0 + 1.2 - 20181105100741 + 20191120104937 diff --git a/src/main/java/org/qora/crosschain/BTCACCT.java b/src/main/java/org/qora/crosschain/BTCACCT.java new file mode 100644 index 00000000..297c6364 --- /dev/null +++ b/src/main/java/org/qora/crosschain/BTCACCT.java @@ -0,0 +1,181 @@ +package org.qora.crosschain; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.ciyam.at.FunctionCode; +import org.ciyam.at.MachineState; +import org.ciyam.at.OpCode; +import org.qora.utils.BitTwiddling; + +import com.google.common.hash.HashCode; +import com.google.common.primitives.Bytes; + +public class BTCACCT { + + private static final byte[] redeemScript1 = HashCode.fromString("76a820").asBytes(); // OP_DUP OP_SHA256 push(0x20 bytes) + private static final byte[] redeemScript2 = HashCode.fromString("87637576a914").asBytes(); // OP_EQUAL OP_IF OP_DROP OP_DUP OP_HASH160 push(0x14 bytes) + private static final byte[] redeemScript3 = HashCode.fromString("88ac6704").asBytes(); // OP_EQUALVERIFY OP_CHECKSIG OP_ELSE push(0x4 bytes) + private static final byte[] redeemScript4 = HashCode.fromString("b17576a914").asBytes(); // OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 push(0x14 bytes) + private static final byte[] redeemScript5 = HashCode.fromString("88ac68").asBytes(); // OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF + + /** + * Returns Bitcoin redeem script. + *

+ *

+	 * OP_DUP OP_SHA256 push(0x20) <SHA256 of secret> OP_EQUAL
+	 * OP_IF
+	 * 	OP_DROP OP_DUP OP_HASH160 push(0x14) <HASH160 of recipient pubkey>
+	 *	OP_EQUALVERIFY OP_CHECKSIG
+	 * OP_ELSE
+	 * 	push(0x04) <refund locktime> OP_CHECKLOCKTIMEVERIFY
+	 *	OP_DROP OP_DUP OP_HASH160 push(0x14) <HASH160 of sender pubkey>
+	 *	OP_EQUALVERIFY OP_CHECKSIG
+	 * OP_ENDIF
+	 * 
+ * + * @param secretHash + * @param senderPubKey + * @param recipientPubKey + * @param lockTime + * @return + */ + public static byte[] buildRedeemScript(byte[] secretHash, byte[] senderPubKey, byte[] recipientPubKey, long lockTime) { + byte[] senderPubKeyHash160 = BTC.hash160(senderPubKey); + byte[] recipientPubKeyHash160 = BTC.hash160(recipientPubKey); + + return Bytes.concat(redeemScript1, secretHash, redeemScript2, recipientPubKeyHash160, redeemScript3, BitTwiddling.toLEByteArray((int) (lockTime & 0xffffffffL)), + redeemScript4, senderPubKeyHash160, redeemScript5); + } + + public static byte[] buildCiyamAT(byte[] secretHash, byte[] destinationQortalPubKey, long refundMinutes) { + // Labels for data segment addresses + int addrCounter = 0; + final int addrHashPart1 = addrCounter++; + final int addrHashPart2 = addrCounter++; + final int addrHashPart3 = addrCounter++; + final int addrHashPart4 = addrCounter++; + final int addrAddressPart1 = addrCounter++; + final int addrAddressPart2 = addrCounter++; + final int addrAddressPart3 = addrCounter++; + final int addrAddressPart4 = addrCounter++; + final int addrRefundMinutes = addrCounter++; + final int addrRefundTimestamp = addrCounter++; + final int addrLastTimestamp = addrCounter++; + final int addrBlockTimestamp = addrCounter++; + final int addrTxType = addrCounter++; + final int addrComparator = addrCounter++; + final int addrAddressTemp1 = addrCounter++; + final int addrAddressTemp2 = addrCounter++; + final int addrAddressTemp3 = addrCounter++; + final int addrAddressTemp4 = addrCounter++; + + // Data segment + ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * 8).order(ByteOrder.LITTLE_ENDIAN); + + // Hash of secret into HashPart1-4 + dataByteBuffer.put(secretHash); + + // Destination Qortal account's public key + dataByteBuffer.put(destinationQortalPubKey); + + // Expiry in minutes + dataByteBuffer.putLong(refundMinutes); + + // Code labels + final int addrTxLoop = 0x36; + final int addrCheckTx = 0x4b; + final int addrCheckSender = 0x64; + final int addrCheckMessage = 0xab; + final int addrPayout = 0xdf; + final int addrRefund = 0x102; + final int addrEndOfCode = 0x109; + + int tempPC; + ByteBuffer codeByteBuffer = ByteBuffer.allocate(addrEndOfCode * 1).order(ByteOrder.LITTLE_ENDIAN); + + // init: + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp); + codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp); + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp) + .putInt(addrRefundTimestamp).putInt(addrRefundMinutes); + codeByteBuffer.put(OpCode.SET_PCS.value); + + // loop: + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrTxLoop - tempPC)); + codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrRefund); + + // txloop: + assert codeByteBuffer.position() == addrTxLoop : "addrTxLoop incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(addrLastTimestamp); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC)); + codeByteBuffer.put(OpCode.STP_IMD.value); + + // checkTx: + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrTxType).put((byte) (addrCheckSender - tempPC)); + codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); + + // checkSender + assert codeByteBuffer.position() == addrCheckSender : "addrCheckSender incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B1.value).putInt(addrAddressTemp1); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B2.value).putInt(addrAddressTemp2); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B3.value).putInt(addrAddressTemp3); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B4.value).putInt(addrAddressTemp4); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC)); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp2).putInt(addrAddressPart2).put((byte) (addrTxLoop - tempPC)); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp3).putInt(addrAddressPart3).put((byte) (addrTxLoop - tempPC)); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC)); + + // checkMessage: + assert codeByteBuffer.position() == addrCheckMessage : "addrCheckMessage incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrHashPart1); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrHashPart2); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrHashPart3); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrHashPart4); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(addrComparator); + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC)); + codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); + + // payout: + assert codeByteBuffer.position() == addrPayout : "addrPayout incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrAddressPart1); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrAddressPart2); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrAddressPart3); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrAddressPart4); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + // refund: + assert codeByteBuffer.position() == addrRefund : "addrRefund incorrect"; + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value); + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + // end-of-code + assert codeByteBuffer.position() == addrEndOfCode : "addrEndOfCode incorrect"; + + final short ciyamAtVersion = 2; + final short numCallStackPages = 0; + final short numUserStackPages = 0; + final long minActivationAmount = 0L; + + return MachineState.toCreationBytes(ciyamAtVersion, codeByteBuffer.array(), dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount); + } + +} diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index 17b5cc66..83a8bb07 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -14,6 +14,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.DigestOutputStream; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutionException; @@ -47,8 +48,28 @@ import org.qortal.settings.Settings; public class BTC { - private static class RollbackBlockChain extends BlockChain { + private static final MessageDigest RIPE_MD160_DIGESTER; + private static final MessageDigest SHA256_DIGESTER; + static { + try { + RIPE_MD160_DIGESTER = MessageDigest.getInstance("RIPEMD160"); + SHA256_DIGESTER = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + private static BTC instance; + + private static File directory; + private static String chainFileName; + private static String checkpointsFileName; + + private static NetworkParameters params; + private static PeerGroup peerGroup; + private static BlockStore blockStore; + + private static class RollbackBlockChain extends BlockChain { public RollbackBlockChain(NetworkParameters params, BlockStore blockStore) throws BlockStoreException { super(params, blockStore); } @@ -57,11 +78,10 @@ public class BTC { public void setChainHead(StoredBlock chainHead) throws BlockStoreException { super.setChainHead(chainHead); } - } + private static RollbackBlockChain chain; private static class UpdateableCheckpointManager extends CheckpointManager implements NewBestBlockListener { - private static final int checkpointInterval = 500; private static final String minimalTestNet3TextFile = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAAApmwX6UCEnJcYIKTa7HO3pFkqqNhAzJVBMdEuGAAAAAPSAvVCBUypCbBW/OqU0oIF7ISF84h2spOqHrFCWN9Zw6r6/T///AB0E5oOO\n"; @@ -130,23 +150,24 @@ public class BTC { } } } - } - - private static BTC instance; - private static final Object instanceLock = new Object(); - - private static File directory; - private static String chainFileName; - private static String checkpointsFileName; - - private static NetworkParameters params; - private static PeerGroup peerGroup; - private static BlockStore blockStore; - private static RollbackBlockChain chain; private static UpdateableCheckpointManager manager; private BTC() { + } + + public static synchronized BTC getInstance() { + if (instance == null) + instance = new BTC(); + + return instance; + } + + public static byte[] hash160(byte[] message) { + return RIPE_MD160_DIGESTER.digest(SHA256_DIGESTER.digest(message)); + } + + public void start() { // Start wallet if (Settings.getInstance().useBitcoinTestNet()) { params = TestNet3Params.get(); @@ -196,20 +217,11 @@ public class BTC { peerGroup.start(); } - public static synchronized BTC getInstance() { + public synchronized void shutdown() { if (instance == null) - instance = new BTC(); + return; - return instance; - } - - public void shutdown() { - synchronized (instanceLock) { - if (instance == null) - return; - - instance = null; - } + instance = null; peerGroup.stop(); diff --git a/src/main/java/org/qortal/utils/BitTwiddling.java b/src/main/java/org/qortal/utils/BitTwiddling.java index e17e6034..ada2c2f5 100644 --- a/src/main/java/org/qortal/utils/BitTwiddling.java +++ b/src/main/java/org/qortal/utils/BitTwiddling.java @@ -21,4 +21,9 @@ public class BitTwiddling { return maxValue; } + /** Convert int to little-endian byte array */ + public static byte[] toLEByteArray(int value) { + return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) }; + } + } diff --git a/src/test/java/org/qora/test/btcacct/Initiate1.java b/src/test/java/org/qora/test/btcacct/Initiate1.java new file mode 100644 index 00000000..859a7d2e --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/Initiate1.java @@ -0,0 +1,107 @@ +package org.qora.test.btcacct; + +import java.security.SecureRandom; +import java.security.Security; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.LegacyAddress; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.params.TestNet3Params; +import org.bitcoinj.script.Script.ScriptType; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.account.PublicKeyAccount; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.crypto.Crypto; +import org.qora.utils.Base58; + +import com.google.common.hash.HashCode; + +/** + * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. + * + * Initiator (wants Qora, has BTC) + * Funds BTC P2SH address + * + * Responder (has Qora, wants BTC) + * Builds Qora ACCT AT and funds it with Qora + * + * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder + * + * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT + * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) + * + * Qora ACCT AT sends its Qora to initiator + * + */ + +public class Initiate1 { + + private static final long REFUND_TIMEOUT = 600L; // seconds + + private static void usage() { + System.err.println(String.format("usage: Initiate1 ")); + System.err.println(String.format("example: Initiate1 6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb \\\n" + + "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" + + "\t123 0.00008642 \\\n" + + "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n" + + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length != 6) + usage(); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + NetworkParameters params = TestNet3Params.get(); + + String yourQortPubKey58 = args[0]; + String yourBitcoinPubKeyHex = args[1]; + + String theirBitcoinPubKeyHex = args[5]; + + try { + System.out.println("Confirm the following is correct based on the info you've given:"); + + byte[] yourQortPubKey = Base58.decode(yourQortPubKey58); + PublicKeyAccount yourQortalAccount = new PublicKeyAccount(null, yourQortPubKey); + System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress())); + + byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); + ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey); + Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); + System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress.toString())); + + byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); + ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); + Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); + System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress.toString())); + + // New/derived info + + byte[] secret = new byte[32]; + new SecureRandom().nextBytes(secret); + System.out.println("\nSecret info (DO NOT share with other party):"); + System.out.println("Secret: " + HashCode.fromBytes(secret).toString()); + + System.out.println("\nGive this info to other party:"); + + byte[] secretHash = Crypto.digest(secret); + System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); + + long lockTime = System.currentTimeMillis() + REFUND_TIMEOUT; + byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinPubKey, theirBitcoinPubKey, lockTime); + System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); + + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + System.out.println("P2SH address: " + p2shAddress.toString()); + } catch (NumberFormatException e) { + usage(); + } + } + +} diff --git a/src/test/java/org/qortal/test/apps/BTCACCTTests.java b/src/test/java/org/qortal/test/apps/BTCACCTTests.java index e33eacfa..499cf743 100644 --- a/src/test/java/org/qortal/test/apps/BTCACCTTests.java +++ b/src/test/java/org/qortal/test/apps/BTCACCTTests.java @@ -70,11 +70,11 @@ public class BTCACCTTests { // For when we want to re-run private static final byte[] prevSecret = HashCode.fromString("30a13291e350214bea5318f990b77bc11d2cb709f7c39859f248bef396961dcc").asBytes(); private static final long prevLockTime = 1539347892L; - private static final boolean usePreviousFundingTx = true; + private static final boolean usePreviousFundingTx = false; private static final boolean doRefundNotRedeem = false; - public void main(String[] args) throws NoSuchAlgorithmException, InsufficientMoneyException, InterruptedException, ExecutionException, UnknownHostException { + public static void main(String[] args) throws NoSuchAlgorithmException, InsufficientMoneyException, InterruptedException, ExecutionException, UnknownHostException { Security.insertProviderAt(new BouncyCastleProvider(), 0); byte[] secret = new byte[32]; @@ -173,7 +173,7 @@ public class BTCACCTTests { private static final byte[] redeemScript4 = HashCode.fromString("b17576a914").asBytes(); private static final byte[] redeemScript5 = HashCode.fromString("88ac68").asBytes(); - private byte[] buildRedeemScript(byte[] secret, byte[] senderPubKey, byte[] recipientPubKey, long lockTime) { + private static byte[] buildRedeemScript(byte[] secret, byte[] senderPubKey, byte[] recipientPubKey, long lockTime) { try { MessageDigest sha256Digester = MessageDigest.getInstance("SHA-256"); @@ -188,7 +188,7 @@ public class BTCACCTTests { } } - private byte[] hash160(byte[] input) { + private static byte[] hash160(byte[] input) { try { MessageDigest rmd160Digester = MessageDigest.getInstance("RIPEMD160"); MessageDigest sha256Digester = MessageDigest.getInstance("SHA-256"); @@ -199,7 +199,7 @@ public class BTCACCTTests { } } - private Transaction buildFundingTransaction(NetworkParameters params, Sha256Hash prevTxHash, long outputIndex, Coin balance, ECKey sigKey, Coin value, + private static Transaction buildFundingTransaction(NetworkParameters params, Sha256Hash prevTxHash, long outputIndex, Coin balance, ECKey sigKey, Coin value, byte[] redeemScriptHash) { Transaction fundingTransaction = new Transaction(params); @@ -218,7 +218,7 @@ public class BTCACCTTests { return fundingTransaction; } - private Transaction buildRedeemTransaction(NetworkParameters params, TransactionOutPoint fundingOutPoint, ECKey recipientKey, Coin value, byte[] secret, + private static Transaction buildRedeemTransaction(NetworkParameters params, TransactionOutPoint fundingOutPoint, ECKey recipientKey, Coin value, byte[] secret, byte[] redeemScriptBytes) { Transaction redeemTransaction = new Transaction(params); redeemTransaction.setVersion(2); @@ -255,7 +255,7 @@ public class BTCACCTTests { return redeemTransaction; } - private Transaction buildRefundTransaction(NetworkParameters params, TransactionOutPoint fundingOutPoint, ECKey senderKey, Coin value, + private static Transaction buildRefundTransaction(NetworkParameters params, TransactionOutPoint fundingOutPoint, ECKey senderKey, Coin value, byte[] redeemScriptBytes, long lockTime) { Transaction refundTransaction = new Transaction(params); refundTransaction.setVersion(2); @@ -294,7 +294,7 @@ public class BTCACCTTests { return refundTransaction; } - private void broadcastWithConfirmation(WalletAppKit kit, Transaction transaction) { + private static void broadcastWithConfirmation(WalletAppKit kit, Transaction transaction) { System.out.println("Broadcasting tx: " + transaction.getTxId().toString()); System.out.println("TX hex: " + HashCode.fromBytes(transaction.bitcoinSerialize()).toString()); @@ -320,7 +320,7 @@ public class BTCACCTTests { } /** Convert int to little-endian byte array */ - private byte[] toLEByteArray(int value) { + private static byte[] toLEByteArray(int value) { return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) }; } From 369a45f5c05f5b87ba75b28e72b92c27ae1dce5b Mon Sep 17 00:00:00 2001 From: catbref Date: Mon, 25 Nov 2019 09:54:53 +0000 Subject: [PATCH 02/15] BTC-ACCT progress Bump bitcoinj to 0.15.5 for fixes. lockTime is int (seconds since epoch), not long (ms since epoch). Improve output of Initiate1. Added (most of) Respond2. --- lib/org/ciyam/at/1.2/at-1.2.jar | Bin 136317 -> 136493 bytes lib/org/ciyam/at/maven-metadata-local.xml | 2 +- pom.xml | 6 +- .../java/org/qora/crosschain/BTCACCT.java | 14 +- .../java/org/qora/test/btcacct/Initiate1.java | 64 +++++- .../java/org/qora/test/btcacct/Respond2.java | 183 ++++++++++++++++++ 6 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 src/test/java/org/qora/test/btcacct/Respond2.java diff --git a/lib/org/ciyam/at/1.2/at-1.2.jar b/lib/org/ciyam/at/1.2/at-1.2.jar index 862c37c623f40a62f40c54e5a4c0fb9612c99825..daa4c474f16095a4d9f8c7c795c632d748bf457d 100644 GIT binary patch literal 136493 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�cGJqm@TQ>;FUP5d2iR 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�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 literal 136317 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

$L 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.0 1.2 - 20191120104937 + 20191121173210 diff --git a/pom.xml b/pom.xml index c9da9284..067c359b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 1.0.6 jar - 0.15.4 + 0.15.5 1.64 ${maven.build.timestamp} 3.6 @@ -406,13 +406,13 @@ org.ciyam at - 1.0 + 1.2 org.bitcoinj bitcoinj-core - ${bitcoin.version} + ${bitcoinj.version} diff --git a/src/main/java/org/qora/crosschain/BTCACCT.java b/src/main/java/org/qora/crosschain/BTCACCT.java index 297c6364..8cb6f062 100644 --- a/src/main/java/org/qora/crosschain/BTCACCT.java +++ b/src/main/java/org/qora/crosschain/BTCACCT.java @@ -3,6 +3,18 @@ package org.qora.crosschain; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutPoint; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptBuilder; +import org.bitcoinj.wallet.Wallet; +import org.bitcoinj.script.Script.ScriptType; import org.ciyam.at.FunctionCode; import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; @@ -40,7 +52,7 @@ public class BTCACCT { * @param lockTime * @return */ - public static byte[] buildRedeemScript(byte[] secretHash, byte[] senderPubKey, byte[] recipientPubKey, long lockTime) { + public static byte[] buildRedeemScript(byte[] secretHash, byte[] senderPubKey, byte[] recipientPubKey, int lockTime) { byte[] senderPubKeyHash160 = BTC.hash160(senderPubKey); byte[] recipientPubKeyHash160 = BTC.hash160(recipientPubKey); diff --git a/src/test/java/org/qora/test/btcacct/Initiate1.java b/src/test/java/org/qora/test/btcacct/Initiate1.java index 859a7d2e..d04f2a41 100644 --- a/src/test/java/org/qora/test/btcacct/Initiate1.java +++ b/src/test/java/org/qora/test/btcacct/Initiate1.java @@ -1,19 +1,39 @@ package org.qora.test.btcacct; +import java.io.File; +import java.math.BigDecimal; import java.security.SecureRandom; import java.security.Security; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionBroadcast; +import org.bitcoinj.kits.WalletAppKit; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.script.Script.ScriptType; +import org.bitcoinj.wallet.SendRequest; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.account.PrivateKeyAccount; import org.qora.account.PublicKeyAccount; +import org.qora.controller.Controller; import org.qora.crosschain.BTC; import org.qora.crosschain.BTCACCT; import org.qora.crypto.Crypto; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; import org.qora.utils.Base58; import com.google.common.hash.HashCode; @@ -41,8 +61,8 @@ public class Initiate1 { private static final long REFUND_TIMEOUT = 600L; // seconds private static void usage() { - System.err.println(String.format("usage: Initiate1 ")); - System.err.println(String.format("example: Initiate1 6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb \\\n" + System.err.println(String.format("usage: Initiate1 ")); + System.err.println(String.format("example: Initiate1 pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj \\\n" + "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" + "\t123 0.00008642 \\\n" + "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n" @@ -57,16 +77,29 @@ public class Initiate1 { Security.insertProviderAt(new BouncyCastleProvider(), 0); NetworkParameters params = TestNet3Params.get(); - String yourQortPubKey58 = args[0]; - String yourBitcoinPubKeyHex = args[1]; + int argIndex = 0; + String yourQortPrivKey58 = args[argIndex++]; + String yourBitcoinPubKeyHex = args[argIndex++]; - String theirBitcoinPubKeyHex = args[5]; + String rawQortAmount = args[argIndex++]; + String rawBitcoinAmount = args[argIndex++]; + + String theirQortPubKey58 = args[argIndex++]; + String theirBitcoinPubKeyHex = args[argIndex++]; try { + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryManager.setRepositoryFactory(repositoryFactory); + } catch (DataException e) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { System.out.println("Confirm the following is correct based on the info you've given:"); - byte[] yourQortPubKey = Base58.decode(yourQortPubKey58); - PublicKeyAccount yourQortalAccount = new PublicKeyAccount(null, yourQortPubKey); + byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58); + PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey); + byte[] yourQortPubKey = yourQortalAccount.getPublicKey(); System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress())); byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); @@ -74,6 +107,10 @@ public class Initiate1 { Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress.toString())); + byte[] theirQortPubKey = Base58.decode(theirQortPubKey58); + PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey); + System.out.println(String.format("Their Qortal address: %s", theirQortalAccount.getAddress())); + byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); @@ -91,7 +128,9 @@ public class Initiate1 { byte[] secretHash = Crypto.digest(secret); System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); - long lockTime = System.currentTimeMillis() + REFUND_TIMEOUT; + int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()).toString(), lockTime)); + byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinPubKey, theirBitcoinPubKey, lockTime); System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); @@ -99,8 +138,17 @@ public class Initiate1 { Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); System.out.println("P2SH address: " + p2shAddress.toString()); + + Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount); + + // Fund P2SH + System.out.println(String.format("\nYou need to fund %s with %s BTC", p2shAddress.toString(), bitcoinAmount.toPlainString())); + + System.out.println("Once this is done, responder should run Respond2 to check P2SH funding and create AT"); } catch (NumberFormatException e) { usage(); + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); } } diff --git a/src/test/java/org/qora/test/btcacct/Respond2.java b/src/test/java/org/qora/test/btcacct/Respond2.java new file mode 100644 index 00000000..89937fb4 --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/Respond2.java @@ -0,0 +1,183 @@ +package org.qora.test.btcacct; + +import java.math.BigDecimal; +import java.security.SecureRandom; +import java.security.Security; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.LegacyAddress; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.params.TestNet3Params; +import org.bitcoinj.script.Script.ScriptType; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.account.PrivateKeyAccount; +import org.qora.account.PublicKeyAccount; +import org.qora.asset.Asset; +import org.qora.controller.Controller; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.crypto.Crypto; +import org.qora.data.transaction.BaseTransactionData; +import org.qora.data.transaction.DeployAtTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.group.Group; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.transaction.DeployAtTransaction; +import org.qora.transaction.Transaction; +import org.qora.utils.Base58; + +import com.google.common.hash.HashCode; + +/** + * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. + * + * Initiator (wants Qora, has BTC) + * Funds BTC P2SH address + * + * Responder (has Qora, wants BTC) + * Builds Qora ACCT AT and funds it with Qora + * + * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder + * + * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT + * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) + * + * Qora ACCT AT sends its Qora to initiator + * + */ + +public class Respond2 { + + private static final long REFUND_TIMEOUT = 600L; // seconds + + private static void usage() { + System.err.println(String.format("usage: Respond2 ")); + System.err.println(String.format("example: Respond2 pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj \\\n" + + "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" + + "\t123 0.00008642 \\\n" + + "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n" + + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" + + "\te43f5ab106b70df2e85656de30e1862891752f81e82f5dfd03abb8465a7346f9 1574441679 2N4R2pSEzLcJgtgAbFuLvviwwEkBrmq6sx4")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length != 9) + usage(); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + NetworkParameters params = TestNet3Params.get(); + + int argIndex = 0; + String yourQortPrivKey58 = args[argIndex++]; + String yourBitcoinPubKeyHex = args[argIndex++]; + + String rawQortAmount = args[argIndex++]; + String rawBitcoinAmount = args[argIndex++]; + + String theirQortPubKey58 = args[argIndex++]; + String theirBitcoinPubKeyHex = args[argIndex++]; + + String secretHashHex = args[argIndex++]; + String rawLockTime = args[argIndex++]; + String rawP2shAddress = args[argIndex++]; + + try { + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryManager.setRepositoryFactory(repositoryFactory); + } catch (DataException e) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + System.out.println("Confirm the following is correct based on the info you've given:"); + + byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58); + PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey); + byte[] yourQortPubKey = yourQortalAccount.getPublicKey(); + System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress())); + + byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); + ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey); + Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); + System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress.toString())); + + byte[] theirQortPubKey = Base58.decode(theirQortPubKey58); + PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey); + System.out.println(String.format("Their Qortal address: %s", theirQortalAccount.getAddress())); + + byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); + ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); + Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); + System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress.toString())); + + System.out.println("Hash of secret: " + secretHashHex); + + // New/derived info + + System.out.println("\nCHECKING info from other party:"); + + int lockTime = Integer.valueOf(rawLockTime); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()).toString(), lockTime)); + + byte[] secretHash = HashCode.fromString(secretHashHex).asBytes(); + System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); + + byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, theirBitcoinPubKey, yourBitcoinPubKey, lockTime); + System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); + + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + System.out.println("P2SH address: " + p2shAddress.toString()); + + if (!p2shAddress.toString().equals(rawP2shAddress)) { + System.err.println(String.format("Derived P2SH address %s does not match given address %s", p2shAddress.toString(), rawP2shAddress)); + System.exit(2); + } + + // TODO: Check for funded P2SH + + + System.out.println("\nYour response:"); + + // If good, deploy AT + byte[] creationBytes = BTCACCT.buildCiyamAT(secretHash, theirQortPubKey, REFUND_TIMEOUT / 60); + System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + + BigDecimal qortAmount = new BigDecimal(rawQortAmount).setScale(8); + + long txTimestamp = System.currentTimeMillis(); + byte[] lastReference = yourQortalAccount.getLastReference(); + + if (lastReference == null) { + System.err.println(String.format("Qortal account %s has no last reference", yourQortalAccount.getAddress())); + System.exit(2); + } + + BigDecimal fee = BigDecimal.ZERO; + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, yourQortPubKey, fee, null); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, "QORT-BTC", "QORT-BTC ACCT", "", "", creationBytes, qortAmount, Asset.QORT); + + Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); + + fee = deployAtTransaction.calcRecommendedFee(); + deployAtTransactionData.setFee(fee); + + deployAtTransaction.sign(yourQortalAccount); + } catch (NumberFormatException e) { + usage(); + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); + } + } + +} From 5c0134c16a1ac80cde8a5208f3b262abf7631129 Mon Sep 17 00:00:00 2001 From: catbref Date: Fri, 6 Dec 2019 11:13:02 +0000 Subject: [PATCH 03/15] work in progress: btc-qort cross-chain trades Streamlined BTC class and switched to memory block store. Split BTCACCTTests into BTCACCT utility class and (so far) three stand-alone apps: Initiate1, Refund2 and Respond2 Moved some Qortal-specific CIYAM AT constants into blockchain config. Removed redundant BTCTests --- .../java/org/qora/crosschain/BTCACCT.java | 55 ++- .../java/org/qortal/at/BlockchainAPI.java | 134 ------- src/main/java/org/qortal/at/QortalATAPI.java | 31 +- .../java/org/qortal/block/BlockChain.java | 21 + src/main/java/org/qortal/crosschain/BTC.java | 358 ++++++++++-------- src/main/resources/blockchain.json | 6 + .../java/org/qora/test/btcacct/Initiate1.java | 28 +- .../java/org/qora/test/btcacct/Refund2.java | 174 +++++++++ .../java/org/qora/test/btcacct/Respond2.java | 52 ++- 9 files changed, 514 insertions(+), 345 deletions(-) delete mode 100644 src/main/java/org/qortal/at/BlockchainAPI.java create mode 100644 src/test/java/org/qora/test/btcacct/Refund2.java diff --git a/src/main/java/org/qora/crosschain/BTCACCT.java b/src/main/java/org/qora/crosschain/BTCACCT.java index 8cb6f062..f6670d44 100644 --- a/src/main/java/org/qora/crosschain/BTCACCT.java +++ b/src/main/java/org/qora/crosschain/BTCACCT.java @@ -6,14 +6,15 @@ import java.nio.ByteOrder; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; -import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionOutPoint; -import org.bitcoinj.script.Script; +import org.bitcoinj.core.Transaction.SigHash; +import org.bitcoinj.core.TransactionInput; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.ScriptBuilder; -import org.bitcoinj.wallet.Wallet; +import org.bitcoinj.script.ScriptChunk; +import org.bitcoinj.script.ScriptOpCodes; import org.bitcoinj.script.Script.ScriptType; import org.ciyam.at.FunctionCode; import org.ciyam.at.MachineState; @@ -25,6 +26,8 @@ import com.google.common.primitives.Bytes; public class BTCACCT { + public static final Coin DEFAULT_BTC_FEE = Coin.valueOf(1000L); // 0.00001000 BTC + private static final byte[] redeemScript1 = HashCode.fromString("76a820").asBytes(); // OP_DUP OP_SHA256 push(0x20 bytes) private static final byte[] redeemScript2 = HashCode.fromString("87637576a914").asBytes(); // OP_EQUAL OP_IF OP_DROP OP_DUP OP_HASH160 push(0x14 bytes) private static final byte[] redeemScript3 = HashCode.fromString("88ac6704").asBytes(); // OP_EQUALVERIFY OP_CHECKSIG OP_ELSE push(0x4 bytes) @@ -60,6 +63,48 @@ public class BTCACCT { redeemScript4, senderPubKeyHash160, redeemScript5); } + public static Transaction buildRefundTransaction(Coin refundAmount, ECKey senderKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, long lockTime) { + NetworkParameters params = BTC.getInstance().getNetworkParameters(); + + Transaction refundTransaction = new Transaction(params); + refundTransaction.setVersion(2); + + refundAmount = refundAmount.subtract(DEFAULT_BTC_FEE); + + // Output is back to P2SH funder + refundTransaction.addOutput(refundAmount, ScriptBuilder.createOutputScript(Address.fromKey(params, senderKey, ScriptType.P2PKH))); + + // Input (without scriptSig prior to signing) + TransactionInput input = new TransactionInput(params, null, new byte[0], fundingOutput.getOutPointFor()); + input.setSequenceNumber(0); // Use 0, not max-value, so lockTime can be used + refundTransaction.addInput(input); + + // Set locktime after inputs added but before input signatures are generated + refundTransaction.setLockTime(lockTime); + + // Generate transaction signature for input + final boolean anyoneCanPay = false; + TransactionSignature txSig = refundTransaction.calculateSignature(0, senderKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay); + + // Build scriptSig with... + ScriptBuilder scriptBuilder = new ScriptBuilder(); + + // transaction signature + byte[] txSigBytes = txSig.encodeToBitcoin(); + scriptBuilder.addChunk(new ScriptChunk(txSigBytes.length, txSigBytes)); + + // sender's public key + byte[] senderPubKey = senderKey.getPubKey(); + scriptBuilder.addChunk(new ScriptChunk(senderPubKey.length, senderPubKey)); + + /// redeem script + scriptBuilder.addChunk(new ScriptChunk(ScriptOpCodes.OP_PUSHDATA1, redeemScriptBytes)); + + refundTransaction.getInput(0).setScriptSig(scriptBuilder.build()); + + return refundTransaction; + } + public static byte[] buildCiyamAT(byte[] secretHash, byte[] destinationQortalPubKey, long refundMinutes) { // Labels for data segment addresses int addrCounter = 0; diff --git a/src/main/java/org/qortal/at/BlockchainAPI.java b/src/main/java/org/qortal/at/BlockchainAPI.java deleted file mode 100644 index 2e91a0f6..00000000 --- a/src/main/java/org/qortal/at/BlockchainAPI.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.qortal.at; - -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toMap; - -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; - -import org.ciyam.at.MachineState; -import org.ciyam.at.Timestamp; -import org.qortal.account.Account; -import org.qortal.block.Block; -import org.qortal.data.block.BlockData; -import org.qortal.data.transaction.ATTransactionData; -import org.qortal.data.transaction.PaymentTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.repository.BlockRepository; -import org.qortal.repository.DataException; -import org.qortal.transaction.Transaction; - -public enum BlockchainAPI { - - QORTAL(0) { - @Override - public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) { - int height = timestamp.blockHeight; - int sequence = timestamp.transactionSequence + 1; - - QortalATAPI api = (QortalATAPI) state.getAPI(); - BlockRepository blockRepository = api.repository.getBlockRepository(); - - try { - Account recipientAccount = new Account(api.repository, recipient); - - while (height <= blockRepository.getBlockchainHeight()) { - BlockData blockData = blockRepository.fromHeight(height); - - if (blockData == null) - throw new DataException("Unable to fetch block " + height + " from repository?"); - - Block block = new Block(api.repository, blockData); - - List transactions = block.getTransactions(); - - // No more transactions in this block? Try next block - if (sequence >= transactions.size()) { - ++height; - sequence = 0; - continue; - } - - Transaction transaction = transactions.get(sequence); - - // Transaction needs to be sent to specified recipient - if (transaction.getRecipientAccounts().contains(recipientAccount)) { - // Found a transaction - - api.setA1(state, new Timestamp(height, timestamp.blockchainId, sequence).longValue()); - - // Hash transaction's signature into other three A fields for future verification that it's the same transaction - byte[] hash = QortalATAPI.sha192(transaction.getTransactionData().getSignature()); - - api.setA2(state, QortalATAPI.fromBytes(hash, 0)); - api.setA3(state, QortalATAPI.fromBytes(hash, 8)); - api.setA4(state, QortalATAPI.fromBytes(hash, 16)); - return; - } - - // Transaction wasn't for us - keep going - ++sequence; - } - - // No more transactions - zero A and exit - api.zeroA(state); - } catch (DataException e) { - throw new RuntimeException("AT API unable to fetch next transaction?", e); - } - } - - @Override - public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) { - QortalATAPI api = (QortalATAPI) state.getAPI(); - TransactionData transactionData = api.fetchTransaction(state); - - switch (transactionData.getType()) { - case PAYMENT: - return ((PaymentTransactionData) transactionData).getAmount().unscaledValue().longValue(); - - case AT: - BigDecimal amount = ((ATTransactionData) transactionData).getAmount(); - - if (amount != null) - return amount.unscaledValue().longValue(); - else - return 0xffffffffffffffffL; - - default: - return 0xffffffffffffffffL; - } - } - }, - BTC(1) { - @Override - public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) { - // TODO BTC transaction support for ATv2 - } - - @Override - public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) { - // TODO BTC transaction support for ATv2 - return 0; - } - }; - - public final int value; - - private static final Map map = stream(BlockchainAPI.values()).collect(toMap(type -> type.value, type -> type)); - - BlockchainAPI(int value) { - this.value = value; - } - - public static BlockchainAPI valueOf(int value) { - return map.get(value); - } - - // Blockchain-specific API methods - - public abstract void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state); - - public abstract long getAmountFromTransactionInA(Timestamp timestamp, MachineState state); - -} diff --git a/src/main/java/org/qortal/at/QortalATAPI.java b/src/main/java/org/qortal/at/QortalATAPI.java index 8995c506..9be0e4c5 100644 --- a/src/main/java/org/qortal/at/QortalATAPI.java +++ b/src/main/java/org/qortal/at/QortalATAPI.java @@ -17,6 +17,8 @@ import org.qortal.account.Account; import org.qortal.account.GenesisAccount; import org.qortal.account.PublicKeyAccount; import org.qortal.asset.Asset; +import org.qortal.block.BlockChain; +import org.qortal.block.BlockChain.CiyamAtSettings; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; import org.qortal.data.block.BlockData; @@ -33,16 +35,11 @@ import com.google.common.primitives.Bytes; public class QortalATAPI extends API { - // Useful constants - private static final BigDecimal FEE_PER_STEP = BigDecimal.valueOf(1.0).setScale(8); // 1 QORT per "step" - private static final int MAX_STEPS_PER_ROUND = 500; - private static final int STEPS_PER_FUNCTION_CALL = 10; - private static final int MINUTES_PER_BLOCK = 10; - // Properties - Repository repository; - ATData atData; - long blockTimestamp; + private Repository repository; + private ATData atData; + private long blockTimestamp; + private final CiyamAtSettings ciyamAtSettings; /** List of generated AT transactions */ List transactions; @@ -54,36 +51,42 @@ public class QortalATAPI extends API { this.atData = atData; this.transactions = new ArrayList<>(); this.blockTimestamp = blockTimestamp; + + this.ciyamAtSettings = BlockChain.getInstance().getCiyamAtSettings(); } // Methods specific to Qortal AT processing, not inherited + public Repository getRepository() { + return this.repository; + } + public List getTransactions() { return this.transactions; } public BigDecimal calcFinalFees(MachineState state) { - return FEE_PER_STEP.multiply(BigDecimal.valueOf(state.getSteps())); + return this.ciyamAtSettings.feePerStep.multiply(BigDecimal.valueOf(state.getSteps())); } // Inherited methods from CIYAM AT API @Override public int getMaxStepsPerRound() { - return MAX_STEPS_PER_ROUND; + return this.ciyamAtSettings.maxStepsPerRound; } @Override public int getOpCodeSteps(OpCode opcode) { if (opcode.value >= OpCode.EXT_FUN.value && opcode.value <= OpCode.EXT_FUN_RET_DAT_2.value) - return STEPS_PER_FUNCTION_CALL; + return this.ciyamAtSettings.stepsPerFunctionCall; return 1; } @Override public long getFeePerStep() { - return FEE_PER_STEP.unscaledValue().longValue(); + return this.ciyamAtSettings.feePerStep.unscaledValue().longValue(); } @Override @@ -303,7 +306,7 @@ public class QortalATAPI extends API { int blockHeight = timestamp.blockHeight; // At least one block in the future - blockHeight += (minutes / MINUTES_PER_BLOCK) + 1; + blockHeight += (minutes / this.ciyamAtSettings.minutesPerBlock) + 1; return new Timestamp(blockHeight, 0).longValue(); } diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 305d9e4a..5d4c50fc 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -155,6 +155,18 @@ public class BlockChain { /** Maximum time to retain online account signatures (ms) for block validity checks, to allow for clock variance. */ private long onlineAccountSignaturesMaxLifetime; + /** Settings relating to CIYAM AT feature. */ + public static class CiyamAtSettings { + /** Fee per step/op-code executed. */ + public BigDecimal feePerStep; + /** Maximum number of steps per execution round, before AT is forced to sleep until next block. */ + public int maxStepsPerRound; + /** How many steps for calling a function. */ + public int stepsPerFunctionCall; + /** Roughly how many minutes per block. */ + public int minutesPerBlock; + } + private CiyamAtSettings ciyamAtSettings; // Constructors, etc. @@ -342,6 +354,10 @@ public class BlockChain { return this.onlineAccountSignaturesMaxLifetime; } + public CiyamAtSettings getCiyamAtSettings() { + return this.ciyamAtSettings; + } + // Convenience methods for specific blockchain feature triggers public long getMessageReleaseHeight() { @@ -437,6 +453,9 @@ public class BlockChain { if (this.founderEffectiveMintingLevel <= 0) Settings.throwValidationError("Invalid/missing \"founderEffectiveMintingLevel\" in blockchain config"); + if (this.ciyamAtSettings == null) + Settings.throwValidationError("No \"ciyamAtSettings\" entry found in blockchain config"); + if (this.featureTriggers == null) Settings.throwValidationError("No \"featureTriggers\" entry found in blockchain config"); @@ -452,6 +471,8 @@ public class BlockChain { this.unitFee = this.unitFee.setScale(8); this.minFeePerByte = this.unitFee.divide(this.maxBytesPerUnitFee, MathContext.DECIMAL32); + this.ciyamAtSettings.feePerStep.setScale(8); + // Pre-calculate cumulative blocks required for each level int cumulativeBlocks = 0; this.cumulativeBlocksByLevel = new ArrayList<>(this.blocksNeededByLevel.size() + 1); diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index 83a8bb07..d55b84f1 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -15,35 +15,34 @@ import java.nio.charset.StandardCharsets; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Date; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.bitcoinj.core.Address; import org.bitcoinj.core.BlockChain; import org.bitcoinj.core.CheckpointManager; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.PeerGroup; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.StoredBlock; -import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; -import org.bitcoinj.core.VerificationException; import org.bitcoinj.core.listeners.NewBestBlockListener; import org.bitcoinj.net.discovery.DnsDiscovery; import org.bitcoinj.params.MainNetParams; import org.bitcoinj.params.TestNet3Params; -import org.bitcoinj.script.Script; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.BlockStoreException; -import org.bitcoinj.store.SPVBlockStore; +import org.bitcoinj.store.MemoryBlockStore; import org.bitcoinj.utils.Threading; -import org.bitcoinj.wallet.KeyChainGroup; import org.bitcoinj.wallet.Wallet; import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener; +import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener; import org.qortal.settings.Settings; public class BTC { @@ -59,33 +58,23 @@ public class BTC { } } + protected static final Logger LOGGER = LogManager.getLogger(BTC.class); + private static BTC instance; - private static File directory; - private static String chainFileName; - private static String checkpointsFileName; + private final NetworkParameters params; + private final String checkpointsFileName; + private final File directory; - private static NetworkParameters params; - private static PeerGroup peerGroup; - private static BlockStore blockStore; - - private static class RollbackBlockChain extends BlockChain { - public RollbackBlockChain(NetworkParameters params, BlockStore blockStore) throws BlockStoreException { - super(params, blockStore); - } - - @Override - public void setChainHead(StoredBlock chainHead) throws BlockStoreException { - super.setChainHead(chainHead); - } - } - private static RollbackBlockChain chain; + private PeerGroup peerGroup; + private BlockStore blockStore; + private BlockChain chain; private static class UpdateableCheckpointManager extends CheckpointManager implements NewBestBlockListener { - private static final int checkpointInterval = 500; + private static final long CHECKPOINT_THRESHOLD = 7 * 24 * 60 * 60; // seconds - private static final String minimalTestNet3TextFile = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAAApmwX6UCEnJcYIKTa7HO3pFkqqNhAzJVBMdEuGAAAAAPSAvVCBUypCbBW/OqU0oIF7ISF84h2spOqHrFCWN9Zw6r6/T///AB0E5oOO\n"; - private static final String minimalMainNetTextFile = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv\n"; + private static final String MINIMAL_TESTNET3_TEXTFILE = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAAApmwX6UCEnJcYIKTa7HO3pFkqqNhAzJVBMdEuGAAAAAPSAvVCBUypCbBW/OqU0oIF7ISF84h2spOqHrFCWN9Zw6r6/T///AB0E5oOO\n"; + private static final String MINIMAL_MAINNET_TEXTFILE = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv\n"; public UpdateableCheckpointManager(NetworkParameters params) throws IOException { super(params, getMinimalTextFileStream(params)); @@ -97,20 +86,35 @@ public class BTC { private static ByteArrayInputStream getMinimalTextFileStream(NetworkParameters params) { if (params == MainNetParams.get()) - return new ByteArrayInputStream(minimalMainNetTextFile.getBytes()); + return new ByteArrayInputStream(MINIMAL_MAINNET_TEXTFILE.getBytes()); if (params == TestNet3Params.get()) - return new ByteArrayInputStream(minimalTestNet3TextFile.getBytes()); + return new ByteArrayInputStream(MINIMAL_TESTNET3_TEXTFILE.getBytes()); throw new RuntimeException("Failed to construct empty UpdateableCheckpointManageer"); } @Override - public void notifyNewBestBlock(StoredBlock block) throws VerificationException { - int height = block.getHeight(); + public void notifyNewBestBlock(StoredBlock block) { + final int height = block.getHeight(); - if (height % checkpointInterval == 0) - checkpoints.put(block.getHeader().getTimeSeconds(), block); + if (height % this.params.getInterval() != 0) + return; + + final long blockTimestamp = block.getHeader().getTimeSeconds(); + final long now = System.currentTimeMillis() / 1000L; + if (blockTimestamp > now - CHECKPOINT_THRESHOLD) + return; // Too recent + + LOGGER.trace(() -> String.format("Checkpointing at block %d dated %s", height, LocalDateTime.ofInstant(Instant.ofEpochSecond(blockTimestamp), ZoneOffset.UTC))); + checkpoints.put(blockTimestamp, block); + + try { + this.saveAsText(new File(BTC.getInstance().getDirectory(), BTC.getInstance().getCheckpointsFileName())); + } catch (FileNotFoundException e) { + // Save failed - log it but it's not critical + LOGGER.warn("Failed to save updated BTC checkpoints: " + e.getMessage()); + } } public void saveAsText(File textFile) throws FileNotFoundException { @@ -118,7 +122,9 @@ public class BTC { writer.println("TXT CHECKPOINTS 1"); writer.println("0"); // Number of signatures to read. Do this later. writer.println(checkpoints.size()); + ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); + for (StoredBlock block : checkpoints.values()) { block.serializeCompact(buffer); writer.println(CheckpointManager.BASE64.encode(buffer.array())); @@ -140,7 +146,9 @@ public class BTC { dataOutputStream.writeInt(0); // Number of signatures to read. Do this later. digestOutputStream.on(true); dataOutputStream.writeInt(checkpoints.size()); + ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); + for (StoredBlock block : checkpoints.values()) { block.serializeCompact(buffer); dataOutputStream.write(buffer.array()); @@ -151,9 +159,37 @@ public class BTC { } } } - private static UpdateableCheckpointManager manager; + private UpdateableCheckpointManager manager; + + // Constructors and instance private BTC() { + if (Settings.getInstance().useBitcoinTestNet()) { + this.params = TestNet3Params.get(); + this.checkpointsFileName = "checkpoints-testnet.txt"; + } else { + this.params = MainNetParams.get(); + this.checkpointsFileName = "checkpoints.txt"; + } + + this.directory = new File("Qortal-BTC"); + + if (!this.directory.exists()) + this.directory.mkdirs(); + + File checkpointsFile = new File(this.directory, this.checkpointsFileName); + try (InputStream checkpointsStream = new FileInputStream(checkpointsFile)) { + this.manager = new UpdateableCheckpointManager(this.params, checkpointsStream); + } catch (FileNotFoundException e) { + // Construct with no checkpoints then + try { + this.manager = new UpdateableCheckpointManager(this.params); + } catch (IOException e2) { + throw new RuntimeException("Failed to create new BTC checkpoints", e2); + } + } catch (IOException e) { + throw new RuntimeException("Failed to load BTC checkpoints", e); + } } public static synchronized BTC getInstance() { @@ -163,160 +199,152 @@ public class BTC { return instance; } + // Getters & setters + + /* package */ File getDirectory() { + return this.directory; + } + + /* package */ String getCheckpointsFileName() { + return this.checkpointsFileName; + } + + /* package */ NetworkParameters getNetworkParameters() { + return this.params; + } + + // Static utility methods + public static byte[] hash160(byte[] message) { return RIPE_MD160_DIGESTER.digest(SHA256_DIGESTER.digest(message)); } - public void start() { - // Start wallet - if (Settings.getInstance().useBitcoinTestNet()) { - params = TestNet3Params.get(); - chainFileName = "bitcoinj-testnet.spvchain"; - checkpointsFileName = "checkpoints-testnet.txt"; - } else { - params = MainNetParams.get(); - chainFileName = "bitcoinj.spvchain"; - checkpointsFileName = "checkpoints.txt"; - } + // Start-up & shutdown + private void start(long startTime) throws BlockStoreException { + StoredBlock checkpoint = this.manager.getCheckpointBefore(startTime - 1); - directory = new File("Qortal-BTC"); - if (!directory.exists()) - directory.mkdirs(); + this.blockStore = new MemoryBlockStore(params); + this.blockStore.put(checkpoint); + this.blockStore.setChainHead(checkpoint); - File chainFile = new File(directory, chainFileName); + this.chain = new BlockChain(this.params, this.blockStore); - try { - blockStore = new SPVBlockStore(params, chainFile); - } catch (BlockStoreException e) { - throw new RuntimeException("Failed to open/create BTC SPVBlockStore", e); - } - - File checkpointsFile = new File(directory, checkpointsFileName); - try (InputStream checkpointsStream = new FileInputStream(checkpointsFile)) { - manager = new UpdateableCheckpointManager(params, checkpointsStream); - } catch (FileNotFoundException e) { - // Construct with no checkpoints then - try { - manager = new UpdateableCheckpointManager(params); - } catch (IOException e2) { - throw new RuntimeException("Failed to create new BTC checkpoints", e2); - } - } catch (IOException e) { - throw new RuntimeException("Failed to load BTC checkpoints", e); - } - - try { - chain = new RollbackBlockChain(params, blockStore); - } catch (BlockStoreException e) { - throw new RuntimeException("Failed to construct BTC blockchain", e); - } - - peerGroup = new PeerGroup(params, chain); - peerGroup.setUserAgent("qortal", "1.0"); - peerGroup.addPeerDiscovery(new DnsDiscovery(params)); - peerGroup.start(); + this.peerGroup = new PeerGroup(this.params, this.chain); + this.peerGroup.setUserAgent("qortal", "1.0"); + this.peerGroup.addPeerDiscovery(new DnsDiscovery(this.params)); + this.peerGroup.start(); } - public synchronized void shutdown() { - if (instance == null) - return; - - instance = null; - - peerGroup.stop(); - - try { - blockStore.close(); - } catch (BlockStoreException e) { - // What can we do? - } + private void stop() { + this.peerGroup.stop(); } + // Utility methods + protected Wallet createEmptyWallet() { - ECKey dummyKey = new ECKey(); - - KeyChainGroup keyChainGroup = KeyChainGroup.createBasic(params); - keyChainGroup.importKeys(dummyKey); - - Wallet wallet = new Wallet(params, keyChainGroup); - - wallet.removeKey(dummyKey); - - return wallet; + return Wallet.createBasic(this.params); } - public void watch(String base58Address, long startTime) throws InterruptedException, ExecutionException, TimeoutException, BlockStoreException { - Wallet wallet = createEmptyWallet(); + private void replayChain(long startTime, Wallet wallet) throws BlockStoreException { + this.start(startTime); - WalletCoinsReceivedEventListener coinsReceivedListener = new WalletCoinsReceivedEventListener() { - @Override - public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { - System.out.println("Coins received via transaction " + tx.getTxId().toString()); - } + final WalletCoinsReceivedEventListener coinsReceivedListener = (someWallet, tx, prevBalance, newBalance) -> { + LOGGER.debug(String.format("Wallet-related transaction %s", tx.getTxId())); }; - wallet.addCoinsReceivedEventListener(coinsReceivedListener); - Address address = Address.fromString(params, base58Address); - wallet.addWatchedAddress(address, startTime); + final WalletCoinsSentEventListener coinsSentListener = (someWallet, tx, prevBalance, newBalance) -> { + LOGGER.debug(String.format("Wallet-related transaction %s", tx.getTxId())); + }; - StoredBlock checkpoint = manager.getCheckpointBefore(startTime); - blockStore.put(checkpoint); - blockStore.setChainHead(checkpoint); - chain.setChainHead(checkpoint); + if (wallet != null) { + wallet.addCoinsReceivedEventListener(coinsReceivedListener); + wallet.addCoinsSentEventListener(coinsSentListener); - chain.addWallet(wallet); - peerGroup.addWallet(wallet); - peerGroup.setFastCatchupTimeSecs(startTime); - - peerGroup.addBlocksDownloadedEventListener((peer, block, filteredBlock, blocksLeft) -> { - if (blocksLeft % 1000 == 0) - System.out.println("Blocks left: " + blocksLeft); - }); - - System.out.println("Starting download..."); - peerGroup.downloadBlockChain(); - - List outputs = wallet.getWatchedOutputs(true); - - peerGroup.removeWallet(wallet); - chain.removeWallet(wallet); - - for (TransactionOutput output : outputs) - System.out.println(output.toString()); - } - - public void watch(Script script) { - // wallet.addWatchedScripts(scripts); - } - - public void updateCheckpoints() { - final long now = new Date().getTime() / 1000 - 86400; - - try { - StoredBlock checkpoint = manager.getCheckpointBefore(now); - blockStore.put(checkpoint); - blockStore.setChainHead(checkpoint); - chain.setChainHead(checkpoint); - } catch (BlockStoreException e) { - throw new RuntimeException("Failed to update BTC checkpoints", e); + // Link wallet to chain and peerGroup + this.chain.addWallet(wallet); + this.peerGroup.addWallet(wallet); } - peerGroup.setFastCatchupTimeSecs(now); + try { + // Sync blockchain using peerGroup, skipping as much as we can before startTime + this.peerGroup.setFastCatchupTimeSecs(startTime); + this.chain.addNewBestBlockListener(Threading.SAME_THREAD, this.manager); + this.peerGroup.downloadBlockChain(); + } finally { + // Clean up + if (wallet != null) { + wallet.removeCoinsReceivedEventListener(coinsReceivedListener); + wallet.removeCoinsSentEventListener(coinsSentListener); - chain.addNewBestBlockListener(Threading.SAME_THREAD, manager); + this.peerGroup.removeWallet(wallet); + this.chain.removeWallet(wallet); + } - peerGroup.addBlocksDownloadedEventListener((peer, block, filteredBlock, blocksLeft) -> { - if (blocksLeft % 1000 == 0) - System.out.println("Blocks left: " + blocksLeft); - }); + this.stop(); + } + } - System.out.println("Starting download..."); - peerGroup.downloadBlockChain(); + private void replayChain(long startTime) throws BlockStoreException { + this.replayChain(startTime, null); + } + + // Actual useful methods for use by other classes + + /** Returns median timestamp from latest 11 blocks, in seconds. */ + public Long getMedianBlockTime() { + // 11 blocks, at roughly 10 minutes per block, means we should go back at least 110 minutes + // but some blocks have been way longer than 10 minutes, so be massively pessimistic + long startTime = (System.currentTimeMillis() / 1000L) - 11 * 60 * 60; // 11 hours before now, in seconds try { - manager.saveAsText(new File(directory, checkpointsFileName)); - } catch (FileNotFoundException e) { - throw new RuntimeException("Failed to save updated BTC checkpoints", e); + replayChain(startTime); + + List latestBlocks = new ArrayList<>(11); + StoredBlock block = this.blockStore.getChainHead(); + for (int i = 0; i < 11; ++i) { + latestBlocks.add(block); + block = block.getPrev(this.blockStore); + } + + latestBlocks.sort((a, b) -> Long.compare(b.getHeader().getTimeSeconds(), a.getHeader().getTimeSeconds())); + + return latestBlocks.get(5).getHeader().getTimeSeconds(); + } catch (BlockStoreException e) { + LOGGER.error(String.format("BTC blockstore issue: %s", e.getMessage())); + return null; + } + } + + public Coin getBalance(String base58Address, long startTime) { + // Create new wallet containing only the address we're interested in, ignoring anything prior to startTime + Wallet wallet = createEmptyWallet(); + Address address = Address.fromString(this.params, base58Address); + wallet.addWatchedAddress(address, startTime); + + try { + replayChain(startTime, wallet); + + // Now that blockchain is up-to-date, return current balance + return wallet.getBalance(); + } catch (BlockStoreException e) { + LOGGER.error(String.format("BTC blockstore issue: %s", e.getMessage())); + return null; + } + } + + public List getUnspentOutputs(String base58Address, long startTime) { + Wallet wallet = createEmptyWallet(); + Address address = Address.fromString(this.params, base58Address); + wallet.addWatchedAddress(address, startTime); + + try { + replayChain(startTime, wallet); + + // Now that blockchain is up-to-date, return outputs + return wallet.getWatchedOutputs(true); + } catch (BlockStoreException e) { + LOGGER.error(String.format("BTC blockstore issue: %s", e.getMessage())); + return null; } } diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 696701fd..1869438c 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -41,6 +41,12 @@ "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, "featureTriggers": { "messageHeight": 0, "atHeight": 0, diff --git a/src/test/java/org/qora/test/btcacct/Initiate1.java b/src/test/java/org/qora/test/btcacct/Initiate1.java index d04f2a41..60001176 100644 --- a/src/test/java/org/qora/test/btcacct/Initiate1.java +++ b/src/test/java/org/qora/test/btcacct/Initiate1.java @@ -1,30 +1,23 @@ package org.qora.test.btcacct; -import java.io.File; import java.math.BigDecimal; import java.security.SecureRandom; import java.security.Security; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; -import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionBroadcast; -import org.bitcoinj.kits.WalletAppKit; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.script.Script.ScriptType; -import org.bitcoinj.wallet.SendRequest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qora.account.PrivateKeyAccount; import org.qora.account.PublicKeyAccount; +import org.qora.asset.Asset; import org.qora.controller.Controller; import org.qora.crosschain.BTC; import org.qora.crosschain.BTCACCT; @@ -99,13 +92,12 @@ public class Initiate1 { byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58); PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey); - byte[] yourQortPubKey = yourQortalAccount.getPublicKey(); System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress())); byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey); Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress.toString())); + System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); byte[] theirQortPubKey = Base58.decode(theirQortPubKey58); PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey); @@ -114,7 +106,15 @@ public class Initiate1 { byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress.toString())); + System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); + + // Some checks + BigDecimal qortAmount = new BigDecimal(rawQortAmount).setScale(8); + BigDecimal yourQortBalance = yourQortalAccount.getConfirmedBalance(Asset.QORT); + if (yourQortBalance.compareTo(qortAmount) <= 0) { + System.err.println(String.format("Your QORT balance %s is less than required %s", yourQortBalance.toPlainString(), qortAmount.toPlainString())); + System.exit(2); + } // New/derived info @@ -129,7 +129,7 @@ public class Initiate1 { System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); - System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()).toString(), lockTime)); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinPubKey, theirBitcoinPubKey, lockTime); System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); @@ -139,10 +139,10 @@ public class Initiate1 { Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); System.out.println("P2SH address: " + p2shAddress.toString()); - Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount); + Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount).add(BTCACCT.DEFAULT_BTC_FEE); // Fund P2SH - System.out.println(String.format("\nYou need to fund %s with %s BTC", p2shAddress.toString(), bitcoinAmount.toPlainString())); + System.out.println(String.format("\nYou need to fund %s with %s BTC (includes redeem/refund fee)", p2shAddress.toString(), bitcoinAmount.toPlainString())); System.out.println("Once this is done, responder should run Respond2 to check P2SH funding and create AT"); } catch (NumberFormatException e) { diff --git a/src/test/java/org/qora/test/btcacct/Refund2.java b/src/test/java/org/qora/test/btcacct/Refund2.java new file mode 100644 index 00000000..05801775 --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/Refund2.java @@ -0,0 +1,174 @@ +package org.qora.test.btcacct; + +import java.security.Security; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +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.params.TestNet3Params; +import org.bitcoinj.script.Script.ScriptType; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.controller.Controller; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.settings.Settings; + +import com.google.common.hash.HashCode; + +/** + * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. + * + * Initiator (wants Qora, has BTC) + * Funds BTC P2SH address + * + * Responder (has Qora, wants BTC) + * Builds Qora ACCT AT and funds it with Qora + * + * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder + * + * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT + * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) + * + * Qora ACCT AT sends its Qora to initiator + * + */ + +public class Refund2 { + + static { + // This must go before any calls to LogManager/Logger + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + } + + private static final long REFUND_TIMEOUT = 600L; // seconds + + private static void usage() { + System.err.println(String.format("usage: Refund2 ")); + System.err.println(String.format("example: Refund2 027fb5828c5e201eaf6de4cd3b0b340d16a191ef848cd691f35ef8f727358c9c \\\n" + + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" + + "\tb837056cdc5d805e4db1f830a58158e1131ac96ea71de4c6f9d7854985e153e2 1575021641 2MvGdGUgAfc7qTHaZJwWmZ26Fg6Hjif8gNy")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length != 5) + usage(); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + + Settings.fileInstance("settings-test.json"); + + NetworkParameters params = TestNet3Params.get(); + + int argIndex = 0; + String yourBitcoinPrivKeyHex = args[argIndex++]; + String theirBitcoinPubKeyHex = args[argIndex++]; + + String secretHashHex = args[argIndex++]; + String rawLockTime = args[argIndex++]; + String rawP2shAddress = args[argIndex++]; + + try { + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryManager.setRepositoryFactory(repositoryFactory); + } catch (DataException e) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + System.out.println("Confirm the following is correct based on the info you've given:"); + + byte[] yourBitcoinPrivKey = HashCode.fromString(yourBitcoinPrivKeyHex).asBytes(); + ECKey yourBitcoinKey = ECKey.fromPrivate(yourBitcoinPrivKey); + Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); + System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); + + byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); + ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); + Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); + System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); + + // New/derived info + + int lockTime = Integer.valueOf(rawLockTime); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); + + byte[] secretHash = HashCode.fromString(secretHashHex).asBytes(); + System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); + + byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinKey.getPubKey(), theirBitcoinPubKey, lockTime); + System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); + + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + System.out.println(String.format("P2SH address: %s", p2shAddress)); + + if (!p2shAddress.toString().equals(rawP2shAddress)) { + System.err.println(String.format("Derived P2SH address %s does not match given address %s", p2shAddress, rawP2shAddress)); + System.exit(2); + } + + // Some checks + long medianBlockTime = BTC.getInstance().getMedianBlockTime(); + System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneId.systemDefault()))); + + long now = System.currentTimeMillis(); + + if (now < medianBlockTime * 1000L) { + System.err.println(String.format("Too soon (%s) to refund based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneId.systemDefault()))); + System.exit(2); + } + + if (now < lockTime * 1000L) { + System.err.println(String.format("Too soon (%s) to refund based on lockTime %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()), LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()))); + System.exit(2); + } + + Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + if (p2shBalance == null) { + System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); + System.exit(2); + } + System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); + + // Grab all P2SH funding transactions (just in case there are more than one) + List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); + + if (fundingOutputs.isEmpty()) { + System.err.println(String.format("Can't refund spent/unfunded P2SH")); + System.exit(2); + } + + if (fundingOutputs.size() != 1) { + System.err.println(String.format("Expecting only one unspent output for P2SH")); + System.exit(2); + } + + Transaction refundTransaction = BTCACCT.buildRefundTransaction(p2shBalance, yourBitcoinKey, fundingOutputs.get(0), redeemScriptBytes, lockTime); + + byte[] refundBytes = refundTransaction.bitcoinSerialize(); + + System.out.println(String.format("\nLoad this transaction into your wallet, sign and broadcast:\n%s\n", HashCode.fromBytes(refundBytes).toString())); + } catch (NumberFormatException e) { + usage(); + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); + } + } + +} diff --git a/src/test/java/org/qora/test/btcacct/Respond2.java b/src/test/java/org/qora/test/btcacct/Respond2.java index 89937fb4..9dac26ed 100644 --- a/src/test/java/org/qora/test/btcacct/Respond2.java +++ b/src/test/java/org/qora/test/btcacct/Respond2.java @@ -1,13 +1,13 @@ package org.qora.test.btcacct; import java.math.BigDecimal; -import java.security.SecureRandom; import java.security.Security; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; 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; @@ -20,7 +20,6 @@ import org.qora.asset.Asset; import org.qora.controller.Controller; import org.qora.crosschain.BTC; import org.qora.crosschain.BTCACCT; -import org.qora.crypto.Crypto; import org.qora.data.transaction.BaseTransactionData; import org.qora.data.transaction.DeployAtTransactionData; import org.qora.data.transaction.TransactionData; @@ -30,8 +29,11 @@ import org.qora.repository.Repository; import org.qora.repository.RepositoryFactory; import org.qora.repository.RepositoryManager; import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.settings.Settings; import org.qora.transaction.DeployAtTransaction; import org.qora.transaction.Transaction; +import org.qora.transform.TransformationException; +import org.qora.transform.transaction.TransactionTransformer; import org.qora.utils.Base58; import com.google.common.hash.HashCode; @@ -60,12 +62,12 @@ public class Respond2 { private static void usage() { System.err.println(String.format("usage: Respond2 ")); - System.err.println(String.format("example: Respond2 pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj \\\n" - + "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" - + "\t123 0.00008642 \\\n" - + "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n" + System.err.println(String.format("example: Respond2 3jjoToDaDpsdUHqaouLGypFeewNVKvtkmdM38i54WVra \\\n" + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" - + "\te43f5ab106b70df2e85656de30e1862891752f81e82f5dfd03abb8465a7346f9 1574441679 2N4R2pSEzLcJgtgAbFuLvviwwEkBrmq6sx4")); + + "\t123 0.00008642 \\\n" + + "\t6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb \\\n" + + "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" + + "\tb837056cdc5d805e4db1f830a58158e1131ac96ea71de4c6f9d7854985e153e2 1575021641 2MvGdGUgAfc7qTHaZJwWmZ26Fg6Hjif8gNy")); System.exit(1); } @@ -74,6 +76,9 @@ public class Respond2 { usage(); Security.insertProviderAt(new BouncyCastleProvider(), 0); + + Settings.fileInstance("settings-test.json"); + NetworkParameters params = TestNet3Params.get(); int argIndex = 0; @@ -108,7 +113,7 @@ public class Respond2 { byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey); Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress.toString())); + System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); byte[] theirQortPubKey = Base58.decode(theirQortPubKey58); PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey); @@ -117,7 +122,7 @@ public class Respond2 { byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress.toString())); + System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); System.out.println("Hash of secret: " + secretHashHex); @@ -126,7 +131,7 @@ public class Respond2 { System.out.println("\nCHECKING info from other party:"); int lockTime = Integer.valueOf(rawLockTime); - System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()).toString(), lockTime)); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); byte[] secretHash = HashCode.fromString(secretHashHex).asBytes(); System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); @@ -137,15 +142,26 @@ public class Respond2 { byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); - System.out.println("P2SH address: " + p2shAddress.toString()); + System.out.println(String.format("P2SH address: %s", p2shAddress)); if (!p2shAddress.toString().equals(rawP2shAddress)) { - System.err.println(String.format("Derived P2SH address %s does not match given address %s", p2shAddress.toString(), rawP2shAddress)); + System.err.println(String.format("Derived P2SH address %s does not match given address %s", p2shAddress, rawP2shAddress)); System.exit(2); } - // TODO: Check for funded P2SH + // Check for funded P2SH + Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount).add(BTCACCT.DEFAULT_BTC_FEE); + Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + if (p2shBalance == null) { + System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); + System.exit(2); + } + if (p2shBalance.isLessThan(bitcoinAmount)) { + System.err.println(String.format("P2SH address %s has lower balance than expected %s BTC", p2shAddress, p2shBalance.toPlainString())); + System.exit(2); + } + System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); System.out.println("\nYour response:"); @@ -173,6 +189,16 @@ public class Respond2 { deployAtTransactionData.setFee(fee); deployAtTransaction.sign(yourQortalAccount); + + 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\n", Base58.encode(signedBytes))); } catch (NumberFormatException e) { usage(); } catch (DataException e) { From 2c4bad64552f2a6767a421c566128208e20dcae4 Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 10 Dec 2019 15:35:57 +0000 Subject: [PATCH 04/15] Interim commit of BTC-QORT cross-chain trade, with partial conversion from secret+hash to using "trade key" --- .../java/org/qora/crosschain/BTCACCT.java | 96 ++++++--- src/main/java/org/qortal/crosschain/BTC.java | 28 ++- .../org/qora/test/apps/BuildCheckpoints.java | 69 ++++++ .../java/org/qora/test/btcacct/Initiate.java | 144 +++++++++++++ .../java/org/qora/test/btcacct/Initiate1.java | 155 -------------- .../java/org/qora/test/btcacct/Redeem.java | 199 ++++++++++++++++++ .../btcacct/{Refund2.java => Refund.java} | 113 ++++++---- .../java/org/qora/test/btcacct/Respond2.java | 4 +- 8 files changed, 572 insertions(+), 236 deletions(-) create mode 100644 src/test/java/org/qora/test/apps/BuildCheckpoints.java create mode 100644 src/test/java/org/qora/test/btcacct/Initiate.java delete mode 100644 src/test/java/org/qora/test/btcacct/Initiate1.java create mode 100644 src/test/java/org/qora/test/btcacct/Redeem.java rename src/test/java/org/qora/test/btcacct/{Refund2.java => Refund.java} (57%) diff --git a/src/main/java/org/qora/crosschain/BTCACCT.java b/src/main/java/org/qora/crosschain/BTCACCT.java index f6670d44..76a68ee6 100644 --- a/src/main/java/org/qora/crosschain/BTCACCT.java +++ b/src/main/java/org/qora/crosschain/BTCACCT.java @@ -3,7 +3,6 @@ package org.qora.crosschain; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; @@ -15,7 +14,6 @@ import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptChunk; import org.bitcoinj.script.ScriptOpCodes; -import org.bitcoinj.script.Script.ScriptType; import org.ciyam.at.FunctionCode; import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; @@ -28,54 +26,48 @@ public class BTCACCT { public static final Coin DEFAULT_BTC_FEE = Coin.valueOf(1000L); // 0.00001000 BTC - private static final byte[] redeemScript1 = HashCode.fromString("76a820").asBytes(); // OP_DUP OP_SHA256 push(0x20 bytes) - private static final byte[] redeemScript2 = HashCode.fromString("87637576a914").asBytes(); // OP_EQUAL OP_IF OP_DROP OP_DUP OP_HASH160 push(0x14 bytes) - private static final byte[] redeemScript3 = HashCode.fromString("88ac6704").asBytes(); // OP_EQUALVERIFY OP_CHECKSIG OP_ELSE push(0x4 bytes) - private static final byte[] redeemScript4 = HashCode.fromString("b17576a914").asBytes(); // OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 push(0x14 bytes) - private static final byte[] redeemScript5 = HashCode.fromString("88ac68").asBytes(); // OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF + private static final byte[] redeemScript1 = HashCode.fromString("76a914").asBytes(); // OP_DUP OP_HASH160 push(0x14 bytes) + private static final byte[] redeemScript2 = HashCode.fromString("88ada97614").asBytes(); // OP_EQUALVERIFY OP_CHECKSIGVERIFY OP_HASH160 OP_DUP push(0x14 bytes) + private static final byte[] redeemScript3 = HashCode.fromString("87637504").asBytes(); // OP_EQUAL OP_IF OP_DROP push(0x4 bytes) + private static final byte[] redeemScript4 = HashCode.fromString("b16714").asBytes(); // OP_CHECKLOCKTIMEVERIFY OP_ELSE push(0x14 bytes) + private static final byte[] redeemScript5 = HashCode.fromString("8768").asBytes(); // OP_EQUAL OP_ENDIF /** * Returns Bitcoin redeem script. *

*

-	 * OP_DUP OP_SHA256 push(0x20) <SHA256 of secret> OP_EQUAL
+	 * OP_DUP OP_HASH160 push(0x14) <trade pubkeyhash> OP_EQUALVERIFY OP_CHECKSIGVERIFY
+	 * OP_HASH160 OP_DUP push(0x14) <sender/refund P2PKH> OP_EQUAL
 	 * OP_IF
-	 * 	OP_DROP OP_DUP OP_HASH160 push(0x14) <HASH160 of recipient pubkey>
-	 *	OP_EQUALVERIFY OP_CHECKSIG
+	 * 	OP_DROP push(0x04 bytes) <refund locktime> OP_CHECKLOCKTIMEVERIFY
 	 * OP_ELSE
-	 * 	push(0x04) <refund locktime> OP_CHECKLOCKTIMEVERIFY
-	 *	OP_DROP OP_DUP OP_HASH160 push(0x14) <HASH160 of sender pubkey>
-	 *	OP_EQUALVERIFY OP_CHECKSIG
+	 *	push(0x14) <redeemer P2PKH> OP_EQUAL
 	 * OP_ENDIF
 	 * 
* - * @param secretHash + * @param tradePubKeyHash * @param senderPubKey * @param recipientPubKey * @param lockTime * @return */ - public static byte[] buildRedeemScript(byte[] secretHash, byte[] senderPubKey, byte[] recipientPubKey, int lockTime) { - byte[] senderPubKeyHash160 = BTC.hash160(senderPubKey); - byte[] recipientPubKeyHash160 = BTC.hash160(recipientPubKey); - - return Bytes.concat(redeemScript1, secretHash, redeemScript2, recipientPubKeyHash160, redeemScript3, BitTwiddling.toLEByteArray((int) (lockTime & 0xffffffffL)), - redeemScript4, senderPubKeyHash160, redeemScript5); + public static byte[] buildScript(byte[] tradePubKeyHash, byte[] senderPubKeyHash, byte[] recipientPubKeyHash, int lockTime) { + return Bytes.concat(redeemScript1, tradePubKeyHash, redeemScript2, senderPubKeyHash, redeemScript3, BitTwiddling.toLEByteArray((int) (lockTime & 0xffffffffL)), + redeemScript4, recipientPubKeyHash, redeemScript5); } - public static Transaction buildRefundTransaction(Coin refundAmount, ECKey senderKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, long lockTime) { + public static Transaction buildRefundTransaction(Coin refundAmount, ECKey tradeKey, byte[] senderPubKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, long lockTime) { NetworkParameters params = BTC.getInstance().getNetworkParameters(); Transaction refundTransaction = new Transaction(params); refundTransaction.setVersion(2); - refundAmount = refundAmount.subtract(DEFAULT_BTC_FEE); - // Output is back to P2SH funder - refundTransaction.addOutput(refundAmount, ScriptBuilder.createOutputScript(Address.fromKey(params, senderKey, ScriptType.P2PKH))); + ECKey senderKey = ECKey.fromPublicOnly(senderPubKey); + refundTransaction.addOutput(refundAmount, ScriptBuilder.createP2PKHOutputScript(senderKey)); // Input (without scriptSig prior to signing) - TransactionInput input = new TransactionInput(params, null, new byte[0], fundingOutput.getOutPointFor()); + TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor()); input.setSequenceNumber(0); // Use 0, not max-value, so lockTime can be used refundTransaction.addInput(input); @@ -84,27 +76,73 @@ public class BTCACCT { // Generate transaction signature for input final boolean anyoneCanPay = false; - TransactionSignature txSig = refundTransaction.calculateSignature(0, senderKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay); + TransactionSignature txSig = refundTransaction.calculateSignature(0, tradeKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay); // Build scriptSig with... ScriptBuilder scriptBuilder = new ScriptBuilder(); + // sender/refund pubkey + scriptBuilder.addChunk(new ScriptChunk(senderPubKey.length, senderPubKey)); + // transaction signature byte[] txSigBytes = txSig.encodeToBitcoin(); scriptBuilder.addChunk(new ScriptChunk(txSigBytes.length, txSigBytes)); - // sender's public key - byte[] senderPubKey = senderKey.getPubKey(); - scriptBuilder.addChunk(new ScriptChunk(senderPubKey.length, senderPubKey)); + // trade public key + byte[] tradePubKey = tradeKey.getPubKey(); + scriptBuilder.addChunk(new ScriptChunk(tradePubKey.length, tradePubKey)); /// redeem script scriptBuilder.addChunk(new ScriptChunk(ScriptOpCodes.OP_PUSHDATA1, redeemScriptBytes)); + // Set input scriptSig refundTransaction.getInput(0).setScriptSig(scriptBuilder.build()); return refundTransaction; } + public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey tradeKey, byte[] recipientPubKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes) { + NetworkParameters params = BTC.getInstance().getNetworkParameters(); + + Transaction redeemTransaction = new Transaction(params); + redeemTransaction.setVersion(2); + + // Output to redeem recipient + ECKey senderKey = ECKey.fromPublicOnly(recipientPubKey); + redeemTransaction.addOutput(redeemAmount, ScriptBuilder.createP2PKHOutputScript(senderKey)); + + // Input (without scriptSig prior to signing) + TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor()); + input.setSequenceNumber(0); // Use 0, not max-value, so lockTime can be used + redeemTransaction.addInput(input); + + // Generate transaction signature for input + final boolean anyoneCanPay = false; + TransactionSignature txSig = redeemTransaction.calculateSignature(0, tradeKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay); + + // Build scriptSig with... + ScriptBuilder scriptBuilder = new ScriptBuilder(); + + // recipient pubkey + scriptBuilder.addChunk(new ScriptChunk(recipientPubKey.length, recipientPubKey)); + + // transaction signature + byte[] txSigBytes = txSig.encodeToBitcoin(); + scriptBuilder.addChunk(new ScriptChunk(txSigBytes.length, txSigBytes)); + + // trade public key + byte[] tradePubKey = tradeKey.getPubKey(); + scriptBuilder.addChunk(new ScriptChunk(tradePubKey.length, tradePubKey)); + + /// redeem script + scriptBuilder.addChunk(new ScriptChunk(ScriptOpCodes.OP_PUSHDATA1, redeemScriptBytes)); + + // Set input scriptSig + redeemTransaction.getInput(0).setScriptSig(scriptBuilder.build()); + + return redeemTransaction; + } + public static byte[] buildCiyamAT(byte[] secretHash, byte[] destinationQortalPubKey, long refundMinutes) { // Labels for data segment addresses int addrCounter = 0; diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index d55b84f1..7eaee53b 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.DigestOutputStream; @@ -28,6 +29,7 @@ import org.bitcoinj.core.BlockChain; import org.bitcoinj.core.CheckpointManager; import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.PeerAddress; import org.bitcoinj.core.PeerGroup; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.StoredBlock; @@ -35,6 +37,7 @@ import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.core.listeners.NewBestBlockListener; import org.bitcoinj.net.discovery.DnsDiscovery; import org.bitcoinj.params.MainNetParams; +import org.bitcoinj.params.RegTestParams; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.BlockStoreException; @@ -107,7 +110,7 @@ public class BTC { return; // Too recent LOGGER.trace(() -> String.format("Checkpointing at block %d dated %s", height, LocalDateTime.ofInstant(Instant.ofEpochSecond(blockTimestamp), ZoneOffset.UTC))); - checkpoints.put(blockTimestamp, block); + this.checkpoints.put(blockTimestamp, block); try { this.saveAsText(new File(BTC.getInstance().getDirectory(), BTC.getInstance().getCheckpointsFileName())); @@ -121,11 +124,11 @@ public class BTC { try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(textFile), StandardCharsets.US_ASCII))) { writer.println("TXT CHECKPOINTS 1"); writer.println("0"); // Number of signatures to read. Do this later. - writer.println(checkpoints.size()); + writer.println(this.checkpoints.size()); ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); - for (StoredBlock block : checkpoints.values()) { + for (StoredBlock block : this.checkpoints.values()) { block.serializeCompact(buffer); writer.println(CheckpointManager.BASE64.encode(buffer.array())); buffer.position(0); @@ -145,11 +148,11 @@ public class BTC { dataOutputStream.writeBytes("CHECKPOINTS 1"); dataOutputStream.writeInt(0); // Number of signatures to read. Do this later. digestOutputStream.on(true); - dataOutputStream.writeInt(checkpoints.size()); + dataOutputStream.writeInt(this.checkpoints.size()); ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); - for (StoredBlock block : checkpoints.values()) { + for (StoredBlock block : this.checkpoints.values()) { block.serializeCompact(buffer); dataOutputStream.write(buffer.array()); buffer.position(0); @@ -165,8 +168,10 @@ public class BTC { private BTC() { if (Settings.getInstance().useBitcoinTestNet()) { - this.params = TestNet3Params.get(); - this.checkpointsFileName = "checkpoints-testnet.txt"; + this.params = RegTestParams.get(); + this.checkpointsFileName = "checkpoints-regtest.txt"; + // TestNet3Params.get(); + // this.checkpointsFileName = "checkpoints-testnet.txt"; } else { this.params = MainNetParams.get(); this.checkpointsFileName = "checkpoints.txt"; @@ -231,7 +236,13 @@ public class BTC { this.peerGroup = new PeerGroup(this.params, this.chain); this.peerGroup.setUserAgent("qortal", "1.0"); - this.peerGroup.addPeerDiscovery(new DnsDiscovery(this.params)); + + if (this.params != RegTestParams.get()) { + this.peerGroup.addPeerDiscovery(new DnsDiscovery(this.params)); + } else { + peerGroup.addAddress(PeerAddress.localhost(this.params)); + } + this.peerGroup.start(); } @@ -306,6 +317,7 @@ public class BTC { block = block.getPrev(this.blockStore); } + // Descending, but order shouldn't matter as we're picking median... latestBlocks.sort((a, b) -> Long.compare(b.getHeader().getTimeSeconds(), a.getHeader().getTimeSeconds())); return latestBlocks.get(5).getHeader().getTimeSeconds(); diff --git a/src/test/java/org/qora/test/apps/BuildCheckpoints.java b/src/test/java/org/qora/test/apps/BuildCheckpoints.java new file mode 100644 index 00000000..44869bb7 --- /dev/null +++ b/src/test/java/org/qora/test/apps/BuildCheckpoints.java @@ -0,0 +1,69 @@ +package org.qora.test.apps; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.TreeMap; + +import org.bitcoinj.core.BlockChain; +import org.bitcoinj.core.CheckpointManager; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.PeerAddress; +import org.bitcoinj.core.PeerGroup; +import org.bitcoinj.core.StoredBlock; +import org.bitcoinj.core.VerificationException; +import org.bitcoinj.core.listeners.NewBestBlockListener; +import org.bitcoinj.params.RegTestParams; +import org.bitcoinj.store.BlockStore; +import org.bitcoinj.store.MemoryBlockStore; + +public class BuildCheckpoints { + + private static final TreeMap checkpoints = new TreeMap<>(); + + public static void main(String[] args) throws Exception { + final NetworkParameters params = RegTestParams.get(); + + final BlockStore store = new MemoryBlockStore(params); + final BlockChain chain = new BlockChain(params, store); + final PeerGroup peerGroup = new PeerGroup(params, chain); + + final InetAddress ipAddress = InetAddress.getLocalHost(); + final PeerAddress peerAddress = new PeerAddress(params, ipAddress); + peerGroup.addAddress(peerAddress); + peerGroup.start(); + + chain.addNewBestBlockListener((block) -> checkpoints.put(block.getHeight(), block)); + + peerGroup.downloadBlockChain(); + peerGroup.stop(); + + final File checkpointsFile = new File("regtest-checkpoints"); + saveAsText(checkpointsFile); + } + + private static void saveAsText(File textFile) { + try (PrintWriter writer = new PrintWriter( + new OutputStreamWriter(new FileOutputStream(textFile), StandardCharsets.US_ASCII))) { + writer.println("TXT CHECKPOINTS 1"); + writer.println("0"); // Number of signatures to read. Do this later. + writer.println(checkpoints.size()); + + ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); + + for (StoredBlock block : checkpoints.values()) { + block.serializeCompact(buffer); + writer.println(CheckpointManager.BASE64.encode(buffer.array())); + buffer.position(0); + } + } catch (FileNotFoundException e) { + return; + } + } + +} diff --git a/src/test/java/org/qora/test/btcacct/Initiate.java b/src/test/java/org/qora/test/btcacct/Initiate.java new file mode 100644 index 00000000..d081f4ba --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/Initiate.java @@ -0,0 +1,144 @@ +package org.qora.test.btcacct; + +import java.security.Security; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.AddressFormatException; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.LegacyAddress; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.params.RegTestParams; +import org.bitcoinj.params.TestNet3Params; +import org.bitcoinj.script.Script.ScriptType; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.controller.Controller; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.settings.Settings; + +import com.google.common.hash.HashCode; + +/** + * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. + * + * Initiator (wants Qora, has BTC) + * Funds BTC P2SH address + * + * Responder (has Qora, wants BTC) + * Builds Qora ACCT AT and funds it with Qora + * + * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder + * + * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT + * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) + * + * Qora ACCT AT sends its Qora to initiator + * + */ + +public class Initiate { + + private static final long REFUND_TIMEOUT = 600L; // seconds + + private static void usage(String error) { + if (error != null) + System.err.println(error); + + System.err.println(String.format("usage: Initiate ()")); + System.err.println(String.format("example: Initiate mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" + + "\t0.00008642 \\\n" + + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length < 3 || args.length > 4) + usage(null); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Settings.fileInstance("settings-test.json"); + NetworkParameters params = RegTestParams.get(); + // TestNet3Params.get(); + + Address yourBitcoinAddress = null; + Coin bitcoinAmount = null; + Address theirBitcoinAddress = null; + Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + + try { + int argIndex = 0; + + yourBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (yourBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Your BTC address is not in P2PKH form"); + + bitcoinAmount = Coin.parseCoin(args[argIndex++]); + + theirBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Their BTC address is not in P2PKH form"); + + if (args.length > argIndex) + bitcoinFee = Coin.parseCoin(args[argIndex++]); + } catch (NumberFormatException | AddressFormatException e) { + usage(String.format("Argument format exception: %s", e.getMessage())); + } + + try { + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryManager.setRepositoryFactory(repositoryFactory); + } catch (DataException e) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + System.out.println("Confirm the following is correct based on the info you've given:"); + + System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); + System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); + System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString())); + System.out.println(String.format("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); + + // New/derived info + + ECKey tradeKey = new ECKey(); + System.out.println("\nSecret info (DO NOT share with other party):"); + System.out.println(String.format("Trade private key: %s", HashCode.fromBytes(tradeKey.getPrivKeyBytes()))); + + System.out.println("\nGive this info to other party:"); + + System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); + + int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); + + byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), yourBitcoinAddress.getHash(), theirBitcoinAddress.getHash(), lockTime); + System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); + + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + System.out.println(String.format("P2SH address: %s", p2shAddress)); + + bitcoinAmount = bitcoinAmount.add(bitcoinFee); + + // Fund P2SH + System.out.println(String.format("\nYou need to fund %s with %s BTC (includes redeem/refund fee of %s)", + p2shAddress.toString(), bitcoinAmount.toPlainString(), bitcoinFee.toPlainString())); + + System.out.println("Once this is done, responder should run Respond to check P2SH funding and create AT"); + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); + } + } + +} diff --git a/src/test/java/org/qora/test/btcacct/Initiate1.java b/src/test/java/org/qora/test/btcacct/Initiate1.java deleted file mode 100644 index 60001176..00000000 --- a/src/test/java/org/qora/test/btcacct/Initiate1.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.qora.test.btcacct; - -import java.math.BigDecimal; -import java.security.SecureRandom; -import java.security.Security; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; - -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.params.TestNet3Params; -import org.bitcoinj.script.Script.ScriptType; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.account.PrivateKeyAccount; -import org.qora.account.PublicKeyAccount; -import org.qora.asset.Asset; -import org.qora.controller.Controller; -import org.qora.crosschain.BTC; -import org.qora.crosschain.BTCACCT; -import org.qora.crypto.Crypto; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.utils.Base58; - -import com.google.common.hash.HashCode; - -/** - * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. - * - * Initiator (wants Qora, has BTC) - * Funds BTC P2SH address - * - * Responder (has Qora, wants BTC) - * Builds Qora ACCT AT and funds it with Qora - * - * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder - * - * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT - * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) - * - * Qora ACCT AT sends its Qora to initiator - * - */ - -public class Initiate1 { - - private static final long REFUND_TIMEOUT = 600L; // seconds - - private static void usage() { - System.err.println(String.format("usage: Initiate1 ")); - System.err.println(String.format("example: Initiate1 pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj \\\n" - + "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" - + "\t123 0.00008642 \\\n" - + "\tJBNBQQDzZsm5do1BrwWAp53Ps4KYJVt749EGpCf7ofte \\\n" - + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559")); - System.exit(1); - } - - public static void main(String[] args) { - if (args.length != 6) - usage(); - - Security.insertProviderAt(new BouncyCastleProvider(), 0); - NetworkParameters params = TestNet3Params.get(); - - int argIndex = 0; - String yourQortPrivKey58 = args[argIndex++]; - String yourBitcoinPubKeyHex = args[argIndex++]; - - String rawQortAmount = args[argIndex++]; - String rawBitcoinAmount = args[argIndex++]; - - String theirQortPubKey58 = args[argIndex++]; - String theirBitcoinPubKeyHex = args[argIndex++]; - - try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); - RepositoryManager.setRepositoryFactory(repositoryFactory); - } catch (DataException e) { - throw new RuntimeException("Repository startup issue: " + e.getMessage()); - } - - try (final Repository repository = RepositoryManager.getRepository()) { - System.out.println("Confirm the following is correct based on the info you've given:"); - - byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58); - PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey); - System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress())); - - byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); - ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey); - Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); - - byte[] theirQortPubKey = Base58.decode(theirQortPubKey58); - PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey); - System.out.println(String.format("Their Qortal address: %s", theirQortalAccount.getAddress())); - - byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); - ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); - Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); - - // Some checks - BigDecimal qortAmount = new BigDecimal(rawQortAmount).setScale(8); - BigDecimal yourQortBalance = yourQortalAccount.getConfirmedBalance(Asset.QORT); - if (yourQortBalance.compareTo(qortAmount) <= 0) { - System.err.println(String.format("Your QORT balance %s is less than required %s", yourQortBalance.toPlainString(), qortAmount.toPlainString())); - System.exit(2); - } - - // New/derived info - - byte[] secret = new byte[32]; - new SecureRandom().nextBytes(secret); - System.out.println("\nSecret info (DO NOT share with other party):"); - System.out.println("Secret: " + HashCode.fromBytes(secret).toString()); - - System.out.println("\nGive this info to other party:"); - - byte[] secretHash = Crypto.digest(secret); - System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); - - int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); - System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); - - byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinPubKey, theirBitcoinPubKey, lockTime); - System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); - - byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); - - Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); - System.out.println("P2SH address: " + p2shAddress.toString()); - - Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount).add(BTCACCT.DEFAULT_BTC_FEE); - - // Fund P2SH - System.out.println(String.format("\nYou need to fund %s with %s BTC (includes redeem/refund fee)", p2shAddress.toString(), bitcoinAmount.toPlainString())); - - System.out.println("Once this is done, responder should run Respond2 to check P2SH funding and create AT"); - } catch (NumberFormatException e) { - usage(); - } catch (DataException e) { - throw new RuntimeException("Repository issue: " + e.getMessage()); - } - } - -} diff --git a/src/test/java/org/qora/test/btcacct/Redeem.java b/src/test/java/org/qora/test/btcacct/Redeem.java new file mode 100644 index 00000000..1ba65bce --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/Redeem.java @@ -0,0 +1,199 @@ +package org.qora.test.btcacct; + +import java.security.Security; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.AddressFormatException; +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.params.RegTestParams; +import org.bitcoinj.params.TestNet3Params; +import org.bitcoinj.script.Script.ScriptType; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.controller.Controller; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.settings.Settings; + +import com.google.common.hash.HashCode; + +/** + * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. + * + * Initiator (wants Qora, has BTC) + * Funds BTC P2SH address + * + * Responder (has Qora, wants BTC) + * Builds Qora ACCT AT and funds it with Qora + * + * Initiator sends trade private key to Responder. + * Responder uses their public key + tx signature + trade pubkey + script as input to BTC P2SH address, releasing BTC amount to responder. + * + * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT + * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) + * + * Qora ACCT AT sends its Qora to initiator + * + */ + +public class Redeem { + + static { + // This must go before any calls to LogManager/Logger + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + } + + private static final long REFUND_TIMEOUT = 600L; // seconds + + private static void usage(String error) { + if (error != null) + System.err.println(error); + + System.err.println(String.format("usage: Redeem ()")); + System.err.println(String.format("example: Redeem 032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" + + "\tmrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" + + "\teb95e1c1a5e9e6733549faec85b71f74f67638ea63b0acf2f077e9d0cb94dfe8 1575653814 2Mtn4aLjjWVEWckdoTMK7P8WbkXJf1ES6yL")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length < 5 || args.length > 6) + usage(null); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Settings.fileInstance("settings-test.json"); + NetworkParameters params = RegTestParams.get(); + // TestNet3Params.get(); + + ECKey yourBitcoinKey = null; + Address theirBitcoinAddress = null; + byte[] tradePrivateKey = null; + int lockTime = 0; + Address p2shAddress = null; + Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + + try { + int argIndex = 0; + + yourBitcoinKey = ECKey.fromPublicOnly(HashCode.fromString(args[argIndex++]).asBytes()); + + theirBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Their BTC address is not in P2PKH form"); + + tradePrivateKey = HashCode.fromString(args[argIndex++]).asBytes(); + if (tradePrivateKey.length != 32) + usage("Trade private key not 32 bytes"); + + lockTime = Integer.parseInt(args[argIndex++]); + + p2shAddress = Address.fromString(params, args[argIndex++]); + if (p2shAddress.getOutputScriptType() != ScriptType.P2SH) + usage("P2SH address invalid"); + + if (args.length > argIndex) + bitcoinFee = Coin.parseCoin(args[argIndex++]); + } catch (NumberFormatException | AddressFormatException e) { + usage(String.format("Argument format exception: %s", e.getMessage())); + } + + try { + RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); + RepositoryManager.setRepositoryFactory(repositoryFactory); + } catch (DataException e) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + System.out.println("Confirm the following is correct based on the info you've given:"); + + System.out.println(String.format("Your Bitcoin address: %s", Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH))); + System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); + System.out.println(String.format("Trade PRIVATE key: %s", HashCode.fromBytes(tradePrivateKey))); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); + System.out.println(String.format("P2SH address: %s", p2shAddress)); + System.out.println(String.format("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); + + // New/derived info + + System.out.println("\nCHECKING info from other party:"); + + ECKey tradeKey = ECKey.fromPrivate(tradePrivateKey); + System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); + + byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), theirBitcoinAddress.getHash(), yourBitcoinKey.getPubKeyHash(), lockTime); + System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); + + byte[] redeemScriptHash = BTC.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); + } + + // Some checks + + System.out.println("\nProcessing:"); + + long medianBlockTime = BTC.getInstance().getMedianBlockTime(); + 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.err.println(String.format("Too soon (%s) to redeem based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC))); + System.exit(2); + } + + Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + if (p2shBalance == null) { + System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); + System.exit(2); + } + System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); + + // Grab all P2SH funding transactions (just in case there are more than one) + List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); + + if (fundingOutputs.isEmpty()) { + System.err.println(String.format("Can't redeem spent/unfunded P2SH")); + System.exit(2); + } + + if (fundingOutputs.size() != 1) { + System.err.println(String.format("Expecting only one unspent output for P2SH")); + System.exit(2); + } + + TransactionOutput fundingOutput = fundingOutputs.get(0); + System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex())); + + Coin redeemAmount = p2shBalance.subtract(bitcoinFee); + Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, tradeKey, yourBitcoinKey.getPubKey(), fundingOutput, redeemScriptBytes); + + byte[] redeemBytes = redeemTransaction.bitcoinSerialize(); + + System.out.println(String.format("\nLoad this transaction into your wallet and broadcast:\n%s\n", HashCode.fromBytes(redeemBytes).toString())); + } catch (NumberFormatException e) { + usage(String.format("Number format exception: %s", e.getMessage())); + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); + } + } + +} diff --git a/src/test/java/org/qora/test/btcacct/Refund2.java b/src/test/java/org/qora/test/btcacct/Refund.java similarity index 57% rename from src/test/java/org/qora/test/btcacct/Refund2.java rename to src/test/java/org/qora/test/btcacct/Refund.java index 05801775..69cddde7 100644 --- a/src/test/java/org/qora/test/btcacct/Refund2.java +++ b/src/test/java/org/qora/test/btcacct/Refund.java @@ -3,16 +3,18 @@ package org.qora.test.btcacct; import java.security.Security; import java.time.Instant; import java.time.LocalDateTime; -import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.List; import org.bitcoinj.core.Address; +import org.bitcoinj.core.AddressFormatException; 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.params.RegTestParams; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -46,7 +48,7 @@ import com.google.common.hash.HashCode; * */ -public class Refund2 { +public class Refund { static { // This must go before any calls to LogManager/Logger @@ -55,31 +57,57 @@ public class Refund2 { private static final long REFUND_TIMEOUT = 600L; // seconds - private static void usage() { - System.err.println(String.format("usage: Refund2 ")); - System.err.println(String.format("example: Refund2 027fb5828c5e201eaf6de4cd3b0b340d16a191ef848cd691f35ef8f727358c9c \\\n" - + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" - + "\tb837056cdc5d805e4db1f830a58158e1131ac96ea71de4c6f9d7854985e153e2 1575021641 2MvGdGUgAfc7qTHaZJwWmZ26Fg6Hjif8gNy")); + private static void usage(String error) { + if (error != null) + System.err.println(error); + + System.err.println(String.format("usage: Refund ()")); + System.err.println(String.format("example: Refund 03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" + + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n" + + "\teb95e1c1a5e9e6733549faec85b71f74f67638ea63b0acf2f077e9d0cb94dfe8 1575653814 2Mtn4aLjjWVEWckdoTMK7P8WbkXJf1ES6yL")); System.exit(1); } public static void main(String[] args) { - if (args.length != 5) - usage(); + if (args.length < 5 || args.length > 6) + usage(null); Security.insertProviderAt(new BouncyCastleProvider(), 0); - Settings.fileInstance("settings-test.json"); + NetworkParameters params = RegTestParams.get(); + // TestNet3Params.get(); - NetworkParameters params = TestNet3Params.get(); + ECKey yourBitcoinKey = null; + Address theirBitcoinAddress = null; + byte[] tradePrivateKey = null; + int lockTime = 0; + Address p2shAddress = null; + Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; - int argIndex = 0; - String yourBitcoinPrivKeyHex = args[argIndex++]; - String theirBitcoinPubKeyHex = args[argIndex++]; + try { + int argIndex = 0; - String secretHashHex = args[argIndex++]; - String rawLockTime = args[argIndex++]; - String rawP2shAddress = args[argIndex++]; + yourBitcoinKey = ECKey.fromPublicOnly(HashCode.fromString(args[argIndex++]).asBytes()); + + theirBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Their BTC address is not in P2PKH form"); + + tradePrivateKey = HashCode.fromString(args[argIndex++]).asBytes(); + if (tradePrivateKey.length != 32) + usage("Trade private key not 32 bytes"); + + lockTime = Integer.parseInt(args[argIndex++]); + + p2shAddress = Address.fromString(params, args[argIndex++]); + if (p2shAddress.getOutputScriptType() != ScriptType.P2SH) + usage("P2SH address invalid"); + + if (args.length > argIndex) + bitcoinFee = Coin.parseCoin(args[argIndex++]); + } catch (NumberFormatException | AddressFormatException e) { + usage(String.format("Argument format exception: %s", e.getMessage())); + } try { RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); @@ -91,50 +119,47 @@ public class Refund2 { try (final Repository repository = RepositoryManager.getRepository()) { System.out.println("Confirm the following is correct based on the info you've given:"); - byte[] yourBitcoinPrivKey = HashCode.fromString(yourBitcoinPrivKeyHex).asBytes(); - ECKey yourBitcoinKey = ECKey.fromPrivate(yourBitcoinPrivKey); - Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); - - byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); - ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); - Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); + System.out.println(String.format("Your Bitcoin address: %s", Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH))); System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); + System.out.println(String.format("Trade PRIVATE key: %s", HashCode.fromBytes(tradePrivateKey))); + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); + System.out.println(String.format("P2SH address: %s", p2shAddress)); + System.out.println(String.format("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); // New/derived info - int lockTime = Integer.valueOf(rawLockTime); - System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); + System.out.println("\nCHECKING info from other party:"); - byte[] secretHash = HashCode.fromString(secretHashHex).asBytes(); - System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); + ECKey tradeKey = ECKey.fromPrivate(tradePrivateKey); + System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); - byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, yourBitcoinKey.getPubKey(), theirBitcoinPubKey, lockTime); - System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); + byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), yourBitcoinKey.getPubKeyHash(), theirBitcoinAddress.getHash(), lockTime); + System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + Address derivedP2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); - Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); - System.out.println(String.format("P2SH address: %s", p2shAddress)); - - if (!p2shAddress.toString().equals(rawP2shAddress)) { - System.err.println(String.format("Derived P2SH address %s does not match given address %s", p2shAddress, rawP2shAddress)); + if (!derivedP2shAddress.equals(p2shAddress)) { + System.err.println(String.format("Derived P2SH address %s does not match given address %s", derivedP2shAddress, p2shAddress)); System.exit(2); } // Some checks + + System.out.println("\nProcessing:"); + long medianBlockTime = BTC.getInstance().getMedianBlockTime(); - System.out.println(String.format("Median block time: %s", LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneId.systemDefault()))); + 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.err.println(String.format("Too soon (%s) to refund based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneId.systemDefault()))); + System.err.println(String.format("Too soon (%s) to refund based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC))); System.exit(2); } if (now < lockTime * 1000L) { - System.err.println(String.format("Too soon (%s) to refund based on lockTime %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()), LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()))); + System.err.println(String.format("Too soon (%s) to refund based on lockTime %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC))); System.exit(2); } @@ -159,13 +184,17 @@ public class Refund2 { System.exit(2); } - Transaction refundTransaction = BTCACCT.buildRefundTransaction(p2shBalance, yourBitcoinKey, fundingOutputs.get(0), redeemScriptBytes, lockTime); + TransactionOutput fundingOutput = fundingOutputs.get(0); + System.out.println(String.format("Using output %s:%d for refund", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex())); + + Coin refundAmount = p2shBalance.subtract(bitcoinFee); + Transaction refundTransaction = BTCACCT.buildRefundTransaction(refundAmount, tradeKey, yourBitcoinKey.getPubKey(), fundingOutput, redeemScriptBytes, lockTime); byte[] refundBytes = refundTransaction.bitcoinSerialize(); - System.out.println(String.format("\nLoad this transaction into your wallet, sign and broadcast:\n%s\n", HashCode.fromBytes(refundBytes).toString())); + System.out.println(String.format("\nLoad this transaction into your wallet and broadcast:\n%s\n", HashCode.fromBytes(refundBytes).toString())); } catch (NumberFormatException e) { - usage(); + usage(String.format("Number format exception: %s", e.getMessage())); } catch (DataException e) { throw new RuntimeException("Repository issue: " + e.getMessage()); } diff --git a/src/test/java/org/qora/test/btcacct/Respond2.java b/src/test/java/org/qora/test/btcacct/Respond2.java index 9dac26ed..5699ad1a 100644 --- a/src/test/java/org/qora/test/btcacct/Respond2.java +++ b/src/test/java/org/qora/test/btcacct/Respond2.java @@ -61,7 +61,7 @@ public class Respond2 { private static final long REFUND_TIMEOUT = 600L; // seconds private static void usage() { - System.err.println(String.format("usage: Respond2 ")); + System.err.println(String.format("usage: Respond2 ")); System.err.println(String.format("example: Respond2 3jjoToDaDpsdUHqaouLGypFeewNVKvtkmdM38i54WVra \\\n" + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" + "\t123 0.00008642 \\\n" @@ -136,7 +136,7 @@ public class Respond2 { byte[] secretHash = HashCode.fromString(secretHashHex).asBytes(); System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); - byte[] redeemScriptBytes = BTCACCT.buildRedeemScript(secretHash, theirBitcoinPubKey, yourBitcoinPubKey, lockTime); + byte[] redeemScriptBytes = BTCACCT.buildScript(secretHash, theirBitcoinPubKey, yourBitcoinPubKey, lockTime); System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); From 8844cc0076dcbba7f6d17c6352f3e185807fee4b Mon Sep 17 00:00:00 2001 From: catbref Date: Fri, 27 Mar 2020 18:24:41 +0000 Subject: [PATCH 05/15] GetTransaction test app to demo fetching any bitcoin transaction using bitcoinj. Plus some AT-API work --- src/main/java/org/qora/at/BlockchainAPI.java | 153 ++++++++++++++++++ .../org/qortal/at/QortalFunctionCode.java | 84 +++++++++- src/main/java/org/qortal/crosschain/BTC.java | 98 ++++++++++- .../org/qora/test/btcacct/GetTransaction.java | 64 ++++++++ .../java/org/qora/test/btcacct/Redeem.java | 2 +- .../java/org/qora/test/btcacct/Refund.java | 2 +- 6 files changed, 390 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/qora/at/BlockchainAPI.java create mode 100644 src/test/java/org/qora/test/btcacct/GetTransaction.java diff --git a/src/main/java/org/qora/at/BlockchainAPI.java b/src/main/java/org/qora/at/BlockchainAPI.java new file mode 100644 index 00000000..b8cd8c90 --- /dev/null +++ b/src/main/java/org/qora/at/BlockchainAPI.java @@ -0,0 +1,153 @@ +package org.qora.at; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toMap; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import org.ciyam.at.MachineState; +import org.ciyam.at.Timestamp; +import org.qora.account.Account; +import org.qora.block.Block; +import org.qora.data.block.BlockData; +import org.qora.data.transaction.ATTransactionData; +import org.qora.data.transaction.PaymentTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.repository.BlockRepository; +import org.qora.repository.DataException; +import org.qora.transaction.Transaction; + +public enum BlockchainAPI { + + QORA(0) { + @Override + public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) { + int height = timestamp.blockHeight; + int sequence = timestamp.transactionSequence + 1; + + QoraATAPI api = (QoraATAPI) state.getAPI(); + BlockRepository blockRepository = api.getRepository().getBlockRepository(); + + try { + Account recipientAccount = new Account(api.getRepository(), recipient); + + while (height <= blockRepository.getBlockchainHeight()) { + BlockData blockData = blockRepository.fromHeight(height); + + if (blockData == null) + throw new DataException("Unable to fetch block " + height + " from repository?"); + + Block block = new Block(api.getRepository(), blockData); + + List transactions = block.getTransactions(); + + // No more transactions in this block? Try next block + if (sequence >= transactions.size()) { + ++height; + sequence = 0; + continue; + } + + Transaction transaction = transactions.get(sequence); + + // Transaction needs to be sent to specified recipient + if (transaction.getRecipientAccounts().contains(recipientAccount)) { + // Found a transaction + + api.setA1(state, new Timestamp(height, timestamp.blockchainId, sequence).longValue()); + + // Hash transaction's signature into other three A fields for future verification that it's the same transaction + byte[] hash = QoraATAPI.sha192(transaction.getTransactionData().getSignature()); + + api.setA2(state, QoraATAPI.fromBytes(hash, 0)); + api.setA3(state, QoraATAPI.fromBytes(hash, 8)); + api.setA4(state, QoraATAPI.fromBytes(hash, 16)); + return; + } + + // Transaction wasn't for us - keep going + ++sequence; + } + + // No more transactions - zero A and exit + api.zeroA(state); + } catch (DataException e) { + throw new RuntimeException("AT API unable to fetch next transaction?", e); + } + } + + @Override + public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) { + QoraATAPI api = (QoraATAPI) state.getAPI(); + TransactionData transactionData = api.fetchTransaction(state); + + switch (transactionData.getType()) { + case PAYMENT: + return ((PaymentTransactionData) transactionData).getAmount().unscaledValue().longValue(); + + case AT: + BigDecimal amount = ((ATTransactionData) transactionData).getAmount(); + + if (amount != null) + return amount.unscaledValue().longValue(); + else + return 0xffffffffffffffffL; + + default: + return 0xffffffffffffffffL; + } + } + + @Override + public TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex) { + // TODO + return null; + } + }, + BTC(1) { + @Override + public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) { + // TODO BTC transaction support for ATv2 + } + + @Override + public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) { + // TODO BTC transaction support for ATv2 + return 0; + } + + @Override + public TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex) { + // TODO + return null; + } + }; + + public static class TransactionOutput { + byte[] recipient; + long amount; + } + + public final int value; + + private static final Map map = stream(BlockchainAPI.values()).collect(toMap(type -> type.value, type -> type)); + + BlockchainAPI(int value) { + this.value = value; + } + + public static BlockchainAPI valueOf(int value) { + return map.get(value); + } + + // Blockchain-specific API methods + + public abstract void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state); + + public abstract long getAmountFromTransactionInA(Timestamp timestamp, MachineState state); + + public abstract TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex); + +} diff --git a/src/main/java/org/qortal/at/QortalFunctionCode.java b/src/main/java/org/qortal/at/QortalFunctionCode.java index f7d089cf..1c68b244 100644 --- a/src/main/java/org/qortal/at/QortalFunctionCode.java +++ b/src/main/java/org/qortal/at/QortalFunctionCode.java @@ -10,6 +10,8 @@ import org.ciyam.at.FunctionData; import org.ciyam.at.IllegalFunctionCodeException; import org.ciyam.at.MachineState; import org.ciyam.at.Timestamp; +import org.qortal.crypto.Crypto; +import org.qortal.settings.Settings; /** * Qortal-specific CIYAM-AT Functions. @@ -20,7 +22,7 @@ import org.ciyam.at.Timestamp; public enum QortalFunctionCode { /** * 0x0500
- * Returns current BTC block's "timestamp" + * Returns current BTC block's "timestamp". */ GET_BTC_BLOCK_TIMESTAMP(0x0500, 0, true) { @Override @@ -30,7 +32,7 @@ public enum QortalFunctionCode { }, /** * 0x0501
- * Put transaction from specific recipient after timestamp in A, or zero if none
+ * Put transaction from specific recipient after timestamp in A, or zero if none. */ PUT_TX_FROM_B_RECIPIENT_AFTER_TIMESTAMP_IN_A(0x0501, 1, false) { @Override @@ -42,13 +44,74 @@ public enum QortalFunctionCode { BlockchainAPI blockchainAPI = BlockchainAPI.valueOf(timestamp.blockchainId); blockchainAPI.putTransactionFromRecipientAfterTimestampInA(recipient, timestamp, state); } + }, + /** + * 0x0502
+ * Get output, using transaction in A and passed index, putting address in B and returning amount.
+ * Return -1 if no such output; + */ + GET_INDEXED_OUTPUT(0x0502, 1, true) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + int outputIndex = (int) (functionData.value1 & 0xffffffffL); + + BlockchainAPI.TransactionOutput output = BlockchainAPI.BTC.getIndexedOutputFromTransactionInA(state, outputIndex); + + if (output == null) { + functionData.returnValue = -1L; + return; + } + + state.getAPI().setB(state, output.recipient); + functionData.returnValue = output.amount; + } + }, + /** + * 0x0510
+ * Convert address in B to 20-byte value in LSB of B1, and all of B2 & B3. + */ + CONVERT_B_TO_PKH(0x0510, 0, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + // Needs to be 'B' sized + byte[] pkh = new byte[32]; + + // Copy PKH part of B to last 20 bytes + System.arraycopy(state.getB(), 32 - 20 - 4, pkh, 32 - 20, 20); + + state.getAPI().setB(state, pkh); + } + }, + /** + * 0x0511
+ * Convert 20-byte value in LSB of B1, and all of B2 & B3 to P2SH.
+ * P2SH stored in lower 25 bytes of B. + */ + CONVERT_B_TO_P2SH(0x0511, 0, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + byte addressPrefix = Settings.getInstance().useBitcoinTestNet() ? (byte) 0xc4 : 0x05; + + convertAddressInB(addressPrefix, state); + } + }, + /** + * 0x0512
+ * Convert 20-byte value in LSB of B1, and all of B2 & B3 to Qortal address.
+ * Qortal address stored in lower 25 bytes of B. + */ + CONVERT_B_TO_QORTAL(0x0512, 0, false) { + @Override + protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + convertAddressInB(Crypto.ADDRESS_VERSION, state); + } }; public final short value; public final int paramCount; public final boolean returnsValue; - private final static Map map = Arrays.stream(QortalFunctionCode.values()) + private static final Map map = Arrays.stream(QortalFunctionCode.values()) .collect(Collectors.toMap(functionCode -> functionCode.value, functionCode -> functionCode)); private QortalFunctionCode(int value, int paramCount, boolean returnsValue) { @@ -100,4 +163,19 @@ public enum QortalFunctionCode { /** Actually execute function */ protected abstract void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException; + private static void convertAddressInB(byte addressPrefix, MachineState state) { + byte[] addressNoChecksum = new byte[1 + 20]; + addressNoChecksum[0] = addressPrefix; + System.arraycopy(state.getB(), 0, addressNoChecksum, 1, 20); + + byte[] checksum = Crypto.doubleDigest(addressNoChecksum); + + // Needs to be 'B' sized + byte[] address = new byte[32]; + System.arraycopy(addressNoChecksum, 0, address, 32 - 1 - 20 - 4, addressNoChecksum.length); + System.arraycopy(checksum, 0, address, 32 - 4, 4); + + state.getAPI().setB(state, address); + } + } diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index 7eaee53b..f46e0536 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import java.net.InetAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.DigestOutputStream; @@ -21,6 +20,7 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -28,22 +28,28 @@ import org.bitcoinj.core.Address; import org.bitcoinj.core.BlockChain; import org.bitcoinj.core.CheckpointManager; import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.PeerAddress; import org.bitcoinj.core.PeerGroup; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.StoredBlock; +import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.core.listeners.BlocksDownloadedEventListener; import org.bitcoinj.core.listeners.NewBestBlockListener; import org.bitcoinj.net.discovery.DnsDiscovery; import org.bitcoinj.params.MainNetParams; import org.bitcoinj.params.RegTestParams; import org.bitcoinj.params.TestNet3Params; +import org.bitcoinj.script.Script.ScriptType; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.BlockStoreException; import org.bitcoinj.store.MemoryBlockStore; import org.bitcoinj.utils.Threading; import org.bitcoinj.wallet.Wallet; +import org.bitcoinj.wallet.WalletTransaction; +import org.bitcoinj.wallet.WalletTransaction.Pool; import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener; import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener; import org.qortal.settings.Settings; @@ -168,10 +174,12 @@ public class BTC { private BTC() { if (Settings.getInstance().useBitcoinTestNet()) { + /* this.params = RegTestParams.get(); this.checkpointsFileName = "checkpoints-regtest.txt"; - // TestNet3Params.get(); - // this.checkpointsFileName = "checkpoints-testnet.txt"; + */ + this.params = TestNet3Params.get(); + this.checkpointsFileName = "checkpoints-testnet.txt"; } else { this.params = MainNetParams.get(); this.checkpointsFileName = "checkpoints.txt"; @@ -256,7 +264,25 @@ public class BTC { return Wallet.createBasic(this.params); } - private void replayChain(long startTime, Wallet wallet) throws BlockStoreException { + private class ReplayHooks { + private Runnable preReplay; + private Runnable postReplay; + + public ReplayHooks(Runnable preReplay, Runnable postReplay) { + this.preReplay = preReplay; + this.postReplay = postReplay; + } + + public void preReplay() { + this.preReplay.run(); + } + + public void postReplay() { + this.postReplay.run(); + } + } + + private void replayChain(long startTime, Wallet wallet, ReplayHooks replayHooks) throws BlockStoreException { this.start(startTime); final WalletCoinsReceivedEventListener coinsReceivedListener = (someWallet, tx, prevBalance, newBalance) -> { @@ -277,12 +303,18 @@ public class BTC { } try { + if (replayHooks != null) + replayHooks.preReplay(); + // Sync blockchain using peerGroup, skipping as much as we can before startTime this.peerGroup.setFastCatchupTimeSecs(startTime); this.chain.addNewBestBlockListener(Threading.SAME_THREAD, this.manager); this.peerGroup.downloadBlockChain(); } finally { // Clean up + if (replayHooks != null) + replayHooks.postReplay(); + if (wallet != null) { wallet.removeCoinsReceivedEventListener(coinsReceivedListener); wallet.removeCoinsSentEventListener(coinsSentListener); @@ -296,7 +328,7 @@ public class BTC { } private void replayChain(long startTime) throws BlockStoreException { - this.replayChain(startTime, null); + this.replayChain(startTime, null, null); } // Actual useful methods for use by other classes @@ -334,7 +366,7 @@ public class BTC { wallet.addWatchedAddress(address, startTime); try { - replayChain(startTime, wallet); + replayChain(startTime, wallet, null); // Now that blockchain is up-to-date, return current balance return wallet.getBalance(); @@ -344,13 +376,13 @@ public class BTC { } } - public List getUnspentOutputs(String base58Address, long startTime) { + public List getOutputs(String base58Address, long startTime) { Wallet wallet = createEmptyWallet(); Address address = Address.fromString(this.params, base58Address); wallet.addWatchedAddress(address, startTime); try { - replayChain(startTime, wallet); + replayChain(startTime, wallet, null); // Now that blockchain is up-to-date, return outputs return wallet.getWatchedOutputs(true); @@ -360,4 +392,54 @@ public class BTC { } } + private static class TransactionStorage { + private Transaction transaction; + + public void store(Transaction transaction) { + this.transaction = transaction; + } + + public Transaction getTransaction() { + return this.transaction; + } + } + + public List getOutputs(byte[] txId, long startTime) { + Wallet wallet = createEmptyWallet(); + + // Add random address to wallet + ECKey fakeKey = new ECKey(); + wallet.addWatchedAddress(Address.fromKey(this.params, fakeKey, ScriptType.P2PKH), startTime); + + final Sha256Hash txHash = Sha256Hash.wrap(txId); + + final TransactionStorage transactionStorage = new TransactionStorage(); + + final BlocksDownloadedEventListener listener = (peer, block, filteredBlock, blocksLeft) -> { + List transactions = block.getTransactions(); + + if (transactions == null) + return; + + for (Transaction transaction : transactions) + if (transaction.getTxId().equals(txHash)) { + System.out.println(String.format("We downloaded block containing tx!")); + transactionStorage.store(transaction); + } + }; + + ReplayHooks replayHooks = new ReplayHooks(() -> this.peerGroup.addBlocksDownloadedEventListener(listener), () -> this.peerGroup.removeBlocksDownloadedEventListener(listener)); + + // Replay chain in the hope it will download transactionId as a dependency + try { + replayChain(startTime, wallet, replayHooks); + + Transaction realTx = transactionStorage.getTransaction(); + return realTx.getOutputs(); + } catch (BlockStoreException e) { + LOGGER.error(String.format("BTC blockstore issue: %s", e.getMessage())); + return null; + } + } + } diff --git a/src/test/java/org/qora/test/btcacct/GetTransaction.java b/src/test/java/org/qora/test/btcacct/GetTransaction.java new file mode 100644 index 00000000..9686a3f2 --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/GetTransaction.java @@ -0,0 +1,64 @@ +package org.qora.test.btcacct; + +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.qora.crosschain.BTC; +import org.qora.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 ")); + System.err.println(String.format("example: GetTransaction 816407e79568f165f13e09e9912c5f2243e0a23a007cec425acedc2e89284660 (mainnet)")); + System.err.println(String.format("example: GetTransaction 3bfd17a492a4e3d6cb7204e17e20aca6c1ab82e1828bd1106eefbaf086fb8a4e (testnet)")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length < 1 || args.length > 1) + usage(null); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Settings.fileInstance("settings-test.json"); + + byte[] transactionId = null; + + try { + int argIndex = 0; + + transactionId = HashCode.fromString(args[argIndex++]).asBytes(); + } catch (NumberFormatException | AddressFormatException e) { + usage(String.format("Argument format exception: %s", e.getMessage())); + } + + // Chain replay start time + long startTime = (System.currentTimeMillis() / 1000L) - 14 * 24 * 60 * 60; // 14 days before now, in seconds + + // Grab all outputs from transaction + List fundingOutputs = BTC.getInstance().getOutputs(transactionId, startTime); + if (fundingOutputs == null) { + System.out.println(String.format("Transaction not found")); + 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())); + } + +} diff --git a/src/test/java/org/qora/test/btcacct/Redeem.java b/src/test/java/org/qora/test/btcacct/Redeem.java index 1ba65bce..503fc710 100644 --- a/src/test/java/org/qora/test/btcacct/Redeem.java +++ b/src/test/java/org/qora/test/btcacct/Redeem.java @@ -167,7 +167,7 @@ public class Redeem { System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); // Grab all P2SH funding transactions (just in case there are more than one) - List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + List fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); if (fundingOutputs.isEmpty()) { diff --git a/src/test/java/org/qora/test/btcacct/Refund.java b/src/test/java/org/qora/test/btcacct/Refund.java index 69cddde7..662bf63e 100644 --- a/src/test/java/org/qora/test/btcacct/Refund.java +++ b/src/test/java/org/qora/test/btcacct/Refund.java @@ -171,7 +171,7 @@ public class Refund { System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); // Grab all P2SH funding transactions (just in case there are more than one) - List fundingOutputs = BTC.getInstance().getUnspentOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + List fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); if (fundingOutputs.isEmpty()) { From 87bb9090f5ce8d770446369f6e0e8abddd52a975 Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 8 Apr 2020 09:29:25 +0100 Subject: [PATCH 06/15] CIYAM AT & cross-chain trading. Bump CIYAM AT requirement to v1.3 Remove multi-blockchain AT aspect for now (BlockchainAPI). For PUT_PREVIOUS_BLOCK_HASH_INTO_A we no longer use SHA256 to condense 64-byte block signature into 32 bytes. Now we put block height into A1 and SHA192 of signature into A2 through A4. This allows possible future lookup of block data using "block hash", with verification that it is the same block. Some AT functions use "address in B" but sometimes we populate B with account's public key instead. So the method "getAccountFromB" is smart and checks for an actual, textual address in B starting with 'Q', otherwise assumes B contains public key. The Settings field "useBitcoinTestNet" (boolean) now replaced with "bitcoinNet" (String) with possible values MAIN (default), TEST3, REGTEST. This allows for more varied development/testing scenarios. Use correct Bitcoin nSequence value 0xFFFFFFFE for P2SH, i.e. enable locktime, disable RBF. Roll REGTEST checkpoints file generator into main BTC class. Yet another rewrite of Bitcoin P2SH scripts for BTC-QORT cross-chain trading. Added associated test classes BuildP2SH, CheckP2SH, DeployAT (unfinished). --- lib/org/ciyam/at/1.2/at-1.2.jar | Bin 136493 -> 143497 bytes lib/org/ciyam/at/1.3/at-1.3.jar | Bin 0 -> 143430 bytes lib/org/ciyam/at/1.3/at-1.3.pom | 9 + lib/org/ciyam/at/maven-metadata-local.xml | 5 +- pom.xml | 2 +- src/main/java/org/qora/at/BlockchainAPI.java | 153 ------------ .../java/org/qora/crosschain/BTCACCT.java | 219 ++++++++++-------- src/main/java/org/qortal/at/QortalATAPI.java | 150 +++++++++--- .../org/qortal/at/QortalFunctionCode.java | 50 +--- src/main/java/org/qortal/crosschain/BTC.java | 118 ++++++++-- .../java/org/qortal/settings/Settings.java | 7 +- .../org/qora/test/apps/BuildCheckpoints.java | 4 +- .../java/org/qora/test/btcacct/BuildP2SH.java | 125 ++++++++++ .../java/org/qora/test/btcacct/CheckP2SH.java | 172 ++++++++++++++ .../java/org/qora/test/btcacct/DeployAT.java | 158 +++++++++++++ .../java/org/qora/test/btcacct/Initiate.java | 2 +- .../java/org/qora/test/btcacct/Redeem.java | 123 +++++----- .../java/org/qora/test/btcacct/Refund.java | 103 ++++---- .../java/org/qora/test/btcacct/Respond2.java | 4 +- 19 files changed, 943 insertions(+), 461 deletions(-) create mode 100644 lib/org/ciyam/at/1.3/at-1.3.jar create mode 100644 lib/org/ciyam/at/1.3/at-1.3.pom delete mode 100644 src/main/java/org/qora/at/BlockchainAPI.java create mode 100644 src/test/java/org/qora/test/btcacct/BuildP2SH.java create mode 100644 src/test/java/org/qora/test/btcacct/CheckP2SH.java create mode 100644 src/test/java/org/qora/test/btcacct/DeployAT.java diff --git a/lib/org/ciyam/at/1.2/at-1.2.jar b/lib/org/ciyam/at/1.2/at-1.2.jar index daa4c474f16095a4d9f8c7c795c632d748bf457d..f343f85a813abaf388b920ed2f2e4978dba3452a 100644 GIT binary patch literal 143497 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

*J*rDDBA3Q>$ol4LGZ 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�!2wA8qt0x{A zQjnsp=0&?1*l2>g{OezX7QR>54}Xax)Nf`ge*ye+1Gt6~clP4_6`%W)cJ}MX`2nSO zM8@o~BBOmQU!j2Cu@sY!k@^_)knu<(4L3!d55A|({3(kmb@+=d9*nIj z;HMfE@fi|S>sIg{2u}JvUhY7y_d6z0Wci!}N#FgzIoCuG>1rK6M856vFk@rEK5fDb z=YWH`!f~>_RpGefb3-ZPfaU(;Z(SJ@Uj{wFcBV4O%UE}?IzBW|k!#tk*SEj?_#G&m zx9tU1$GxcG2sS^3&D+O-fQ_2j+S$ydc*{hpQoB-((s3xfiIy^GD(0FzNdf=Msxl(2 zC2-7jLX?14HDjENuJ(y%Pa>Y$z|(hLeHqJXMcVTP?HkM@IO^Z^a>wJ7<<2G#3^dq1 zq~`bu0VSq_0l=uSokSwhHuD;+9#=M9F!l=32%NQ$64u)IX%m>9vvOFecIBHD<>Yw| z6#h0k^;;=|U{Cuyi&>nvC7`ymqPqTH;+X$O95GdW!Y83{4*O=+*^&SOU%c?uk(mqsDP5;(d4n>hZz4JUBrkNT=8 z!<<9{4GoRpCDN?XT_^z>WKkQ@F1}DLLTMxHj+YdhmQh6g=65&1lfNB}^7<|yfjzG^ zuq#iRZGYtc^lb6u?Blx9Pk*S3_xL(D#%1_3f7Sdn;2Jm0su~AtT3g;shNS+?D|QVP zC#Ujf%D}X1p{F&c`-WH8uI~2+?imagy4OIogL1x{rHI%&jcWRPgId6>pI(asE>Fco z4AAi-=!?!ZKc>1qE--S(YZV2VaoL;tD?{n`?}I<;w|%m_UA^lW;`mb>#g`=%NS0Fq z`O6;Si)mGI-7&mOu2+6i$JsrDD(jM7iazI^NGEzbJRK49r|PCKw>A{6<|*Ro+G;pD z2k(`~S_e+qsj7fToqkgvscPbBlB|1h$%Jy3)Eq^8U$RG>(WY7jFEZ8CLq@6m$?l({ z6g;gi06vz%8{<%@_Zlc{DHZwzee2XM?t~|SPFa!W4h5IPR>4-mngs5@<(Np21gw6T z#C)jIGu}!#w?Eoxv)(`dqm0sn5=U>8@h{+mX<%h!`;+`@uIRr5DVg=25V?q2!+!3tB9idHB1r>Q zMzM-ORnP@Iysp8y&`fGL_=WB^j?YV`iZ_i1{Zb&iE>0qqg}Ov<-@+i@!Y)D+W+mm7 zr8h{@?54Q_yGGV%uxmtblG|M)2fIeW&0wBq3Zj0nYs_Dm4u*KLq@^1< zU5&(U$%ytX6kA8i(0VirJW0nskkwmsD#sp1<_vAbdVL8LD79!7`b2Wdr7%6ITJ9PQ zTW5H$jwlbV;A3EZZy9O$Ro3Zr_|EfDaIWQcm0>sVZfhHx2D|ql`stnWwYiYD-B z&VU)nAAZdv2E{wKJOLf6t@w~YhRNSm;dKZVbKv3y2`q|(6-})$V8_UQ0D>oF5kG>H z`TWk#<|DTN7O|Xf=mUlZ5;|2W0x*K%lCrlMnY;<}QM)OIwU>-b@r2C>)-#?&Fi_@d zgV{HlLs?hn5fH3-(u3Wr-D^WBO`%O;lTIOz1yu(l19)|`$g(#sh+~~j<6s!Y$Hj;N z<;=;>e@|u!C1_N%$7A2=w8y$HS$*!^PVy0cU~&D{lMgnG*F2F0tg>&kWwJG^j(30i zy0(#RpVTFe672pyw zdcnK+m2dS^O48*|46BJj8l*-o@lZmXw$W&EVw=aF5iF80>RoCFxykr&I z0okOl!HChRJ;xGX>Zj_R>d&~zmm8F5mbWfqiIvzB!vSkDY$Ez|8=12RJJu;jyvDe^ z8v6*I=&@b@ezFZD_3dqi zddkwktrPV0s_6n}^_@QDz~x0^+Ey{&3Xjquy18=;$Z*}~K%v@UN) zuNH5PvDQEihEm}J9>;y?+ngY9(ajGoC4@liLlhF90^GFK$xIi?Or>+IyjVeL>|(M* zT-;BSv=&>R!#n+>j#2Dr&QFJJIF34Zn)o2hE9RPM3-+*^G1&w{kF!0dm;6 z__ZhQ1kc*w?X(V<=hKMkUw*3{NM*tTxex1t;a=YP*?8OX;>rR8-uY}wQOZctAjqOQ z#dn0nMfWUAKYF{bbW%wAIz-lW^2OkW$b6H?a8-t;5>UDNm71cZ2-*?bCa*#r4pZAy zKfu`ZrNHzKl{PexDJ@nuB6})kYhimr%fOiLkRl^~g|DIBb}E3|TwrE~!)d=?$W0&@ zoaLO(l_x2aHrCZjpmoENGj;u*a-B|^j8OEHc50+ zA6$)@UUjVB!QLn-EbUaUT{FUqLR({CriMmc3jOWS-TM1Q zO-FE&CJgtF3h?har&4D~0_C))q3OBZ-R*o_(%MZXhZjg)vlDFBM}^L159L zh+qSz`B1z$B%!`quTen|-67ugDw7`j{_X zxp^qpDhwrQ5{d-gE(~Q&QH3~`)eI$JLAoE*Mf&K7N@?;idN}4V_%$yyFpSC%`dfl( zgOK3keSj8T_msVfec1M23l%JXODoC_$|@t%e=rx2uJIm5uzSRtfuj+U7(f zIhe_5hSU@mU%mPDu-@fk=Jy+XrR#vR+BY=xPCeJ%SHud0FlgJgg_v+7}5#DU7Q_)L_+T6KI z#3*UZjshFXo9vk=v?aS0JrhTP>eqmMYDyU4G&=2BhZMyw`J5lJs&8zX@xv|^@jD*l zjf)aoP%iP{IWa|rc=;*EaJHF(@8rrTREVXBd-7}P1%if!f{qPsc@Doq?vDDF)Kgj9 z2t0(J=?|aJ?=(s=M{j~ql|7{%D3nWi{zj9pUJ=mSNB-i?Hor}v?E(JSy zw2ih1spIfb`xNuwe;V+o%wWu1%K9nsEyO?kn>7N7jUNvu*US zICjul19=GBrWX$puQ(5SL>S74k3k74Tf@B*;Y4J2y~VR%c*T9F@V@ruOHKNV?>Bu8 zi8Y#hL|+;(J0AoeFX46L-g!*x%lV;Z{M6g1p zAlEqdidC|W<~s?}b{dI*s9ypzy3t0_Ahn9kO5O>aEt(pJ2?nOG%sJ3Gvq>jp&6JQ8 z+7F=!b5t7Pj3(`NqSYPJX~^UunqZLj;D{ubgngFT-SS0rEr0U8Qqo(;#Nhzula@@m zk~r7;%ip@Uvra>X`(VGq@s9(49RKd#W^3qy{R+nGc$~Q?@1TK9AR49|T5h1LO5)Nq zDpMDQX=1-C?JWyILX8QKlRfZiC-GM;80f zm)9BQcY>kexhN=P2YuKk{Lvd9uqeC}*v8~$JPa}m|qE0OZ ziUU=OaobE2`dpaJhQ+eOOr8=&42XbpT+Yp=ZcCiI-lwiG%%$geLOy!^SU>8NwOT8#ZM7KRL5{!UCO^pDUmAYU_*NGGU`EBUb~}yye%|! z{Om6)A(ghFOTV?S7j`vHT4fh2H`pU= zytD=1@*!!}Mt7c5S8E!Z;m02CT)}1s1$)9M8%5Q-<-F2ahhuF;*2EGf`6<68>T?B} zn@P){O{fGqxG5;>A+Vyn3I|*uE!a)ieX$O2%fj{HHd;1_*gp(8)lR!YF$N5pv1%{IFz{V#^m=_0063Pm$NNj3SFJ$^iTwNgeNsPFA z{TtbqOrs*`WyoE=?%P&7@l-bQYjkBy&KZr!>EkUYDS$i-(;W>f;!zOvr8c%&RkB#M z>x*h%_!S^=7-lb7fnT*DPR*k_&z`)Be?Vi@J zE}2q%B2L)vS9RtmWy%SeMe40HQV9(?NAQ>ebu`*0Za#P!$sJdc=X;KlJgvTp$d5&) zMB==B2d%JId2%TSR)%jvQcE*Kr55}O#4v|KqK55km!k8;{~Pt>!h_E8z@{ehpH0ob z`{3D{|1E10tJ{K63mHwn!%xEK+B=BSpHrqyrW)IJHIBH-U~#n`o&mM$Uk;2eupgTD zX{FN;?B^o~;~d9sWN%K}AU+{QpwJ>F{6RBX9n2GeyY zUsC=$qTw%dZL^*yG#JIJ{je0P*G@&6@B>$l+aa~3oTM$XUAEaMmw!}6fRo~)0a$gx zDR!cA)+KN9d1tR0CKeNkSCbR2!iFY4udA~Kh$NGqP}V(dELR3BrXxUN;>Y}il({fn z9YYADe`6c~q$*O-TeF}M%rvza-xESFc&3$fG|NF!Yr-iK0QppGC}asA8Fe@WeN)_;cvGI@!(aC{^hEV_S!u4R0*f-c%_ z2M~ik6xx{N1w*EEp5=vp`A%M@K9#3^pkjNVMG#HHR2vzFYk}x(0ZVCtt^WuEyaE(m zF*5A(ozP9ZgjA3!YNo`AN0QuakQQ3Usm^`%!Rgj08BAQ`+>$U+v7N>N`c~b?sy2mv z&`+hMI)ag;HA#3v<)$5`L8{Vo=L-26o+)ua8Pk~->4@2=VV)LQ5y=|RE=K=?ND{V* z?rN+mA%z|Yoo;fo5q42ywCp1Pz`yt*QLq>`^?+MZca^R7a_;X5qP4as$28aoA^o!v z`u8UYaB!mrHbQ@b8?hE?QHe}QO5F;0kb(@zR+XLtH4z-#gjj=){t9j^Jh+wp1KGz9 zcy!nO1HV0soE${^-%FM5pNS$N9HeD`bUOoo{khI+%v|?>JH&p+yx9Y1>O~nu<7%=) z-V~}u*-Y4Fr=4K#jiZA>K`UEJHdGdw5INsNBgGOF8OcYtW(>E=Cg0OOhN`3~wAIQ# zvunvWyOjdBT$lH}p~+$J8pN{U0SFT* zPb;d9GC1Q2icKv`Ssg#<7d*FU%A~zjSmk6;`<$brJ<9&x)4Ne=UMG~aw#Q+OUe|Ai zQmVoEx#6%5`SutvML6OTMF?6q@*}zFklWI~+IdI6virDQhBwcbB-687Imm4uw8@yy zNT9U!1y;RUsjHEv-I}6bX08I!x8f==tSF5yEGT)}IEx$DtY@jJ4e#)PFVk#GrEYZR{TASTPL` zU^_7pXA%Oqedg@g=2Vr?kWlCY)NjMH*qRrZ+&UN}0Tz!Ja(~*feMIh#K330XtC#P=8MiChR)}1tVKy3LT2sGF0XAO70!OS%tZlW$=xp7 zawyJ&KL_moOpY^o(q{ZELfZF5qQ#LCS%m_*JnjNN!#Skg?to0BCc2U#EaVlUXGcd( zUeCSki6eV`?V;4qjAtIpF`bWa{pxMpTQ>H%7x%|0Y*`wIY>}eLzLFQJ>RSWBP)rc^ z!EbmzVdkEkG0z<}G0kH82w9x6ltoJ%*`l+_6o>Jdw9oRI3HS*NETcJJ9EGP(7Upv7 z^#p(EwkR@N0IrHBrO@Gk#fP)v;ex`M3QCGGu>HY8K>}A5{Sm-Hgy8`%;DgTl0>T`L zmFxUXGAfoGm&D%3C0c#axCa}GL0Adn)I$ua)g}ASXYXX0Sh#L*0`*ylqG6FyM?<$j z18}>u%}t-*-NJ@^P1_`=a_!A}CaTfDu!>$G^r2IfmJ09_f7e5kCk_hZAJo~(^$n`N zqLoaZa29zam>I^FrZX}%^bXIu!i*elq@gv+ukZ~ny2?&n69AZDAbKHBs6k@K6=`IF zAm!2hAhdGx5U|Ne=Rt#PV`Z-M%@8o!gU?e(3crY=U?4zi9mX*y$exb;00X7Fr~6G|%eQ1XaD zQ|ramkpHdHMLv6W>GlI#bh2so)cLE*oZp!>0@fsJ`r@LM=eO0D)3YD<`$kFuu+C6? zbS`lhUnz4zWmacyy9<=UNun8e1cl52wM%F5Lk zeNuG1H5D6`L0>7w9dT1OS5r`x_RfDOq;REcC}Q&uR1YueGc_jYrl4ljMd^#`tyMpW zwzu?WQ#M#86tFuTkLp{-l8(c6$7!+Z!|RUMHnH2V&F{MNe{0Nk)#dik<{WFgkzbt_ z=xGSqk0Q_4f!|cHoI7kuO3a;0>nTh0SiKsI?`dx_YaW4p!h}ENT)w5E*EjmMw)c6J z+=a^9_Y3V_&56n{{u%m^f(_QMR)~JXf; z^$);YQLD})DKysTqMj|6{5)$>Z#++!J%MA2%8TsNPDY|yZS%+S6xrqYV75xv8|Ctj zstR$oMG_%>+hUJ2Oe6fs@9U@Dv;QCp)sq23D1k&kiDaS9fzwoFV&Ggvq9MYtfMAQq zwL#;|=~ZI4(3=t>_x8OTjfsF(gT=6b$9(FiF*S|AFq4aNzWC%@$Vj$=*giahMOvOV zZ^55}$U+rxU$)UF^LvuYr`sn5%L}+dV4PxbthgQ>PMtRLFGuxsf!Ni;Rb(rBQGzPM1 zDeA|Fqayr$>&~9C>Ti-ez>FX+*r+q(iTa=RlL7LCC^9K@vF()oOqsewt+MMej^LCh z;ImF{AZP}S>;|^(HE_-CkJ&+&ZteJGCr)k-{fM7M*+EQd5@win|6<+r<$Z?l*{3Z? z2jVs1w1+6KL|^6ihxELC()YOm#?C0NRI`h9UzMo{PzJ<&;H=0lFNnyVmDyzVaebo? zh)g~G6djz$kQ5P{1nSK5E~WWfzRp!m@#dc8pJLYuiCznzd|Zx)e*=#DWLs^$KS~zz zfR_WeCSiDK>;fGjPeaZ~^RCG2hSu|>UXlH-?s1X=I!k_&_wRgTp*}ZNdBrWiGE3SK z(7vi#i2K%=e1BQ^d$vj*o^HvS-Jn}9q#brdnoOepSS+WE>m76yPnbx8#HMNt(>B7Q ziUeN0?-!2BNL_U95R>40qZl&_+?gh66P2H_Hgf z2^!;v@SI$_1M4nESJXU~Z1MHWOke!-CpK0*vyOsHRcMGjr<6haiWjHl!c|(+*}<$rtW9mUyKod^Mf$PyAOif zj3N2VvOnO9iYN_VdmzOdPT58~SevSU8T|^GCL&p)`TjnIL`TGxP19oK(u^^jsf}^F zGMYcwNt4@spG%ACYag+ivt*2;b>icdsJ!p{dyI_ zrsYS`FnGVybf|B|4UfgDr7{~3q*IE}=n&IOuQNl}r}6SdYkMVv`4`f!DFrd{4qn!5 z^NX`f*=+xDHb56%qZg;e98wshPKH_|z-N1*V{B4~-GN$TZua>QHbd7_Iy*ck+c=(& z-+L)(+cuqdh;L;o!F$TM)nIQsQ7!7T%n!mbQyl9rCTh-dp2%At#~Qq4?0A%GWUoqIL53e&yX{+%|yLThOcyzQ=*|WVbt9Av0mv_Jbzg5Q8QTq+bXv%YCOHqT);` zGOUbS`NA!a3!@Y`v%tp2;XPWB5x-qaR)qPHmeMs^rGWJ;{JV}%OPK^sjo}C5h<Wm z-_UL4j)U=_%*$z9rO?>1jy}7s&++pgzYgtViGg(USX&sU&u|#85g0KKe-x>>BGGrV zFbyh*P~iZ_OIl=p7&eUhv5uSDn>i-Mx=HIBOW%Onp~aYGjxVNQm4U!#Skaug`wt6Z z^YrqwQigFI-4?HldyB-&1sh*ic{ulKI){H$NGPTyRNOsY2bJ)@K)v#ul6=9_{X~ z#v*zkPV1D_29!aVgjaSZ6;kYeUqkeYUg@YElsE{A*P`MOE&hc;b{}r8P zEKK}cu;@Jfv!D9+=e2B29j8B0v!(6xQld=*R~$$VkYq}2vs!@IcbL%ba$FEKlT>8U zoW8O!v&4Qj$`d1JL;MtUz`TKdq5mEP3BUG;E-g)h3Q6(aG?8Y1yr5JpF{rwb&#Dz% zYOR7N*Y^Ybe$#$Qb=C_GbV17I`7jUy+#Vd-H76+EVXrblO@t@tQ%^d%{Nk_bt{XWn zeN<5}E>o6kg165(x*-ZFb%D?$0bJx^vN`Cq0BYeHs-g0kE~W&?2gVN@ZAS5{?Rl`O zT^NioA@8_0FWy844TiHbW7O?1>dF72bL-CDbF)aO+b2v+wQ91H@t454luIcVo&X<_ zkQxRb46Wh2j@27Y)6CXlTbYQSx1VVw(EGghY2)Hc&;c3qymf1WTCPU(2tB#t*xgV( z{qGDrA9ngD9q8{G1J_cr-EF)L6r&kRTlTguP?F6PuybWp%{qDV2!i*0!Xi`)C&^xK zU1Ir~DqbDVm?v;u7lX}qkGG=*7yO18APDGkZb#)@Lp&;KM~ja0$qp&S8%YRg0br+?mq$6cP;7SB}e@f5_rZe`+-b4Z#!i=*> zakt@+=rg&2Hda6IUZL+I&QXS*<{k<&c{%twVW>IchW4x%l zP646FDSI*LH5EB#tg07J&HxYDw;^sqIUCz!s#te1Np_|>;@UO6KW(&yJz36aGT}&{ zIQh5CmDYC^>)ONpr_%QkBYYkzX9CTaYcI2wE1$fx`_1UWi#S;dWofN|1wl(>RF%KH z(%~dY)h0O(GKhBxbGn0Qy*~$@8Q->Pa1#E)KpsifS$t{|x#!j9k}i^~bTdd<6aH-J zOxzv@c+3fu4LIUcW<=wT!FhpbZ?$@kjg^+VA*Z>AEGMk%A4E99UXXF;CYwe!@Z!42 z-~Pz|RRN+tszmu2(t}*~CQ3RUg;&J;+XL0~>gm~`0Wc$`&Z*_AG?%S-%phCdqPCCx z)u^$g{FO^EWALgJe~Nmf9F}uR zY&zm&eI&zAv0OW)H0e_FLK}SL^Yp_fNt56c99MHvit2O&e$XV?Lsd0AwGWN;p-7HcN2wE$S zYB29TpA^bx_+h3|Qt=m91OylOyL!z{U`x_GWb3hr>B z5!m>@W-C`R*(UQQM+?9|eRJAa~5Kcp2-$ApE<8? zl6}MGJ-j!{uAA9O%N+-emvX3sOk=`8x!cx5D0JQfFT=;)OxMTp`_l9G7}pOczJa9P zqjnz(*)zMWFTTu2`R~Y(A%sLjGOq{>+~Z6m2_=CyuEowNhM>#1GHbsvU*yOcT8A|> zsTC>?gAeA=l*5emq*wz%_$3E5OTCVIg^P^#?r1_$UVk;ZikHBJ@PeNr9$1zbLFeFlpdz$rYgyD@y)OUc6LLIRV#Sl>^;g&L{ph|Q%TcTR= z0Z(XAHmM76$FqP55%&g#F;I|kQI)|>g(0{VM?IDFm_7VO z4#n)7?Ssn6p}3NMsJNy({o&95ef9f?6Cpyu0GItEz?}d2nW?7tuTR3r?e9fHAfZJZ z4VBe|BeBJ4U2D2gi*ngdFFF&$oUO@i{(KS+NI{|74i@Sp-fg0`$dv%47x~;RzucWP zF0cPNKv5n3i7-2D`V#f4J3p|LJj{lWJ?*p`ebi>qF3n1snJWTuD#jq&8<^m_Qeq z+BKm$;8D2>wq5h+xcHni^4oG}5xgq3kH4P9$XGhtnLE$jlJx+I=;HI#SAH4sz&MqS zP@nw=L9wFEuv%^OyUYI4YI2z~%Ii1iHJ2g8(@Eq;yIC0>S6T#%U%4)`Xk#p&p)d_N z)WOGg>T43TA>LM&C=A-Wnh1v}}|Wq1VK8eSuQowA)*vBxj~gZay-@(WFf_C27hFIDWdjOmQybTmr&h z!z{Wb@y)_$W$_O69V4}b-NV4h`H|t>;rf)d=a3IhJ*cZO`z54JZJUy>-9DE7w-z3gWjgV0WZ@HS_Y_(@;GU| z>ex9Zx>APVJ-oJ4mjundEY`_F*Je4M&01qx<{MtWL`&6|{;R@Fd^rn|f2lB&_vew0 zu~h>io+IBg`N0fywlvEBQmlLTFN&}JP^|lJiUqm#jHQX(z!Ynmvf%M=+~yH`Cxk+$$fz6mxX;#qf409>ePYl<~NgArs`TZK0RH9uDq_glx_a%`?qRy-|0-_NsVWreOC`vpm@L zNmF_;P4rVSEScJ8msBvqN??RXcV=mM6+t#u(pjXBgry^*FfU^G!c|-gf5|Wepll@W z6N(sJMukZ<>Lbk}e0TqWpOB^Rm#`Wo&fhe5?sQi67(mAW{vAzmpkx99JIasPh3N$@$R%?2JZ{Jp}=Dd1n52sBH5H0z~d!JI61W z;LD-J>_sY9dB1co!yosHf+hxF1Lt0 z_egW}Ouwh2jU$A5AMu&YD)oUv(j*Mpm0lbym=MlI5UN0r0rP$9db`ZR-8_|1f2ijK$=$1 zN~{luP4bBNe;9iUpgOj#Yc#G{4c~w+F_v-H5MUS3)t}*8t!#dE5?3tnPCkb!&`EZ;!?veDeoPC*V85CKv9d*76Yw-m@H;R-r+~5Ds0Wt z);0PDnVFK!cR-)y2UuVY!@F#2JZ&929Dg4yUS0}(<`tV3K}M9}?J)vPA*~roN$yV% z#XOLqCA8C@?m_|drIO&)PcSAHdu@sFUVdL5QcJiT`u4F>(&QBGI@XmC6M{Mtwbj5% z?}@GBGS;MMXL+sMadY}Pg{dM{wo5-DAJu1gUx-jVqr}aJ#EvJ+on}(ShbF$^J8v<| zBrO9Prbkp0&WwYqDdXzeVRsDWjh^ z?E7KYEAVWAB48yulBgvuLZ?+EsRpm9z=e{(5Z^K86MzdPZ{_}8C~1AWgl=O43&%^= zeiGb4*MQ5{)IIS8-VQHWOJ%bNYmUS7m8~5V(4q6CsBsSHR?7iLtKkMzMVPLzw3H3+ zaVxda?>MuMRSTg z%4}Jqeq!c0Az?-DuCXS~-BsWHO%FFtITOn7@_NqOCQ^)DHoR50vCllHBUlX;ma)P;IL~_ z$79aV$7{lx;{r2cDdb|PV9K&tG$r4Ev~Sw z8}+Ej;T2ne%~w2T)G_*jzHZdbsOgf&38g94Q+kt2PPA*XcrcFri0j?)DYs<%3C>hP zavLpe?GYUBaHK+Qa(RNwZog^d9AZW_lBWLVt51Fx63_m)Uynv8(ewen9oo7eHS$dm z4DazG{gW_6>G^ZX64FCq28#h?*jB{c9~5iHe%SUo{ze>S2~h_!7&M(ci=ZAhn0c~O z2{L|RW6JQv7${ZKW(^P8rbpiFl!mg%(NqD5z zSS&Ox_t2}zjR5hhwAu@!2;9#g4Rv)Y_VRzssahd6KA8Y9`u*QA`X9lLk2#ef>U&}V zd_2^)B3iS%kj-Z*U1~{O$bv5Qk4e>?|FsFU6pjr?hH>Cit=APZ{}bq|LZq1zgl2rh z?B3b$36svt^!LB=Q8rGC1+~9o@*6o!f=RlZkFy#^WO@7dT=q)p#7j8o`&1 z{r;tQs{?8tXUEO}NtpZwUG;jY4+U24dQ7Cd$FVHM&0w2s0n7CHSk|!<*);jGn_CgZ zZDRYoOC#6^M%g!~St)9pb&H3jV3w1-NsDDIj;k-hd`hwHv2LFD$(ki-;?Hy4Y?BQ3 z30H?+J$_Q9P3hr?cJGpLMeZD!h7#t1+?8Eb>Y zE;jkQX~$+Q)I0TjeaXm>^rFYGV5#b#ij3&he6kimkUIZ6NdF_`q4c+Of-pU-0of=S z_4nI=R3uWj&++Q$%F-kHP^x78p!=g$aKo0&X)8c8aaw}5za!~#Z8o8hgyQl!mv@V& ztn1-lk<^+n%g^5vns&zr1o4W|pOu+J{p&(kjlUS&byB3$91Y;uX&+QzyBR$xLR-c? z(t2-n|3=6(i%8Qx3QV|WM_d7ZC8VeHwilfU1#R)5-1~y-*$d*E%Ir1PSWSEnOY!!~eyet)J`?=Xmh$fFzo>of)oGQqDx;{Vq#f!d1Irraxdg`osyHp`jrW9D z%0R<5x|`)8I@51OTIOaM9y#CzO5;?~>~9tx3yh>2qk7Xo!30r@M(wPEY8vHPK10k3 z7`=9)H*x@zA)%QeS`<7l(>3ZOTv>|xAi)2STvtENz(s!3;zF$j3;o$^T{Z4jyLbO+ z1AWSpEoj9@Y&Koe>{&~c(s`)m=X9*R__{@$;KoHfZ==p*)1A9pe$5*BR}aBi79Kbw z3@L2~lfZ7MMa#=%1LpR);=9&cwd2c+Zgbb$3G~Sp+_w5K&bQ7xvmYI-@KYkk$`rh^ zc8*j$JV{{%hMV0TL1hXa1EIk~TFJ%?L?!M`0ORDrpkN1)uT}wN6|TriKVmX^oJX>0 z+9)C)G?w@6Fmv?wAanG-v=+tw;viNbCGq4-D7%$Jx7{k}fYz98%3n4f*H2`gjKL$@ z!_lA-;sNq5z8C2JjvrVg?S$gVw-V$xgHMw=$q zgBPDwf#bucy&YP7v@wxXc}?zi$wyTn+H#;h@@T>Qwx53JyOqXO@tuY3m`Wf%_4jMU zHLvif7XI8&Ml+mhf|yq=(n20_j(CwK>F&PG3yg3J_CSgJmyLlt&+U2C;!93X$mE@{ z!!#MkwNiO9vi^jjBq#p|575-@xzEb+=4E)U*{SNq8ee=D)k(>84b3TP8<_@6SKDg} zHa&Ann5siPofv}ahfr8L*zs-d2&QL=RxFOe4WwsNP)d+1m~b;Os#EiQmX1VL2E2J0 znGB48**&VYw82W>&j_*P`t2R22#?J+8(yL50o(%{-u{44;F7rJ2xN4uXN1%(*_DG8 z$W-h5?3io4`??<{+2k1J4}7u>&DDANJ_W8Pm=lTMHkkQu>7(`bjuNgWOgqd662@w> zGb>z>(o|C8M^Byi{ql)eW5$SC6)a9XQMLW1jy|2tr{S}zdh_QpGM6s4h1Ba!s89;` z&WydoGu*DCI~fd%PNnGP4flXNT(wG54tLTgvf@{L)u(-!H*#iKBhw1y99rU?Nu(C} zaAKnQUcx)}_Z=z(wzDI841g|7Erp6}o7$GH8KS+5yCLfNonD?m$WgW^Sa9wtvF7pT zvr%F1w-n`qeVV01uz684nr!_xL5|_1m>JHyUmV-j4se48q%+X5Ckt_$!RWCsQ*yZ6 zJz_oPt4mW?=m^t`Pk=d$y6CxQ=(k=AN7#_#?|T$8y>|_Ym7Uo(unn@k;xnAZO023= z7HuMx*D&4#)DZB9xd3xTGhA@Uo4u!uHy3;%aiB~C{7N|fcM{^i@+Atuyank!vH(6p0Y(vx ziZE;iJ+Fh4d%>Yg6EIfylcn5y>m14qM~314yU?pf$I)MM5%Iga`cc-zPBU{32*aHrT^%O7Aud+k^mmDbFGLAJ-(n4l*E}ZW-01dd^iIvuhJ4m<+j=FV zx-CTpfkwn=;ttVpQ9&m+xzW+Vs(ZIpgDUzDhrjY6g~ql#6k+C}+;L%7DjJCAD}Q&J z@s~O1tV-npi{sb_5fqaraLXw8?WaEE&9=;0$dMLx3jR;>6 z$Cb7zQnwEGeJR|=*(&8)R@MmQDe%VYi(I7xFIub30sB$TVd^)nBO#|TMo&V+cDc9Kq3#9+ z2ZjEE2s(_Ys&V<$0+#XLowk5_=*^V6y|$v?H^vr8d6O%7D>YmLuxsHBuARU>QPy2 zHY8$b(Tr=V;xTxFH~%-(en`gj(*6H~+JEY1|C{epi2=zevJpB7XVNq(st@`gBF;hW zMxAZsR@+VO5aoRn7*e19GW#$?44|{T{N7l*Ip)K1JO2K9{|4z-Si~uAwBD_==2+$b z!j8Y>HWcmtWk|5oSe=@E^VUcg%YM1Et`5ey^2B|^ptx6mzPAGCtPY`w+t*8yz4B@< zIP{g}?)R_Wzp?fr)c1ik6@n{Dllt)rV7>!uzzaS z;ed&gq~fUxe?So>2ANxx`6T@X5$k&{l>n=3Fo9J-7RH~9Ew;rr?&Fn1pDb<* ziwe7%Z$Pnc|}rJEF*P-{+5Mv>Mx?L)VK|3Nnhk(Zj8s{ zxV=O%3OPj&70nKrXj@heQnHgsKU;{jvM*z4uHTp%klF3eZgzE$Drh{LQNk3Xa2h9t z`tR6-tD)dyEHZ{D8}1Kr-%c3$->5f5itaFCZOmADEtgj&XEWS~Y-FPq%O%Tr<4eV~ zi^quKb4WuQulLcR$Sfe+Dy^8*(A}UNb;dGQ7}9l1r@11N%7CZP(r)+5Ui@Pb<)Mi_ z{Rg?a@vm`>{XaD>MHxF_T=QP#bTERGky4_R5%2?%&`iw~B!;RFQdXCw$&1JJSml;7 zv|pI^&>8sx?Sq67&3Og-r0_$dky^rksmAqT{46bn<8S?D{9IsYTSd6ost9ymt995M z00peg3PL<%^%^$cCuUt~4w+OgEKo+gN$JvJB?~M{Xd~Cnug!} zzoCui@SOzE!x-kA%kb*@cW9&d2yNYLl~Jt^&6|a?E2&lrc4-jn93<0lQYb}5Q%kt7 zG?qUT`V?Q^%;S{Z(y*;EpAF5cmcFGtoKq@vEW!T#pjWj_Kj>8-%zoAg@9YkP$KOPC#jET*rZzl)FwaV+ zh3N#LPhhA|IHK+jP>oIu>oJ9WCdY(4W!bFUd!gg~`E8fNZ_v%0C>RIb*03L#lV*0| z?Md&+pkgY@@f{bN!QD2m;P{MXuqVkBR)EN{OU+w43vB8$j=X2lGJlXF@&qVMe9#bY868y57}q(6ecb29q)Ih(pM=%wL1@tXLTSJKBnpBS2U@(nCQ zTIoviDw>e+W^YN8lQbRK2p-c9%_n;BTHIVg*?agC=fMk z_ZElQz;`PTY*M8d+f_shY@V<6ufl+9oi8Ox%AN2iyX2}iYyGLvg9F^`(!e*301>n1 z@ILK;FBWJEyW>Geeq(oGW`~$4Yr+~#Eha>m=P_ZCq1}Vr{L0?DcMO>KS~h0fP3hz| zOdhv6wCZ)TP>yXU%J;OI-V7UC9!)zHpV>Gy`>AV*hDm-@Css6Y{*|ef(`<~a)89L? zo%Xq42sRW^JWj_i`|T*>r?a&*QJ{#mOx(PY8KYDToOU0s#Sv8HFvUSwsUFh(n{O|c0{EL(K` zds@mr5;sY8I~pD!@L>KOc>k>)S^Z_mhH$P*i3Y)mhP4ImjFI=F5$K$iP7(>nilqU! ziLhU#(&%C#NAdmz!?!`j zLe|*`JK`>7Ovb~%av25|@W1|5z`y@M-pNxoEgMju#)~#x9Ql>h)5Q-%`(}r+zBhwh zMKMl0%uoi+91q@&g*Kf;G!$^M05g_uwL3bfk>43=lyxPNkfwfd+4@4gG|3;qPMd1hX zq^6N>oC#Yh2!bcM&f|oCC63#$NN1F(&p%?redE1T6N7VHKG$|mrjHz{;m9A}(7jtE zc7DD&;6|={I>E|K@&E@wbDw`Hv8T}+qAj?0+5yr@OQU&qv2DM+`$(7Fk4B<4iq!{u zSQV!XtpH{CJmFkndAr0jdD+BmL|Mr+Wj?)2h~|3PSt5+bi03He^^<==FX1ofwfzmf z=yH_*13eWW^kP4tC!_K`t@s~YC(k?E;Uf@w;{Q53Xa8?O>2FsfzBs6v6F|Dah#A`3r|2cn-z~?yW$G^2E-;k@=T^Po$lIRX8E0w zd=mJo8RKjuNt|j;M1jxapeHQDY%>&r-;q67hgTqd#(0J!EooY2VkV8asIcNr|x@_KGrZFNi7`nL-F~~-51jaujhqA! zbn_m&H|}TS8^& zUlBbVb_gv-@0gP#nv9M!oSsnIQfV)kLA4_aA5u%tl)U|wuV+d*Z;~m=@+TKPY<#J) zQgF69dG2>WacX{H-(cjD_*)Hh{?c?DCR6bkVp0H}UDGIQg!%z)BIQZ_??RphBP

4#AcXvP#sad=+IJu*Fs$afA8O2EifqEmu_=_?exk=`#np+A7BbXkuk zsf-v0larbLFW3olcQ4Wj`oj&O)Epv8+H}vaF$$b}1fhBvGdKdE*FyNOQ7fevy3GSxy@|}d~H$Ytjx5lh)AdZ=jQ;%g`@;;ZId}_VBy}UhGt)8ic15qVGYiBmR1q0^i~`to zHFwPk4a-HgKVPl?OMeSm_%&je`-ZMJPvxaA%B^~Q-4y6yGiZ}YoiL(nnVkvsj;tCj6fd>s1X&qtFP>7N1u=!6w?)5P1B|NY1RC(#3 znyy6>15-WEa~&vzXU$dOaP0FU3Tzo%b~J9#atdZh0Fbt!K zNJ4s;O~bP$LcPNjGk{qq-Xc1K#T^M}NGeAtg-hK-w2qoccgHAF`K?kH^MXiq%Jat` zcytBxO?^v&=mj6cuRQ0l>FrSI{Wj7iec~;FR5z+KtfrJH8nMnj2S;r^xPX~dKEE7cngATlD%tN9Qr1dv?VB+Tl^89PlRVO9)Rb7uDKDRq)a=7pdF7%? zTn6=wP@n?+)95TvWtj|{qjDk5&>dh}Q~waw=EBQqwBLoyw!byVDEbnM&s<{fc7#HbKm63`#|p}DEnH0ekI<~7FK=ug2OYs!s7|2VXHdnS^kz^l*|_WaDZ6;D(y7&p7%Tbmi=%1edT4 zf357B zK95(zSmAAkNKYg#omjzI)`H)j8xSolv3O>xy0NG$LH ziG^M4)I3K*%=Hg4RYTm)-B zv^7M6E|;>UjrU3>b(e?%Nz;bEnFW!xZ80ME1Fe|JUiv7|S^-zh76M9$MymWDr$lO! zQ!e>v{d|*%{8#>i^^N~w7FJi;0B6vMDBKgNa1+skUh+}M#i5kw+@bj=nY$ry!3KFw z?LcNB;)66@lNAS!9~a|Q_gnakySH1fXp^EPe0Lpkf!1SKT&~g2uD6Z($t$U-bdnDXHS2%sHw=X zcc36VWXX_H=H!DkW$Zn{bXm{@VX*A*@=#{h$tu*(5-5+tY8j`}J(tcnXg9}n@ zqj2Gwc5ETuhsG;+a>eh?k+ROD@{r&y!l^rr&d8lFw~bE>S7my^Iy1sfDD;iYiT;fc zaz@Q0k85gA2Bb;RYX7E5oe*w;i~<`i(qD{1;68_dL0HEafk%j{v5!9Ytt08*G^sYn zKNtllnrUAy6YXxx6Bg!0MY;bkMgit_O}q*SFzA2JuK!#6`kyobhbBI(4PJD^%bTz+LO z@b2}7Ko}7K1Mh$aJw`45V2I$VGDeb590Yz$U!vZ_htzs zW)s+>3SXU9mS`%_8Q;78-QGtp%z3H|2N)HdWp{af?$}QJ-60y2os;WmmDE~rCl_F# zz`_SXXSWJSt8Y5Q+LJ}YYfPOPe{9R#A+A4OZDGXIBA%ozWPw``S-Uyr9Aznjzqs*O zxV8}ztRV7Qd~lJw@q`0~A4FMB&F&n=!)jrmY(xGd&<%mXJDaj{;1OY~h^&p70-PJU zOsS4D`g}%ZI~fi%E8&*6P^b4@^pUV^6TtvFmwpo--~~qcp z-m_l+&buW*GqOs?^&MHc4{7F6 zI*lmHArr?U8gzWEc#V0S{WQ^MmOIyPm>zT<8RAr^a;V~!*8y{aC?=uuva>8A?)fz~ zE2ONIXLa93qxs2NRH>Dk1aV`Pp_g=V8R1agmOEZja}cq0^aKgs+V%^>@mILTG4~R_ zWJ6Aw#-YSb+tpDu&yF{eoVI?vt_sg z;LsBdX9q2MU^v`tLP?lpPxwIgIr!^9Ho>{`?Mq%BUf4*ey&=_z%yyemGL7=%3sytZENQ#*a6aX{ z3eGMbVT2Nw1*}+@@J@s>c<&zgYADm*r6*flB=>PyI%iI~p#nAy+_DRjM0rtZV_%HX>=%*G5o%FcJX^PKG8qRCiIB4X!0UFK-4ipP3hQ{KgSE->=}L|I zt{V|v@RjqkuZ)++>o>9!JNIK0P)wf@sZI_;zpRpIf8VWoU)G~*cZOG%_uw?Wq9-yR zvfV|8cr0rHpI0{!rp}maEzq4SN3?G{A?c4CpgY%&x{n!Qf4Y1}p7;g{o_6(KukEn@ zjTLS`3p)ZGCeV!;F)^MUB?r@nD`3p_u&IBeYz@CEJS;f#wpt#peU?c(vwojI*i zza9pX@5L#FPQL0J_-JjdabmfCIZSzG21#u&OoiI^53|D&>zDu>Ww()9j8R&-Zf>;9 z+cU2eI)y6T;M~l1Wm9k%_9FYST38d+JbmThMc!6*Q!*I#lB)uA9PibRcwfWwj@ z-d0u9Q#4+=ez(aD!dh25q~a!;4=F?K4q?66+m7wZ%o)hl8&B|gvau?FfUrGBPN@5r$U75k(xtW59Bu?8x) z>adM>vsDw9j(}Db)R#XXDd+N6$Y}lVo${s_R83-{D}U|+40u!;!gZ|u#^_lD4U&q_ zBwbV(T-aK_nJ$i;aR=%92n**?71FP*;aYr){_c1a_5}h%B&`IfHen0BCq=Y# z%kcmCLB2-ejnq0#DD-#(tUSZl-{V-Ga?CAM4mw!1-^?NSYVl`BdhMkLtd-5&lszc2 zf3@4DnWWY{TJ9$!PYIU{hvhl5o-9c8cD(uSn0RxPeWTKx-+!KYh7h$dFwAG9p(^wv z1R9`foZxgfQ=07BOx#IZIX?10dqOnCg_lsVTN2UE^9_qA}J6>+;(0jud8}+hec5n)EF^O!9Wwg$ffQZwQyj&d+(?wBE+PUu#a&7 z7^#7gLW*tTC}ia*rR{7ga_mRaRZvWAHX>Uj;i6R+a;(YarN$uJWmU| zk3n})L@*JTU65VYuj6T*G-hH!oTcYy_A*2Fz6(kS2mqd)e{Io#4EWbG0y?Lwb zE7V*F4wY>8^HsqW(A>vnJ+H@p7q_F=suZWEk9?!8qTZ3OmVQm1k4hQECT#q8+nY1X za5y*742^a)|0kgA;`jV9xg5iafyFT&3t1%ZaeaXq(Fi3u4jPFzZHb9$vI^eZbst49~|_>#)6qq`J-CF=FxcPpagz<%%J+ zN*{4+D0|q*D0mFuL>(T!o_}Tyx1wMJ^YFN5Yy&ymxUozN!y;!fi{l=?ep&+0c7(Gy zfR;g4!^xm9S#c%SY2~M-7@ti)S)PYZQ%4RpZ0d8EnU;1dSO9(Hz`-R%D zkF74gO|(!AJyfTCnQ84CsVogw4msrxvDQ1SJFCf^R(Aw8^r~Zq&Wn_$z6{iAp*9E% z*_t$7>mpK~x!A4|)$#ufWYd;Qq?n-ROyV;MvUsz^%n+ z;5qjn*9!bN^keLvKiwkF@nbXcWANL@1*d_Bascn?&=k0Z`*9TA$2oBypT8QylHs&* z2E2jBj~gfl4*bRi9_Cf?F**r6)9&MhRp9;H{{9#QA(()haSs8Gc1!_B^?*$r8Ll56 zrSmar`0;tGANzNJ`+WmAhR#w5JrnuFHq$4q&P)R^SiR_nbdAM|Ut`-3+SQq|Kkj1q+ixWn+Mm*A*O^+Z zlzmo?FTlp+puOcdC9X1s*E1MEoYI9c<6OS0u9KG3EH0fwg-bn+TdPP8ii$~M7ZfUs zl8jTqPpeCMbE;~@&A6aqucptSE-xvrIw`^ieZJp@k4ZNdHsNf@VOJF<U16{^nRer)nWJG!oG3Ps<=ni<~Ikqg&=VM|LGyKPr3r>~Jgr@!-iPLyo*8iU35N>HZg-@!ct|70a|6rb7~ z8Lgvp?-n>8G?u44%=3lJZsr6oNiaF)IK@`}6iuVdZc?A~ekqqeWm1hwSkGJE03T-D zLJj;HO~Z%XvsagC=ya0VKMnt-+NC{d zA%`)lS=5wZ#tBs<7e~_^sFtt$niJz16@y`0R1(e_b(6f_wj(!RWi4fthx1%q*zWiv z-CjqhXrbLuny>IpQVRABtO7-yuIih9dDlG%2UHn~IzyF^zOsI9 zR|H5J7(47wXzMRc5?jk49I$J|_N85pV3{;my#A|Twis?5U9eymNN(9(;b0f2O;TI^ zAg-`&B3t;8zixRHF&GAEwA+v?iKP!7^ZeZC60md&$!<^8s3D2`eM1$;BaHJwZUmM; z&x2TlKfr9?8H2);}{B6iJWQ?BDeZ@X#=nH$u?}eB{*i6we!ydS}e*D88uh%?+ z4T9cF0lLGB{0QB>Q)&K~23;~mh?jzj*->-hvB&Ecc_ix>pD%Z`1>Yf6TzB~lhaf3& z-jft&Ad%fg$R0jKxshd4A2pLUN3deYj>4Hz(XDOGp72Ym`|Ea z?(QLX%Wa-y)ItN&lJ6-2c93|{`=S$Cuq$UVE^ga^T(u|k6R63K>6Q3*@qd0>S$DWecpix~NtNf}W6d zhsCB`6Wh=z_9^z2&|aQzG!n0wnFDJ~KG1Y^?bk+p5qbr{nXR~%fqIYgi+&L0V@Uj| zt-ntX_8-c;i#$#2Gx1rRRZ9M)Y3-k%bB;w69#RW_6kzqN!Wg*#+Bsi%2C-K0#hhBb zA73K{S6wHgk@G3ymmTI<%h6`c1`)#uN~qY%8Eu2rB;ywvdfb>H`s;gw&;ob3(VfWY z^r8<00jwAY;)TL-b)V?tYqqJWARnZf?F;KL@#Al{qT>-6#HF^;il-9zCgbvT9u`+k ze07F=(9$ELT6n$-NOupe;ki@%>{aCz$>A5@oQm2t_~A-HhMxy#16VLYj{HU9Vu^nJ zJLkN7xOe??<$Qb#WJ`y0o+3v%scVO4W8pkbRu{!*wgg|hQ2X7`NfZ+-inivW!^pR> zD=hp#&6&eg^Asvg>bhhT4}rTm-Rl(;{wM~w^Mv3Eb3!WwY1Rmd&k``W0mJOl4urF; z1(Mu&9C^ZwS#$FX{YOwe1IT_=sfPmi=!M5F%J zvqK6_@l9sg@XZLc%n|+AY~i~Vyf`;_>W0`-Y?c_4I^gW6-15fQz>)ao(mntSE+tz; zbmFhvo?6;kgA~wF8KdBWljJTP8XHDDbrbB;dR=bIHRl3&Gp?!u){**Ok=(B3^+aca z!4}Gv?ar)Cr6YhJpr_Kgny0x&0v9FK*$e*n1=T{QrAB`^zt1nrUkV0Vh_J~Fid6ik;LbCX{Km~b?4myEH^tN@XqO;)w=TDVJ=*oWqCPPjulhb#7(psUhGtqXdC zLTk<)@EDv`y{zf%+9fJSC95mI=Tq-jWrzz;vuLZnqA{l6@{HN6G-dO;u#^ znsY6DBWKkntL;4Ch%>g;wszYOv@vY}Z$%zY%waKueQm#My}%)gU3zfZ3vf}t%@29O z5s_ut>x|p0gSd3{3lvgjmxxYuuzKyj3O#~(>@v4nex+`+w(GO6=a~o&XU7T@((4N;@x&naOr4DFmwoEWaDtfO1 zTyfnNSvO{xod!5hli1rXyC4g$X>8IbK+^BC;R(2|sx4kMaVl9tYkiD*?eL!ZSbYj* zrZjNPye)e#@t($5-+#%>Y4Do8O#=kDu9_`gHPZ4}Qfl7~dod8aEU@(~;_m%guR9vH zaUeU`B04RV?)+MB-%Wcr@t!7F-?L3lKD(a2%>saz>Sle z!n(e`0L7jYICd8D*`@`5s}+$9XBYP;MONAP6sXrrwxLl#AoXt#3zqv&o4%NJ)s2DP zLyKx@`H-j=v(|HXtV4@(JTi!+@{LQI10s-dLmYx><&er3IOe;Aka5Et>S?)<9uAvG zI3e7eKfQYEn_Kz@!>HIJ?HLoiNb22N`XIw|t#Qw{FmEE}4^EMHcg1U&*1O?uB4%X^ z@y2%}8XW=xQHD7rw0_On4|6OJP$8Jc+La{c&(1~LrAEx+r3-bWXg1LI$DMJG4$R`F z%XDyQB6zzy;J1${+ib@XrhJAi2$CKSI3{A52$=n9+6I36mE1egBv`sXU@f{hh9=9; zD;R8#H;}M9zy$0L%L@6fW{Wo97q~&d2D%8;v%0+0)M9`K$ap{mSPM8E7yw)g>RH1c zU&t4ho>06_pF$`WJP8S|YU_J|Kf1OI<50jUbSzy{jD8h(BNANo)~9ZH*b8Dz({69D zUOKmE!u|j;sBh|5m_jAsA<*A6uSmNoVKd1vZMwt3jA-3b^vl32k@GaJAcR`LFOiJZ zulTz;Vfl$M?Yixt$k9 z{Kj3Cwt@R%zKaJ(3Xus92lQ212#o<7d_hd)v ziKU0H3dw92Zmcgf+24o}Eo53fZmNra=+8=8chnWA7vyG7(iN8%L{`_})o1U`PUy;R zCQg{iZYBF5*7_~NHAL6m^Sm>eDvSz$8p;RyrO&JSG}Zy+ioSehfiT>Hj_h9?e-EPV?O4Cp*9!Jxfv|wfq9zmB|Ljus?*$`=l}0v)IwO%s!(@nd z7bm&Dg7n{nepTH{0ujJ?O6igV^P#;0^}mGiQrfZtiGt>(zT)zigXSf!Qu#%8^{eXz ze1q=F#UJ>K)V{LI3QU*CKB`Lx#1(3d9GG3oeB_)^+6?v?!1UA#tOB`7TfexA*k1~a z9yyuLzC)1d58(kp-)ms9?*qaMnr9NiapxX6-o$2MV;AJbEy?3=p2y!QFRyVguca@q zJge}ZU7%Nc>Cy5>m(l+GL#6{_qX*38^QTYXz{UdFe^wGCtgTJV46NnsO}-mAS=iZ% zx*3_+fBf-xSs@~3T((CLHF#36S`!sjD%9rtMaD4KW`>0T|K`T$bc4Yj+y#vhS{z!SGK#b{?3j+Zo|v=0 z)BcfeR}&n5ykJMZw=>^SgNhr)D*idDrawu$CzhoyMcTKyX>Wf?8YrFBX@(lB(IH!p zOrW{%tWx$*r!5XZ{(f8?-*M}M4XWiP-(j<<+WpH+1mqhNRJ`xN1-HS~ z803#QEHQ-Vcp4dJDCI)wbm&^!K%t`|n(@Zf4EGq635K3NUa3fvU=x+CE8ncI`j<)7 zOv)2mNuZEJQ&BsEep?!Ouyx$|)J3KxBtO(F6|2PQN`CnXEzmlL8==gTmB0;=`Ehd@ z+10auwT#tQAjd&jK;8jn8~CHw_AM8zsvfZkB&t3TrGADznlkD#dB@;mmgjC@M#!qa zx~&C z(H|d=?wD)yye>=clHk)sl^J0uT_Unniiy@>WTcM&UUN;tCh`xps0*+niUb;&6ffI91t9!^(zdQ!+zh-t(&W-uZt*m~rp1Q~UVUX|4o@NN$ z;Et)wWs6}-LRY|cB2qEo6a%R#f5otg{y&YK2{={T`^OI@vxJnPpE89eV{yw^l$kO` zZUhi7HT^&38v*i}GEz8q=(rS0bls9wj$`t+Mkq35U@!f8Aln^ypYhNGx zw7jkU@p>XDDbD6sw10$ZZ@9x1G1I)32W0|}k6(|tL|)Qz&`H41BiVP{=;<%di!(n%Ls%&%)H#Da$~;Of_TDu9nErBk<9=7u zog1hgGb2{Dw`Z;G^j6aTm0?ldeONX_6{zdKG&#_^PYOy%24G4DAnmk97_#buS=dX}|X z$?&>Nxd6W$ySG7KNpxqBYPRyp)NIo7Xv?ezazu3^S@Np8^XQVl^}Yr%I|*8yYdy@h zHNC5bc<{@C9)-=@kEoeg*C5s@l3Qi;lEAWoadrX$yn`ip`M7LyatkJsHxd0dS^Bwo z`2G%$V5fA`ZmX3?O;*2*4UEKM%7dS4UomfG-0{~zP-mmvwAsO@(=t9E#Wbgp+Uoe_Lb;EB z#(RZ65epW&qWJtL#lB$$UroyO`>l%ZU4?;(`EDMe+4;Yydpd;vDl07B^g@oM*WQv- zu(+Tsq0sijW%n<^*THVVEppqfpL-h$we;*+C)C+|HMr0+JAbuQuU*lr441!dihUop z2{7z6SGqasJp6szk&=TeE4L~+jEjBmC+_pVrNL$A94+E+ILf(QzU(%i!G^YySGwR- z8JED>ONCy(?qx=U@gKW;hzF+@m#+m@WxW}hg&x+R09C+4w zrK0skxW==j4kwARZ)V>*_^*9<7ox(IDswt8T_+^m>7o4P$`>meMExW9U&?3HFS_U7 zd3p3?Z(4M~>q~bsY8<~5Mzha&QMy%M*O`gF>FZXldGLd4&|hwYZJNXG;_EhRzny=4 z?UkvA%L}W7RIA&Yw+%@@m=Jj=nOW()?XUyr=5Eec`|JXC*j&(xzZ`o(BxgX4Lm^Ck z!!uber;%%MnR{+1t`a=Nns_y~$FU&i!rk+>2NhbCQbNMF?`G~_esNefAzvUX!(Os; zr^upq9Z!MdTbk#hcXQo&nExR9yyQ!Ei=$#$+=1V?^FptwK9<-2(9m%3!nqa3a_MD* zKdjz=MrBBg$ zoUyz?mdNUN83Ar5_Mhf|cQwk&s=1`R*^7nPqeBoRYpQ2IDI!@JX{;Na6<(zr+=&L_$S(O*LtE|KNP|pCQW%$hJTdFb#bPH@ zg_P{qTSneQG-{4G@hR@i%Gj}A_VBJC>xr1wA0=m1d+Pma`-NI0s(I`4$(!#cKJmLe zBh=NVY&zZ8-!40+XhR)1t{jc@xz+<_4XH>?geA0=)LzdCk8=pe&B> zt=+N4U2I3C7vJMF4m*G5#%AU>+I?Poc8EFBEM8s2H z=Fk@%EPZ0;$E29&(h2We1J62^WEHL6)1vmXk^Sc`wryg)`OEl~4{ui%-*KKP`NwDN z+M$bDC)^ISsT(HdOb%fUL>|y@{pZyF7qfC zQ~m?hu+1tD)V*yqr(oZqs# z=UYu<>Wtcvz>s*DHnEu{mB}g{{-E46{%al#iZm8WkkJ3vCCIsuh_-DXjn25gTZ?!7Pn#1u4g%pid>m9Ec z47-)?6IrcOqkOpuED%JE*aFS7xGEcY{Hw`&sK~)jZd1$E7RD zIYuMrQqYeT))*0XpQqTAqxiYv6(e4A{!IBrY7|a=QKhafW3?IE#4LYS zp2SaagGIOWLU)E9DULr@o=R0w^kr8~ajD4KA9*dgspV1A?6%~mbI)!DoZ=wgsM{^0 zeIakhm9{;7t??u6Z7T*&C1{AOqgGx$Z(MnD{F}9~$@-qqc=msMcX5<6tK5;xta6U9`FJc(uCyrAb_?nF%mK4? z#eD*Ek#1ghvwj`qx7XtPq-{4{t~62B^StV2qe`fmArrH}A`V~?T4i9oFn{q9K55hb z{G&N~F*ZUH5a%;>;%is%g`?mjK>OzWRY0W;^8XYw);7`C)7@jS(O8d8K>sk%(ELUF z;rrlk+L#5e#P15w3eido&X-t_IJ}D7v?Bjrv~rw4zzm}!`cJa`8-osWf7S>FU*Z8z zpPLp+f`1FBn~_E5pGEcjJ)LMzptJ!pVq}bjJG8B4IRr36;L%8m0X_}>4})F0K_n-C zj3-Vw}wQn4A~ zVjl*QK>OVQttG<1U|EJ>m{Mr{AvvFGj;!Kk?;9B&0J05KlM(ck4;wU}JB$P=?DjPY z0w5z4$Zt6|Xg(7Z2{MlQ=B)uhW5DkiEs_EwdWPniuiAWW91;{3KUYQDBvJ-tQI;?^ zXg+lb38L-`{^W!SQd@-$n$Pni-6yCg44cRqF&RMX{OMuXYQY3` ziDQH27fK^R{mic}Ji!F@0S#eUG`|rG5;Q%L%W?qpSSN67+Gt|oDxkgt%E1CD%69Xb1o7^m zl^Y*W07(TnC1dTB# z!Ph-p!XE-iTTx!1s|g`NJY3*dW1R{h6{BpL(t(g5F0OaF#>^g&gitoQ=|M;kA5Uv3 zHCO^jVJMpj2G}G;h>sWCPb^viNa()PGKP>KKAte%)K3N^bl)X`rwA;6QiS-pmm_hz zGa#WAtZD%vL3})FPLpdjAnio8_;YIr3F70|Y7+K|W0K_UAS8&7t14=Vs{`GQ0~(`R z;=wQ#vz;J5UL)$6dNqAT*d3ds1o82e8t6p+4aT&(pD5n7Cft9d4Uqd z$AkDMwd(Bo;Sx!Jm5FbC& z|K`bhK)H#si76VJqzv)#!|opbTL9@fisTXtAwhinHRt`SY=A_&4>Q*8Wjurg@o@_- zGvZ>8H>Pxq?gjBO9Xxa~)-LB9gaq;Ne_pI)IRi*bIFTfUA_xiMCn#Q*2E(ql@Z`jV0`-LDxg}{Auz^7Cs#L55_lWw zN0PK6`S^n8carmVFHc`TqNkrPHh&;%wp|ynMLZakp9LD6cz;?N+`Xj@syRoZ3Z|bQ$Y}-a>acGd})W0clh&G{e9Muaqn3%A}7r*@t zau+t@N{BfJXtXV5)-ZUl^DF%KMv9Y${C#S~EiAq{*a{*Y*4WIsQG zwJ_27dzBDunp4W>FgtCgmo~DJV*suZKmheU1Bf1O&f3n-J|th{!WHC9P8mCwNFZR+ z#t@8MGO!%MT?n3dQKKwm2DEk!$E>n7xSAjZ0k0PgY~A9$C!Frby!9AYp1qQ2#5o zb<@&#I7plta#QNTZo31rcsNL)8eg<&8*MY!G7wqB8RYS39UcxAsbpIDL^v^dyz;m_ zNT`}=UXvgNI^!+S1*3g-%D6m;Fpk{P&co)_f;T@5p^s6v5M>N%#fXSw+9#!k%Y#TG z`HF-uHZN)`E)Sv&_s>C5JAi$HpidavZ&V$Z2NB1Q+XpYwM#pF#Sqqm3QAcE;f;x78 ze9^(>LF7T{>dK|b1MM%?y0|=uK3)lTQ?c#4yAzKGn;gxznbMWOv`^dsmj}_u2aV1I z9nAhXZiLH&=%bieJfw}uV>QL)LG%&D_EusM@V&FB_!4c7%Y*16!6Pjao7ZfK%Y*1c zG;E3>2d*bpS`VjjrE@cdOJSg4~Z(JTkA5NPC zx=k>79AFJG{C)|Wd^MJu?lr~arGsn$93Dg;&U3ux*!HajqkbG7L?4=*MQnzc_T31< z^kC>NIp z(TB>_I4&yi=QzM*^cSHExIBnH8XhMz9S6M2C|=h^Je~^Vr3Lbw^aY6&P;)Qi@*w&^ zo?1=k5sPto5Pcv|C#Lh7uHo_^`am8QOXtOw;qoB*KpyN#=WV=!%Y*0xc?Kh$*M19^ z2hj)es609^v=WyG(FgKCFgkDeE-nwE59Fy&be?%FE)Sv)ja==mUA!0-Y!P7?%gp2XYsBI?ucXmj}@Ya<6bY z@1Li*JcvG!`zzCVE81~+5PcxG)1~u*I&pareIU0hrSn{0;_@K+KyDyO=Sg?t@USO~ zkvXJv809Mh_CGR8kupJMH`2-S-w9y^x^+Ux!V9u}dp_23;70gg+S@a18y$CJplh5A$yZ z#{vmEhhw2W1yr_KRBh-60n&U%2fcZIFBeBH`lkbB2+YXlK>_J00A_|PVBAh1b%7 literal 136493 zcma&M1z23mwk?cn;}V?U?(Xh%aCditOK^904esvl?(Po3gL{BHviCXnzx%xZd-wLY zTB>{Ys;V{T8gtaBDK7&K0SEHuaU0<*_t%Giy}*9F1I3hu=%l`gGsyqT3OZvp7ok z)KNXRBHM;nsY$cXK;>Y38|u*R&<6SU$Us0Y{$m0VA0zwfVD?{Q|GzE%O#F|9k%hZ~ z&Huv=;U9JePXA>J^H-;>%k_I3P#_@gC?Fu1|7j{DFGX);ZQ$sbl>Gb^R}6i4o84uj zKxUfsuB#PK@T;Bl346> zFVbgvSkhQQe?md+-%wXst0zmV*7*B`hwi3Zo?C1zJln6gvlOBY*}Bm)tU;z3s#ScI zF&05ClNK?yF%p!4vwUm(m3-CMtq*<+1j#PwWt%0qZH{TeY?E{%a6xSofbwFTG9@b6 zMz~4oW2iF*48;j)%Tit?bK5>yt7oFgId?t8;7o;0Oh^9qEV#*WreZlB#^aj2&MXa{ ziTF5_hdsE-fepbpT#R|R$*ja6W7!I!(W`;K* zFS~+I(;i?^6?D$*`GMy(7xOs?gUgf=z6L)%jtQQ}W^)8MCv@yJ(bTBz8)4tiWK{Kq zHK8`gMKg>1ICL&OSvd*Kam*zkI4XN+OF3dK6<003oYv|hi#UP>t|~$yce})d*{e;# zAvKG&6F}MU-XK;sB9V6n%!e+XA~4~Ershu1|Ito2-hjTQTq=!L+=f)+6n0ejOE4Yd znXt9d z6gg;DVqsp*{FxL?O!t`H%xoOywFLJ7U;8EXm%$%tB-!j%i4X^?OBa2iiu&gOh8 zMyDp6Gq^C0k=Ai)ZFv{Wu&RA)i`%6%)^Ihd#gr82&5L_cuP7p0&fxC56hZr0`s7N;G;NlR2Utuo8ZUmi(w)yT5|+$+u=2tS0H`bx5P4$!>CTej^dyp{wqjVOLO7?xGR* z>Jo#QGbZbLCiR!>UiQ+&%iB=~R&7p{Pcg-%e%{B*0Z6>N3HUBDqNcTN0VthiBmj&J; zE;hmY~jXiw^l{zu9L^QS9l& zsGxo#U(D0RkDYjWwK!+Cy`69q`vR-{p#d`bF!X}~nP>MrkCaQi?1heA3k;dKS0(GD zjaAOqAq`%c!LK_M7ToxB;LGki+(_CIYOpoJ{UssrU2tojUPe}%&kAtF9pZkVL!7LBpYA(7a&C5)JKuBi4Md!dp-?F)CTr+XQ-1G#IjVVtJbfWMeZ;6LuD1W- zNy4l7L-UH)@|M@KpUMkRz*to|d;_7ccJ6^3tx$VGSG`U z)$*lO!2d;QaiKV%H2M1rOG)>~*F|mVXNre~tc4A)Y`-ybIOZv_-?yX9}Y zf4fdv9c%RR2jj*J7ws%59S=^zACox~X$%mvu z%E}G~wvGlyP8MHnmEG-3{?3qDDmH2#8PYo#nH^S3vQ$wQ4!yu8o|tNJPMVl3DmRea z7pf-C62A`>GkLS6U8vUbcf5agk=yxo{qXj|^^_?*5yZr`2s6Xumf3fYqck3$H^)1u z94tdPu7bwDUUMYfvUmLHeJK{ILi^UiMm@R zvB{{<)|Lio2CTQ+SWIvuMGmb4`{)6#F;*r)ev>S2F79EuEc-nvHYyRO?qMBoT#Y4; z?bh*nrwwe`T)Xj1cZL;dmvU&P9+n1VJZ^||-x@`1&!6rBf9vM^bgYpw6VU{rgT!lY@22W!u zfR+B3qbd#Ge^VC+`6;|#Rz;iWx{TqNR&7;qRu!qRvqsIKQtQGFe)XA%^*%JWJub0K z`b9Jt$vZx;voKzkRDvNQLzwY)_Yfjs2o3hOn7*k+mf1AUpoQ4(v`T}gsOGZ~ii{Il zI9$@P>9ID|qAlbVb~4$)R#|=eOqU6b@;$~((v4&8qQ3&MvlxzV5w3ONppNHEjyMpr zC!j;}0Y2EH@v~HvmTCpc=?NkwcfgSAGRG68g(vgEgv4~Ba^`33FFE8`hZ%vEW$us; z;;Xcj^<^^|F&#sY;Cosrz>ZY{98Y5`_Dv|=G3tlpS_8JP@YMMd0_++RetE;HE7uGd^dtj9BQX$x1kclkIAKOG>8> zf+$W2W1PI8p|gNN$ZND2odG?;PWBtn=2=+hxvKoCo>Qv{D`O7 z{&R;^5<-S3hl7VkwbU@c?_guTKIH3;FV6rTm`%js$i(rjljeh?;^^xYv8=qtD>#DB z{OY|MR>qKg54TJXxbFVhhargmsnDBi&x0bDd$On;y*a-?z@I6yM>0IM6G_fas(0pV zzp=Vx=I-3IMxf;48z4BwwjK}7g{3JzWb@LxvLF62W2Ocyrgh@zl2{X;XUOG=6SnS-gyOE; zm-7f8(0?LaAb%>33n&Q40~iPh#eX84$X8<%B@;6n6I&-SHzO0fKOpxvzy-wu<@yAX zgSUf(8$VOR_!BDZ1tA$B5%B7_V9Cmceu8-{v^~W!2U=kGBvYUwy@U89+BdO^)>^ya;+%=oFKNxT(Y#5MlfB4Dxu`sb_aMgW=BXbDV9=2W8M1pqIGYN@ zDDjaACGsM|KXJv1zaNSfQlbWzsxXTaPAA$NO*Wk%uu^MpDg<{3!gX_-0@>+xxU>)LeM2tt+wHy!B5TN4HX5^zI;RD8+ zHKCMx25BWq$XjXU5T|Wfg)c;koDLesm_^7E;F&*@d2U z-8_Dy{nK@B^Ses6AFez2a2?ryx=zf^#K`%Nzy2@BjeqGAL=XP$XZYc`kwL9Qd~~Bv z^xFC>AAZB(Kz{V_;i!0O}#4#0dT(kF1-f$5&Wzp#c&sN z){p_7<|uJE!BT?81Ji0HriqMDrgY2;j+D$7E!qlBEs<#_>n`5PJQ`y+)Q$?1v8dp+$TM62$-MFmY$w zzevQNdybTq`S05-O2x_%O$Ez)D&BOyhR!BeGAF16V;&PGF(Ng`W@}BGR z{eHUI2gD7s*+ddxldKbJ^-UuytlZYK#n`gSHVs@g+ZFFL4n;q-B1|8KU>>;Xl_gj^ z!R7oV3b#xn^hjHa1Af$rn{SqD3K@BYkv&IO2;U!&!Q3`lUzkQ^8&j$TwQ4b{o^Bag z-b8ZOmr<+4E1COd?&F_pvE;DfN4z|RL}yod z5MM7^(Uc*ysFJX#MN+0(0O3$|dSm>>7>hu2=BdJ$hdJA5MJ4FbYsM53LmYy~*xGF6 zTxD{h(yi?LM%m(9`a0a&%oMUBLO>a@|2n3OtEuJZg!`4CBNNe5f0gxo7u^+iPzGjJ z<2fUvwA^};)m|r`WCNMQ?wNwnV=~D0Yi5xwvSh3_qsCY($hk0EY@O*j6X#0w^Ilyj zslx0Uv_2#4Uhi1)>Ew+_6HL(}iX|62`Cg)J;TZWC$^k<;KFS3(3{ubg*cva{UaQZh zOQlVf$zj0SsZnJZuLC;Q4xg{nyCen$jFd*c1|dhYx$LWLTG26f!wT@8@(gyvDvW^! z#0q}x7W4KhO8LQfB|?u$Emm4maAw081@`{7z%4BjBKEYr645(sND|#UCFnrzvK>0T zdqpn^p((XaWjSiBM-}ON9oZ31V8O!%+H9m-x8$kR}6`#5JE4 zRiGn4M}tQCEyqRF?A2fF?Nk)scKQS-W{zkR8I35il5qegE!T(*r^eEx!d&ZiPqxlc zJ9x0By^TYIMr|(qO}v-5kyEKP6)<*J#v6)#CJVWjy#MXdahMz2e<-VRN-YMMY-Fx0 z^++|0t!btBox9GzFj}t2ocRd@m11p~48JhnlHS?JF31k}KDx+{``kyntex%SZ*V}K z{Oog2jD(O_fpZj>Z4UL9$5g4=N3iTHJksSnrg6c z@aU1p4?$BA(SZBGiEz?IiPgz9AdyGd$uF5Ag+oFm`CD4eRm;OFr||+s7>Q3gH5&}l zf&f?QLRfI;cztJ8VI~xq4TJ=eKDSSr6Pu%n`?jwIy$n-BI|*=@;0dd?hvB)}}=LW=Jmz zy>*8OL!}kBRV8WEMLMx4aUl7I`ejqh$kh$@@(t`Kbc6G<07?d0?{>|-Qv?Alf8VnJ zp(1x@OAMib;tg>YtTfV`*vK3it5iy@PMbgC3H5{2vYm2UwC)L1aA5{klC6>U3%Xh1 zbdJpT{Mm3LGTlvSNhF+IDs?16q+Etq9ph{Cel^A zj$rzJv9P_k+EA_urCbktNO(#mmf&7{l$4URe9izQ3t&;LD{2btr#}1!w7SS|yq}TO z?4#@m;|BSy-6B0;#`T$Rn7L0Fi1*tf6Ccr;uTrUzJu!)u>-`L$R!HV3T6Sjz<$sXB z^`W-X*@SIGds-Y zI@)4+{1sEZ4Z=G_f(9GxPk3k5}C7?6|#&w{P;(K4JlaCW*r$gV5&y^Z3JPzh%QtwHbCGnc>Opbp@g$Y^1k(k8Ra z^@VhJN^FnIp>`mFa`mj^h#=cwubp7Th*56|Xa``Y9P&A*6KPOin%W^D$!`4Ca%#YX zCriXe^-PFdGpW&yHXdN~36lVn$}7AEgs+OF5MFUylN;(d)SOB9(KX>aU$f+2tEk}N zTe&x#g)FFf2qV54jaB}1CpK^61wb~A4s^l%K-@fAV*WrL3L!#gyh>m?{tPY9r(BVa z_8`jh6rP;lTsc`h2yDwReh3Z_l>ZohSj!k>F=;_0Q6F&(Fb&)6QPC!e=KYC1V6S7< z`ME*7QdwZ{(2Ukw_&P>a)$AdMo6yz_TB3&`Tbz(0}*LfVLGd-rWZuh5~ zFF{myQYD!pe+jW2eWU1z+ox{EkWzE++PFd*m~hw%>}C8$=AV1kO2K#UFPOH+<2~T$ zOkds_567$F3>Va=+tjCTUJ-`l@IC%qJtb z%$=y;xK<|uQREVk(#31>q>yT+A2cv!XxJ$F%opr5kF+QbY`Nr!3BskKZc2alNlVeT zr$}4dwT*&EL|5yhvItr_tffiE`4eD_Nj=|}at^S^`Y4BFH`+KiR84XBQ*&U|f;4?Z z(t}x6jd2^!)KmLx(B?@`45x76-m+qfiQ>N-&SalM&oYH(lstzDW|YMvX)r?`zHeF5 z61GzQX8X=!JIC>i!SbEaLw9;^v(P(N;J&@k^rgZS$DQ^xn^LncUtI<#rWTJyAk1C$ zinXeY#{<)`?g?VacM)s$`qMw-be|+P2>ByUTmDBVWd1KGT(_MUMCME2wvWe|OKy`- z*YyQC32Fnz#li?D2~+#ebrqb7Y_ng)W5qk0e9d{IB|t_+@&q9aN_CbGYh751K5}@N zh&1c^{q%y+!?Iv)4D*BW)ZN>TDmZ&)t5@=w8%BkMHz8>)8zG|RNe_bPkyzf(do;)L zSJu<#4nKLm>im4c`_oF*GpDyeeQawKB}&c_VhPGdaIzTj{;J4mG1nem$DcEw0?<$( zhu(iQI0=Np()>J=u0SiI5e_40(2WwQ!-b_^4f1)xtXj23x>_ER)^K!aQ^4nfv_612 z88(gHRq*RXlnS(!k=kVHBR(%yhR2ZS@y_gur?lPHmqa=BwjvH;cJ}9Y;lj0pT(Lc) zZ%+%mQ#`wrH$Q~*q1bt_x7mO7ET@kV1~Ogt=PtZmMOs-=k>w8w4%8l8AI2D0=-u)@ zEDLs!9*$n^0u9zpf2KC#`D8^+-qcyOt}=}HNXv^QLqU$|r{5!}Lk(X>1rGND6H&iF zvs6Uo*QXk}z&A5a4|ifV(VB~?ZNd&J&R08lzb{7$?-iEs|y4r_Qy@y~dIO^v>n z{D>#?{}K0C*#CulO8>&WCb~o+{su}#ILjnO>UBBg@W6s(btN~dUBBiN=BDzW^sAfH zyuZ$4m}XP4_Kz|I#@x)7^&`e?Z9S&>%q})~93O|Lk-md$^sAy11qc3YaPH^D;L~w? z`LfN-JKt3H5GRYVoIitg_+G9bYMq{ZViQNX=|-*#(@9g&v~`C!Z>e^A|FSy<8c@{0 z6saFc5((39+~07Z+r1n=)n)EXH7(oEF8V0QS=84ZkiN{M6KuSnhe`w3r!rVwvY5VT z=Ymot3_l2w_j?ivE;U-eFT=Ostu@O zvttv@1A5laIxO9TiJ|>t1gZjQne)zO_2gl+tIjec^j>W=#XWl^=6_C&e8&zUT0=s4S{ zbJ!_mT}Gc({}eWEHW4K{zFAH;@ZH^%trmHCZ}$<)Yrov^u3TB4km-vzRQlX3&+gG~ zOQ4hh#W1=%C{GmjSk*3bm3%bkAnO>@+2#G3nXd!;FU;kU`06d9(jJ65biYKfabs*S zY{-lFA20{+=xj0;J^V)gUb(Q(;U*6VWDj0BBR3jxCg=)^z*x1Rwr$U}uAje@TY{mz zrx281nN?^qjG5-jF;<`mUc%$_{uzgMEpa(SA947W`}qC|M8-9haa$J`(~C663qHs0zo5;C zmpRUm63SrPWrKykp|zpea68U+ha-SQp==q51-SiScxZAu+ey7b6dMmM+s*CF zPH*1%o3@0?-vGgVhq2;I!cb@c7X0N;9@kg$fxlj7NU~eM?ATQgi>Uq0@WcT!SrLC2 zUQ!F;lUe`g=p}4*o?0cj3U*5Go>&1y;tz&*kJ=pSn-7KuR>u)yvcX#0$CwqV-V(G9 z(1`hq;ptSJ8{0!a%5J><#qdm&u@tqj8~gP;@kT=pcEMt>nB(%Epp$AM=mieG#sdf1 z4)i%fMI@|SPxs6u_N~e|xtDJ68A3~{U4rl+BoejOAH`Pg+|Xey6Gr(^Ms3=bCo$xE z<>kWpPdPO+Sl5eJA>Jx2D_hhV;nEmVUvm_=a4_K9BB*f68PTJ+WYUNGSWQ`rgLo&W zK9G;Z1w6$*h!gHl5>=_Z_n*OO)ch5C_PzEFwopKmD{GZ!?wf#kb$I&Ja3A%BRYMTl zO1iVo?LuuXhKec@E5$rT4J_|M=$+g@1I*bU;SKn|1G;}FP9?2B1n<4bT8lD3`vd4~ zHEpPZ3ON|iVWsG5i>?&=z`yp_{RMPc)V$B|o~+jo6tb-3e`ub<5KB8$yY9;5D%ZB> zb|#nE;rSz>UN>kf3(C4SmP7w4&BW@(X8 z=!KshU62pZvd+jIa61{HSiu}w6!de&jIa2zlBGu6ya-uXY!3;W{0pAk5_i#cg|{V+ zmsEz0@$b$_5h5|RzrYT%p&-_@v5;DJK@Q%f=KK3|aN0tJ=lM5t+Co<#)#HtWYAM=-S)KPND{8&U7tQTpW!A-N2vIRfN%JJv5Z|FkgLn+AxMz%%TWp;So7_#1MNL$jE zlI80$zRs$V9e;pbHIuYA;vcX(9R2`1djaxch!3y}qYD2E>>^1gEzphDw$GBLL6SV* z(W>GmyI*BVxMYkiz+~WzM;suS;VR-Q(bV6v7FdHoJpz~>lDQA?na}yg&ie)9lWs|A zD03^qKUe$ncwb|@G+!$0;avP;;*h_eS1z=UCzGsJ)yH+)p$YZ|`)4qo-Ef@|egxye z|H$IlSpSK3BFzmts?V``lDRM{T7s%kLjySAOQ`Hr0|g@=(dc5Www`9{s=90ToEP?& zAb&k&SQD|fIdPHYF`emnne}V`=wq#s-8*UEpIgvZbq0Opx#RHjVsLKRFCJkLC8=1> zp#}@{cZ7bbC(4LmH>rH%eYVem?f3Dj^(BMP?atG;Uo3|K$-QeMIXj;tq=HXWKMxx4 za$@YF2>e8w)|Pqh@R{iEIrrhA+k__$PFiX~KLaRX&?oT{mT-O>>FG#3Qm}kX-J<}o zh+v$E%RLdMoAt%8h4EFtYaOyJcVr{t+jUDoHn1({ZLwplO|s|#5O~LS$wjz=x@U5B z$i{|dg%JkCCqx6Mu@w!U*mhy7SQ=7!$!Z=Ot1Vr%<@Yt zp=c!Bxw5m%jEj}0zJ z&YQ6!eIP51g!&~pvj?#z>oarrf0dQ}y`#O;h$@%zLFl|;{-?z(3)kP_6{W1DhO2`9 zJ{4;$%&mSwwvpb{0Q5zrFIG(ApYkY*aSE0trCHFE8*Sm_7QDv@EA z?ukov56rY!tWC*EDJ{?Qq-)w!suOGSOjPVqj#{dyD#$1cZYki=r5#}e&+(KsjZWI2 zs9{y9u6i&@x?5rhW9blcV{2+J05&6wUGV8tG_>YvVKg_;^VAdDOH zRqdsR!Vsw!dYe}|2rSzN*O))i${bmE+MA}OKNx|L;5m-=pjkMD; zq`{ylZ@&pr>eZ#N2JoSTa?C!sX$a)sjnTnU73b87y&5o*cy1Si)!U&O zbdIp!FvAwW2k9slc;BIco$i1(%6!xG->Y>EkjK6_m>8k*jzg&K3sw6vuv9^(vq+rS zpnIr=E6l^%b!Y{6V?*X4p-QB6%;=UH!W2!!vQ-I4W4Xm>WYwWhA4q2Zk`f}M$y1-X zFb2<_GSus`fq&YjA9g~jG3Ih_VdNfMl;H6D1X4t65>vtGi#2TP4|JRA5M-ZO$KAvk zwj0@Ls1Mzk`nLe@hzNL6Zk1UQ@X;ZBh3C@JQC3vmSAMHPy^Ov1R%G3&>4u0P2}BCl zaD=xEWV!ekS1zepa||QMepB#LG+&g)V8Cpt7%b;k(~O zHoIQNZ+!o+I_1b-jx;a`Euu~Wo*U8cxY`1!{5>Yi#ew2^yf$d*qwPp^=46C6q_=S~ zE(8&sq(D9?L>!*z*|@79FM-@g7_SI}?VagR0)vr!ak*0a816m^SfASgCUP`CyU*tW zeb&N&h+(%_nBV}4FHrrxBqJeicN}r9VL=>~>T3c6DFOE(Zto=NsD660?+xx`cy1w! z+uMpR`z!r4=%tvtw|I*nxhQ!vf#SK4S;90gf$RWSHAZ?TL*Zj#&0{~>yS*^vUY1>) z@IGhdK-?9yml}qR0i-^gPxWle`@K}A&@ITy^ejLHa~VO8+nMW0N@4Zy|ZPp2ciBEiLO( zpvxQl3M)pI6bsIqv1D~BHO5kV$%S}2Ir_5k)kwj5;bVy)qx9cIV7=QvA}~kEz44A) zfNAkO7wseV|0e?9>iiLbkN+hC4_1AMz|-IoKYh3mxSWK87ca!9*s)C`Hf}qwM`HT8 z!OsSGqgHGq_Cf%yXab3qxi#Aj(}DO>s2++h-4l-V?kogr`wy{j$5QlS^~KZ^P1Dx) zc{{mbRsbue*H$PPRIbo* zdIt5uB_v+TM|^mttQm~Xr}s8s?kWw>H?o;_d5b-?lpD$Qk!^M6m4l#53CYc2N?ZMu z>nZfu)`>`sbt*L@fhOURPyt>ych^k4*eAU;Qrx9~7$9z9p+c!2si8^p?AzN_} zp2pEEBrR&Y-*}5Kp8L&*2CQP$N!lQ8^PvI1m{t5Y4Os498nEB54-FWNZBlcY7s1*> zENKz^FAW$RiH-RAKla+Svp@~oF+f0W=s`dT|6g0Tf9K4sG!J3$BYnbAm)ji#$b=Ek zW)CuRer?pCP1btf=HRZ|_04;BlbcyXG4~pQeE__{UC~vtoxNI%nUQ@?aW`%+Mv3mK zZ>)}oY84ofyI78Ur;p2C9;-bQg}nkfeHp6zzYe!-{k+V-j~q&h!6~D{wPnZin}D!? zA9=6nJTXa@degOg7pH5!-4>lbS7mSpiJ_H`%MA6GxHuwC?H#4Lw#Qv#T@((o_hW+h zt%jtG(UxX^xbCp|dSGA>WYi73)U6AIJ}s6=(?o4>0u%fFPm9?0(&$q z4C;AW7FVwM?9B31D;*q@%X(wz3D;_g<0YW?FbRl2N25wiY6I(fFM-S`*>#RE+_$+n z`<~4SX*tqdE7>8iG~X!r!<_UMBwU$D*m1;XrNr}cP zf)%scF0~s|Nu$*7CAqD%{I!t|v`l5{IeI>;xURCizL=#e_4IIDPb{H;&xbt$ zVR{Uh_0x|O+rLKBj}19bqf{|5?gUqN-L0bD%t#CfoLqZVq4Krs0+Wh84`$%P)I9}l z27KzVj1I)Gb1axdN!tgF0aUWrDS#qLaO1}@BNdFy7?5nj71)uUhECl3(u%}H-BEpZ zEZ9b&dK4@J*yFAi2~tbY+lwB$3&t6=M``E!_QlcQ$2}8Zk@X|uKG%Web2>zk)b)Vp zo2;0S=qc&5N>q`wa{|7FK@W}CRZeb{^k7YGoKM6mDP!B#rG2H&aX8eP0$>X!7{p9& zRQV32@nm8<_^JiJu{)-K=}v9zPsBpb8MmyPUZ{PJVnO0&Iq<7x=GA9GqGC*DLE>RC z_IJWYT%SOJoa1s_NF(}WJi@5YQUr#b>AsOhgd3T#%arll1-8$53vL$d$*3DlVRMKL zIplb#AyJg*8B5hKmEo@7Cp+~zl14;mJbD#B*^wuRY36-85sOr3;RsIfG4cNgvd#>*7yy z%&l{BqoPN6YU7Vnsf{}ZO}h#OA=axHf?gr|fX}XZY%*`=A-E5`JQt&rN@t zwELaxw>e-DT(|fiKCKZQ5@Els0h8f0`g0u$nNQgrmHi4cxh_T$6Odk+3F^~^F|HgB zJ^su`dI*jE796sS^S0sROeM+JR2lB7ewLDlYST3GNC?b2oh3UmlDG-RXNgN8jI$}1 zPI&RxCZ2;)0z&d3(faS|v>laly=rkyG^1YE>ZBV{@&`OVD^kU|&PoMzx^V*M2c~8R zW}oBX7(m{&dzY_1Ihg_;A6#tm9qYfky(8oi2e1)CT^}*Oqi#FxSDUl=h_5crR?&a6 zW)*Mz#nvT8Hk|ML>e)JUq+VmR2V;Zf`9+NS9dp}d|MK|QD><3^$-@)6chi&whK#=$ z>~{KGl;a)6V1Y_PpTF#q$__Dee0nO2FYe|2Vo9=sYhz7JsiK*E4f8@1Bii}KgN8C7 z&?mQ^%G)=VX)bPoJBIjYZ?cfD-W$;GM~%OpS6+{FPJcVi)~r;tz1EV&N>8U!!sT(6 z&)$*k>!-!5hTAs(?|a=SLofc%W_Ul8u92PygFr>NeZEG@`v4!a7S;MiUguSKH=5GE zNjUYVZmE;ju+1$I#wfdru9@SfotJBPSVo=o+2AjDi4Kuq2zhU$fNQN2cXnWS*&dQB zWyf#R$Dl$xwrao$zf+}Inlo|)w+BBC)tr~YRs+s{m%UF_JxEUUi(fPF>~?iYdR)v5 zCuo14Fc$adXOk6^Bs6%sQM<;|4Jxdyz56ov3;o2RxYa*qMk8Bg*8m%){?>6lKdx&d z+x(+O5AW;Q2(SZ$<;B4$F5-p9h(y_h*GHbbuOhB&@2bZdV&Vu=R(?g49kv%OX3f0? zcZ!t8_|U+Jmo~AW^r%{=)81{I9^)Svct#dAJU8hW4_ZXdnUtditGBQvT+(Y)ICVkN3YDk-wlS5oE``6e zy_mGVMo4&Gy%BAiZY-V*e1>nU~sjS3d7O&i^+y-2i4Y;Ah zDvQ%HbV3|QKATrHq>>Daj~rH%L1=+8gIv zG?pjnLRsm9a!O8g34}PiywVn6R~usVx!(`0WMP6k=;Fu8@PkyKD%f`YV_makNVHm` z`@VtGv*xlL*vQQxyOSFBK%sF9ZvjVfmGcSxie=L!MsA+W<^Q5m6=w_)0MU3P`=hCm(zVczXH2h=s) z&UM72np?WRRto*43G^}TN^rzO62H=b8_8?f_cGm%^I&n23CGlKhH(c44N7>+sH|+4 zlYrZ{C!d@b^CnxK4Pp3p<`Ll57f2zpRjgbcoW^_C04l(`YdrERGrhk=yH0&3Bi#J?rp<&S=Np3;H@w{ch{KUB?WH zV0Yd-9`G&p-K^Nhm{T`|;;WfoHm5JR7xdT1q#d5 zX`_4qo=u)KQ#H3#KNx`wi(N$+Hac$gp(SU+Ff+mgGgF8m+Tth<=FJKzxFc{Pe?^&1 zu3v+feX{3-{Do3A=gVwO=&&h{y160Bp!7m14md%EjecpE2|A5hu|?!Bv?FZJ?SUZl za^+&H$ZO9CfVTCXVz^2v3+oVH_#zZNi#^w%Y8ii2t6LWi_%u^(vf;gD*H8_Z2D_!J4f$%Mf=#Bc1fx z%$l{Pe*lPnrWnC`*4b|h?jd_#s4h#lM0(ch1vLcR5U#DX3?sjO1aHP}*&L9ea%tt( zN_c|zSHo)QAIL&)*Q8q{MT+rS?a?8>C|XflIqs=8Q2IyhSDcpNfll;xAlfFjyY4V(N|$18IY*73o#Q?2z>XBd$I@H2 zPy0VxwtJvuc|~h4%ixa(4}S0&?--ythx6spFKDX@5Hi{I3>cw%C|AyCOYxfGJf((F zP(B1|mbS(x9Aj^DTuGsQ&s#xS6w{1nJDkRHQ`C9o)R&wyuwguy+wIe*lnO!Um9In8 z>5bJ2QD;1uHLzkVP&{@(4c7q*_g2Rj9NU41RrGU6Dmu4BiO%kRiC5s>kYLsfxr~pT z-9fS(fg z8r?2=c5I171^6?Xv)JKVIJnh`I z@l4NA^e+7H)o{$-Gzh`qGHCQJ?(j7SAiGBZ>*=E3v$juQmJo`kLTm!K4w{twVBg?* zG#j5OH*SkfSBTM|O(%g`C-6jn2ZA%4-wjV@=a)L@v_2be z%>5N}mH&4`tgW6ePi#ZG_#Nn5{Z>2GPtH#ekA0uqD41Uni}q0P0_5}&vj?<2A#bk{ zGJ`Jlx!xgZt_WKqiIx~4Gt{}OOk3TE#)n;0gFn@axxs3Msny@wao{J~7Q7u)R(yJ_ zw{fFr$00Oc-W9P%A~1r!W^#e~ZYpu5@c_{^nLX&bT234E79M5jWBhzxT@mUvHI)#r z9#K%2nm9_ITvX>ZN9z*8`hAn+dG$?^&O_4DAlQkBhu*nvxRCRgpA$9@)lX9fDULd| zg7p=K0*{r9IfI8Lr_4?ozCr$2UE}YP)w2tCxas6uDXb@GR9WO9)@tFbqim|_hv}bx zw0kPnS~V#jmL+1gfz6?+0CH^t# zMi)h?*bk~}ws(D2!Ys*67IByxz{o8l^N#d0cVr{P*^}e!(C8^<{|Ryx`-;&dp&~r; z5K`u`*Zjo0Y#=}t61^SktajsPBIt}v2y9sEakk|bXTfA^zWYaIc!HA&dN&AxHt=xZ zA#7Tts$LPENq#+aYVyz=^a^LT^``OcB*-||m_v7pNN}LV~obO^0b9;P1)|!Pg*;g})-`Pe{ zCKT@VJ*UTC*b~^R=*xdD9Mo|wYzVWT-3=MRC5?p`>Ge)XsH411(PuLD9A4or3sI70 z{Q}71T5f__Zc4uO_!WR-%G<-$Jkbj7f@oPDsZTiqYLXI@ho%}G@ywh7aHcnHtO(9( z1V5{UzNH7hV5ff03M89zlxN^W>YR(PtG^>zp?Pyl{r!O8vdF*Gr>ddTIDPurROcG; z;}&ly82GWLXI5eq%uN~e+f3B0K9f7#)jBwOI%p~h?n@x`^NTu#387IrJQ7mZr_m)x zyQ_O*pL&KcX1Ks^69!B?qqD#$i38=d?}3nN(ql?|3vq;VGIls*x~A)Q1WQ9&K;Eo$ zNIgn7)g4jhj6l++-nMn~@3!jDXAN7fL*Gif=?(3dzUtT*tgpDZz^c4_uL;x{qc`I@ zu0*^+)Pu9DoXH5hKxxxE-V`Q8{-%EHJ2M=}Pjv^DOA*vXdZ&_G4eup$%p<)0=_PJv zI?$V_VNulyHYun~m?iKKW)kfbO_Q`i_E=cKHl#(5+NpI$GZ!el45LHQpmR)~+bP@} z>fkZ(Rw(QWw@G*REw>qFljhEM#v_OTwJgG6fA2kY22J=o>dAW01#;WCsteiZ52rH3 zxzKIV{EqM951=@cpg6r077JhIBs-<^Z!x1rezC%RSD0?jgEvuT3P`@IHBYEzW4UU$jtq(d)0Dh`3q(&xC}8=Z214f-dlymxh`G1F}w-ZK;uD! zy9EvI?(XivAwUSwxVyW%YjAfbXo9;FJg}S0WPNMR@BjB)$NSnRoN-qD)>BpE9)ltW zD{rxk?ozlb4fe`)j5uxH38Fn3`XB94@F7GM!UR#56I|za=>kg`DwHkrF*HgQ?mrPa z0|l!XkVp5o0&Rt(y3=AF=+09lw8Q!^o?aJGhJ~?s^dS)K6>AZ~bP#UNo_V0Ka^VYoUNF>`5&V#RhfqEN2cs)@*+S95@_ z?zaBUScWW;b_rN(D5}*)R?k*0I=21!660I;wFp%))19#Tx3NF+@~6`RdY!S>6c!3Gi6!Hj9|S^By6ZT zPP9I#Oau+9P4h7o(nr}*+Hx{orjVbQ9pmSd3Td7~Z}M-&K4SRA$%gVv!K9f3sbU8` zox(ds9PgG%oV+MDS_h)&!U|>@{cAzI`!v@QAz6wCe{@m`NtVz`8Z@ zJDZuW{Pr3Xq)O^k1zMz$^Gw&<=|*xaq*%^AD|A>)3Mzf~NPDhsDhIi%`!41{o2#3u zl_b87-c(RY1Gyx;2kUWw+Y0ywBot8%Kr-KtW>%dT?Im@i$d)49)@gn)uNkg0?$nqD zFSkXSdh{B8V>g`aeHE<<-_LdNcOUB`s; zo3+&jzm9-tgMh4HOZcvC>vdM)0AeW z3NuUO`vd1c&#S<2`jZg^TKG$tYd(1lj;1(=;$!F{pc6x(J`HG5Ju5^ zn-vv+;5lB}u=0*Tv7yLPXi_PSZvP>&U&Trla8CRjP^gl2F>UbzUH$lmqWq%qKzV`k z#ISzWk`*Zk4_d;22_&$8z9saN?lGY>ebdz zBk;fwYyHQ0$CGDBSO*F#;oXY{;KokrO7y&GvDUr~o z!S3(4NO(k7np3P`!vcvL81(%&^Mv0C*6Z}G%*_|WU#M8RF2FN&owB3?@hn8^kNr1l zv@+cuP&Q{{Zfv^m{Sl`ex7xIc%G@*&_b|ILJYs4(;(|0hdO@Webt4f*m27Nsp zKq-(d{9H(KT=jFV-Ngz$*K0BcBX&H=sRJSoA2t1YdjI6^$XxDVc5AFsXJ6KWvq6)k z8?d`hMBDUW!mzTag`=-FoKbYRK!K8vQw2CkPm7HUrvO_hbw;6UU>MHw#TY>UfbBm^ zY<(~>|Gz7-KN`$`aa<6Q@1GKbD1AwOmly$Q=<2r$z@a&(gp&yh{*Ykyss9tt{JX@C zewA2a^Y(W6G4C_W?~S3ax>5)nD9fBj@6gLAH6k5iw}+CvNY3_IsloG^&%lwtODySE zi8UN3szXXFf5DXbb&%CliHQ;&zVSl=2j)RaY?}91iJ^=+OgX!jkGzx_d!$w`+J}_b z7R&2mfPpKmhwI8!wymExi#}Cx^%8Scp1OQ(oA!5*eIJ>G4vP*|Rk~Xsr;-RtS@)KTNwZk!je!3l_rTY~ zA!-ME-RDxG8l1H77cLXlIj!rVmYikfxO;gY5miT}ILAw5gGeDHyMIa#1g1=IqZC=) z&Ljux0(m`1cQOH!OL-GKp~5@+F@xugC9zT-Jv(oG2FEqBKRJd>G{2Hq-)Yrz!3{C! zwB<<1nGkY>pX#>OgJugPmaxFETVhl{hP9I2%pGeuN4#o%<5d{TFsdCf+l%}Kb+9+w zhm?!O6lSOZG>hI<9mJBIz+A@imWdOh^JNOyu=&pt6B(dCt$=*RkRS{;(f{jLjP;L@ zT){#HO%9nmo|c-L>Nu-k!74Xb^f`JA(dS&z$>4?2 zo>b)Q&V;!depXJL&Ra`N5TN1a=XM|6v!VV0Qv*=-fw(-AvSCRMYw>MF^pnm^EP>3#!O@)@~Fu_oImlg%dK zwoK*Q+#%7oa+I<6E0Zpz-Bto6bSY69EV$sndq-3Sz%q)p`j*)8S&A!k9HI-xi(;Xm zV}=Vn^|0i!y@lrC$}(44QjNf$ydz}Ju9TpKRrOeS@`j}u1t_`9sCTm$0?(ZdmkbjC z-^c7_E4=0DKg(g&YarDps`Y?+cYUdvnopC=v4eVi82gi>7zXHZZ81Zejv87#zxr`D zyj?$1!UM(@2D}j$3v(Hn+AX+EYD%at@Q2SsiAOQmffH67e$z3oMBGf`#N?}gn(`Tr z3)w0y`=RHsbToZ4joxYVI053i>}I86$%U%C!o^)|XWUe> zJo|WQqvW#dTVMeVvJB1_=8rXT1C&uSe2;sQ`j%YOev4z{H$5U07wF`^JXy&HSfVdP z+HiQngYYkIQz&oyIl?auK7Azx=m)TOZbSzdd>#9dcKwPnDS^AEG(Ev3`Z}mkVTrek zT1~8qyy6;9rd98Uye|=nKMx`5?Gg#e0y1ikeF;LQq1AUk9bKTAWfVIcR=NoSod*T~ zQQg?UJzfop8Uf1f-$YS}SwS=&kb1BE$Co8D+wYg9f|bmaEYS0a94NjirAt&!xd+{k zj1oi0kG$6ZhD;-_zh0uza^}d%%m&3XF3?*LjTh$!8`fB95EZ=ELaO_yecHKO+r#xW zRy*pfWDZF9J|oF~2~B&@x|JIQgejB7pnCY6pJ`(K_o|(VuZ=Ow-QlH${83=^Z7`^8mMksUP zFXeqfy3-xYIo1{RATS|fYmRSJd zDv~8qZ%16+Qc^D|vx~Sf>kF1TrT)vvrP?y3N{aTH4z2w`jGdPf_35jFe4>czD9 z&RZcmEf7aCNvxQ8wwJrPo1sHH$dp^)?ha%$p^_bO`&l|FU)L1(XIT#yYtuNM{lX66 zZhS4K2V@B1xD7P;oZ=6x(eXW)C#Kb*!)^7h)Le3i3eBB6@-Jy*T&fo2@p|{)g1MAGuz`yK5`-QVFp=9u zcEbjQ;C(OrBU|Gz$lxXX%CSAI2wehq$g@C8_Jpjj^u3(o32R^KryqJ?0~TEg_%geV z7qkBRxNhxn=+w5rmvzJK>4>$X6}BCX^n#j7`#MGAC=-61Yf3J*s=?BwMo72EE+)sU=m zW+pUI)jO6mCIAPOq+qJ?!5PiGaz$S{Pc9#?|M>}i?qqzd(Yr_|&)g`QTH3Al#bP_L z4#Vx=w10B$vxfqZN(BAmXO!^|k)fcWFeQupfWa>@H1WaA`-2Md;%A`v7dgX(LZcE0 z2if(~#vggnR4jo+=^E<+NPuhds|<0S(O@*_NTLrtP1?Ss9sa_CT!Ox^L&Msa%~2*@ z*0z%GWw?wx4!1j{7P(uO#thnld=I>^e7}|*e(k`I{t#M!`-Rr2W{Q&a<(QLttlJmQ zBwj*Lr<6W2oF8E~Y$5niRb)AsX^3;y1TTzf#a3$h-9#GA^;{-Oja7QZctdG7yq=*D zXPLdnsuKi>nz@s%yOj+*%=ev;m_sVE8v3ZxTs3UnKH&7>QTkjSrfJfKW7;?wV#&x+ zK`FIMuhz_1Dv^0-Cy9b_cP;mtb)^EeU!OHBk=K6_M-75Q`aU1Q8@D)BRa6ZM6Mu0` zMJ-~h@%^RW3~U_K7VwOaYZ0{}uL8eg+*u$K1o2|E7Z1=o#0HulV`#VBOG1g{!nT}MeZIe83}m`MkE09Hj&`$388`TlqTSUq)r z8005{O}nJ=D!6&cyAvWdlEAfMaClTifS?`ufe-IW#dsPai|J zQhD*r3sgEFma#e3K2UoQ@-<{Fv8s`R)S%1d{I@k1zTZVSM)sODh{V2Xd+~U&dix;l z{`%&S^OOJAFyhXLs#K>H`Y}N|x|VzE-z;vWWc&p?GdS^)5~Qz@XXLg>GRUkQG)MFe zU77RS9~O6FkPyP+!U6PriGx60E)W*i^NGdvhOoG!icc)A>=TRomZjXF32+`-EMGzo z!PAU1Ot_nmt?4V={=(D3L1faFbHz?Mr$tVbp{*jxR>rj7whB)KNhJ^AvIU_2VsVuc z3R1Dp1P7Mct$*Wb+6BMxH0wu?f5+2Q{=(Cg`8qgponH#cb-3eRy`1&O3X6sCw+(}l z2&x2S_8b3(r|Cc8X;e>mT9M|X+%G&0#Rh_>{b6zEAS~{?{_~Dk+a10`~Uu$oWU4rDhMe0!RvVND8xkt0+Hn;UV3xIo{So z+}p76A#j@9=@Xn5`3p{iaan`FX@7IDs<1C0PKA65WB&0|$n-~Mudpxw*QZc}KPFf{ z_`?ikF3?RN7>MG>h=D*Hw+i$4ny{{ewF6Rx)F#91>qrFc^`}s{S0u0H>1v{@?^1t` zIyioW{E4< z(ekxur`m=Zo#ux7X$I<{A8OUPQ#jv{M-^vb{=~jTCIrk%4t+hW2?vJ&q+xQ5Ti2FUT0AE#<3xu<<04W(f%2<+M9p5fhQ2O)yyu z1wbhy7UaIRff-*K6**Q%(LD$+8X4!>RJCZ0f6{6bM`PS z8%HA#*LQWu1amW`(y(Vzqi)E)&cH?LF5$dPJ#N*q#&;i^i?vd#>xcGB)c& z5-*o`NcO>DHKty1?snf>;n<1Ru>cQ%DrI>T6i4r~J2UdAzDEo1Fz`MIz{~=u#1!^t z=-h+1{!V5Qa+cPKefquZczE{fkr5A}oV-gawUA3HMURVA+%zCrjR4MUiia#$>x6;W zYE*;~P37aT@fr%jt|Zr%0U?@-$A;(Idr>kG#E15z@&0bu)Hi%Ik_tE!RT0EELl>_t zHguykEu;Nars3c}h9*cJWwmnyD@kzLR|J%g;95}Q8be^U*BHne*b&i7Dlk_&)3b)P z0dQfZRrw$8#Ud85`wXcJn`dw2)Kn-XPN;P+9$x+DM^=8zTZ$gC;9UL3^#IGC^?>5M z{FBd1>MLk4A1faUGUhbg8^Q)OO!`j%u+U4c6GKksf&OuU5eJ}GHzjWuOypUIO7T5G zaYI$ zw6!5(4yY+ykRBqN%E>=M>!)q`PUqUYT@^!7G#;Isvd(U?s|1eeO%lzT;K$p;Qio3i zBE*(%6KQUZd90byy`sHRaoyRyIqr#}bVuo$@3cd;2S~ltgg*PBEE_0{m7q`^Rhk$o z_c1inkuyNCm3nDp7jIP=NQzUBB*WB$J~jj1YSbWmDby3&k6tTa@79ODh>efM-ZQS_ z^KFc;qm3MGd56JvOV_SAk>(;RoKh3j{6ipDYRyzFqbZ9?7Qr@R3b6!?0S4`NqOO~$ z2qKc!e9bZ#4Jwq>;Sc>r-3IPqDLL?qeIUuP#0Fm~h&m?a+PT;7eH`^yQ6>;wqYg?i z4gl{`vq?61fJGlx1rb0nBihuCAsmtE1MgSl`$1_cGTTS8$y^P=prLnM(#S3=i>{j$ zOJ8(hmj$m)Zbq{houEz(dhxRW;E}YUj}w*S7}` zkr91aI4y?tT`>(7PeCfuB(vRH@1rKy%)t&0Ge;6f!HyJ^NsknEn4&iq zZ~2gZDvU2>OfZJ-?OGwe{&DG;$Fa|ov8olqxC@!wu7k+1k%%B`TdOcv>Xi2q!)BSW z!gmv_HE8becZii6ZeF#JvA6HAyBCk`e@(Aqprisx{)sAA0Tw^gkhDq;bnP0HSVw(8nasnzavH3BbZV_erwwQY=`)}nS?4? zDWIu9))P^N(G71hRI@Y8B&=SDKkHfWEqcYTwfxG0oU*2NsEVX`$a-n{jiQFeov}_U z=g$qvzQoZBh25I|8m7am2cL(;1$#D7AfjaS{K&g2o~ss~tBrCm&%2lH7T3a0Tqi4eQ&S~=CZ7~P#hC! zj!E z4#+(-#Uzxb$6TMai~ibKsHNSu;knK^U$UbjkO3EH_en2Yhh?ZjVO~jQOc@i?QG}n( z4b)j=CJ!0i?$AESn|&S@v<1-x1t>t_)a9o@S#OiJ438J}Pfdv*sh80chNFWnDIfie zT?ETT**~jbeP-3s-;rS!u*MWc{YiA2mOnJBF2){Y0QfAkIEnrv)AW`y(YMU{^RO-mV+MGVPcXWvL(M zbm9qRg?&B_*{8ldEyyY)nwziS<&=zug7se?2f6s09*amQGAkIg2$%U@*@k|)9Vs%O zR6?U*hqqycM^6fH1=pFpJBJoieKy0dsXS7|Q!9F3dQ}{jsT`{tKyJgvaUC0Fi^o>b z9R}HbT7Z+YuhZ&*y{KK4pYh7s=vx)3>{^t^8j3lGzC_Ox1KaMJbZj)*( zhXx(yD{W4h5;sC%iS8qNtuYStm8ilKNt36~Jcqp)_X6w^iA|CC?WpYSA0O?) zJ3tW-w`^r+U2KuVzWgfmf#3zM$T#J0VZN*fmsaAYZwu=dy``)0+wl)bakOeM-r(Fd z&|R_}+7??J-mz8i;sro?_GUU>Xoo?CJ$&xwfHt4B)xs_m+h>n`?G$GH-GKv$(DQje zK{U8>oMY6Eb5-@aFqJ-4?w(0AsYoLJa_zABYR)cE(V7|+%^(tS*wer6VF!BU5j~hU zqnYk1!F&J8Po?59_Q@YeP{h_Boh^<^hT z2rO0ze0D!vJ_Bh=2f7~MoF5~PK5Vg*E>G0Fdm@$lEA=n^ z7PhOWWD$xzRo_}zmO9~k2-ieP)?QMcz#e^P<>m|F@AAJ6z>e>tKAKDlpwR5@^F>(4 zcRPVA6*>vvTI$O8y$hjE6B@@<=MB8P!5dh+fzM>ktKQd)F?#h)oTP_9h^fSPk*5`gdDdgEOFrmp*7$Fo z!PxN4!`(jHvtV1uI=ISHmacIH&L*~j(n|2@ggbZd!BAUa*r^L@1a^%77>-VJlg`lG zq~Tr5C5!(duLPA=!nygum94Go-!1HVqV+k=4+ArLuaY1ZcBfh^ORL`b+g_N6GbjAu z(?KN~4!{=$$PqVZQ)HF;CDmKyE!1k)!DGf8q@S{-EWUTvKUEtdv-F1=ALCs+(4C_S zX^vP$-z(XXrW7fgmJ&3t$#w~#v4O}?ucxqMxCGQd9R-0Jm`L@h^THSg4xNsf$~w_` ztC~FX(4`z0WVValBw|eo)pjt>h0hMlQ*nbr+ihlE&^Ct;9A3UV>kzh!*6>r>0%gQ_L_MiJN#Ir@q} z8Q8&CC4+>^8z{k}vR98tl!Reippz>xx@t--!nLs~A zNGFtk55|pxwtbx4tsJuWy&Wg8Ul3JrNWyD>Q;7rWq_;CP9eESBZ&%9ZJ6qsjcZ+5{ z33~io7*3utSMA_jn7$+P)4Vv5r#P{fddoouII=ERLK+Ii6_%LZ%@wQ~RJ=Eu)|*1T z;*GBRPT1l8Z^LDsu1+iwq)7h@pQnEnDdar&?^d3!5TB>_ai4_^LJ12=V}PwO%wU^; z*IIof@jQVs&3f$r|D{&{-<4W{w+wGzv3kS&Z>g0vw9pzRq{8O^(OZZ0&-z0CFPDHt zcDprfx6J3_QwYd!%p|m2o&~;{mX}AND2+h99e;8O02|hyS-(NQ2hzPcZHGCL3sA$1 zZFp~4%FStiIh-A;XJgjg@x~ zWz15+uf@52z5mFI6K^V%xl7(&6Jm8Xz!)!{b~*FjhOt6;@cd1!yXdI^co!{qL|Vo7 zcuxT3HGtGz{>C{8bLm8#qo%Km7S0sQTCmgz-R*}SxiqzA^OKEOXZmKw_jiL`To{^5 z4PTcayL3`&d4JAM%Qhk@`&jcPz}YMp^?F_~dXxjh);flD^mXfal5@9{26w);a<6Em z_&pnO6Bb4-ZL_*2XM6NGjd~;;9obu|irbr5yI~u%~WTxdW`$iF{1S z;JQ@@G73mWeSz;=+xZf6a@`P8=sVTNtY$KSHH_NqLzO@rMs$O_){wN}CWRkNS>n~< zs4n4!>RMMaEV99rE*ynE8jUX7i>ColbGD;Hk$VrNMA=SQv#HkQ7Bw2@z`JTV05OQg zfg?Kq+nx2~!x__mCtdlooRwdOSP+A_f2pH9OM^aTC?6upzyIYxjA2RRA~y*-0vnmC zEJ6f)!6&L@*rAv_pOB31jp-+aAl`J&#;r`-DL0q*_dkBZd@^w{C+?u%hDoj3Pd)Wa zEIv~v(b~MJ+9L?Ueg2nnWg0=oZI9IZY~3X7`)#4WrUDqegD6*KAIs zi^_S9nJX-^@Sn0u%@E}ZC;F2(%Jt!qvbKw{k`NNx8!5*i-o=j$)+lJ=y-3 za>ZKb=g(WMGO3Vpp+mI^2N|D*@-u}M=A1qY zx8%aTr29dLw;`8d48y=|(Cmb)EP$3P;eAC%sT zdLavKW<6yZE=+OD(KC6^KRZa)rhgI5XS7o)*Us9zqve}x=b*#H^}3fq52B8m+-c$P zGlQt3eD)#gC>Dr13iL}IHF;*TbPMLxiD(`l1eqXCx-kis2SlB&>tcl?E_A>Tf*og0 zO7xb=%~fOozoaXcpzi*^Nmo=z+zuunkGIKxwr%>|8$*@8L81eI57tRrd3qX?{E?yv za#SRUsN`7*Z|ZiD#KaUTbKe)Q9bU4keLVm#OPTes8RMq8R3SY;@vMvP?u4}7rL$*e zuZM(%y>>auqmh#0B#U0QKmIvdew<=|-Zf>@`tpQ~XA_qG;;S@8DGpUu6o$52iScfl zo<`T@=L1gnG~#~bMf48jCS6sqbL9KLPMS)R$-D~jU5YV=5_#72u`x0e&}VoYK7kgu&4NDgSh_i>9 zN+=tKPG59YLVsL_!L!aJ3Rs2h3kJY^oTcuP-d5&HET*9q7ZE^m!|Lp04n#*^S{39` zGyObVw+hl;3qbQh?28`#Ty@T9L45KD zbBhn3d1TbZS=>IH#M`DZX0xr(f+URrN|SepU7*ZF)srhx(cw*UBJ`UCu=CIU?XdzQ z?JgG&u|S~gMnXp31)gkn!Y*NKM6n*~aU_;&KA9x;E9uMrHOp7W21y1%nd`0zK|F{m ze9QguzG~6Y!P8nAmezU!{rd@<4V8ws0ki7>Rq zQ}NVr7w($|s#H>ec)Lk@NtRI*+|EAp2PgqAG|&1&$#}fGvdIB56LtsidgwIfVPTRs zKBELH%}{JQThDDcc2BKDb;^l3`!SUr9n?hV1 zRmv;Pu->N8h{>Jh>U?s%On1fG$WYIqS(f^{gNPxeLiZ1PXkGnMStpOzL`a7*d7ke4 zia)91v-zFpW3=oY-WeBAHQ2lF<;SS$8%|G3E#_uPiT)aQU5BDZD=0KoZt2r9!jJYS)QwH~(OA>w^K zUYtfIE%aOnq^-j5evV6mvs{Bog5w`UcbQ;lOi9Kqh-n^nN!ipB;r(bFT8a!z-XUAAkM2zk%D9^}|a1<-7>v1Wu!i#TvFG5)r zMip*%C~=Xqz}hn^xub#GH1R`^`r;K^o0p(oo-IMTURspAUYG)_RHb-@9;Vah@Je%?NTrA{j%Y9SjmbRx%RD!LtcS%!9jB}%ZdUC2ksw2~NZ ztK-@@_z^Ie_^alIcE~hiwn!+id+kz*#mYf_sC`h6g@)g;jBpp=z7EArGmZ z4i$<&!2KiwHV36?M!fVhz4@Ej_26L|lL>ilI>UYzIy+S((2)J^P_#FJ9~DFv zqM;T#GF#BvNAJ4hUo?w$%q zMRM^=aAK!&C03u}RD(YZ>yggIQ`3bcf&5R?b%iutT~kQYjrzYf-Mpu!tEn>!X}VfX z9+0MMg)nn|HJ~9Njxv4wSJM@N45vT$LWTal>Bc`b-Q2T}v=&Cu5J|>B2IIvn`FBJ} z(}hTq9dsD}r|H)GZo0*PHQh3-(zz!|@}$YqHCRI@f_DnibZx};m!aH9V?qgFBwS3# zU&qNYlu&_9&@F`Jn%xZ96X(4VzgO~w+(ZB8MQE?jpu!tcWRd^8Hu)n;R$PRfJAqpH zh_CoJl!J&=&}*S`KDlHE==u>#@|i%6?!or3{;W}Ck{>>wu#E0tUcX`Fxqj{$v(D4u z4eh6S_0IjUwtS%c8?WaJ7ceOv1^78{0fyyv!W@DohVm~s`JV{P-p%HJcKlSMX6QKnPqXcXYF2w;bL{0c}j`l!sP~HEF}-fPbAyJ>&bO z?IX#7XjFCM(&KW=Y7}O4k)!n-xp&ZpJdlb~z~H7I0|p*{uVH{5Vmo~EX7k@!J*qT6S2-ghnq>(2#jI^CuUgJsWdy#sO2fIb&Pttv*QgKEm2Bkk|r*oFfPNURSJZ7LS=gb4H`!+(V;aM8iAXyd4xGeEnp|@h_z(g8%NLB^&0|w*3uu&X_@IWVr?i4Z(F^(yx%;c{uhT{UbY70{V*=(0T(#GLqvC`Q)O3?th zG7N4(l{N`Ywq^y-6w)VBZzsRA+8?p{{xzEXR)EfyT23>Jt3{q>NiRb)vc8YO5?~a; z#rlXf57VkO=veGTvy0H_#VCiWgUb8M)!3f%Wih7Mw`*3}y)HAmdP)Vz zm^P9nVGU)M$3_)pl&q1`IDqmISUoxy2zJR^2f^y-_pa}3SC33JH&n{Le=czO_4Hm?2Xvg2-wFE>b&>i(XAAwxl{;x+Q#c)a=XweA zN7w=VG8apCrLQ}s1Na8k6G9guD8f%A$k|;{+UFL4xr_%^(X639H}!{smYn)mn78a? zdR_>SB~928Ac5pDdbC`V2(_sXb54-MmAGyp&e?c~LNOrykFg;Okw&!Us!b)?WMXkF zir@kk=))1w5dhO%Lx03cPCsu!KqfOjtrr0v(=1^)r}TGh{l%DUhIdIiafeftc~+`n zBGL6!_pSDVwY8QS4T!<+X&HRji)d+&Ne}n4FZxmYsf3Y6NJ^E0E-Az(RjBB;Ozg#J zPZ15a2`Qy)0$=dxrmPr|ql#RGMu-SNqY>tVsfD@@Nb&--I;56}!f+2My6rfp_fXWc zE^>Py5_@!e9oY!IcYt)wL1X@faFV;gu(a_0eatfsWm&|bDY-85uW?~4CH_P*0%eF? z2nZ3DW4mH#+G*_=6<9=TZGT(oT`4esB>k_t^uIQGe}(h>XP5qH%KQX%=!qO$)7I^`EtA_-8fJy<(aTSU+T?Xu)S)A~05ce|98g|T8qj?r zgZQ(@$}WT(fHwH*C&>~D<6^kJTHsZPw43pXh=mH765dc_N-mq0iGf{2Aeo$7r6!aR zYcCs;&*K=ehI|r!s#=M`QoAYQO;yK@=a;y8Tf{1~xHoxtM@VX=Qjq2)x`Hhqlrg)l zM^4VZ?oOm=v!h}|<0E%}5u4%9C~BW9e8((4F-H-LX|G0=d>?cw109=LFZ}!k+68UL zBzsdi7CuaL0%OYSJTnLF)DG%Ojf5`8wf@N@AzZeQSx$Z&Mb}LxYmcr}x_%u~qmXKR zGgsmZadI5q*UBefGw@2X^i}5oV;rm@Sx&WPPfeQUdmvxP&%ZTk51k^)|E|XVs5AeM zFTsykXvNS964Vse**OgNIB}U;RwhsRPX>vm%nv8eKa#Jfc8Frq{QV)A@1p}g}Bq&l> zX&~6k?+gW+aZ}yvY_h1dVACDi)=NBS1X0OZ?Y=bL%P(V}JsIzn0{ej;G<|}+g*YgTUVl1n1a#@A9TD92lmAyJrd@;m+%IGc@-e^rYQVbeOWmeDia1lru}Q9{=8 z%U^NF@FguWFJ#LFwMrLqr4OmkZ>WTLG8O>7u)w=`Gq}a;n&B?b0m6OkBtg z(t1f>X@o**rVarwW4=7_{ST(n}3$ zrOjz&sz5Y*=QQ$;k!gPuqQPu@(qJaXSq;#j8YRp0*k@k(E>CDo)*eXLLiWdHxb&vt z$?dSc>{$T}l8_{UC7g@Jyk@!u?m?z02fzd;a#`+4c?MZ?TwjEBnu>m`9v87?AI~A@ z%kuVi>`YMxU-51NMoB!*v=T^GsoKwOX6Pu^#r_BPWPko3^MnO-;dpFQF~SjZ3CDzj zbkJ61l)Ig#u`1wy;!x}~f1ca=Guh``YCLaOWtRkG#99ezr2J-JD`#3=cKN_BCkdg5Kz9yRo=t(&yIV+>0pwLDwJi&@uz;VJQMh3*Oj43r`civhoi0w~~3VKjG~yl-&7ZzoDi3^TAT?$c5zd$go{Xukkq zaKy)c)lH!OuvjVMFa5ogvBJqPA46@1qHQdK`vX_oJLKNv*+BXi>D{d2Psd!3m4G~_ z28ImiCY~#yY${I4dTD?i$!b~hQT>U^ov=P&WP)-0v@LC$z2r^x4y+T9q9p%wa8jh7PFvRSb0b+Kn5vMj& z@ljhuE2_kFT!g$z!JS2!scVUpmXU;u;tYvWpFGrm=$mj~h~vO|8CnDQE90iAWcap< z;hjT?5l-Ct#Ks}4*LPfbE2~8e>#4Q0x26$fPJY{MIlOI+uc}_11x*MriCM&EaR)_R z%s{-4KS~dfw(6}Z1c*;*WPNf@$Z>`W>Ynf*-I$2zQ0y-xl$aog`@%i+L`r_Gtq<)Ko697sl3}6Fwc$ctzh+b(e&S59ii7 zam81g7;~N9^!3R+!_QO`_Rho*=3YFPxo+PYToKR7zn87+5|Fb&?bJ=Uq5GV9(H-CF zCY~XDh3?gL_-QEZu&6)&OWZp9orF68+^fv+IVg}w9I&pX6O?Lb`zA}v6B&6-k?q2z zqK{Z-u`LC>cgN86@hS^d17P4tE8xlq8|{+n{qEhE@Tj@`$8^0l@(`tFx|X?AgH_U? zC~oU}&Jm_E7b=2UH1T1X-Zhui1WWn!rSP}h0!p#a7p=M>8;pR^>?`>}gEhup z1!*+29HR*_Nt9-9ss?dfj2pmOg0wv%Y^mI=`6hRn1w(m{6wO{+v^3iDlh_Tmsy4JA z6p^Wj=z6J`daL-@W|0_iE#Q~j)oa!6lo?D-zl#9GgNm9ovV&9$9Iz{~3ebpi+r#1$Y7;RxGKKTYe3txgn` zxyf9D4Hx_|pnwM5HSC1~@WX7qn2~rfqkeo1=lp_}Q(8l)^?+G(j=SKP34$u>y&}bN z1%i6jTY;Qn$FhR%_w;Yl29$SBb<;&!S00_Pi}w2f=cRh2tRUfHP4HO>CfTU~^p-JK zIAe1kkXILU>|eRC1ApA$O4%YPSG8U1mG3%&Zeu90xU?Vjscuc2c|kpMhUa5V*~N@Ev;En(M+{bM7^Hg^Lk0@sGK1NoO(Nd zy{7bnWzm%DoChUjw>}N7qoxn3x9T`V62(>DUSE9Da}xCdomKK&VNwc+vk{zL&BH-F z1#~ziiDWEqZ)e{^uNS)&?5HZZ@qwmfAsalz@B&s9aTKdpkV+d7wG-(!52kgrLJ1+0 z;(e#iY+b`Pus~e=-pk#rkx_+p@J5E*Q&bMag9W~k4n*n>2kxD4C*7>HLpPwv54Ecr z%|=Tgae6sNFNACKqpT2Ivd*@af}?< z>5dK#DH9W?+t>aDSBFHXh#><9M_-OposV6&zqs>0u4l_U)9eZJ#RuChGRP;*Z-oM{ zsp*KZe30x;ZFb2mLJv06vgY);680C$|7-)z!$bm-?P>}xx zju73g(FNhKtvn0Omud5oeQCe)If~H=bBmVNK|F=)J0HSw<0gQ~%sRO-mbB!Dp}Gac z=1py5UB9r%#Mg9E=T{);F z`lN3Y7kQtWQPYgs=e&_VjBh-rgoiRfP19rFEH+MbqMW6XeF83a5dBrNBt*!&Nwcrnt!1Ev*tJO;5Eh zj!a0S+(te{;~9AUp>NfyiSD#po%S<8z{bvuHprrZb`>4Wz?&xF|2;vQQ)yZ&9_Kd7 zY^Ut)I`r0V5U5F|LNiZ9E znR+!pnhw)XD!HM4B#=ld-7I+a(yu!x&_E^~Tl^V!imzNk!|mfm2JFikM62rlWY6>H zlNGs86;q;)oIHg(jj%krT%9Iwgi=DxJRv`=hZjlYT*@r|vRtPl1J$q{w$Hz5)?^xCaKofleV2=c-ym%8E= zR%=oI)+bAL&mesK`kPXxOvU9caaNSCw||37cMr6^Al?Cj=z#>Fs=PqYD^%NwJX_dCWN3i>6Q((st7Nn5*aTh^#&*Xz4wOp~tgSsvv4Q0ERm7sFD4b0M2- z_|!9TTUeV2kXVeYkJ#s229Xk0u@mYa<3iZc0gJBFaG06(A<)!GwG^YT+#X15RbNDp z$3$d=6Mde%dU)pN`1AaH{A>pEZH=x{C_6iB?{hf4ZJf}N{(EN^bJNY=7X?r;3=fgTgsCiY1F5*jQ#A&q* z;rwJU(^{wM0b@0#NgmWwx%{};kY%N>GPt?ESo%G|u!GNMq%5}txE&(&$m&U09*#LNn?nM9}l0JOJt z`!gJqE^@rF!r!t-SgRgawkl5q|HRO7tqzz=Ksdkjzc{}#roUq7{&0Q|{}Dr{^sgAY zr~Elc44pIk5hQ2x{~kkU`Na82{O0`rilIX({~bg3Cvy|R`4vJqKgXxc%^$xqHyJi( zEB+tG-tw=?t!w{Q6eJeXNF&|dNH0n0Zdi0T(jWqhPH6?{Zt3nuy1PM1=}>+ry50A^ zy|3rl*9-mtK69*bjydLWd=L8FiFzP&6Y%)~nVVEV=H~zQ`2m@mfX`1$@~6)a2`YH~ z%jd@d`22+b^7*O#)92U4@wd;<>j*b3X4t14x#H?%m_6px%XrGNEFmbp`18zG+F4%T z$z@3yC7d!Y97EfD@DfSKX9bhPCox6Zu>RiMD(l>Pp80DYi;N#JkSYpH6flUwz-BAHG(qiV zrQueBK3QU19TSrp>wD6EXXkl@a=8a+OMWKNtuMv4!37w_d|2eaSXgX&admbBO!==| z^GVyHUN9CAugH@aBj4xEMiU@ivM)CyoH70N<&m& zS11ds{D>$>QAIV+`h!LW&2DoD8nh3mrRoVRlZIrNfpphm;gGf3RCG5tcMPpw~cCG$Q@tFg>wEX*{|IjC;_Q-2HBv)5^at zVS67XZy8x5hC$VI5!}t*_NgN5qkqXL6-bebM4{_QSswY$V_<$7Pmd>-1uqAV=BxK0 zB_wRx31xRa#jp?sBCfw6LhVA?p?$eg-TP*xDE<_M+$XRuF^7Vr;`$mLaAbKaHPxUri92z=K`>MpWZbdaf(>q;A%- zShQ2BcpU}kT2P;-pw>UGm&(#zI8uymQ?uw{AzJ1-%0FMd3|n;P{ea>f{@3E={QH9X z7qoFvalvL2TtEsNqRQ|Q_ExHhB@w1vl7yTh@h7gVe$o21DVDc?u)xmU5$s>Px)fji z8fxnb$HRqB+aEkGeq4WFrvg-YF~T9~sQ$$v>#0Yw0R#OedoRY2Z#C_xrR3Dum~p#- z97P=0)$0;Z$ux&`2A^R%q6@9K9FR_nN--eJ`{V)2Y1d@78xq5N1Og*ZKbyDT7V>Yn zZ6!+`60!|_zIoiu|U##qNWjNmh#764^fue9jai_S~v26^{-N!$|B14{0BdsU)uE^fO zdn8$7GD|vIAzWDmeu#`SeaE>|Z2`e!!^Omf-dJ)T7=#r$@iRugEgRoM?BRDvN%;X+ z%!uNai{065REvg)8~B6h!r<*Fogx8i7TIPHWGzH^WKZM=N!{Zj$?ke#@~VkC`i_%t zErJWY361V&MJgRgoY*Wq@{JR76cjgkf!8s2{*1^OxSi=_oH|toWKAjb`N%nNT%G{j zac+fyWd0#?cgamNyd_a2sW1{xEd%LQvUU+xn_2Os9KU%D&9HpstI-E3=L?j`MMr7)pGa#mU(z;(e=Nkb4MaS?e87k(`w&-jt`1 zF||3ytf4{-bvwaS^68@Ig?PTT_MCbfIo$f}EzbfOZcZk8UzU>-F-m1H0a1pu0i{Ww zMawC^&cg~Ma{d-(w~#CERi?7AQvMVZCjU?BBBDs9RoYVGQlFo1RBUOq6<{l?5}C{9 zEVm|Gyd((|{AUUKz#RJ?10^i`?_Sj3`W7Xi{{n`MS0ax{B!q>o#9(C}(nz3X_o}6j z&8gli59ZOQIL9cZV@QE?#@mO@FV@r60WRe72KN&34l3$g-_}wS)pn}K;lc1S0Nqn> zwM3Qy`IV?iYMvvMQn^J9auU#$=>TA-(DEx-WLofY6+Cn%257&mzJu*AygT1<94N?pkNEmwVi79X(drPLG8=T^zt#Xc22Z1~sz3;>h>!}=TI5?Kh5e+oqoK?4W zgee~9Y0~15q~02X#~18jx?IHh^Q~TdX(WWlnqYadwPz;EcIG0y-NzCgV5rt;&_g9k z+v4aZvX&_pUmE0hXhzXbR31gL$i#fPO-!F1+{SRzSPb*kkgqW$_t{4d&zhHh*&xvn zD;SWAwr54AX~OyqGUL9i~LDquXiF8ZcIJn^Of3*+~g~^kqTd| zBGyU_>TT0q%o~+*riZK1980=pWF4)@rhWzI#^-c-LF~MzL_Wk#d_~gVS0VNr8t$-np^e#>?YM0vLN6Ue%$q-3lyGd1uL%Xh(uoV~d3g*u|9im?J+1 z-o3)iG!o$-$vf$m63t>$oEj|d_!#f}!2A|+Cu)_RPZfBJ(P52Yt&QY_;<&GC0{P%< z(#ba1QK`Pt0~$61HGBNU&QY)jt^FyqP9VU|cVdF(K-RnMh^D)lq&zL=<6hj?g;>y8 z!5Zdp_T=>{nP$#}Qe#wdth`dohCFK!oyI90PZ%AnG@a-mO}4ygNJZ~i>1WKTGRCjZ z>HNaWeqeG&Q#5mTAE9eyvAC%2GY#f%Y<4_t6d{>lvZNo�cGJqm@TQ>;FUP5d2iR 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�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 diff --git a/lib/org/ciyam/at/1.3/at-1.3.jar b/lib/org/ciyam/at/1.3/at-1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..9393b73b30344a8072abf7a7b045ecdecf5b627c GIT binary patch literal 143430 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

}^CN!XEsbOqXG10dmZ_lG^UEo`$ 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

{{Uk_OXB>M^=>vr0)t?cVfpj&5U#n+ms|vZ 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 z zh3`q+3-3Wx@2btWoI(=?P1Vgtwx^5OM3UwpKoZ#pB=7J>B);7VvL}YPL%F?h1{%

1z7xv*!(Y{*Yt)Y(>n3mA-)OYVb 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

dv~zR_K+gSF|mm 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 + + 4.0.0 + org.ciyam + at + 1.3 + POM was created from install:install-file + diff --git a/lib/org/ciyam/at/maven-metadata-local.xml b/lib/org/ciyam/at/maven-metadata-local.xml index 65fd3fa6..6e8cda0e 100644 --- a/lib/org/ciyam/at/maven-metadata-local.xml +++ b/lib/org/ciyam/at/maven-metadata-local.xml @@ -3,11 +3,12 @@ org.ciyam at - 1.2 + 1.3 1.0 1.2 + 1.3 - 20191121173210 + 20200408081355 diff --git a/pom.xml b/pom.xml index 067c359b..15509805 100644 --- a/pom.xml +++ b/pom.xml @@ -406,7 +406,7 @@ org.ciyam at - 1.2 + 1.3 diff --git a/src/main/java/org/qora/at/BlockchainAPI.java b/src/main/java/org/qora/at/BlockchainAPI.java deleted file mode 100644 index b8cd8c90..00000000 --- a/src/main/java/org/qora/at/BlockchainAPI.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.qora.at; - -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toMap; - -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; - -import org.ciyam.at.MachineState; -import org.ciyam.at.Timestamp; -import org.qora.account.Account; -import org.qora.block.Block; -import org.qora.data.block.BlockData; -import org.qora.data.transaction.ATTransactionData; -import org.qora.data.transaction.PaymentTransactionData; -import org.qora.data.transaction.TransactionData; -import org.qora.repository.BlockRepository; -import org.qora.repository.DataException; -import org.qora.transaction.Transaction; - -public enum BlockchainAPI { - - QORA(0) { - @Override - public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) { - int height = timestamp.blockHeight; - int sequence = timestamp.transactionSequence + 1; - - QoraATAPI api = (QoraATAPI) state.getAPI(); - BlockRepository blockRepository = api.getRepository().getBlockRepository(); - - try { - Account recipientAccount = new Account(api.getRepository(), recipient); - - while (height <= blockRepository.getBlockchainHeight()) { - BlockData blockData = blockRepository.fromHeight(height); - - if (blockData == null) - throw new DataException("Unable to fetch block " + height + " from repository?"); - - Block block = new Block(api.getRepository(), blockData); - - List transactions = block.getTransactions(); - - // No more transactions in this block? Try next block - if (sequence >= transactions.size()) { - ++height; - sequence = 0; - continue; - } - - Transaction transaction = transactions.get(sequence); - - // Transaction needs to be sent to specified recipient - if (transaction.getRecipientAccounts().contains(recipientAccount)) { - // Found a transaction - - api.setA1(state, new Timestamp(height, timestamp.blockchainId, sequence).longValue()); - - // Hash transaction's signature into other three A fields for future verification that it's the same transaction - byte[] hash = QoraATAPI.sha192(transaction.getTransactionData().getSignature()); - - api.setA2(state, QoraATAPI.fromBytes(hash, 0)); - api.setA3(state, QoraATAPI.fromBytes(hash, 8)); - api.setA4(state, QoraATAPI.fromBytes(hash, 16)); - return; - } - - // Transaction wasn't for us - keep going - ++sequence; - } - - // No more transactions - zero A and exit - api.zeroA(state); - } catch (DataException e) { - throw new RuntimeException("AT API unable to fetch next transaction?", e); - } - } - - @Override - public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) { - QoraATAPI api = (QoraATAPI) state.getAPI(); - TransactionData transactionData = api.fetchTransaction(state); - - switch (transactionData.getType()) { - case PAYMENT: - return ((PaymentTransactionData) transactionData).getAmount().unscaledValue().longValue(); - - case AT: - BigDecimal amount = ((ATTransactionData) transactionData).getAmount(); - - if (amount != null) - return amount.unscaledValue().longValue(); - else - return 0xffffffffffffffffL; - - default: - return 0xffffffffffffffffL; - } - } - - @Override - public TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex) { - // TODO - return null; - } - }, - BTC(1) { - @Override - public void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state) { - // TODO BTC transaction support for ATv2 - } - - @Override - public long getAmountFromTransactionInA(Timestamp timestamp, MachineState state) { - // TODO BTC transaction support for ATv2 - return 0; - } - - @Override - public TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex) { - // TODO - return null; - } - }; - - public static class TransactionOutput { - byte[] recipient; - long amount; - } - - public final int value; - - private static final Map map = stream(BlockchainAPI.values()).collect(toMap(type -> type.value, type -> type)); - - BlockchainAPI(int value) { - this.value = value; - } - - public static BlockchainAPI valueOf(int value) { - return map.get(value); - } - - // Blockchain-specific API methods - - public abstract void putTransactionFromRecipientAfterTimestampInA(String recipient, Timestamp timestamp, MachineState state); - - public abstract long getAmountFromTransactionInA(Timestamp timestamp, MachineState state); - - public abstract TransactionOutput getIndexedOutputFromTransactionInA(MachineState state, int outputIndex); - -} diff --git a/src/main/java/org/qora/crosschain/BTCACCT.java b/src/main/java/org/qora/crosschain/BTCACCT.java index 76a68ee6..2671d76f 100644 --- a/src/main/java/org/qora/crosschain/BTCACCT.java +++ b/src/main/java/org/qora/crosschain/BTCACCT.java @@ -1,7 +1,8 @@ package org.qora.crosschain; +import java.math.BigDecimal; import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import java.util.function.Function; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; @@ -11,6 +12,7 @@ import org.bitcoinj.core.Transaction.SigHash; import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.crypto.TransactionSignature; +import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptChunk; import org.bitcoinj.script.ScriptOpCodes; @@ -26,124 +28,149 @@ public class BTCACCT { public static final Coin DEFAULT_BTC_FEE = Coin.valueOf(1000L); // 0.00001000 BTC - private static final byte[] redeemScript1 = HashCode.fromString("76a914").asBytes(); // OP_DUP OP_HASH160 push(0x14 bytes) - private static final byte[] redeemScript2 = HashCode.fromString("88ada97614").asBytes(); // OP_EQUALVERIFY OP_CHECKSIGVERIFY OP_HASH160 OP_DUP push(0x14 bytes) - private static final byte[] redeemScript3 = HashCode.fromString("87637504").asBytes(); // OP_EQUAL OP_IF OP_DROP push(0x4 bytes) - private static final byte[] redeemScript4 = HashCode.fromString("b16714").asBytes(); // OP_CHECKLOCKTIMEVERIFY OP_ELSE push(0x14 bytes) + /* + * OP_TUCK (to copy public key to before signature) + * OP_CHECKSIGVERIFY (sig & pubkey must verify or script fails) + * OP_HASH160 (convert public key to PKH) + * OP_DUP (duplicate PKH) + * OP_EQUAL (does PKH match refund PKH?) + * OP_IF + * OP_DROP (no need for duplicate PKH) + * + * OP_CHECKLOCKTIMEVERIFY (if this passes, leftover stack is so script passes) + * OP_ELSE + * OP_EQUALVERIFY (duplicate PKH must match redeem PKH or script fails) + * OP_HASH160 (hash secret) + * OP_EQUAL (do hashes of secrets match? if true, script passes else script fails) + * OP_ENDIF + */ + + private static final byte[] redeemScript1 = HashCode.fromString("7dada97614").asBytes(); // OP_TUCK OP_CHECKSIGVERIFY OP_HASH160 OP_DUP push(0x14 bytes) + private static final byte[] redeemScript2 = HashCode.fromString("87637504").asBytes(); // OP_EQUAL OP_IF OP_DROP push(0x4 bytes) + private static final byte[] redeemScript3 = HashCode.fromString("b16714").asBytes(); // OP_CHECKLOCKTIMEVERIFY OP_ELSE push(0x14 bytes) + private static final byte[] redeemScript4 = HashCode.fromString("88a914").asBytes(); // OP_EQUALVERIFY OP_HASH160 push(0x14 bytes) private static final byte[] redeemScript5 = HashCode.fromString("8768").asBytes(); // OP_EQUAL OP_ENDIF /** * Returns Bitcoin redeem script. *

*

-	 * OP_DUP OP_HASH160 push(0x14) <trade pubkeyhash> OP_EQUALVERIFY OP_CHECKSIGVERIFY
-	 * OP_HASH160 OP_DUP push(0x14) <sender/refund P2PKH> OP_EQUAL
+	 * OP_TUCK OP_CHECKSIGVERIFY
+	 * OP_HASH160 OP_DUP push(0x14) <refunder pubkeyhash> OP_EQUAL
 	 * OP_IF
 	 * 	OP_DROP push(0x04 bytes) <refund locktime> OP_CHECKLOCKTIMEVERIFY
 	 * OP_ELSE
-	 *	push(0x14) <redeemer P2PKH> OP_EQUAL
+	 * 	push(0x14) <redeemer pubkeyhash> OP_EQUALVERIFY
+	 * 	OP_HASH160 push(0x14 bytes) <hash of secret> OP_EQUAL
 	 * OP_ENDIF
 	 * 
* - * @param tradePubKeyHash + * @param refunderPubKeyHash * @param senderPubKey * @param recipientPubKey * @param lockTime * @return */ - public static byte[] buildScript(byte[] tradePubKeyHash, byte[] senderPubKeyHash, byte[] recipientPubKeyHash, int lockTime) { - return Bytes.concat(redeemScript1, tradePubKeyHash, redeemScript2, senderPubKeyHash, redeemScript3, BitTwiddling.toLEByteArray((int) (lockTime & 0xffffffffL)), - redeemScript4, recipientPubKeyHash, redeemScript5); + public static byte[] buildScript(byte[] refunderPubKeyHash, int lockTime, byte[] redeemerPubKeyHash, byte[] secretHash) { + return Bytes.concat(redeemScript1, refunderPubKeyHash, redeemScript2, BitTwiddling.toLEByteArray((int) (lockTime & 0xffffffffL)), + redeemScript3, redeemerPubKeyHash, redeemScript4, secretHash, redeemScript5); } - public static Transaction buildRefundTransaction(Coin refundAmount, ECKey tradeKey, byte[] senderPubKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, long lockTime) { + /** + * Builds a custom transaction to spend P2SH. + * + * @param amount + * @param spendKey + * @param recipientPubKeyHash + * @param fundingOutput + * @param redeemScriptBytes + * @param lockTime + * @param scriptSigBuilder + * @return + */ + public static Transaction buildP2shTransaction(Coin amount, ECKey spendKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, Long lockTime, Function scriptSigBuilder) { NetworkParameters params = BTC.getInstance().getNetworkParameters(); - Transaction refundTransaction = new Transaction(params); - refundTransaction.setVersion(2); + Transaction transaction = new Transaction(params); + transaction.setVersion(2); // Output is back to P2SH funder - ECKey senderKey = ECKey.fromPublicOnly(senderPubKey); - refundTransaction.addOutput(refundAmount, ScriptBuilder.createP2PKHOutputScript(senderKey)); + transaction.addOutput(amount, ScriptBuilder.createP2PKHOutputScript(spendKey.getPubKeyHash())); // Input (without scriptSig prior to signing) TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor()); - input.setSequenceNumber(0); // Use 0, not max-value, so lockTime can be used - refundTransaction.addInput(input); + if (lockTime != null) + input.setSequenceNumber(BTC.LOCKTIME_NO_RBF_SEQUENCE); // Use max-value, so no lockTime and no RBF + else + input.setSequenceNumber(BTC.NO_LOCKTIME_NO_RBF_SEQUENCE); // Use max-value - 1, so lockTime can be used but not RBF + transaction.addInput(input); // Set locktime after inputs added but before input signatures are generated - refundTransaction.setLockTime(lockTime); + if (lockTime != null) + transaction.setLockTime(lockTime); // Generate transaction signature for input final boolean anyoneCanPay = false; - TransactionSignature txSig = refundTransaction.calculateSignature(0, tradeKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay); + TransactionSignature txSig = transaction.calculateSignature(0, spendKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay); - // Build scriptSig with... - ScriptBuilder scriptBuilder = new ScriptBuilder(); - - // sender/refund pubkey - scriptBuilder.addChunk(new ScriptChunk(senderPubKey.length, senderPubKey)); - - // transaction signature + // Calculate transaction signature byte[] txSigBytes = txSig.encodeToBitcoin(); - scriptBuilder.addChunk(new ScriptChunk(txSigBytes.length, txSigBytes)); - // trade public key - byte[] tradePubKey = tradeKey.getPubKey(); - scriptBuilder.addChunk(new ScriptChunk(tradePubKey.length, tradePubKey)); - - /// redeem script - scriptBuilder.addChunk(new ScriptChunk(ScriptOpCodes.OP_PUSHDATA1, redeemScriptBytes)); + // Build scriptSig using lambda and tx signature + Script scriptSig = scriptSigBuilder.apply(txSigBytes); // Set input scriptSig - refundTransaction.getInput(0).setScriptSig(scriptBuilder.build()); + transaction.getInput(0).setScriptSig(scriptSig); - return refundTransaction; + return transaction; } - public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey tradeKey, byte[] recipientPubKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes) { - NetworkParameters params = BTC.getInstance().getNetworkParameters(); + public static Transaction buildRefundTransaction(Coin refundAmount, ECKey refundKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, long lockTime) { + Function refundSigScriptBuilder = (txSigBytes) -> { + // Build scriptSig with... + ScriptBuilder scriptBuilder = new ScriptBuilder(); - Transaction redeemTransaction = new Transaction(params); - redeemTransaction.setVersion(2); + // transaction signature + scriptBuilder.addChunk(new ScriptChunk(txSigBytes.length, txSigBytes)); - // Output to redeem recipient - ECKey senderKey = ECKey.fromPublicOnly(recipientPubKey); - redeemTransaction.addOutput(redeemAmount, ScriptBuilder.createP2PKHOutputScript(senderKey)); + // redeem public key + byte[] refundPubKey = refundKey.getPubKey(); + scriptBuilder.addChunk(new ScriptChunk(refundPubKey.length, refundPubKey)); - // Input (without scriptSig prior to signing) - TransactionInput input = new TransactionInput(params, null, redeemScriptBytes, fundingOutput.getOutPointFor()); - input.setSequenceNumber(0); // Use 0, not max-value, so lockTime can be used - redeemTransaction.addInput(input); + // redeem script + scriptBuilder.addChunk(new ScriptChunk(ScriptOpCodes.OP_PUSHDATA1, redeemScriptBytes)); - // Generate transaction signature for input - final boolean anyoneCanPay = false; - TransactionSignature txSig = redeemTransaction.calculateSignature(0, tradeKey, redeemScriptBytes, SigHash.ALL, anyoneCanPay); + return scriptBuilder.build(); + }; - // Build scriptSig with... - ScriptBuilder scriptBuilder = new ScriptBuilder(); - - // recipient pubkey - scriptBuilder.addChunk(new ScriptChunk(recipientPubKey.length, recipientPubKey)); - - // transaction signature - byte[] txSigBytes = txSig.encodeToBitcoin(); - scriptBuilder.addChunk(new ScriptChunk(txSigBytes.length, txSigBytes)); - - // trade public key - byte[] tradePubKey = tradeKey.getPubKey(); - scriptBuilder.addChunk(new ScriptChunk(tradePubKey.length, tradePubKey)); - - /// redeem script - scriptBuilder.addChunk(new ScriptChunk(ScriptOpCodes.OP_PUSHDATA1, redeemScriptBytes)); - - // Set input scriptSig - redeemTransaction.getInput(0).setScriptSig(scriptBuilder.build()); - - return redeemTransaction; + return buildP2shTransaction(refundAmount, refundKey, fundingOutput, redeemScriptBytes, lockTime, refundSigScriptBuilder); } - public static byte[] buildCiyamAT(byte[] secretHash, byte[] destinationQortalPubKey, long refundMinutes) { + public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, byte[] secret) { + Function redeemSigScriptBuilder = (txSigBytes) -> { + // Build scriptSig with... + ScriptBuilder scriptBuilder = new ScriptBuilder(); + + // secret + scriptBuilder.addChunk(new ScriptChunk(secret.length, secret)); + + // transaction signature + scriptBuilder.addChunk(new ScriptChunk(txSigBytes.length, txSigBytes)); + + // redeem public key + byte[] redeemPubKey = redeemKey.getPubKey(); + scriptBuilder.addChunk(new ScriptChunk(redeemPubKey.length, redeemPubKey)); + + // redeem script + scriptBuilder.addChunk(new ScriptChunk(ScriptOpCodes.OP_PUSHDATA1, redeemScriptBytes)); + + return scriptBuilder.build(); + }; + + return buildP2shTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, null, redeemSigScriptBuilder); + } + + public static byte[] buildQortalAT(byte[] secretHash, String destinationQortalAddress, long refundMinutes, BigDecimal initialPayout) { // Labels for data segment addresses int addrCounter = 0; final int addrHashPart1 = addrCounter++; @@ -155,6 +182,9 @@ public class BTCACCT { final int addrAddressPart3 = addrCounter++; final int addrAddressPart4 = addrCounter++; final int addrRefundMinutes = addrCounter++; + final int addrHashTempIndex = addrCounter++; + final int addrHashTempLength = addrCounter++; + final int addrInitialPayoutAmount = addrCounter++; final int addrRefundTimestamp = addrCounter++; final int addrLastTimestamp = addrCounter++; final int addrBlockTimestamp = addrCounter++; @@ -164,19 +194,30 @@ public class BTCACCT { final int addrAddressTemp2 = addrCounter++; final int addrAddressTemp3 = addrCounter++; final int addrAddressTemp4 = addrCounter++; + final int addrHashTemp1 = addrCounter++; + final int addrHashTemp2 = addrCounter++; + final int addrHashTemp3 = addrCounter++; + final int addrHashTemp4 = addrCounter++; // Data segment - ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * 8).order(ByteOrder.LITTLE_ENDIAN); + ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * 8); // Hash of secret into HashPart1-4 dataByteBuffer.put(secretHash); // Destination Qortal account's public key - dataByteBuffer.put(destinationQortalPubKey); + dataByteBuffer.put(Bytes.ensureCapacity(destinationQortalAddress.getBytes(), 32, 0)); // Expiry in minutes dataByteBuffer.putLong(refundMinutes); + // Temp buffer for hashing any passed secret + dataByteBuffer.putLong(addrHashTemp1); + dataByteBuffer.putLong(32L); + + // Initial payout amount + dataByteBuffer.putLong(initialPayout.unscaledValue().longValue()); + // Code labels final int addrTxLoop = 0x36; final int addrCheckTx = 0x4b; @@ -187,13 +228,17 @@ public class BTCACCT { final int addrEndOfCode = 0x109; int tempPC; - ByteBuffer codeByteBuffer = ByteBuffer.allocate(addrEndOfCode * 1).order(ByteOrder.LITTLE_ENDIAN); + ByteBuffer codeByteBuffer = ByteBuffer.allocate(addrEndOfCode * 1); // init: codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp); codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp); codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp) .putInt(addrRefundTimestamp).putInt(addrRefundMinutes); + + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrAddressPart1); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PAY_TO_ADDRESS_IN_B.value).putInt(addrInitialPayoutAmount); + codeByteBuffer.put(OpCode.SET_PCS.value); // loop: @@ -204,7 +249,7 @@ public class BTCACCT { // txloop: assert codeByteBuffer.position() == addrTxLoop : "addrTxLoop incorrect"; - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(addrLastTimestamp); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(addrLastTimestamp); codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator); tempPC = codeByteBuffer.position(); codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC)); @@ -220,10 +265,7 @@ public class BTCACCT { // checkSender assert codeByteBuffer.position() == addrCheckSender : "addrCheckSender incorrect"; codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B1.value).putInt(addrAddressTemp1); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B2.value).putInt(addrAddressTemp2); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B3.value).putInt(addrAddressTemp3); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B4.value).putInt(addrAddressTemp4); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B_IND.value).putInt(addrAddressTemp1); tempPC = codeByteBuffer.position(); codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC)); tempPC = codeByteBuffer.position(); @@ -236,23 +278,16 @@ public class BTCACCT { // checkMessage: assert codeByteBuffer.position() == addrCheckMessage : "addrCheckMessage incorrect"; codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value); - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrHashPart1); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrHashPart2); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrHashPart3); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrHashPart4); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_SHA256_A_WITH_B.value).putInt(addrComparator); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B_IND.value).putInt(addrHashTemp1); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrHashPart1); + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_WITH_B.value).putInt(addrHashTempIndex).putInt(addrHashTempLength); tempPC = codeByteBuffer.position(); codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC)); codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); // payout: assert codeByteBuffer.position() == addrPayout : "addrPayout incorrect"; - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B1.value).putInt(addrAddressPart1); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B2.value).putInt(addrAddressPart2); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B3.value).putInt(addrAddressPart3); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B4.value).putInt(addrAddressPart4); - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrAddressPart1); codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); codeByteBuffer.put(OpCode.FIN_IMD.value); diff --git a/src/main/java/org/qortal/at/QortalATAPI.java b/src/main/java/org/qortal/at/QortalATAPI.java index 9be0e4c5..9016bb17 100644 --- a/src/main/java/org/qortal/at/QortalATAPI.java +++ b/src/main/java/org/qortal/at/QortalATAPI.java @@ -17,19 +17,24 @@ import org.qortal.account.Account; import org.qortal.account.GenesisAccount; import org.qortal.account.PublicKeyAccount; import org.qortal.asset.Asset; +import org.qortal.block.Block; import org.qortal.block.BlockChain; import org.qortal.block.BlockChain.CiyamAtSettings; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; import org.qortal.data.block.BlockData; +import org.qortal.data.block.BlockSummaryData; import org.qortal.data.transaction.ATTransactionData; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.MessageTransactionData; +import org.qortal.data.transaction.PaymentTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.group.Group; +import org.qortal.repository.BlockRepository; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.transaction.AtTransaction; +import org.qortal.transaction.Transaction; import com.google.common.primitives.Bytes; @@ -108,31 +113,90 @@ public class QortalATAPI extends API { } @Override - public void putPreviousBlockHashInA(MachineState state) { + public void putPreviousBlockHashIntoA(MachineState state) { try { - BlockData blockData = this.repository.getBlockRepository().fromHeight(this.getPreviousBlockHeight()); + int previousBlockHeight = this.repository.getBlockRepository().getBlockchainHeight() - 1; + + // We only need signature, so only request a block summary + List blockSummaries = this.repository.getBlockRepository().getBlockSummaries(previousBlockHeight, previousBlockHeight); + if (blockSummaries == null || blockSummaries.size() != 1) + throw new RuntimeException("AT API unable to fetch previous block hash?"); // Block's signature is 128 bytes so we need to reduce this to 4 longs (32 bytes) - byte[] blockHash = Crypto.digest(blockData.getSignature()); + // To be able to use hash to look up block, save height (8 bytes) and rehash with SHA192 (24 bytes) + this.setA1(state, previousBlockHeight); - this.setA(state, blockHash); + byte[] sigHash192 = sha192(blockSummaries.get(0).getSignature()); + this.setA2(state, fromBytes(sigHash192, 0)); + this.setA3(state, fromBytes(sigHash192, 8)); + this.setA4(state, fromBytes(sigHash192, 16)); } catch (DataException e) { throw new RuntimeException("AT API unable to fetch previous block?", e); } } @Override - public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) { + public void putTransactionAfterTimestampIntoA(Timestamp timestamp, MachineState state) { // Recipient is this AT String recipient = this.atData.getATAddress(); - BlockchainAPI blockchainAPI = BlockchainAPI.valueOf(timestamp.blockchainId); - blockchainAPI.putTransactionFromRecipientAfterTimestampInA(recipient, timestamp, state); + int height = timestamp.blockHeight; + int sequence = timestamp.transactionSequence + 1; + + BlockRepository blockRepository = this.getRepository().getBlockRepository(); + + try { + Account recipientAccount = new Account(this.getRepository(), recipient); + int currentHeight = blockRepository.getBlockchainHeight(); + + while (height <= currentHeight) { + BlockData blockData = blockRepository.fromHeight(height); + + if (blockData == null) + throw new DataException("Unable to fetch block " + height + " from repository?"); + + Block block = new Block(this.getRepository(), blockData); + + List blockTransactions = block.getTransactions(); + + // No more transactions in this block? Try next block + if (sequence >= blockTransactions.size()) { + ++height; + sequence = 0; + continue; + } + + Transaction transaction = blockTransactions.get(sequence); + + // Transaction needs to be sent to specified recipient + if (transaction.getRecipientAccounts().contains(recipientAccount)) { + // Found a transaction + + this.setA1(state, new Timestamp(height, timestamp.blockchainId, sequence).longValue()); + + // Hash transaction's signature into other three A fields for future verification that it's the same transaction + byte[] sigHash192 = sha192(transaction.getTransactionData().getSignature()); + this.setA2(state, fromBytes(sigHash192, 0)); + this.setA3(state, fromBytes(sigHash192, 8)); + this.setA4(state, fromBytes(sigHash192, 16)); + + return; + } + + // Transaction wasn't for us - keep going + ++sequence; + } + + // No more transactions - zero A and exit + this.zeroA(state); + } catch (DataException e) { + throw new RuntimeException("AT API unable to fetch next transaction?", e); + } } @Override public long getTypeFromTransactionInA(MachineState state) { - TransactionData transactionData = this.fetchTransaction(state); + TransactionData transactionData = this.getTransactionFromA(state); switch (transactionData.getType()) { case PAYMENT: @@ -154,9 +218,23 @@ public class QortalATAPI extends API { @Override public long getAmountFromTransactionInA(MachineState state) { - Timestamp timestamp = new Timestamp(state.getA1()); - BlockchainAPI blockchainAPI = BlockchainAPI.valueOf(timestamp.blockchainId); - return blockchainAPI.getAmountFromTransactionInA(timestamp, state); + TransactionData transactionData = this.getTransactionFromA(state); + + switch (transactionData.getType()) { + case PAYMENT: + return ((PaymentTransactionData) transactionData).getAmount().unscaledValue().longValue(); + + case AT: + BigDecimal amount = ((ATTransactionData) transactionData).getAmount(); + + if (amount != null) + return amount.unscaledValue().longValue(); + + // fall-through to default + + default: + return 0xffffffffffffffffL; + } } @Override @@ -168,8 +246,8 @@ public class QortalATAPI extends API { @Override public long generateRandomUsingTransactionInA(MachineState state) { - // The plan here is to sleep for a block then use next block's signature and this transaction's signature to generate pseudo-random, but deterministic, - // value. + // The plan here is to sleep for a block then use next block's signature + // and this transaction's signature to generate pseudo-random, but deterministic, value. if (!isFirstOpCodeAfterSleeping(state)) { // First call @@ -182,7 +260,7 @@ public class QortalATAPI extends API { // Second call // HASH(A and new block hash) - TransactionData transactionData = this.fetchTransaction(state); + TransactionData transactionData = this.getTransactionFromA(state); try { BlockData blockData = this.repository.getBlockRepository().getLastBlock(); @@ -206,7 +284,7 @@ public class QortalATAPI extends API { // Zero B in case of issues or shorter-than-B message this.zeroB(state); - TransactionData transactionData = this.fetchTransaction(state); + TransactionData transactionData = this.getTransactionFromA(state); byte[] messageData = null; @@ -236,7 +314,7 @@ public class QortalATAPI extends API { @Override public void putAddressFromTransactionInAIntoB(MachineState state) { - TransactionData transactionData = this.fetchTransaction(state); + TransactionData transactionData = this.getTransactionFromA(state); // We actually use public key as it has more potential utility (e.g. message verification) than an address byte[] bytes = transactionData.getCreatorPublicKey(); @@ -265,9 +343,7 @@ public class QortalATAPI extends API { @Override public void payAmountToB(long unscaledAmount, MachineState state) { - byte[] publicKey = state.getB(); - - PublicKeyAccount recipient = new PublicKeyAccount(this.repository, publicKey); + Account recipient = getAccountFromB(state); long timestamp = this.getNextTransactionTimestamp(); byte[] reference = this.getLastReference(); @@ -285,9 +361,7 @@ public class QortalATAPI extends API { @Override public void messageAToB(MachineState state) { byte[] message = state.getA(); - byte[] publicKey = state.getB(); - - PublicKeyAccount recipient = new PublicKeyAccount(this.repository, publicKey); + Account recipient = getAccountFromB(state); long timestamp = this.getNextTransactionTimestamp(); byte[] reference = this.getLastReference(); @@ -306,7 +380,7 @@ public class QortalATAPI extends API { int blockHeight = timestamp.blockHeight; // At least one block in the future - blockHeight += (minutes / this.ciyamAtSettings.minutesPerBlock) + 1; + blockHeight += Math.max(minutes / this.ciyamAtSettings.minutesPerBlock, 1); return new Timestamp(blockHeight, 0).longValue(); } @@ -380,7 +454,7 @@ public class QortalATAPI extends API { } /** Returns transaction data from repository using block height & sequence from A1, checking the transaction signatures match too */ - /* package */ TransactionData fetchTransaction(MachineState state) { + /* package */ TransactionData getTransactionFromA(MachineState state) { Timestamp timestamp = new Timestamp(state.getA1()); try { @@ -415,11 +489,6 @@ public class QortalATAPI extends API { * Timestamp is block's timestamp + position in AT-Transactions list. * * We need increasing timestamps to preserve transaction order and hence a correct signature-reference chain when the block is processed. - * - * As Qora blocks must share the same milliseconds component in their timestamps, this allows us to generate up to 1,000 AT-Transactions per AT without - * issue. - * - * As long as ATs are not allowed to generate that many per block, e.g. by limiting maximum steps per execution round, then we should be fine. */ // XXX THE ABOVE IS NO LONGER TRUE IN QORTAL! @@ -443,4 +512,27 @@ public class QortalATAPI extends API { } } + /** + * Returns Account (possibly PublicKeyAccount) based on value in B. + *

+ * If bytes in B start with 'Q' then use B as an address, but only if valid. + *

+ * Otherwise, assume B is a public key. + * @return + */ + private Account getAccountFromB(MachineState state) { + byte[] bBytes = state.getB(); + + if (bBytes[0] == 'Q') { + int zeroIndex = Bytes.indexOf(bBytes, (byte) 0); + if (zeroIndex > 0) { + String address = new String(bBytes, 0, zeroIndex); + if (Crypto.isValidAddress(address)) + return new Account(this.repository, address); + } + } + + return new PublicKeyAccount(this.repository, bBytes); + } + } diff --git a/src/main/java/org/qortal/at/QortalFunctionCode.java b/src/main/java/org/qortal/at/QortalFunctionCode.java index 1c68b244..e0d6cfe2 100644 --- a/src/main/java/org/qortal/at/QortalFunctionCode.java +++ b/src/main/java/org/qortal/at/QortalFunctionCode.java @@ -1,6 +1,5 @@ package org.qortal.at; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; @@ -10,6 +9,7 @@ import org.ciyam.at.FunctionData; import org.ciyam.at.IllegalFunctionCodeException; import org.ciyam.at.MachineState; import org.ciyam.at.Timestamp; +import org.qortal.crosschain.BTC; import org.qortal.crypto.Crypto; import org.qortal.settings.Settings; @@ -20,52 +20,6 @@ import org.qortal.settings.Settings; * */ public enum QortalFunctionCode { - /** - * 0x0500
- * Returns current BTC block's "timestamp". - */ - GET_BTC_BLOCK_TIMESTAMP(0x0500, 0, true) { - @Override - protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - functionData.returnValue = Timestamp.toLong(state.getAPI().getCurrentBlockHeight(), BlockchainAPI.BTC.value, 0); - } - }, - /** - * 0x0501
- * Put transaction from specific recipient after timestamp in A, or zero if none. - */ - PUT_TX_FROM_B_RECIPIENT_AFTER_TIMESTAMP_IN_A(0x0501, 1, false) { - @Override - protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - Timestamp timestamp = new Timestamp(functionData.value2); - - String recipient = new String(state.getB(), StandardCharsets.UTF_8); - - BlockchainAPI blockchainAPI = BlockchainAPI.valueOf(timestamp.blockchainId); - blockchainAPI.putTransactionFromRecipientAfterTimestampInA(recipient, timestamp, state); - } - }, - /** - * 0x0502
- * Get output, using transaction in A and passed index, putting address in B and returning amount.
- * Return -1 if no such output; - */ - GET_INDEXED_OUTPUT(0x0502, 1, true) { - @Override - protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - int outputIndex = (int) (functionData.value1 & 0xffffffffL); - - BlockchainAPI.TransactionOutput output = BlockchainAPI.BTC.getIndexedOutputFromTransactionInA(state, outputIndex); - - if (output == null) { - functionData.returnValue = -1L; - return; - } - - state.getAPI().setB(state, output.recipient); - functionData.returnValue = output.amount; - } - }, /** * 0x0510
* Convert address in B to 20-byte value in LSB of B1, and all of B2 & B3. @@ -90,7 +44,7 @@ public enum QortalFunctionCode { CONVERT_B_TO_P2SH(0x0511, 0, false) { @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - byte addressPrefix = Settings.getInstance().useBitcoinTestNet() ? (byte) 0xc4 : 0x05; + byte addressPrefix = Settings.getInstance().getBitcoinNet() == BTC.BitcoinNet.MAIN ? 0x05 : (byte) 0xc4; convertAddressInB(addressPrefix, state); } diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index f46e0536..43001c6a 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -10,8 +10,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -19,8 +22,9 @@ import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.function.Function; +import java.util.TreeMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -46,16 +50,19 @@ import org.bitcoinj.script.Script.ScriptType; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.BlockStoreException; import org.bitcoinj.store.MemoryBlockStore; +import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.utils.Threading; import org.bitcoinj.wallet.Wallet; -import org.bitcoinj.wallet.WalletTransaction; -import org.bitcoinj.wallet.WalletTransaction.Pool; import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener; import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener; import org.qortal.settings.Settings; public class BTC { + public static final MonetaryFormat FORMAT = new MonetaryFormat().minDecimals(8).postfixCode(); + public static final long NO_LOCKTIME_NO_RBF_SEQUENCE = 0xFFFFFFFFL; + public static final long LOCKTIME_NO_RBF_SEQUENCE = NO_LOCKTIME_NO_RBF_SEQUENCE - 1; + private static final MessageDigest RIPE_MD160_DIGESTER; private static final MessageDigest SHA256_DIGESTER; static { @@ -69,6 +76,29 @@ public class BTC { protected static final Logger LOGGER = LogManager.getLogger(BTC.class); + public enum BitcoinNet { + MAIN { + @Override + public NetworkParameters getParams() { + return MainNetParams.get(); + } + }, + TEST3 { + @Override + public NetworkParameters getParams() { + return TestNet3Params.get(); + } + }, + REGTEST { + @Override + public NetworkParameters getParams() { + return RegTestParams.get(); + } + }; + + public abstract NetworkParameters getParams(); + } + private static BTC instance; private final NetworkParameters params; @@ -85,24 +115,60 @@ public class BTC { private static final String MINIMAL_TESTNET3_TEXTFILE = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAAApmwX6UCEnJcYIKTa7HO3pFkqqNhAzJVBMdEuGAAAAAPSAvVCBUypCbBW/OqU0oIF7ISF84h2spOqHrFCWN9Zw6r6/T///AB0E5oOO\n"; private static final String MINIMAL_MAINNET_TEXTFILE = "TXT CHECKPOINTS 1\n0\n1\nAAAAAAAAB+EH4QfhAAAH4AEAAABjl7tqvU/FIcDT9gcbVlA4nwtFUbxAtOawZzBpAAAAAKzkcK7NqciBjI/ldojNKncrWleVSgDfBCCn3VRrbSxXaw5/Sf//AB0z8Bkv\n"; - public UpdateableCheckpointManager(NetworkParameters params) throws IOException { - super(params, getMinimalTextFileStream(params)); + public UpdateableCheckpointManager(NetworkParameters params, File checkpointsFile) throws IOException { + super(params, getMinimalTextFileStream(params, checkpointsFile)); } public UpdateableCheckpointManager(NetworkParameters params, InputStream inputStream) throws IOException { super(params, inputStream); } - private static ByteArrayInputStream getMinimalTextFileStream(NetworkParameters params) { + private static ByteArrayInputStream getMinimalTextFileStream(NetworkParameters params, File checkpointsFile) { if (params == MainNetParams.get()) return new ByteArrayInputStream(MINIMAL_MAINNET_TEXTFILE.getBytes()); if (params == TestNet3Params.get()) return new ByteArrayInputStream(MINIMAL_TESTNET3_TEXTFILE.getBytes()); + if (params == RegTestParams.get()) + return newRegTestCheckpointsStream(checkpointsFile); // We have to build this + throw new RuntimeException("Failed to construct empty UpdateableCheckpointManageer"); } + private static ByteArrayInputStream newRegTestCheckpointsStream(File checkpointsFile) { + try { + final NetworkParameters params = RegTestParams.get(); + + final BlockStore store = new MemoryBlockStore(params); + final BlockChain chain = new BlockChain(params, store); + final PeerGroup peerGroup = new PeerGroup(params, chain); + + final InetAddress ipAddress = InetAddress.getLoopbackAddress(); + final PeerAddress peerAddress = new PeerAddress(params, ipAddress); + peerGroup.addAddress(peerAddress); + peerGroup.start(); + + final TreeMap checkpoints = new TreeMap<>(); + chain.addNewBestBlockListener((block) -> checkpoints.put(block.getHeight(), block)); + + peerGroup.downloadBlockChain(); + peerGroup.stop(); + + saveAsText(checkpointsFile, checkpoints.values()); + + return new ByteArrayInputStream(Files.readAllBytes(checkpointsFile.toPath())); + } catch (BlockStoreException e) { + throw new RuntimeException(e); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Override public void notifyNewBestBlock(StoredBlock block) { final int height = block.getHeight(); @@ -119,22 +185,22 @@ public class BTC { this.checkpoints.put(blockTimestamp, block); try { - this.saveAsText(new File(BTC.getInstance().getDirectory(), BTC.getInstance().getCheckpointsFileName())); + saveAsText(new File(BTC.getInstance().getDirectory(), BTC.getInstance().getCheckpointsFileName()), this.checkpoints.values()); } catch (FileNotFoundException e) { // Save failed - log it but it's not critical LOGGER.warn("Failed to save updated BTC checkpoints: " + e.getMessage()); } } - public void saveAsText(File textFile) throws FileNotFoundException { + private static void saveAsText(File textFile, Collection checkpointBlocks) throws FileNotFoundException { try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(textFile), StandardCharsets.US_ASCII))) { writer.println("TXT CHECKPOINTS 1"); writer.println("0"); // Number of signatures to read. Do this later. - writer.println(this.checkpoints.size()); + writer.println(checkpointBlocks.size()); ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); - for (StoredBlock block : this.checkpoints.values()) { + for (StoredBlock block : checkpointBlocks) { block.serializeCompact(buffer); writer.println(CheckpointManager.BASE64.encode(buffer.array())); buffer.position(0); @@ -173,16 +239,24 @@ public class BTC { // Constructors and instance private BTC() { - if (Settings.getInstance().useBitcoinTestNet()) { - /* - this.params = RegTestParams.get(); - this.checkpointsFileName = "checkpoints-regtest.txt"; - */ - this.params = TestNet3Params.get(); - this.checkpointsFileName = "checkpoints-testnet.txt"; - } else { - this.params = MainNetParams.get(); - this.checkpointsFileName = "checkpoints.txt"; + BitcoinNet bitcoinNet = Settings.getInstance().getBitcoinNet(); + this.params = bitcoinNet.getParams(); + + switch (bitcoinNet) { + case MAIN: + this.checkpointsFileName = "checkpoints.txt"; + break; + + case TEST3: + this.checkpointsFileName = "checkpoints-testnet.txt"; + break; + + case REGTEST: + this.checkpointsFileName = "checkpoints-regtest.txt"; + break; + + default: + throw new IllegalStateException("Unsupported Bitcoin network: " + bitcoinNet.name()); } this.directory = new File("Qortal-BTC"); @@ -196,7 +270,7 @@ public class BTC { } catch (FileNotFoundException e) { // Construct with no checkpoints then try { - this.manager = new UpdateableCheckpointManager(this.params); + this.manager = new UpdateableCheckpointManager(this.params, checkpointsFile); } catch (IOException e2) { throw new RuntimeException("Failed to create new BTC checkpoints", e2); } @@ -222,7 +296,7 @@ public class BTC { return this.checkpointsFileName; } - /* package */ NetworkParameters getNetworkParameters() { + public NetworkParameters getNetworkParameters() { return this.params; } diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 83d46d8b..7ed761cd 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -20,6 +20,7 @@ import org.eclipse.persistence.exceptions.XMLMarshalException; import org.eclipse.persistence.jaxb.JAXBContextFactory; import org.eclipse.persistence.jaxb.UnmarshallerProperties; import org.qortal.block.BlockChain; +import org.qortal.crosschain.BTC.BitcoinNet; // All properties to be converted to JSON via JAXB @XmlAccessorType(XmlAccessType.FIELD) @@ -91,7 +92,7 @@ public class Settings { // Which blockchains this node is running private String blockchainConfig = null; // use default from resources - private boolean useBitcoinTestNet = false; + private BitcoinNet bitcoinNet = BitcoinNet.MAIN; // Repository related /** Queries that take longer than this are logged. (milliseconds) */ @@ -345,8 +346,8 @@ public class Settings { return this.blockchainConfig; } - public boolean useBitcoinTestNet() { - return this.useBitcoinTestNet; + public BitcoinNet getBitcoinNet() { + return this.bitcoinNet; } public Long getSlowQueryThreshold() { diff --git a/src/test/java/org/qora/test/apps/BuildCheckpoints.java b/src/test/java/org/qora/test/apps/BuildCheckpoints.java index 44869bb7..5b7f2d44 100644 --- a/src/test/java/org/qora/test/apps/BuildCheckpoints.java +++ b/src/test/java/org/qora/test/apps/BuildCheckpoints.java @@ -16,8 +16,6 @@ import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.PeerAddress; import org.bitcoinj.core.PeerGroup; import org.bitcoinj.core.StoredBlock; -import org.bitcoinj.core.VerificationException; -import org.bitcoinj.core.listeners.NewBestBlockListener; import org.bitcoinj.params.RegTestParams; import org.bitcoinj.store.BlockStore; import org.bitcoinj.store.MemoryBlockStore; @@ -33,7 +31,7 @@ public class BuildCheckpoints { final BlockChain chain = new BlockChain(params, store); final PeerGroup peerGroup = new PeerGroup(params, chain); - final InetAddress ipAddress = InetAddress.getLocalHost(); + final InetAddress ipAddress = InetAddress.getLoopbackAddress(); final PeerAddress peerAddress = new PeerAddress(params, ipAddress); peerGroup.addAddress(peerAddress); peerGroup.start(); diff --git a/src/test/java/org/qora/test/btcacct/BuildP2SH.java b/src/test/java/org/qora/test/btcacct/BuildP2SH.java new file mode 100644 index 00000000..fd2b9bf4 --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/BuildP2SH.java @@ -0,0 +1,125 @@ +package org.qora.test.btcacct; + +import java.security.Security; +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.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.controller.Controller; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.settings.Settings; + +import com.google.common.hash.HashCode; + +public class BuildP2SH { + + private static void usage(String error) { + if (error != null) + System.err.println(error); + + System.err.println(String.format("usage: BuildP2SH ()")); + System.err.println(String.format("example: BuildP2SH " + + "mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" + + "\t0.00008642 \\\n" + + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n" + + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\t1585920000")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length < 5 || args.length > 6) + usage(null); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Settings.fileInstance("settings-test.json"); + + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + + Address refundBitcoinAddress = null; + Coin bitcoinAmount = null; + Address redeemBitcoinAddress = null; + byte[] secretHash = null; + int lockTime = 0; + Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + + int argIndex = 0; + try { + refundBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Refund BTC address must be in P2PKH form"); + + bitcoinAmount = Coin.parseCoin(args[argIndex++]); + + redeemBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Redeem BTC address must be in P2PKH form"); + + secretHash = HashCode.fromString(args[argIndex++]).asBytes(); + if (secretHash.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 > 7 * 24 * 60 * 60) + usage("Locktime (seconds) should be at between 10 minutes and 1 week from now"); + + if (args.length > argIndex) + bitcoinFee = Coin.parseCoin(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) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + System.out.println("Confirm the following is correct based on the info you've given:"); + + System.out.println(String.format("Refund Bitcoin address: %s", refundBitcoinAddress)); + System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString())); + + System.out.println(String.format("Redeem Bitcoin address: %s", redeemBitcoinAddress)); + System.out.println(String.format("Redeem miner's fee: %s", BTC.FORMAT.format(bitcoinFee))); + + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); + System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash))); + + byte[] redeemScriptBytes = BTCACCT.buildScript(refundBitcoinAddress.getHash(), lockTime, redeemBitcoinAddress.getHash(), secretHash); + System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); + + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + System.out.println(String.format("P2SH address: %s", p2shAddress)); + + bitcoinAmount = bitcoinAmount.add(bitcoinFee); + + // Fund P2SH + System.out.println(String.format("\nYou need to fund %s with %s (includes redeem/refund fee of %s)", + p2shAddress.toString(), BTC.FORMAT.format(bitcoinAmount), BTC.FORMAT.format(bitcoinFee))); + + System.out.println("Once this is done, responder should run Respond to check P2SH funding and create AT"); + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); + } + } + +} diff --git a/src/test/java/org/qora/test/btcacct/CheckP2SH.java b/src/test/java/org/qora/test/btcacct/CheckP2SH.java new file mode 100644 index 00000000..ac090e69 --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/CheckP2SH.java @@ -0,0 +1,172 @@ +package org.qora.test.btcacct; + +import java.security.Security; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.LegacyAddress; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.script.Script.ScriptType; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.controller.Controller; +import org.qora.crosschain.BTC; +import org.qora.crosschain.BTCACCT; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.settings.Settings; + +import com.google.common.hash.HashCode; + +public class CheckP2SH { + + private static void usage(String error) { + if (error != null) + System.err.println(error); + + System.err.println(String.format("usage: CheckP2SH ()")); + System.err.println(String.format("example: CheckP2SH " + + "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n" + + "mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" + + "\t0.00008642 \\\n" + + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n" + + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\t1585920000")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length < 6 || args.length > 7) + usage(null); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Settings.fileInstance("settings-test.json"); + + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + + Address p2shAddress = null; + Address refundBitcoinAddress = null; + Coin bitcoinAmount = null; + Address redeemBitcoinAddress = null; + byte[] secretHash = null; + int lockTime = 0; + Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + + int argIndex = 0; + try { + p2shAddress = Address.fromString(params, args[argIndex++]); + if (p2shAddress.getOutputScriptType() != ScriptType.P2SH) + usage("P2SH address invalid"); + + refundBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Refund BTC address must be in P2PKH form"); + + bitcoinAmount = Coin.parseCoin(args[argIndex++]); + + redeemBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Redeem BTC address must be in P2PKH form"); + + secretHash = HashCode.fromString(args[argIndex++]).asBytes(); + if (secretHash.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 > 7 * 24 * 60 * 60) + usage("Locktime (seconds) should be at between 10 minutes and 1 week from now"); + + if (args.length > argIndex) + bitcoinFee = Coin.parseCoin(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) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + System.out.println("Confirm the following is correct based on the info you've given:"); + + System.out.println(String.format("Refund Bitcoin address: %s", redeemBitcoinAddress)); + System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString())); + + System.out.println(String.format("Redeem Bitcoin address: %s", refundBitcoinAddress)); + System.out.println(String.format("Redeem miner's fee: %s", BTC.FORMAT.format(bitcoinFee))); + + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); + System.out.println(String.format("Hash of secret: %s", HashCode.fromBytes(secretHash))); + + System.out.println(String.format("P2SH address: %s", p2shAddress)); + + byte[] redeemScriptBytes = BTCACCT.buildScript(refundBitcoinAddress.getHash(), lockTime, redeemBitcoinAddress.getHash(), secretHash); + System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); + + byte[] redeemScriptHash = BTC.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); + } + + bitcoinAmount = bitcoinAmount.add(bitcoinFee); + + long medianBlockTime = BTC.getInstance().getMedianBlockTime(); + 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) to redeem based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC))); + + // Check P2SH is funded + final long startTime = lockTime - 86400; + + Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), startTime); + if (p2shBalance == null) { + System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); + System.exit(2); + } + System.out.println(String.format("P2SH address %s balance: %s", p2shAddress, BTC.FORMAT.format(p2shBalance))); + + // Grab all P2SH funding transactions (just in case there are more than one) + List fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), startTime); + if (fundingOutputs == null) { + System.err.println(String.format("Can't find outputs for P2SH")); + System.exit(2); + } + + System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); + + for (TransactionOutput fundingOutput : fundingOutputs) + System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.FORMAT.format(fundingOutput.getValue()))); + + if (fundingOutputs.isEmpty()) { + System.err.println(String.format("Can't redeem spent/unfunded P2SH")); + System.exit(2); + } + + if (fundingOutputs.size() != 1) { + System.err.println(String.format("Expecting only one unspent output for P2SH")); + System.exit(2); + } + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); + } + } + +} diff --git a/src/test/java/org/qora/test/btcacct/DeployAT.java b/src/test/java/org/qora/test/btcacct/DeployAT.java new file mode 100644 index 00000000..da05c567 --- /dev/null +++ b/src/test/java/org/qora/test/btcacct/DeployAT.java @@ -0,0 +1,158 @@ +package org.qora.test.btcacct; + +import java.math.BigDecimal; +import java.security.Security; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.qora.account.PrivateKeyAccount; +import org.qora.asset.Asset; +import org.qora.controller.Controller; +import org.qora.crosschain.BTCACCT; +import org.qora.crypto.Crypto; +import org.qora.data.transaction.BaseTransactionData; +import org.qora.data.transaction.DeployAtTransactionData; +import org.qora.data.transaction.TransactionData; +import org.qora.group.Group; +import org.qora.repository.DataException; +import org.qora.repository.Repository; +import org.qora.repository.RepositoryFactory; +import org.qora.repository.RepositoryManager; +import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; +import org.qora.settings.Settings; +import org.qora.transaction.DeployAtTransaction; +import org.qora.transaction.Transaction; +import org.qora.transform.TransformationException; +import org.qora.transform.transaction.TransactionTransformer; +import org.qora.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 ()")); + System.err.println(String.format("example: DeployAT " + + "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n" + + "\t3.1415 \\\n" + + "\tQgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v \\\n" + + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\t1585920000 \\\n" + + "\t0.0001")); + System.exit(1); + } + + public static void main(String[] args) { + if (args.length < 5 || args.length > 6) + usage(null); + + Security.insertProviderAt(new BouncyCastleProvider(), 0); + Settings.fileInstance("settings-test.json"); + + byte[] refundPrivateKey = null; + BigDecimal qortAmount = null; + String redeemAddress = null; + byte[] secretHash = null; + int lockTime = 0; + BigDecimal initialPayout = BigDecimal.ZERO.setScale(8); + + int argIndex = 0; + try { + refundPrivateKey = Base58.decode(args[argIndex++]); + if (refundPrivateKey.length != 32) + usage("Refund private key must be 32 bytes"); + + qortAmount = new BigDecimal(args[argIndex++]); + if (qortAmount.signum() <= 0) + usage("QORT amount must be positive"); + + redeemAddress = args[argIndex++]; + if (!Crypto.isValidAddress(redeemAddress)) + usage("Redeem address invalid"); + + secretHash = HashCode.fromString(args[argIndex++]).asBytes(); + if (secretHash.length != 20) + usage("Hash of secret must be 20 bytes"); + + lockTime = Integer.parseInt(args[argIndex++]); + + if (args.length > argIndex) + initialPayout = new BigDecimal(args[argIndex++]).setScale(8); + } 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) { + throw new RuntimeException("Repository startup issue: " + e.getMessage()); + } + + try (final Repository repository = RepositoryManager.getRepository()) { + System.out.println("Confirm the following is correct based on the info you've given:"); + + PrivateKeyAccount refundAccount = new PrivateKeyAccount(repository, refundPrivateKey); + System.out.println(String.format("Refund Qortal address: %s", refundAccount.getAddress())); + + System.out.println(String.format("QORT amount (INCLUDING FEES): %s", qortAmount.toPlainString())); + + System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); + + System.out.println(String.format("Redeem Qortal address: %s", redeemAddress)); + + // New/derived info + + System.out.println("\nCHECKING info from other party:"); + + System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); + System.out.println("Make sure this is BEFORE P2SH lockTime to allow you to refund AT before P2SH refunded"); + + // Deploy AT + final int BLOCK_TIME = 60; // seconds + final int refundTimeout = (lockTime - (int) (System.currentTimeMillis() / 1000L)) / BLOCK_TIME; + + byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, redeemAddress, refundTimeout, initialPayout); + System.out.println("CIYAM 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); + } + + BigDecimal fee = BigDecimal.ZERO; + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, refundAccount.getPublicKey(), fee, null); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, "QORT-BTC", "QORT-BTC ACCT", "", "", creationBytes, qortAmount, 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\n", Base58.encode(signedBytes))); + } catch (NumberFormatException e) { + usage(String.format("Number format exception: %s", e.getMessage())); + } catch (DataException e) { + throw new RuntimeException("Repository issue: " + e.getMessage()); + } + } + +} diff --git a/src/test/java/org/qora/test/btcacct/Initiate.java b/src/test/java/org/qora/test/btcacct/Initiate.java index d081f4ba..e5f185f4 100644 --- a/src/test/java/org/qora/test/btcacct/Initiate.java +++ b/src/test/java/org/qora/test/btcacct/Initiate.java @@ -121,7 +121,7 @@ public class Initiate { int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); - byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), yourBitcoinAddress.getHash(), theirBitcoinAddress.getHash(), lockTime); + byte[] redeemScriptBytes = null; // BTCACCT.buildScript(tradeKey.getPubKeyHash(), yourBitcoinAddress.getHash(), theirBitcoinAddress.getHash(), lockTime); System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); diff --git a/src/test/java/org/qora/test/btcacct/Redeem.java b/src/test/java/org/qora/test/btcacct/Redeem.java index 503fc710..fd2a1061 100644 --- a/src/test/java/org/qora/test/btcacct/Redeem.java +++ b/src/test/java/org/qora/test/btcacct/Redeem.java @@ -4,18 +4,16 @@ import java.security.Security; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.util.Arrays; import java.util.List; import org.bitcoinj.core.Address; -import org.bitcoinj.core.AddressFormatException; 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.params.RegTestParams; -import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qora.controller.Controller; @@ -30,25 +28,6 @@ import org.qora.settings.Settings; import com.google.common.hash.HashCode; -/** - * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. - * - * Initiator (wants Qora, has BTC) - * Funds BTC P2SH address - * - * Responder (has Qora, wants BTC) - * Builds Qora ACCT AT and funds it with Qora - * - * Initiator sends trade private key to Responder. - * Responder uses their public key + tx signature + trade pubkey + script as input to BTC P2SH address, releasing BTC amount to responder. - * - * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT - * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) - * - * Qora ACCT AT sends its Qora to initiator - * - */ - public class Redeem { static { @@ -56,16 +35,17 @@ public class Redeem { System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); } - private static final long REFUND_TIMEOUT = 600L; // seconds - private static void usage(String error) { if (error != null) System.err.println(error); - System.err.println(String.format("usage: Redeem ()")); - System.err.println(String.format("example: Redeem 032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" + System.err.println(String.format("usage: Redeem ()")); + System.err.println(String.format("example: Redeem " + + "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n" + "\tmrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" - + "\teb95e1c1a5e9e6733549faec85b71f74f67638ea63b0acf2f077e9d0cb94dfe8 1575653814 2Mtn4aLjjWVEWckdoTMK7P8WbkXJf1ES6yL")); + + "\tec199a4abc9d3bf024349e397535dfee9d287e174aeabae94237eb03a0118c03 \\\n" + + "\t736563726574 \\\n" + + "\t1585920000")); System.exit(1); } @@ -75,39 +55,44 @@ public class Redeem { Security.insertProviderAt(new BouncyCastleProvider(), 0); Settings.fileInstance("settings-test.json"); - NetworkParameters params = RegTestParams.get(); - // TestNet3Params.get(); - ECKey yourBitcoinKey = null; - Address theirBitcoinAddress = null; - byte[] tradePrivateKey = null; - int lockTime = 0; + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + Address p2shAddress = null; + Address refundBitcoinAddress = null; + byte[] redeemPrivateKey = null; + byte[] secret = null; + int lockTime = 0; Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + int argIndex = 0; try { - int argIndex = 0; - - yourBitcoinKey = ECKey.fromPublicOnly(HashCode.fromString(args[argIndex++]).asBytes()); - - theirBitcoinAddress = Address.fromString(params, args[argIndex++]); - if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) - usage("Their BTC address is not in P2PKH form"); - - tradePrivateKey = HashCode.fromString(args[argIndex++]).asBytes(); - if (tradePrivateKey.length != 32) - usage("Trade private key not 32 bytes"); - - lockTime = Integer.parseInt(args[argIndex++]); - p2shAddress = Address.fromString(params, args[argIndex++]); if (p2shAddress.getOutputScriptType() != ScriptType.P2SH) usage("P2SH address invalid"); + refundBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Refund BTC 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++]); + if (args.length > argIndex) bitcoinFee = Coin.parseCoin(args[argIndex++]); - } catch (NumberFormatException | AddressFormatException e) { - usage(String.format("Argument format exception: %s", e.getMessage())); + } catch (IllegalArgumentException e) { + usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage())); } try { @@ -120,21 +105,22 @@ public class Redeem { try (final Repository repository = RepositoryManager.getRepository()) { System.out.println("Confirm the following is correct based on the info you've given:"); - System.out.println(String.format("Your Bitcoin address: %s", Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH))); - System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); - System.out.println(String.format("Trade PRIVATE key: %s", HashCode.fromBytes(tradePrivateKey))); + System.out.println(String.format("Redeem PRIVATE key: %s", HashCode.fromBytes(redeemPrivateKey))); + System.out.println(String.format("Redeem miner's fee: %s", BTC.FORMAT.format(bitcoinFee))); System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); - System.out.println(String.format("P2SH address: %s", p2shAddress)); - System.out.println(String.format("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); // New/derived info - System.out.println("\nCHECKING info from other party:"); + byte[] secretHash = BTC.hash160(secret); + System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); - ECKey tradeKey = ECKey.fromPrivate(tradePrivateKey); - System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); + ECKey redeemKey = ECKey.fromPrivate(redeemPrivateKey); + Address redeemAddress = Address.fromKey(params, redeemKey, ScriptType.P2PKH); + System.out.println(String.format("Redeem recipient (PKH): %s (%s)", redeemAddress, HashCode.fromBytes(redeemAddress.getHash()))); - byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), theirBitcoinAddress.getHash(), yourBitcoinKey.getPubKeyHash(), lockTime); + System.out.println(String.format("P2SH address: %s", p2shAddress)); + + byte[] redeemScriptBytes = BTCACCT.buildScript(refundBitcoinAddress.getHash(), lockTime, redeemAddress.getHash(), secretHash); System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); @@ -159,7 +145,10 @@ public class Redeem { System.exit(2); } - Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + // Check P2SH is funded + final long startTime = ((int) (System.currentTimeMillis() / 1000L)) - 86400; + + Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), startTime); if (p2shBalance == null) { System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); System.exit(2); @@ -167,8 +156,16 @@ public class Redeem { System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); // Grab all P2SH funding transactions (just in case there are more than one) - List fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); - System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); + List fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), startTime); + if (fundingOutputs == null) { + System.err.println(String.format("Can't find outputs for P2SH")); + System.exit(2); + } + + System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); + + for (TransactionOutput fundingOutput : fundingOutputs) + System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.FORMAT.format(fundingOutput.getValue()))); if (fundingOutputs.isEmpty()) { System.err.println(String.format("Can't redeem spent/unfunded P2SH")); @@ -184,7 +181,9 @@ public class Redeem { System.out.println(String.format("Using output %s:%d for redeem", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex())); Coin redeemAmount = p2shBalance.subtract(bitcoinFee); - Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, tradeKey, yourBitcoinKey.getPubKey(), fundingOutput, redeemScriptBytes); + System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(redeemAmount), BTC.FORMAT.format(bitcoinFee))); + + Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, secret); byte[] redeemBytes = redeemTransaction.bitcoinSerialize(); diff --git a/src/test/java/org/qora/test/btcacct/Refund.java b/src/test/java/org/qora/test/btcacct/Refund.java index 662bf63e..3969c590 100644 --- a/src/test/java/org/qora/test/btcacct/Refund.java +++ b/src/test/java/org/qora/test/btcacct/Refund.java @@ -4,18 +4,16 @@ import java.security.Security; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.util.Arrays; import java.util.List; import org.bitcoinj.core.Address; -import org.bitcoinj.core.AddressFormatException; 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.params.RegTestParams; -import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qora.controller.Controller; @@ -55,16 +53,17 @@ public class Refund { System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); } - private static final long REFUND_TIMEOUT = 600L; // seconds - private static void usage(String error) { if (error != null) System.err.println(error); - System.err.println(String.format("usage: Refund ()")); - System.err.println(String.format("example: Refund 03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" + System.err.println(String.format("usage: Refund ()")); + System.err.println(String.format("example: Refund " + + "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n" + + "\tef027fb5828c5e201eaf6de4cd3b0b340d16a191ef848cd691f35ef8f727358c9c01b576fb7e \\\n" + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n" - + "\teb95e1c1a5e9e6733549faec85b71f74f67638ea63b0acf2f077e9d0cb94dfe8 1575653814 2Mtn4aLjjWVEWckdoTMK7P8WbkXJf1ES6yL")); + + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\t1585920000")); System.exit(1); } @@ -74,39 +73,44 @@ public class Refund { Security.insertProviderAt(new BouncyCastleProvider(), 0); Settings.fileInstance("settings-test.json"); - NetworkParameters params = RegTestParams.get(); - // TestNet3Params.get(); - ECKey yourBitcoinKey = null; - Address theirBitcoinAddress = null; - byte[] tradePrivateKey = null; - int lockTime = 0; + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + Address p2shAddress = null; + byte[] refundPrivateKey = null; + Address redeemBitcoinAddress = null; + byte[] secretHash = null; + int lockTime = 0; Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + int argIndex = 0; try { - int argIndex = 0; - - yourBitcoinKey = ECKey.fromPublicOnly(HashCode.fromString(args[argIndex++]).asBytes()); - - theirBitcoinAddress = Address.fromString(params, args[argIndex++]); - if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) - usage("Their BTC address is not in P2PKH form"); - - tradePrivateKey = HashCode.fromString(args[argIndex++]).asBytes(); - if (tradePrivateKey.length != 32) - usage("Trade private key not 32 bytes"); - - lockTime = Integer.parseInt(args[argIndex++]); - 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"); + + redeemBitcoinAddress = Address.fromString(params, args[argIndex++]); + if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + usage("Their BTC address must be in P2PKH form"); + + secretHash = HashCode.fromString(args[argIndex++]).asBytes(); + if (secretHash.length != 20) + usage("HASH160 of secret must be 20 bytes"); + + lockTime = Integer.parseInt(args[argIndex++]); + if (args.length > argIndex) bitcoinFee = Coin.parseCoin(args[argIndex++]); - } catch (NumberFormatException | AddressFormatException e) { - usage(String.format("Argument format exception: %s", e.getMessage())); + } catch (IllegalArgumentException e) { + usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage())); } try { @@ -119,21 +123,21 @@ public class Refund { try (final Repository repository = RepositoryManager.getRepository()) { System.out.println("Confirm the following is correct based on the info you've given:"); - System.out.println(String.format("Your Bitcoin address: %s", Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH))); - System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); - System.out.println(String.format("Trade PRIVATE key: %s", HashCode.fromBytes(tradePrivateKey))); + System.out.println(String.format("Refund PRIVATE key: %s", HashCode.fromBytes(refundPrivateKey))); + System.out.println(String.format("Redeem Bitcoin address: %s", redeemBitcoinAddress)); System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); System.out.println(String.format("P2SH address: %s", p2shAddress)); - System.out.println(String.format("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); + System.out.println(String.format("Refund miner's fee: %s", BTC.FORMAT.format(bitcoinFee))); // New/derived info System.out.println("\nCHECKING info from other party:"); - ECKey tradeKey = ECKey.fromPrivate(tradePrivateKey); - System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); + ECKey refundKey = ECKey.fromPrivate(refundPrivateKey); + Address refundAddress = Address.fromKey(params, refundKey, ScriptType.P2PKH); + System.out.println(String.format("Refund recipient (PKH): %s (%s)", refundAddress, HashCode.fromBytes(refundAddress.getHash()))); - byte[] redeemScriptBytes = BTCACCT.buildScript(tradeKey.getPubKeyHash(), yourBitcoinKey.getPubKeyHash(), theirBitcoinAddress.getHash(), lockTime); + byte[] redeemScriptBytes = BTCACCT.buildScript(refundAddress.getHash(), lockTime, redeemBitcoinAddress.getHash(), secretHash); System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); @@ -163,7 +167,10 @@ public class Refund { System.exit(2); } - Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); + // Check P2SH is funded + final long startTime = ((int) (System.currentTimeMillis() / 1000L)) - 86400; + + Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), startTime); if (p2shBalance == null) { System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); System.exit(2); @@ -171,8 +178,16 @@ public class Refund { System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); // Grab all P2SH funding transactions (just in case there are more than one) - List fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); - System.out.println(String.format("Found %d unspent output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); + List fundingOutputs = BTC.getInstance().getOutputs(p2shAddress.toString(), startTime); + if (fundingOutputs == null) { + System.err.println(String.format("Can't find outputs for P2SH")); + System.exit(2); + } + + System.out.println(String.format("Found %d output%s for P2SH", fundingOutputs.size(), (fundingOutputs.size() != 1 ? "s" : ""))); + + for (TransactionOutput fundingOutput : fundingOutputs) + System.out.println(String.format("Output %s:%d amount %s", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex(), BTC.FORMAT.format(fundingOutput.getValue()))); if (fundingOutputs.isEmpty()) { System.err.println(String.format("Can't refund spent/unfunded P2SH")); @@ -188,11 +203,13 @@ public class Refund { System.out.println(String.format("Using output %s:%d for refund", HashCode.fromBytes(fundingOutput.getParentTransactionHash().getBytes()), fundingOutput.getIndex())); Coin refundAmount = p2shBalance.subtract(bitcoinFee); - Transaction refundTransaction = BTCACCT.buildRefundTransaction(refundAmount, tradeKey, yourBitcoinKey.getPubKey(), fundingOutput, redeemScriptBytes, lockTime); + System.out.println(String.format("Spending %s of output, with %s as mining fee", BTC.FORMAT.format(refundAmount), BTC.FORMAT.format(bitcoinFee))); - byte[] refundBytes = refundTransaction.bitcoinSerialize(); + Transaction redeemTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutput, redeemScriptBytes, lockTime); - System.out.println(String.format("\nLoad this transaction into your wallet and broadcast:\n%s\n", HashCode.fromBytes(refundBytes).toString())); + byte[] redeemBytes = redeemTransaction.bitcoinSerialize(); + + System.out.println(String.format("\nLoad this transaction into your wallet and broadcast:\n%s\n", HashCode.fromBytes(redeemBytes).toString())); } catch (NumberFormatException e) { usage(String.format("Number format exception: %s", e.getMessage())); } catch (DataException e) { diff --git a/src/test/java/org/qora/test/btcacct/Respond2.java b/src/test/java/org/qora/test/btcacct/Respond2.java index 5699ad1a..f7f5ccbc 100644 --- a/src/test/java/org/qora/test/btcacct/Respond2.java +++ b/src/test/java/org/qora/test/btcacct/Respond2.java @@ -136,7 +136,7 @@ public class Respond2 { byte[] secretHash = HashCode.fromString(secretHashHex).asBytes(); System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); - byte[] redeemScriptBytes = BTCACCT.buildScript(secretHash, theirBitcoinPubKey, yourBitcoinPubKey, lockTime); + byte[] redeemScriptBytes = null; // BTCACCT.buildScript(secretHash, theirBitcoinPubKey, yourBitcoinPubKey, lockTime); System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); @@ -166,7 +166,7 @@ public class Respond2 { System.out.println("\nYour response:"); // If good, deploy AT - byte[] creationBytes = BTCACCT.buildCiyamAT(secretHash, theirQortPubKey, REFUND_TIMEOUT / 60); + byte[] creationBytes = null; // BTCACCT.buildQortalAT(secretHash, theirQortPubKey, REFUND_TIMEOUT / 60); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); BigDecimal qortAmount = new BigDecimal(rawQortAmount).setScale(8); From 2ed2cc0fab309e55971aaf38fd4d9eefb09980ef Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 8 Apr 2020 10:09:52 +0100 Subject: [PATCH 07/15] Correct package names and minor clean --- .../org/qortal/at/QortalFunctionCode.java | 1 - src/main/java/org/qortal/crosschain/BTC.java | 2 +- .../{qora => qortal}/crosschain/BTCACCT.java | 6 +- .../java/org/qora/test/btcacct/Initiate.java | 144 ------------ .../java/org/qora/test/btcacct/Respond2.java | 209 ------------------ .../test/apps/BuildCheckpoints.java | 2 +- .../test/btcacct/BuildP2SH.java | 20 +- .../test/btcacct/CheckP2SH.java | 20 +- .../test/btcacct/DeployAT.java | 44 ++-- .../test/btcacct/GetTransaction.java | 10 +- .../{qora => qortal}/test/btcacct/Redeem.java | 20 +- .../{qora => qortal}/test/btcacct/Refund.java | 38 +--- 12 files changed, 72 insertions(+), 444 deletions(-) rename src/main/java/org/{qora => qortal}/crosschain/BTCACCT.java (99%) delete mode 100644 src/test/java/org/qora/test/btcacct/Initiate.java delete mode 100644 src/test/java/org/qora/test/btcacct/Respond2.java rename src/test/java/org/{qora => qortal}/test/apps/BuildCheckpoints.java (98%) rename src/test/java/org/{qora => qortal}/test/btcacct/BuildP2SH.java (91%) rename src/test/java/org/{qora => qortal}/test/btcacct/CheckP2SH.java (94%) rename src/test/java/org/{qora => qortal}/test/btcacct/DeployAT.java (84%) rename src/test/java/org/{qora => qortal}/test/btcacct/GetTransaction.java (82%) rename src/test/java/org/{qora => qortal}/test/btcacct/Redeem.java (94%) rename src/test/java/org/{qora => qortal}/test/btcacct/Refund.java (88%) diff --git a/src/main/java/org/qortal/at/QortalFunctionCode.java b/src/main/java/org/qortal/at/QortalFunctionCode.java index e0d6cfe2..0ca9f5d6 100644 --- a/src/main/java/org/qortal/at/QortalFunctionCode.java +++ b/src/main/java/org/qortal/at/QortalFunctionCode.java @@ -8,7 +8,6 @@ import org.ciyam.at.ExecutionException; import org.ciyam.at.FunctionData; import org.ciyam.at.IllegalFunctionCodeException; import org.ciyam.at.MachineState; -import org.ciyam.at.Timestamp; import org.qortal.crosschain.BTC; import org.qortal.crypto.Crypto; import org.qortal.settings.Settings; diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index 43001c6a..f7f9a979 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -503,7 +503,7 @@ public class BTC { }; ReplayHooks replayHooks = new ReplayHooks(() -> this.peerGroup.addBlocksDownloadedEventListener(listener), () -> this.peerGroup.removeBlocksDownloadedEventListener(listener)); - + // Replay chain in the hope it will download transactionId as a dependency try { replayChain(startTime, wallet, replayHooks); diff --git a/src/main/java/org/qora/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java similarity index 99% rename from src/main/java/org/qora/crosschain/BTCACCT.java rename to src/main/java/org/qortal/crosschain/BTCACCT.java index 2671d76f..a0480317 100644 --- a/src/main/java/org/qora/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -1,4 +1,4 @@ -package org.qora.crosschain; +package org.qortal.crosschain; import java.math.BigDecimal; import java.nio.ByteBuffer; @@ -19,7 +19,7 @@ import org.bitcoinj.script.ScriptOpCodes; import org.ciyam.at.FunctionCode; import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; -import org.qora.utils.BitTwiddling; +import org.qortal.utils.BitTwiddling; import com.google.common.hash.HashCode; import com.google.common.primitives.Bytes; @@ -214,7 +214,7 @@ public class BTCACCT { // Temp buffer for hashing any passed secret dataByteBuffer.putLong(addrHashTemp1); dataByteBuffer.putLong(32L); - + // Initial payout amount dataByteBuffer.putLong(initialPayout.unscaledValue().longValue()); diff --git a/src/test/java/org/qora/test/btcacct/Initiate.java b/src/test/java/org/qora/test/btcacct/Initiate.java deleted file mode 100644 index e5f185f4..00000000 --- a/src/test/java/org/qora/test/btcacct/Initiate.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.qora.test.btcacct; - -import java.security.Security; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; - -import org.bitcoinj.core.Address; -import org.bitcoinj.core.AddressFormatException; -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.ECKey; -import org.bitcoinj.core.LegacyAddress; -import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.params.RegTestParams; -import org.bitcoinj.params.TestNet3Params; -import org.bitcoinj.script.Script.ScriptType; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.controller.Controller; -import org.qora.crosschain.BTC; -import org.qora.crosschain.BTCACCT; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.settings.Settings; - -import com.google.common.hash.HashCode; - -/** - * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. - * - * Initiator (wants Qora, has BTC) - * Funds BTC P2SH address - * - * Responder (has Qora, wants BTC) - * Builds Qora ACCT AT and funds it with Qora - * - * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder - * - * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT - * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) - * - * Qora ACCT AT sends its Qora to initiator - * - */ - -public class Initiate { - - private static final long REFUND_TIMEOUT = 600L; // seconds - - private static void usage(String error) { - if (error != null) - System.err.println(error); - - System.err.println(String.format("usage: Initiate ()")); - System.err.println(String.format("example: Initiate mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" - + "\t0.00008642 \\\n" - + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o")); - System.exit(1); - } - - public static void main(String[] args) { - if (args.length < 3 || args.length > 4) - usage(null); - - Security.insertProviderAt(new BouncyCastleProvider(), 0); - Settings.fileInstance("settings-test.json"); - NetworkParameters params = RegTestParams.get(); - // TestNet3Params.get(); - - Address yourBitcoinAddress = null; - Coin bitcoinAmount = null; - Address theirBitcoinAddress = null; - Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; - - try { - int argIndex = 0; - - yourBitcoinAddress = Address.fromString(params, args[argIndex++]); - if (yourBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) - usage("Your BTC address is not in P2PKH form"); - - bitcoinAmount = Coin.parseCoin(args[argIndex++]); - - theirBitcoinAddress = Address.fromString(params, args[argIndex++]); - if (theirBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) - usage("Their BTC address is not in P2PKH form"); - - if (args.length > argIndex) - bitcoinFee = Coin.parseCoin(args[argIndex++]); - } catch (NumberFormatException | AddressFormatException e) { - usage(String.format("Argument format exception: %s", e.getMessage())); - } - - try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); - RepositoryManager.setRepositoryFactory(repositoryFactory); - } catch (DataException e) { - throw new RuntimeException("Repository startup issue: " + e.getMessage()); - } - - try (final Repository repository = RepositoryManager.getRepository()) { - System.out.println("Confirm the following is correct based on the info you've given:"); - - System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); - System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); - System.out.println(String.format("Bitcoin redeem amount: %s", bitcoinAmount.toPlainString())); - System.out.println(String.format("Bitcoin redeem fee: %s", bitcoinFee.toPlainString())); - - // New/derived info - - ECKey tradeKey = new ECKey(); - System.out.println("\nSecret info (DO NOT share with other party):"); - System.out.println(String.format("Trade private key: %s", HashCode.fromBytes(tradeKey.getPrivKeyBytes()))); - - System.out.println("\nGive this info to other party:"); - - System.out.println(String.format("Trade pubkeyhash: %s", HashCode.fromBytes(tradeKey.getPubKeyHash()))); - - int lockTime = (int) ((System.currentTimeMillis() / 1000L) + REFUND_TIMEOUT); - System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneOffset.UTC), lockTime)); - - byte[] redeemScriptBytes = null; // BTCACCT.buildScript(tradeKey.getPubKeyHash(), yourBitcoinAddress.getHash(), theirBitcoinAddress.getHash(), lockTime); - System.out.println(String.format("Redeem script: %s", HashCode.fromBytes(redeemScriptBytes))); - - byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); - - Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); - System.out.println(String.format("P2SH address: %s", p2shAddress)); - - bitcoinAmount = bitcoinAmount.add(bitcoinFee); - - // Fund P2SH - System.out.println(String.format("\nYou need to fund %s with %s BTC (includes redeem/refund fee of %s)", - p2shAddress.toString(), bitcoinAmount.toPlainString(), bitcoinFee.toPlainString())); - - System.out.println("Once this is done, responder should run Respond to check P2SH funding and create AT"); - } catch (DataException e) { - throw new RuntimeException("Repository issue: " + e.getMessage()); - } - } - -} diff --git a/src/test/java/org/qora/test/btcacct/Respond2.java b/src/test/java/org/qora/test/btcacct/Respond2.java deleted file mode 100644 index f7f5ccbc..00000000 --- a/src/test/java/org/qora/test/btcacct/Respond2.java +++ /dev/null @@ -1,209 +0,0 @@ -package org.qora.test.btcacct; - -import java.math.BigDecimal; -import java.security.Security; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; - -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.params.TestNet3Params; -import org.bitcoinj.script.Script.ScriptType; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.account.PrivateKeyAccount; -import org.qora.account.PublicKeyAccount; -import org.qora.asset.Asset; -import org.qora.controller.Controller; -import org.qora.crosschain.BTC; -import org.qora.crosschain.BTCACCT; -import org.qora.data.transaction.BaseTransactionData; -import org.qora.data.transaction.DeployAtTransactionData; -import org.qora.data.transaction.TransactionData; -import org.qora.group.Group; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.settings.Settings; -import org.qora.transaction.DeployAtTransaction; -import org.qora.transaction.Transaction; -import org.qora.transform.TransformationException; -import org.qora.transform.transaction.TransactionTransformer; -import org.qora.utils.Base58; - -import com.google.common.hash.HashCode; - -/** - * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. - * - * Initiator (wants Qora, has BTC) - * Funds BTC P2SH address - * - * Responder (has Qora, wants BTC) - * Builds Qora ACCT AT and funds it with Qora - * - * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder - * - * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT - * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) - * - * Qora ACCT AT sends its Qora to initiator - * - */ - -public class Respond2 { - - private static final long REFUND_TIMEOUT = 600L; // seconds - - private static void usage() { - System.err.println(String.format("usage: Respond2 ")); - System.err.println(String.format("example: Respond2 3jjoToDaDpsdUHqaouLGypFeewNVKvtkmdM38i54WVra \\\n" - + "\t032783606be32a3e639a33afe2b15f058708ab124f3b290d595ee954390a0c8559 \\\n" - + "\t123 0.00008642 \\\n" - + "\t6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb \\\n" - + "\t03aa20871c2195361f2826c7a649eab6b42639630c4d8c33c55311d5c1e476b5d6 \\\n" - + "\tb837056cdc5d805e4db1f830a58158e1131ac96ea71de4c6f9d7854985e153e2 1575021641 2MvGdGUgAfc7qTHaZJwWmZ26Fg6Hjif8gNy")); - System.exit(1); - } - - public static void main(String[] args) { - if (args.length != 9) - usage(); - - Security.insertProviderAt(new BouncyCastleProvider(), 0); - - Settings.fileInstance("settings-test.json"); - - NetworkParameters params = TestNet3Params.get(); - - int argIndex = 0; - String yourQortPrivKey58 = args[argIndex++]; - String yourBitcoinPubKeyHex = args[argIndex++]; - - String rawQortAmount = args[argIndex++]; - String rawBitcoinAmount = args[argIndex++]; - - String theirQortPubKey58 = args[argIndex++]; - String theirBitcoinPubKeyHex = args[argIndex++]; - - String secretHashHex = args[argIndex++]; - String rawLockTime = args[argIndex++]; - String rawP2shAddress = args[argIndex++]; - - try { - RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); - RepositoryManager.setRepositoryFactory(repositoryFactory); - } catch (DataException e) { - throw new RuntimeException("Repository startup issue: " + e.getMessage()); - } - - try (final Repository repository = RepositoryManager.getRepository()) { - System.out.println("Confirm the following is correct based on the info you've given:"); - - byte[] yourQortPrivKey = Base58.decode(yourQortPrivKey58); - PrivateKeyAccount yourQortalAccount = new PrivateKeyAccount(repository, yourQortPrivKey); - byte[] yourQortPubKey = yourQortalAccount.getPublicKey(); - System.out.println(String.format("Your Qortal address: %s", yourQortalAccount.getAddress())); - - byte[] yourBitcoinPubKey = HashCode.fromString(yourBitcoinPubKeyHex).asBytes(); - ECKey yourBitcoinKey = ECKey.fromPublicOnly(yourBitcoinPubKey); - Address yourBitcoinAddress = Address.fromKey(params, yourBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Your Bitcoin address: %s", yourBitcoinAddress)); - - byte[] theirQortPubKey = Base58.decode(theirQortPubKey58); - PublicKeyAccount theirQortalAccount = new PublicKeyAccount(repository, theirQortPubKey); - System.out.println(String.format("Their Qortal address: %s", theirQortalAccount.getAddress())); - - byte[] theirBitcoinPubKey = HashCode.fromString(theirBitcoinPubKeyHex).asBytes(); - ECKey theirBitcoinKey = ECKey.fromPublicOnly(theirBitcoinPubKey); - Address theirBitcoinAddress = Address.fromKey(params, theirBitcoinKey, ScriptType.P2PKH); - System.out.println(String.format("Their Bitcoin address: %s", theirBitcoinAddress)); - - System.out.println("Hash of secret: " + secretHashHex); - - // New/derived info - - System.out.println("\nCHECKING info from other party:"); - - int lockTime = Integer.valueOf(rawLockTime); - System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); - - byte[] secretHash = HashCode.fromString(secretHashHex).asBytes(); - System.out.println("Hash of secret: " + HashCode.fromBytes(secretHash).toString()); - - byte[] redeemScriptBytes = null; // BTCACCT.buildScript(secretHash, theirBitcoinPubKey, yourBitcoinPubKey, lockTime); - System.out.println("Redeem script: " + HashCode.fromBytes(redeemScriptBytes).toString()); - - byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); - - Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); - System.out.println(String.format("P2SH address: %s", p2shAddress)); - - if (!p2shAddress.toString().equals(rawP2shAddress)) { - System.err.println(String.format("Derived P2SH address %s does not match given address %s", p2shAddress, rawP2shAddress)); - System.exit(2); - } - - // Check for funded P2SH - Coin bitcoinAmount = Coin.parseCoin(rawBitcoinAmount).add(BTCACCT.DEFAULT_BTC_FEE); - - Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), lockTime - REFUND_TIMEOUT); - if (p2shBalance == null) { - System.err.println(String.format("Unable to check P2SH address %s balance", p2shAddress)); - System.exit(2); - } - if (p2shBalance.isLessThan(bitcoinAmount)) { - System.err.println(String.format("P2SH address %s has lower balance than expected %s BTC", p2shAddress, p2shBalance.toPlainString())); - System.exit(2); - } - System.out.println(String.format("P2SH address %s balance: %s BTC", p2shAddress, p2shBalance.toPlainString())); - - System.out.println("\nYour response:"); - - // If good, deploy AT - byte[] creationBytes = null; // BTCACCT.buildQortalAT(secretHash, theirQortPubKey, REFUND_TIMEOUT / 60); - System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); - - BigDecimal qortAmount = new BigDecimal(rawQortAmount).setScale(8); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = yourQortalAccount.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", yourQortalAccount.getAddress())); - System.exit(2); - } - - BigDecimal fee = BigDecimal.ZERO; - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, yourQortPubKey, fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, "QORT-BTC", "QORT-BTC ACCT", "", "", creationBytes, qortAmount, Asset.QORT); - - Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - deployAtTransaction.sign(yourQortalAccount); - - 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\n", Base58.encode(signedBytes))); - } catch (NumberFormatException e) { - usage(); - } catch (DataException e) { - throw new RuntimeException("Repository issue: " + e.getMessage()); - } - } - -} diff --git a/src/test/java/org/qora/test/apps/BuildCheckpoints.java b/src/test/java/org/qortal/test/apps/BuildCheckpoints.java similarity index 98% rename from src/test/java/org/qora/test/apps/BuildCheckpoints.java rename to src/test/java/org/qortal/test/apps/BuildCheckpoints.java index 5b7f2d44..48cc570c 100644 --- a/src/test/java/org/qora/test/apps/BuildCheckpoints.java +++ b/src/test/java/org/qortal/test/apps/BuildCheckpoints.java @@ -1,4 +1,4 @@ -package org.qora.test.apps; +package org.qortal.test.apps; import java.io.File; import java.io.FileNotFoundException; diff --git a/src/test/java/org/qora/test/btcacct/BuildP2SH.java b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java similarity index 91% rename from src/test/java/org/qora/test/btcacct/BuildP2SH.java rename to src/test/java/org/qortal/test/btcacct/BuildP2SH.java index fd2b9bf4..23c7ab7c 100644 --- a/src/test/java/org/qora/test/btcacct/BuildP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java @@ -1,4 +1,4 @@ -package org.qora.test.btcacct; +package org.qortal.test.btcacct; import java.security.Security; import java.time.Instant; @@ -11,15 +11,15 @@ import org.bitcoinj.core.LegacyAddress; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.controller.Controller; -import org.qora.crosschain.BTC; -import org.qora.crosschain.BTCACCT; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.settings.Settings; +import org.qortal.controller.Controller; +import org.qortal.crosschain.BTC; +import org.qortal.crosschain.BTCACCT; +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.settings.Settings; import com.google.common.hash.HashCode; diff --git a/src/test/java/org/qora/test/btcacct/CheckP2SH.java b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java similarity index 94% rename from src/test/java/org/qora/test/btcacct/CheckP2SH.java rename to src/test/java/org/qortal/test/btcacct/CheckP2SH.java index ac090e69..befa1963 100644 --- a/src/test/java/org/qora/test/btcacct/CheckP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java @@ -1,4 +1,4 @@ -package org.qora.test.btcacct; +package org.qortal.test.btcacct; import java.security.Security; import java.time.Instant; @@ -13,15 +13,15 @@ import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.controller.Controller; -import org.qora.crosschain.BTC; -import org.qora.crosschain.BTCACCT; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.settings.Settings; +import org.qortal.controller.Controller; +import org.qortal.crosschain.BTC; +import org.qortal.crosschain.BTCACCT; +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.settings.Settings; import com.google.common.hash.HashCode; diff --git a/src/test/java/org/qora/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java similarity index 84% rename from src/test/java/org/qora/test/btcacct/DeployAT.java rename to src/test/java/org/qortal/test/btcacct/DeployAT.java index da05c567..36dbdc5a 100644 --- a/src/test/java/org/qora/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -1,4 +1,4 @@ -package org.qora.test.btcacct; +package org.qortal.test.btcacct; import java.math.BigDecimal; import java.security.Security; @@ -7,26 +7,26 @@ import java.time.LocalDateTime; import java.time.ZoneId; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.account.PrivateKeyAccount; -import org.qora.asset.Asset; -import org.qora.controller.Controller; -import org.qora.crosschain.BTCACCT; -import org.qora.crypto.Crypto; -import org.qora.data.transaction.BaseTransactionData; -import org.qora.data.transaction.DeployAtTransactionData; -import org.qora.data.transaction.TransactionData; -import org.qora.group.Group; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.settings.Settings; -import org.qora.transaction.DeployAtTransaction; -import org.qora.transaction.Transaction; -import org.qora.transform.TransformationException; -import org.qora.transform.transaction.TransactionTransformer; -import org.qora.utils.Base58; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.asset.Asset; +import org.qortal.controller.Controller; +import org.qortal.crosschain.BTCACCT; +import org.qortal.crypto.Crypto; +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.settings.Settings; +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.Base58; import com.google.common.hash.HashCode; @@ -115,7 +115,7 @@ public class DeployAT { // Deploy AT final int BLOCK_TIME = 60; // seconds - final int refundTimeout = (lockTime - (int) (System.currentTimeMillis() / 1000L)) / BLOCK_TIME; + final int refundTimeout = (lockTime - (int) (System.currentTimeMillis() / 1000L)) / BLOCK_TIME; byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, redeemAddress, refundTimeout, initialPayout); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); diff --git a/src/test/java/org/qora/test/btcacct/GetTransaction.java b/src/test/java/org/qortal/test/btcacct/GetTransaction.java similarity index 82% rename from src/test/java/org/qora/test/btcacct/GetTransaction.java rename to src/test/java/org/qortal/test/btcacct/GetTransaction.java index 9686a3f2..c97762b9 100644 --- a/src/test/java/org/qora/test/btcacct/GetTransaction.java +++ b/src/test/java/org/qortal/test/btcacct/GetTransaction.java @@ -1,4 +1,4 @@ -package org.qora.test.btcacct; +package org.qortal.test.btcacct; import java.security.Security; import java.util.List; @@ -6,8 +6,8 @@ import java.util.List; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.TransactionOutput; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.crosschain.BTC; -import org.qora.settings.Settings; +import org.qortal.crosschain.BTC; +import org.qortal.settings.Settings; import com.google.common.hash.HashCode; @@ -23,8 +23,8 @@ public class GetTransaction { System.err.println(error); System.err.println(String.format("usage: GetTransaction ")); - System.err.println(String.format("example: GetTransaction 816407e79568f165f13e09e9912c5f2243e0a23a007cec425acedc2e89284660 (mainnet)")); - System.err.println(String.format("example: GetTransaction 3bfd17a492a4e3d6cb7204e17e20aca6c1ab82e1828bd1106eefbaf086fb8a4e (testnet)")); + System.err.println(String.format("example (mainnet): GetTransaction 816407e79568f165f13e09e9912c5f2243e0a23a007cec425acedc2e89284660")); + System.err.println(String.format("example (testnet): GetTransaction 3bfd17a492a4e3d6cb7204e17e20aca6c1ab82e1828bd1106eefbaf086fb8a4e")); System.exit(1); } diff --git a/src/test/java/org/qora/test/btcacct/Redeem.java b/src/test/java/org/qortal/test/btcacct/Redeem.java similarity index 94% rename from src/test/java/org/qora/test/btcacct/Redeem.java rename to src/test/java/org/qortal/test/btcacct/Redeem.java index fd2a1061..a4907813 100644 --- a/src/test/java/org/qora/test/btcacct/Redeem.java +++ b/src/test/java/org/qortal/test/btcacct/Redeem.java @@ -1,4 +1,4 @@ -package org.qora.test.btcacct; +package org.qortal.test.btcacct; import java.security.Security; import java.time.Instant; @@ -16,15 +16,15 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.controller.Controller; -import org.qora.crosschain.BTC; -import org.qora.crosschain.BTCACCT; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.settings.Settings; +import org.qortal.controller.Controller; +import org.qortal.crosschain.BTC; +import org.qortal.crosschain.BTCACCT; +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.settings.Settings; import com.google.common.hash.HashCode; diff --git a/src/test/java/org/qora/test/btcacct/Refund.java b/src/test/java/org/qortal/test/btcacct/Refund.java similarity index 88% rename from src/test/java/org/qora/test/btcacct/Refund.java rename to src/test/java/org/qortal/test/btcacct/Refund.java index 3969c590..f22a436a 100644 --- a/src/test/java/org/qora/test/btcacct/Refund.java +++ b/src/test/java/org/qortal/test/btcacct/Refund.java @@ -1,4 +1,4 @@ -package org.qora.test.btcacct; +package org.qortal.test.btcacct; import java.security.Security; import java.time.Instant; @@ -16,36 +16,18 @@ import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.script.Script.ScriptType; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.qora.controller.Controller; -import org.qora.crosschain.BTC; -import org.qora.crosschain.BTCACCT; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryFactory; -import org.qora.repository.RepositoryManager; -import org.qora.repository.hsqldb.HSQLDBRepositoryFactory; -import org.qora.settings.Settings; +import org.qortal.controller.Controller; +import org.qortal.crosschain.BTC; +import org.qortal.crosschain.BTCACCT; +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.settings.Settings; import com.google.common.hash.HashCode; -/** - * Initiator must be Qora-chain so that initiator can send initial message to BTC P2SH then Qora can scan for P2SH add send corresponding message to Qora AT. - * - * Initiator (wants Qora, has BTC) - * Funds BTC P2SH address - * - * Responder (has Qora, wants BTC) - * Builds Qora ACCT AT and funds it with Qora - * - * Initiator sends recipient+secret+script as input to BTC P2SH address, releasing BTC amount - fees to responder - * - * Qora nodes scan for P2SH output, checks amount and recipient and if ok sends secret to Qora ACCT AT - * (Or it's possible to feed BTC transaction details into Qora AT so it can check them itself?) - * - * Qora ACCT AT sends its Qora to initiator - * - */ - public class Refund { static { From d2eb8b0c2b55d68de990ef2fbf97fbfbf09e6f8c Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 8 Apr 2020 18:06:42 +0100 Subject: [PATCH 08/15] Legacy QORA block reward fix If there's no more unrewarded legacy QORA held, then quickly return from Block.distributeBlockRewardToQoraHolders() instead of causing divide-by-zero. --- src/main/java/org/qortal/block/Block.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index fccd3142..4614c81c 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1693,6 +1693,9 @@ public class Block { LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString())); BigDecimal sharedAmount = BigDecimal.ZERO; + if (totalQoraHeld.signum() <= 0) + return sharedAmount; + for (int h = 0; h < qoraHolders.size(); ++h) { AccountBalanceData qoraHolder = qoraHolders.get(h); From 7ded8954c6b4f407b5a5049e004223b6840b525a Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 8 Apr 2020 18:10:19 +0100 Subject: [PATCH 09/15] Fix Transaction.calcRecommendedFee() --- .../java/org/qortal/transaction/Transaction.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index 75e31f8c..bc8974f1 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -345,18 +345,10 @@ public abstract class Transaction { BigDecimal maxBytePerUnitFee = BlockChain.getInstance().getMaxBytesPerUnitFee(); - BigDecimal recommendedFee = BigDecimal.valueOf(dataLength).divide(maxBytePerUnitFee, MathContext.DECIMAL32).setScale(8); + BigDecimal unitFeeCount = BigDecimal.valueOf(dataLength).divide(maxBytePerUnitFee, RoundingMode.UP); - // security margin - recommendedFee = recommendedFee.add(new BigDecimal("0.00000001")); - - if (recommendedFee.compareTo(BlockChain.getInstance().getUnitFee()) <= 0) { - recommendedFee = BlockChain.getInstance().getUnitFee(); - } else { - recommendedFee = recommendedFee.setScale(0, RoundingMode.CEILING); - } - - return recommendedFee.setScale(8); + BigDecimal recommendedFee = BlockChain.getInstance().getUnitFee().multiply(unitFeeCount).setScale(8); + return recommendedFee; } /** From 3eaeb927ecdc4597d339a90ca194c3281bec992c Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 8 Apr 2020 18:10:24 +0100 Subject: [PATCH 10/15] More work on QORT-BTC ACCT Requires fix in CIYAM AT v1.3.2 New version of Qortal cross-trade AT code. Change how Qortal addresses are managed in QortalATAPI from using base58 strings (that are too long) to using hex form (25 bytes) as they need to fix into 32 byte A/B register. Generate AT addresses using DeployAtTransaction's signature instead of convoluted hash of AT data like name, description, etc. Add startTime as arg to GetTransaction test app. Add missing fields (name, description, ATType, tags) to DeployAT test app. --- pom.xml | 2 +- src/main/java/org/qortal/at/AT.java | 9 +- src/main/java/org/qortal/at/QortalATAPI.java | 53 ++++--- src/main/java/org/qortal/crosschain/BTC.java | 19 +-- .../java/org/qortal/crosschain/BTCACCT.java | 132 ++++++++++++------ src/main/java/org/qortal/crypto/Crypto.java | 14 +- src/main/java/org/qortal/data/at/ATData.java | 8 ++ .../transaction/DeployAtTransaction.java | 26 +++- .../org/qortal/test/btcacct/DeployAT.java | 7 +- .../qortal/test/btcacct/GetTransaction.java | 14 +- 10 files changed, 192 insertions(+), 92 deletions(-) diff --git a/pom.xml b/pom.xml index 15509805..83cb4b4d 100644 --- a/pom.xml +++ b/pom.xml @@ -406,7 +406,7 @@ org.ciyam at - 1.3 + 1.3.2 diff --git a/src/main/java/org/qortal/at/AT.java b/src/main/java/org/qortal/at/AT.java index a59970b3..23bf02cd 100644 --- a/src/main/java/org/qortal/at/AT.java +++ b/src/main/java/org/qortal/at/AT.java @@ -5,6 +5,7 @@ import java.nio.ByteBuffer; import java.util.List; import org.ciyam.at.MachineState; +import org.ciyam.at.Timestamp; import org.qortal.asset.Asset; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; @@ -48,7 +49,13 @@ public class AT { short version = (short) ((creationBytes[0] & 0xff) | (creationBytes[1] << 8)); // Little-endian if (version >= 2) { - MachineState machineState = new MachineState(deployATTransactionData.getCreationBytes()); + // Just enough AT data to allow API to query initial balances, etc. + ATData skeletonAtData = new ATData(atAddress, creatorPublicKey, creation, assetId); + + long blockTimestamp = Timestamp.toLong(height, 0); + QortalATAPI api = new QortalATAPI(repository, skeletonAtData, blockTimestamp); + + MachineState machineState = new MachineState(api, deployATTransactionData.getCreationBytes()); this.atData = new ATData(atAddress, creatorPublicKey, creation, machineState.version, assetId, machineState.getCodeBytes(), machineState.getIsSleeping(), machineState.getSleepUntilHeight(), machineState.getIsFinished(), machineState.getHadFatalError(), diff --git a/src/main/java/org/qortal/at/QortalATAPI.java b/src/main/java/org/qortal/at/QortalATAPI.java index 9016bb17..27f67ac0 100644 --- a/src/main/java/org/qortal/at/QortalATAPI.java +++ b/src/main/java/org/qortal/at/QortalATAPI.java @@ -4,6 +4,7 @@ import java.math.BigDecimal; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.ciyam.at.API; @@ -35,11 +36,15 @@ import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.transaction.AtTransaction; import org.qortal.transaction.Transaction; +import org.qortal.transaction.Transaction.TransactionType; +import org.qortal.utils.Base58; import com.google.common.primitives.Bytes; public class QortalATAPI extends API { + private static final byte[] ADDRESS_PADDING = new byte[32 - Account.ADDRESS_LENGTH]; + // Properties private Repository repository; private ATData atData; @@ -316,18 +321,30 @@ public class QortalATAPI extends API { public void putAddressFromTransactionInAIntoB(MachineState state) { TransactionData transactionData = this.getTransactionFromA(state); - // We actually use public key as it has more potential utility (e.g. message verification) than an address - byte[] bytes = transactionData.getCreatorPublicKey(); + String address; + if (transactionData.getType() == TransactionType.AT) { + // Use AT address from transaction data, as transaction's public key will always be fake + address = ((ATTransactionData) transactionData).getATAddress(); + } else { + byte[] publicKey = transactionData.getCreatorPublicKey(); + address = Crypto.toAddress(publicKey); + } - this.setB(state, bytes); + // Convert to byte form as this only takes 25 bytes, + // compared to string-form's 34 bytes, + // and we only have 32 bytes available. + byte[] addressBytes = Bytes.ensureCapacity(Base58.decode(address), 32, 0); // pad to 32 bytes + + this.setB(state, addressBytes); } @Override public void putCreatorAddressIntoB(MachineState state) { - // We actually use public key as it has more potential utility (e.g. message verification) than an address - byte[] bytes = atData.getCreatorPublicKey(); + byte[] publicKey = atData.getCreatorPublicKey(); + String address = Crypto.toAddress(publicKey); + byte[] addressBytes = Bytes.ensureCapacity(address.getBytes(), 32, 0); - this.setB(state, bytes); + this.setB(state, addressBytes); } @Override @@ -490,10 +507,7 @@ public class QortalATAPI extends API { * * We need increasing timestamps to preserve transaction order and hence a correct signature-reference chain when the block is processed. */ - - // XXX THE ABOVE IS NO LONGER TRUE IN QORTAL! - // return this.blockTimestamp + this.transactions.size(); - throw new RuntimeException("AT timestamp code not fixed!"); + return this.blockTimestamp + this.transactions.size(); } /** Returns AT account's lastReference, taking newly generated ATTransactions into account */ @@ -515,21 +529,22 @@ public class QortalATAPI extends API { /** * Returns Account (possibly PublicKeyAccount) based on value in B. *

- * If bytes in B start with 'Q' then use B as an address, but only if valid. + * If first byte in B starts with either address version bytes,
+ * and bytes 26 to 32 are zero, then use as an address, but only if valid. *

* Otherwise, assume B is a public key. - * @return */ private Account getAccountFromB(MachineState state) { byte[] bBytes = state.getB(); - if (bBytes[0] == 'Q') { - int zeroIndex = Bytes.indexOf(bBytes, (byte) 0); - if (zeroIndex > 0) { - String address = new String(bBytes, 0, zeroIndex); - if (Crypto.isValidAddress(address)) - return new Account(this.repository, address); - } + if ((bBytes[0] == Crypto.ADDRESS_VERSION || bBytes[0] == Crypto.AT_ADDRESS_VERSION) + && Arrays.mismatch(bBytes, Account.ADDRESS_LENGTH, 32, ADDRESS_PADDING, 0, ADDRESS_PADDING.length) == -1) { + // Extract only the bytes containing address + byte[] addressBytes = Arrays.copyOf(bBytes, Account.ADDRESS_LENGTH); + // If address (in byte form) is valid... + if (Crypto.isValidAddress(addressBytes)) + // ...then return an Account using address (converted to Base58 + return new Account(this.repository, Base58.encode(addressBytes)); } return new PublicKeyAccount(this.repository, bBytes); diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index f7f9a979..2ad069f7 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -466,18 +467,6 @@ public class BTC { } } - private static class TransactionStorage { - private Transaction transaction; - - public void store(Transaction transaction) { - this.transaction = transaction; - } - - public Transaction getTransaction() { - return this.transaction; - } - } - public List getOutputs(byte[] txId, long startTime) { Wallet wallet = createEmptyWallet(); @@ -487,7 +476,7 @@ public class BTC { final Sha256Hash txHash = Sha256Hash.wrap(txId); - final TransactionStorage transactionStorage = new TransactionStorage(); + final AtomicReference foundTransaction = new AtomicReference<>(); final BlocksDownloadedEventListener listener = (peer, block, filteredBlock, blocksLeft) -> { List transactions = block.getTransactions(); @@ -498,7 +487,7 @@ public class BTC { for (Transaction transaction : transactions) if (transaction.getTxId().equals(txHash)) { System.out.println(String.format("We downloaded block containing tx!")); - transactionStorage.store(transaction); + foundTransaction.set(transaction); } }; @@ -508,7 +497,7 @@ public class BTC { try { replayChain(startTime, wallet, replayHooks); - Transaction realTx = transactionStorage.getTransaction(); + Transaction realTx = foundTransaction.get(); return realTx.getOutputs(); } catch (BlockStoreException e) { LOGGER.error(String.format("BTC blockstore issue: %s", e.getMessage())); diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index a0480317..1ee78f2c 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -16,9 +16,11 @@ import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptChunk; import org.bitcoinj.script.ScriptOpCodes; +import org.ciyam.at.API; import org.ciyam.at.FunctionCode; import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; +import org.qortal.utils.Base58; import org.qortal.utils.BitTwiddling; import com.google.common.hash.HashCode; @@ -170,9 +172,10 @@ public class BTCACCT { return buildP2shTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, null, redeemSigScriptBuilder); } - public static byte[] buildQortalAT(byte[] secretHash, String destinationQortalAddress, long refundMinutes, BigDecimal initialPayout) { + public static byte[] buildQortalAT(byte[] secretHash, String recipientQortalAddress, long refundMinutes, BigDecimal initialPayout) { // Labels for data segment addresses int addrCounter = 0; + // Constants (with corresponding dataByteBuffer.put*() calls below) final int addrHashPart1 = addrCounter++; final int addrHashPart2 = addrCounter++; final int addrHashPart3 = addrCounter++; @@ -185,6 +188,8 @@ public class BTCACCT { final int addrHashTempIndex = addrCounter++; final int addrHashTempLength = addrCounter++; final int addrInitialPayoutAmount = addrCounter++; + final int addrExpectedTxType = addrCounter++; + // Variables final int addrRefundTimestamp = addrCounter++; final int addrLastTimestamp = addrCounter++; final int addrBlockTimestamp = addrCounter++; @@ -200,72 +205,99 @@ public class BTCACCT { final int addrHashTemp4 = addrCounter++; // Data segment - ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * 8); + ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE); // Hash of secret into HashPart1-4 - dataByteBuffer.put(secretHash); + assert dataByteBuffer.position() == addrHashPart1 * MachineState.VALUE_SIZE : "addrHashPart1 incorrect"; + dataByteBuffer.put(Bytes.ensureCapacity(secretHash, 32, 0)); - // Destination Qortal account's public key - dataByteBuffer.put(Bytes.ensureCapacity(destinationQortalAddress.getBytes(), 32, 0)); + // Recipient Qortal address, decoded from Base58 + assert dataByteBuffer.position() == addrAddressPart1 * MachineState.VALUE_SIZE : "addrAddressPart1 incorrect"; + byte[] recipientAddressBytes = Base58.decode(recipientQortalAddress); + dataByteBuffer.put(Bytes.ensureCapacity(recipientAddressBytes, 32, 0)); // Expiry in minutes + assert dataByteBuffer.position() == addrRefundMinutes * MachineState.VALUE_SIZE : "addrRefundMinutes incorrect"; dataByteBuffer.putLong(refundMinutes); - // Temp buffer for hashing any passed secret + // Source location and length for hashing any passed secret + assert dataByteBuffer.position() == addrHashTempIndex * MachineState.VALUE_SIZE : "addrHashTempIndex incorrect"; dataByteBuffer.putLong(addrHashTemp1); + assert dataByteBuffer.position() == addrHashTempLength * MachineState.VALUE_SIZE : "addrHashTempLength incorrect"; dataByteBuffer.putLong(32L); // Initial payout amount + assert dataByteBuffer.position() == addrInitialPayoutAmount * MachineState.VALUE_SIZE : "addrInitialPayoutAmount incorrect"; dataByteBuffer.putLong(initialPayout.unscaledValue().longValue()); + // We're only interested in MESSAGE transactions + assert dataByteBuffer.position() == addrExpectedTxType * MachineState.VALUE_SIZE : "addrExpectedTxType incorrect"; + dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value); + // Code labels - final int addrTxLoop = 0x36; - final int addrCheckTx = 0x4b; - final int addrCheckSender = 0x64; - final int addrCheckMessage = 0xab; - final int addrPayout = 0xdf; - final int addrRefund = 0x102; - final int addrEndOfCode = 0x109; + final int addrTxLoop = 0x0036; + final int addrCheckTx = 0x004b; + final int addrRefund = 0x00c6; + final int addrEndOfCode = 0x00cd; int tempPC; ByteBuffer codeByteBuffer = ByteBuffer.allocate(addrEndOfCode * 1); - // init: - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrRefundTimestamp); - codeByteBuffer.put(OpCode.SET_DAT.value).putInt(addrLastTimestamp).putInt(addrRefundTimestamp); - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp) - .putInt(addrRefundTimestamp).putInt(addrRefundMinutes); + /* Initialization */ + // Use AT creation 'timestamp' as starting point for finding transactions sent to AT + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrLastTimestamp); + // Calculate refund 'timestamp' by adding minutes to above 'timestamp', then save into addrRefundTimestamp + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp) + .putInt(addrLastTimestamp).putInt(addrRefundMinutes); + + // Load recipient's address into B register codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrAddressPart1); + // Send initial payment to recipient so they have enough funds to message AT if all goes well codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PAY_TO_ADDRESS_IN_B.value).putInt(addrInitialPayoutAmount); + // Set restart position to after this opcode codeByteBuffer.put(OpCode.SET_PCS.value); - // loop: - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp); - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BLT_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrTxLoop - tempPC)); - codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrRefund); + /* Main loop */ - // txloop: + // Fetch current block 'timestamp' + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp); + // If we're past refund 'timestamp' then go refund everything back to AT creator + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrRefund - tempPC)); + + /* Transaction processing loop */ assert codeByteBuffer.position() == addrTxLoop : "addrTxLoop incorrect"; + + // Find next transaction to this AT since the last one (if any) codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(addrLastTimestamp); + // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator); + // If addrComparator is zero (i.e. A is non-zero, transaction was found) then branch to addrCheckTx tempPC = codeByteBuffer.position(); codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC)); + // Stop and wait for next block codeByteBuffer.put(OpCode.STP_IMD.value); - // checkTx: - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType); - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrTxType).put((byte) (addrCheckSender - tempPC)); - codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); + /* Check transaction */ + assert codeByteBuffer.position() == addrCheckTx : "addrCheckTx incorrect"; - // checkSender - assert codeByteBuffer.position() == addrCheckSender : "addrCheckSender incorrect"; + // Update our 'last found transaction's timestamp' using 'timestamp' from transaction + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp); + // Extract transaction type (message/payment) from transaction and save type in addrTxType + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType); + // If transaction type is not MESSAGE type then go look for another transaction + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrTxType).putInt(addrExpectedTxType).put((byte) (addrTxLoop - tempPC)); + + /* Check transaction's sender */ + + // Extract sender address from transaction into B register codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value); + // Save B register into data segment starting at addrAddressTemp1 codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B_IND.value).putInt(addrAddressTemp1); + // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. tempPC = codeByteBuffer.position(); codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC)); tempPC = codeByteBuffer.position(); @@ -275,26 +307,38 @@ public class BTCACCT { tempPC = codeByteBuffer.position(); codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC)); - // checkMessage: - assert codeByteBuffer.position() == addrCheckMessage : "addrCheckMessage incorrect"; - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B_IND.value).putInt(addrHashTemp1); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrHashPart1); - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_HASH160_WITH_B.value).putInt(addrHashTempIndex).putInt(addrHashTempLength); - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BNZ_DAT.value).putInt(addrComparator).put((byte) (addrPayout - tempPC)); - codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(addrTxLoop); + /* Check 'secret' in transaction's message */ - // payout: - assert codeByteBuffer.position() == addrPayout : "addrPayout incorrect"; + // Extract message from transaction into B register + codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value); + // Save B register into data segment starting at addrHashTemp1 + codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B_IND.value).putInt(addrHashTemp1); + // Load B register with expected hash result + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrHashPart1); + // Perform HASH160 using source data at addrHashTemp1 through addrHashTemp4. (Location and length specified via addrHashTempIndex and addrHashTemplength). + // Save the equality result (1 if they match, 0 otherwise) into addrComparator. + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.CHECK_HASH160_WITH_B.value).putInt(addrComparator).putInt(addrHashTempIndex).putInt(addrHashTempLength); + // If hashes don't match, addrComparator will be zero so go find another transaction + tempPC = codeByteBuffer.position(); + codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrTxLoop - tempPC)); + + /* Success! Pay balance to intended recipient */ + + // Load B register with intended recipient address. codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrAddressPart1); + // Pay AT's balance to recipient codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); + // We're finished forever codeByteBuffer.put(OpCode.FIN_IMD.value); - // refund: + /* Refund balance back to AT creator */ assert codeByteBuffer.position() == addrRefund : "addrRefund incorrect"; + + // Load B register with AT creator's address. codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value); + // Pay AT's balance back to AT's creator. codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); + // We're finished forever codeByteBuffer.put(OpCode.FIN_IMD.value); // end-of-code diff --git a/src/main/java/org/qortal/crypto/Crypto.java b/src/main/java/org/qortal/crypto/Crypto.java index 36c06585..c940c6ab 100644 --- a/src/main/java/org/qortal/crypto/Crypto.java +++ b/src/main/java/org/qortal/crypto/Crypto.java @@ -108,14 +108,15 @@ public class Crypto { return isValidTypedAddress(address, ADDRESS_VERSION, AT_ADDRESS_VERSION); } + public static boolean isValidAddress(byte[] addressBytes) { + return areValidTypedAddressBytes(addressBytes, ADDRESS_VERSION, AT_ADDRESS_VERSION); + } + public static boolean isValidAtAddress(String address) { return isValidTypedAddress(address, AT_ADDRESS_VERSION); } private static boolean isValidTypedAddress(String address, byte...addressVersions) { - if (addressVersions == null || addressVersions.length == 0) - return false; - byte[] addressBytes; try { @@ -125,6 +126,13 @@ public class Crypto { return false; } + return areValidTypedAddressBytes(addressBytes, addressVersions); + } + + private static boolean areValidTypedAddressBytes(byte[] addressBytes, byte...addressVersions) { + if (addressVersions == null || addressVersions.length == 0) + return false; + // Check address length if (addressBytes == null || addressBytes.length != Account.ADDRESS_LENGTH) return false; diff --git a/src/main/java/org/qortal/data/at/ATData.java b/src/main/java/org/qortal/data/at/ATData.java index 0ab73bdc..b12439e0 100644 --- a/src/main/java/org/qortal/data/at/ATData.java +++ b/src/main/java/org/qortal/data/at/ATData.java @@ -46,6 +46,14 @@ public class ATData { this.frozenBalance = BigDecimal.valueOf(frozenBalance, 8); } + /** For constructing skeleton ATData with bare minimum info. */ + public ATData(String ATAddress, byte[] creatorPublicKey, long creation, long assetId) { + this.ATAddress = ATAddress; + this.creatorPublicKey = creatorPublicKey; + this.creation = creation; + this.assetId = assetId; + } + // Getters / setters public String getATAddress() { diff --git a/src/main/java/org/qortal/transaction/DeployAtTransaction.java b/src/main/java/org/qortal/transaction/DeployAtTransaction.java index 191adeec..8995b626 100644 --- a/src/main/java/org/qortal/transaction/DeployAtTransaction.java +++ b/src/main/java/org/qortal/transaction/DeployAtTransaction.java @@ -8,12 +8,15 @@ import java.util.ArrayList; import java.util.List; import org.ciyam.at.MachineState; +import org.ciyam.at.Timestamp; import org.qortal.account.Account; import org.qortal.asset.Asset; import org.qortal.at.AT; +import org.qortal.at.QortalATAPI; import org.qortal.block.BlockChain; import org.qortal.crypto.Crypto; import org.qortal.data.asset.AssetData; +import org.qortal.data.at.ATData; import org.qortal.data.transaction.DeployAtTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.repository.DataException; @@ -79,6 +82,7 @@ public class DeployAtTransaction extends Transaction { /** Returns AT version from the header bytes */ private short getVersion() { byte[] creationBytes = deployATTransactionData.getCreationBytes(); + // XXX this is currently little-endian, but ok for the moment as newer versions will be reported as 512+ so tests like version >= 2 will work return (short) ((creationBytes[0] & 0xff) | (creationBytes[1] << 8)); // Little-endian } @@ -87,6 +91,13 @@ public class DeployAtTransaction extends Transaction { if (this.deployATTransactionData.getAtAddress() != null) return; + // For new version, simply use transaction signature + if (this.getVersion() > 1) { + String atAddress = Crypto.toATAddress(this.deployATTransactionData.getSignature()); + this.deployATTransactionData.setAtAddress(atAddress); + return; + } + int blockHeight = this.getHeight(); if (blockHeight == 0) blockHeight = this.repository.getBlockRepository().getBlockchainHeight() + 1; @@ -189,8 +200,21 @@ public class DeployAtTransaction extends Transaction { // Check creation bytes are valid (for v2+) if (this.getVersion() >= 2) { // Do actual validation + ensureATAddress(); + + // Just enough AT data to allow API to query initial balances, etc. + String atAddress = this.deployATTransactionData.getAtAddress(); + byte[] creatorPublicKey = this.deployATTransactionData.getCreatorPublicKey(); + long creation = this.deployATTransactionData.getTimestamp(); + ATData skeletonAtData = new ATData(atAddress, creatorPublicKey, creation, assetId); + + int height = this.repository.getBlockRepository().getBlockchainHeight() + 1; + long blockTimestamp = Timestamp.toLong(height, 0); + + QortalATAPI api = new QortalATAPI(repository, skeletonAtData, blockTimestamp); + try { - new MachineState(deployATTransactionData.getCreationBytes()); + new MachineState(api, deployATTransactionData.getCreationBytes()); } catch (IllegalArgumentException e) { // Not valid return ValidationResult.INVALID_CREATION_BYTES; diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index 36dbdc5a..98e2daa7 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -129,8 +129,13 @@ public class DeployAT { } BigDecimal fee = BigDecimal.ZERO; + String name = "QORT-BTC cross-chain trade"; + String description = String.format("Qortal-Bitcoin cross-chain trade between %s and %s", refundAccount.getAddress(), redeemAddress); + 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, "QORT-BTC", "QORT-BTC ACCT", "", "", creationBytes, qortAmount, Asset.QORT); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, qortAmount, Asset.QORT); Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); diff --git a/src/test/java/org/qortal/test/btcacct/GetTransaction.java b/src/test/java/org/qortal/test/btcacct/GetTransaction.java index c97762b9..48af4ebf 100644 --- a/src/test/java/org/qortal/test/btcacct/GetTransaction.java +++ b/src/test/java/org/qortal/test/btcacct/GetTransaction.java @@ -22,32 +22,32 @@ public class GetTransaction { if (error != null) System.err.println(error); - System.err.println(String.format("usage: GetTransaction ")); - System.err.println(String.format("example (mainnet): GetTransaction 816407e79568f165f13e09e9912c5f2243e0a23a007cec425acedc2e89284660")); - System.err.println(String.format("example (testnet): GetTransaction 3bfd17a492a4e3d6cb7204e17e20aca6c1ab82e1828bd1106eefbaf086fb8a4e")); + System.err.println(String.format("usage: GetTransaction ")); + System.err.println(String.format("example (mainnet): GetTransaction 816407e79568f165f13e09e9912c5f2243e0a23a007cec425acedc2e89284660 1585317000")); + System.err.println(String.format("example (testnet): GetTransaction 3bfd17a492a4e3d6cb7204e17e20aca6c1ab82e1828bd1106eefbaf086fb8a4e 1584376000")); System.exit(1); } public static void main(String[] args) { - if (args.length < 1 || args.length > 1) + if (args.length < 2 || args.length > 2) usage(null); Security.insertProviderAt(new BouncyCastleProvider(), 0); Settings.fileInstance("settings-test.json"); byte[] transactionId = null; + int startTime = 0; try { int argIndex = 0; transactionId = HashCode.fromString(args[argIndex++]).asBytes(); + + startTime = Integer.parseInt(args[argIndex++]); } catch (NumberFormatException | AddressFormatException e) { usage(String.format("Argument format exception: %s", e.getMessage())); } - // Chain replay start time - long startTime = (System.currentTimeMillis() / 1000L) - 14 * 24 * 60 * 60; // 14 days before now, in seconds - // Grab all outputs from transaction List fundingOutputs = BTC.getInstance().getOutputs(transactionId, startTime); if (fundingOutputs == null) { From 98506a038bf2ee604ad0b562bdcefe7caaf00317 Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 14 Apr 2020 17:19:44 +0100 Subject: [PATCH 11/15] Loads of work on CIYAM AT support, including BTC-QORT cross-chain trading. We require AT v1.3.4 now! Updated AT-related logging. Added "isInitial" flag to AT state data so that state data created at deployment is not added to serialized block data. Updated BTC-QORT AT code and tests to cover various scenarios. Added missing 'testNtpOffset' to various test versions of 'settings.json'. Added missing 'ciyamAtSettings' to various test blockchain configs. Loads of AT-related additions/fixes/etc. to core code, e.g Block --- pom.xml | 5 +- src/main/java/org/qortal/at/AT.java | 80 +- src/main/java/org/qortal/at/QortalATAPI.java | 106 +- .../java/org/qortal/at/QortalATLogger.java | 26 - .../java/org/qortal/at/QortalAtLogger.java | 2182 +++++++++++++++++ .../org/qortal/at/QortalAtLoggerFactory.java | 24 + .../org/qortal/at/QortalFunctionCode.java | 28 +- src/main/java/org/qortal/block/Block.java | 47 +- .../java/org/qortal/block/BlockChain.java | 2 +- .../java/org/qortal/block/BlockMinter.java | 10 +- .../java/org/qortal/crosschain/BTCACCT.java | 265 +- src/main/java/org/qortal/crypto/Crypto.java | 12 + .../java/org/qortal/data/at/ATStateData.java | 20 +- .../repository/hsqldb/HSQLDBATRepository.java | 76 +- .../hsqldb/HSQLDBDatabaseUpdates.java | 5 + .../org/qortal/transaction/AtTransaction.java | 4 + .../transaction/DeployAtTransaction.java | 4 +- .../org/qortal/transaction/Transaction.java | 7 +- .../java/org/qortal/test/apps/orphan.java | 18 +- .../java/org/qortal/test/btcacct/AtTests.java | 403 +++ .../org/qortal/test/btcacct/BuildP2SH.java | 2 +- .../org/qortal/test/btcacct/CheckP2SH.java | 2 +- .../org/qortal/test/btcacct/DeployAT.java | 32 +- .../java/org/qortal/test/btcacct/Redeem.java | 2 +- .../java/org/qortal/test/btcacct/Refund.java | 2 +- .../org/qortal/test/common/BlockUtils.java | 4 +- src/test/resources/test-chain-old-asset.json | 6 + src/test/resources/test-chain-v1.json | 6 + .../test-chain-v2-founder-rewards.json | 6 + src/test/resources/test-chain-v2-minting.json | 6 + .../resources/test-chain-v2-qora-holder.json | 6 + src/test/resources/test-chain-v2.json | 6 + .../resources/test-settings-old-asset.json | 1 + src/test/resources/test-settings-v1.json | 1 + .../test-settings-v2-founder-rewards.json | 1 + .../resources/test-settings-v2-minting.json | 1 + .../test-settings-v2-qora-holder.json | 1 + src/test/resources/test-settings-v2.json | 1 + 38 files changed, 3144 insertions(+), 266 deletions(-) delete mode 100644 src/main/java/org/qortal/at/QortalATLogger.java create mode 100644 src/main/java/org/qortal/at/QortalAtLogger.java create mode 100644 src/main/java/org/qortal/at/QortalAtLoggerFactory.java create mode 100644 src/test/java/org/qortal/test/btcacct/AtTests.java diff --git a/pom.xml b/pom.xml index 83cb4b4d..a866afd6 100644 --- a/pom.xml +++ b/pom.xml @@ -9,6 +9,7 @@ 0.15.5 1.64 ${maven.build.timestamp} + 1.3.4 3.6 1.8 1.2.2 @@ -405,8 +406,8 @@ org.ciyam - at - 1.3.2 + AT + ${ciyam-at.version} diff --git a/src/main/java/org/qortal/at/AT.java b/src/main/java/org/qortal/at/AT.java index 23bf02cd..1ba5d8e2 100644 --- a/src/main/java/org/qortal/at/AT.java +++ b/src/main/java/org/qortal/at/AT.java @@ -54,17 +54,18 @@ public class AT { long blockTimestamp = Timestamp.toLong(height, 0); QortalATAPI api = new QortalATAPI(repository, skeletonAtData, blockTimestamp); + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); - MachineState machineState = new MachineState(api, deployATTransactionData.getCreationBytes()); + MachineState machineState = new MachineState(api, loggerFactory, deployATTransactionData.getCreationBytes()); this.atData = new ATData(atAddress, creatorPublicKey, creation, machineState.version, assetId, machineState.getCodeBytes(), - machineState.getIsSleeping(), machineState.getSleepUntilHeight(), machineState.getIsFinished(), machineState.getHadFatalError(), - machineState.getIsFrozen(), machineState.getFrozenBalance()); + machineState.isSleeping(), machineState.getSleepUntilHeight(), machineState.isFinished(), machineState.hadFatalError(), + machineState.isFrozen(), machineState.getFrozenBalance()); byte[] stateData = machineState.toBytes(); byte[] stateHash = Crypto.digest(stateData); - this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, BigDecimal.ZERO.setScale(8)); + this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, BigDecimal.ZERO.setScale(8), true); } else { // Legacy v1 AT // We would deploy these in 'dead' state as they will never be run on Qortal @@ -107,7 +108,7 @@ public class AT { this.atData = new ATData(atAddress, creatorPublicKey, creation, version, Asset.QORT, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance); - this.atStateData = new ATStateData(atAddress, height, creation, null, null, BigDecimal.ZERO.setScale(8)); + this.atStateData = new ATStateData(atAddress, height, creation, null, null, BigDecimal.ZERO.setScale(8), true); } } @@ -133,34 +134,87 @@ public class AT { this.repository.getATRepository().delete(this.atData.getATAddress()); } - public List run(long blockTimestamp) throws DataException { + public List run(int blockHeight, long blockTimestamp) throws DataException { String atAddress = this.atData.getATAddress(); QortalATAPI api = new QortalATAPI(repository, this.atData, blockTimestamp); - QortalATLogger logger = new QortalATLogger(); + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); byte[] codeBytes = this.atData.getCodeBytes(); - // Fetch latest ATStateData for this AT (if any) + // Fetch latest ATStateData for this AT ATStateData latestAtStateData = this.repository.getATRepository().getLatestATState(atAddress); - // There should be at least initial AT state data + // There should be at least initial deployment AT state data if (latestAtStateData == null) - throw new IllegalStateException("No initial AT state data found"); + throw new IllegalStateException("No previous AT state data found"); // [Re]create AT machine state using AT state data or from scratch as applicable - MachineState state = MachineState.fromBytes(api, logger, latestAtStateData.getStateData(), codeBytes); + MachineState state = MachineState.fromBytes(api, loggerFactory, latestAtStateData.getStateData(), codeBytes); state.execute(); - int height = this.repository.getBlockRepository().getBlockchainHeight() + 1; long creation = this.atData.getCreation(); byte[] stateData = state.toBytes(); byte[] stateHash = Crypto.digest(stateData); BigDecimal atFees = api.calcFinalFees(state); - this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, atFees); + this.atStateData = new ATStateData(atAddress, blockHeight, creation, stateData, stateHash, atFees, false); return api.getTransactions(); } + public void update(int blockHeight, long blockTimestamp) throws DataException { + // [Re]create AT machine state using AT state data or from scratch as applicable + QortalATAPI api = new QortalATAPI(repository, this.atData, blockTimestamp); + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + + byte[] codeBytes = this.atData.getCodeBytes(); + MachineState state = MachineState.fromBytes(api, loggerFactory, this.atStateData.getStateData(), codeBytes); + + // Save latest AT state data + this.repository.getATRepository().save(this.atStateData); + + // Update AT info in repository too + this.atData.setIsSleeping(state.isSleeping()); + this.atData.setSleepUntilHeight(state.getSleepUntilHeight()); + this.atData.setIsFinished(state.isFinished()); + this.atData.setHadFatalError(state.hadFatalError()); + this.atData.setIsFrozen(state.isFrozen()); + Long frozenBalance = state.getFrozenBalance(); + this.atData.setFrozenBalance(frozenBalance != null ? BigDecimal.valueOf(frozenBalance, 8) : null); + this.repository.getATRepository().save(this.atData); + } + + public void revert(int blockHeight, long blockTimestamp) throws DataException { + String atAddress = this.atData.getATAddress(); + + // Delete old AT state data from repository + this.repository.getATRepository().delete(atAddress, blockHeight); + + if (this.atStateData.isInitial()) + return; + + // Load previous state data + ATStateData previousStateData = this.repository.getATRepository().getLatestATState(atAddress); + if (previousStateData == null) + throw new DataException("Can't find previous AT state data for " + atAddress); + + // [Re]create AT machine state using AT state data or from scratch as applicable + QortalATAPI api = new QortalATAPI(repository, this.atData, blockTimestamp); + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + + byte[] codeBytes = this.atData.getCodeBytes(); + MachineState state = MachineState.fromBytes(api, loggerFactory, previousStateData.getStateData(), codeBytes); + + // Update AT info in repository + this.atData.setIsSleeping(state.isSleeping()); + this.atData.setSleepUntilHeight(state.getSleepUntilHeight()); + this.atData.setIsFinished(state.isFinished()); + this.atData.setHadFatalError(state.hadFatalError()); + this.atData.setIsFrozen(state.isFrozen()); + Long frozenBalance = state.getFrozenBalance(); + this.atData.setFrozenBalance(frozenBalance != null ? BigDecimal.valueOf(frozenBalance, 8) : null); + this.repository.getATRepository().save(this.atData); + } + } diff --git a/src/main/java/org/qortal/at/QortalATAPI.java b/src/main/java/org/qortal/at/QortalATAPI.java index 27f67ac0..9455e59e 100644 --- a/src/main/java/org/qortal/at/QortalATAPI.java +++ b/src/main/java/org/qortal/at/QortalATAPI.java @@ -1,12 +1,13 @@ package org.qortal.at; import java.math.BigDecimal; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.ciyam.at.API; import org.ciyam.at.ExecutionException; import org.ciyam.at.FunctionData; @@ -44,6 +45,7 @@ import com.google.common.primitives.Bytes; public class QortalATAPI extends API { private static final byte[] ADDRESS_PADDING = new byte[32 - Account.ADDRESS_LENGTH]; + private static final Logger LOGGER = LogManager.getLogger(QortalATAPI.class); // Properties private Repository repository; @@ -128,13 +130,14 @@ public class QortalATAPI extends API { throw new RuntimeException("AT API unable to fetch previous block hash?"); // Block's signature is 128 bytes so we need to reduce this to 4 longs (32 bytes) - // To be able to use hash to look up block, save height (8 bytes) and rehash with SHA192 (24 bytes) + // To be able to use hash to look up block, save height (8 bytes) and partial signature (24 bytes) this.setA1(state, previousBlockHeight); - byte[] sigHash192 = sha192(blockSummaries.get(0).getSignature()); - this.setA2(state, fromBytes(sigHash192, 0)); - this.setA3(state, fromBytes(sigHash192, 8)); - this.setA4(state, fromBytes(sigHash192, 16)); + byte[] signature = blockSummaries.get(0).getSignature(); + // Save some of minter's signature and transactions signature, so middle 24 bytes of the full 128 byte signature. + this.setA2(state, fromBytes(signature, 52)); + this.setA3(state, fromBytes(signature, 60)); + this.setA4(state, fromBytes(signature, 68)); } catch (DataException e) { throw new RuntimeException("AT API unable to fetch previous block?", e); } @@ -143,7 +146,7 @@ public class QortalATAPI extends API { @Override public void putTransactionAfterTimestampIntoA(Timestamp timestamp, MachineState state) { // Recipient is this AT - String recipient = this.atData.getATAddress(); + String atAddress = this.atData.getATAddress(); int height = timestamp.blockHeight; int sequence = timestamp.transactionSequence + 1; @@ -151,39 +154,44 @@ public class QortalATAPI extends API { BlockRepository blockRepository = this.getRepository().getBlockRepository(); try { - Account recipientAccount = new Account(this.getRepository(), recipient); int currentHeight = blockRepository.getBlockchainHeight(); + List blockTransactions = null; while (height <= currentHeight) { - BlockData blockData = blockRepository.fromHeight(height); + if (blockTransactions == null) { + BlockData blockData = blockRepository.fromHeight(height); - if (blockData == null) - throw new DataException("Unable to fetch block " + height + " from repository?"); + if (blockData == null) + throw new DataException("Unable to fetch block " + height + " from repository?"); - Block block = new Block(this.getRepository(), blockData); + Block block = new Block(this.getRepository(), blockData); - List blockTransactions = block.getTransactions(); + blockTransactions = block.getTransactions(); + } // No more transactions in this block? Try next block if (sequence >= blockTransactions.size()) { ++height; sequence = 0; + blockTransactions = null; continue; } Transaction transaction = blockTransactions.get(sequence); // Transaction needs to be sent to specified recipient - if (transaction.getRecipientAccounts().contains(recipientAccount)) { + List recipientAccounts = transaction.getRecipientAccounts(); + List recipientAddresses = recipientAccounts.stream().map(Account::getAddress).collect(Collectors.toList()); + if (recipientAddresses.contains(atAddress)) { // Found a transaction this.setA1(state, new Timestamp(height, timestamp.blockchainId, sequence).longValue()); - // Hash transaction's signature into other three A fields for future verification that it's the same transaction - byte[] sigHash192 = sha192(transaction.getTransactionData().getSignature()); - this.setA2(state, fromBytes(sigHash192, 0)); - this.setA3(state, fromBytes(sigHash192, 8)); - this.setA4(state, fromBytes(sigHash192, 16)); + // Copy transaction's partial signature into the other three A fields for future verification that it's the same transaction + byte[] signature = transaction.getTransactionData().getSignature(); + this.setA2(state, fromBytes(signature, 8)); + this.setA3(state, fromBytes(signature, 16)); + this.setA4(state, fromBytes(signature, 24)); return; } @@ -245,7 +253,7 @@ public class QortalATAPI extends API { @Override public long getTimestampFromTransactionInA(MachineState state) { // Transaction's "timestamp" already stored in A1 - Timestamp timestamp = new Timestamp(state.getA1()); + Timestamp timestamp = new Timestamp(this.getA1(state)); return timestamp.longValue(); } @@ -340,11 +348,10 @@ public class QortalATAPI extends API { @Override public void putCreatorAddressIntoB(MachineState state) { + // Simply use raw public key byte[] publicKey = atData.getCreatorPublicKey(); - String address = Crypto.toAddress(publicKey); - byte[] addressBytes = Bytes.ensureCapacity(address.getBytes(), 32, 0); - this.setB(state, addressBytes); + this.setB(state, publicKey); } @Override @@ -377,7 +384,7 @@ public class QortalATAPI extends API { @Override public void messageAToB(MachineState state) { - byte[] message = state.getA(); + byte[] message = this.getA(state); Account recipient = getAccountFromB(state); long timestamp = this.getNextTransactionTimestamp(); @@ -404,6 +411,9 @@ public class QortalATAPI extends API { @Override public void onFinished(long finalBalance, MachineState state) { + if (finalBalance <= 0) + return; + // Refund remaining balance (if any) to AT's creator Account creator = this.getCreator(); long timestamp = this.getNextTransactionTimestamp(); @@ -421,7 +431,7 @@ public class QortalATAPI extends API { @Override public void onFatalError(MachineState state, ExecutionException e) { - state.getLogger().error("AT " + this.atData.getATAddress() + " suffered fatal error: " + e.getMessage()); + LOGGER.error("AT " + this.atData.getATAddress() + " suffered fatal error: " + e.getMessage()); } @Override @@ -432,7 +442,7 @@ public class QortalATAPI extends API { if (qortalFunctionCode == null) throw new IllegalFunctionCodeException("Unknown Qortal function code 0x" + String.format("%04x", rawFunctionCode) + " encountered"); - qortalFunctionCode.preExecuteCheck(2, true, state, rawFunctionCode); + qortalFunctionCode.preExecuteCheck(paramCount, returnValueExpected, rawFunctionCode); } @Override @@ -450,29 +460,23 @@ public class QortalATAPI extends API { | (bytes[start + 4] & 0xffL) << 32 | (bytes[start + 5] & 0xffL) << 40 | (bytes[start + 6] & 0xffL) << 48 | (bytes[start + 7] & 0xffL) << 56; } - /** Returns SHA2-192 digest of input - used to verify transaction signatures */ - public static byte[] sha192(byte[] input) { - try { - // SHA2-192 - MessageDigest sha192 = MessageDigest.getInstance("SHA-192"); - return sha192.digest(input); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA-192 not available"); - } + /** Returns partial transaction signature, used to verify we're operating on the same transaction and not naively using block height & sequence. */ + public static byte[] partialSignature(byte[] fullSignature) { + return Arrays.copyOfRange(fullSignature, 8, 32); } - /** Verify transaction's SHA2-192 hashed signature matches A2 thru A4 */ - private static void verifyTransaction(TransactionData transactionData, MachineState state) { - // Compare SHA2-192 of transaction's signature against A2 thru A4 - byte[] hash = sha192(transactionData.getSignature()); + /** Verify transaction's partial signature matches A2 thru A4 */ + private void verifyTransaction(TransactionData transactionData, MachineState state) { + // Compare end of transaction's signature against A2 thru A4 + byte[] sig = transactionData.getSignature(); - if (state.getA2() != fromBytes(hash, 0) || state.getA3() != fromBytes(hash, 8) || state.getA4() != fromBytes(hash, 16)) + if (this.getA2(state) != fromBytes(sig, 8) || this.getA3(state) != fromBytes(sig, 16) || this.getA4(state) != fromBytes(sig, 24)) throw new IllegalStateException("Transaction signature in A no longer matches signature from repository"); } /** Returns transaction data from repository using block height & sequence from A1, checking the transaction signatures match too */ /* package */ TransactionData getTransactionFromA(MachineState state) { - Timestamp timestamp = new Timestamp(state.getA1()); + Timestamp timestamp = new Timestamp(this.getA1(state)); try { TransactionData transactionData = this.repository.getTransactionRepository().fromHeightAndSequence(timestamp.blockHeight, @@ -503,11 +507,11 @@ public class QortalATAPI extends API { /** Returns the timestamp to use for next AT Transaction */ private long getNextTransactionTimestamp() { /* - * Timestamp is block's timestamp + position in AT-Transactions list. + * Use block's timestamp. * - * We need increasing timestamps to preserve transaction order and hence a correct signature-reference chain when the block is processed. + * This is OK because AT transactions are always generated locally and order is preserved in Transaction.getDataComparator(). */ - return this.blockTimestamp + this.transactions.size(); + return this.blockTimestamp; } /** Returns AT account's lastReference, taking newly generated ATTransactions into account */ @@ -535,7 +539,7 @@ public class QortalATAPI extends API { * Otherwise, assume B is a public key. */ private Account getAccountFromB(MachineState state) { - byte[] bBytes = state.getB(); + byte[] bBytes = this.getB(state); if ((bBytes[0] == Crypto.ADDRESS_VERSION || bBytes[0] == Crypto.AT_ADDRESS_VERSION) && Arrays.mismatch(bBytes, Account.ADDRESS_LENGTH, 32, ADDRESS_PADDING, 0, ADDRESS_PADDING.length) == -1) { @@ -550,4 +554,14 @@ public class QortalATAPI extends API { return new PublicKeyAccount(this.repository, bBytes); } + /* Convenience methods to allow QortalFunctionCode package-visibility access to A/B-get/set methods. */ + + protected byte[] getB(MachineState state) { + return super.getB(state); + } + + protected void setB(MachineState state, byte[] bBytes) { + super.setB(state, bBytes); + } + } diff --git a/src/main/java/org/qortal/at/QortalATLogger.java b/src/main/java/org/qortal/at/QortalATLogger.java deleted file mode 100644 index df01247a..00000000 --- a/src/main/java/org/qortal/at/QortalATLogger.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.qortal.at; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class QortalATLogger implements org.ciyam.at.LoggerInterface { - - // NOTE: We're logging on behalf of org.qortal.at.AT, not ourselves! - private static final Logger LOGGER = LogManager.getLogger(AT.class); - - @Override - public void error(String message) { - LOGGER.error(message); - } - - @Override - public void debug(String message) { - LOGGER.debug(message); - } - - @Override - public void echo(String message) { - LOGGER.info(message); - } - -} diff --git a/src/main/java/org/qortal/at/QortalAtLogger.java b/src/main/java/org/qortal/at/QortalAtLogger.java new file mode 100644 index 00000000..703972a6 --- /dev/null +++ b/src/main/java/org/qortal/at/QortalAtLogger.java @@ -0,0 +1,2182 @@ +package org.qortal.at; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.spi.ExtendedLoggerWrapper; +import org.apache.logging.log4j.util.MessageSupplier; +import org.apache.logging.log4j.util.Supplier; + +/** + * Extended Logger interface with convenience methods for + * the ERROR, DEBUG and ECHO custom log levels. + *

Compatible with Log4j 2.6 or higher.

+ */ +public final class QortalAtLogger extends ExtendedLoggerWrapper implements org.ciyam.at.AtLogger { + private static final long serialVersionUID = 4740841485167L; + private final ExtendedLoggerWrapper logger; + + private static final String FQCN = QortalAtLogger.class.getName(); + private static final Level ERROR = Level.forName("ERROR", 200); + private static final Level DEBUG = Level.forName("DEBUG", 500); + private static final Level ECHO = Level.forName("ECHO", 400); + + private QortalAtLogger(final Logger logger) { + super((AbstractLogger) logger, logger.getName(), logger.getMessageFactory()); + this.logger = this; + } + + /** + * Returns a custom Logger with the name of the calling class. + * + * @return The custom Logger for the calling class. + */ + public static QortalAtLogger create() { + final Logger wrapped = LogManager.getLogger(); + return new QortalAtLogger(wrapped); + } + + /** + * Returns a custom Logger using the fully qualified name of the Class as + * the Logger name. + * + * @param loggerName The Class whose name should be used as the Logger name. + * If null it will default to the calling class. + * @return The custom Logger. + */ + public static QortalAtLogger create(final Class loggerName) { + final Logger wrapped = LogManager.getLogger(loggerName); + return new QortalAtLogger(wrapped); + } + + /** + * Returns a custom Logger using the fully qualified name of the Class as + * the Logger name. + * + * @param loggerName The Class whose name should be used as the Logger name. + * If null it will default to the calling class. + * @param messageFactory The message factory is used only when creating a + * logger, subsequent use does not change the logger but will log + * a warning if mismatched. + * @return The custom Logger. + */ + public static QortalAtLogger create(final Class loggerName, final MessageFactory messageFactory) { + final Logger wrapped = LogManager.getLogger(loggerName, messageFactory); + return new QortalAtLogger(wrapped); + } + + /** + * Returns a custom Logger using the fully qualified class name of the value + * as the Logger name. + * + * @param value The value whose class name should be used as the Logger + * name. If null the name of the calling class will be used as + * the logger name. + * @return The custom Logger. + */ + public static QortalAtLogger create(final Object value) { + final Logger wrapped = LogManager.getLogger(value); + return new QortalAtLogger(wrapped); + } + + /** + * Returns a custom Logger using the fully qualified class name of the value + * as the Logger name. + * + * @param value The value whose class name should be used as the Logger + * name. If null the name of the calling class will be used as + * the logger name. + * @param messageFactory The message factory is used only when creating a + * logger, subsequent use does not change the logger but will log + * a warning if mismatched. + * @return The custom Logger. + */ + public static QortalAtLogger create(final Object value, final MessageFactory messageFactory) { + final Logger wrapped = LogManager.getLogger(value, messageFactory); + return new QortalAtLogger(wrapped); + } + + /** + * Returns a custom Logger with the specified name. + * + * @param name The logger name. If null the name of the calling class will + * be used. + * @return The custom Logger. + */ + public static QortalAtLogger create(final String name) { + final Logger wrapped = LogManager.getLogger(name); + return new QortalAtLogger(wrapped); + } + + /** + * Returns a custom Logger with the specified name. + * + * @param name The logger name. If null the name of the calling class will + * be used. + * @param messageFactory The message factory is used only when creating a + * logger, subsequent use does not change the logger but will log + * a warning if mismatched. + * @return The custom Logger. + */ + public static QortalAtLogger create(final String name, final MessageFactory messageFactory) { + final Logger wrapped = LogManager.getLogger(name, messageFactory); + return new QortalAtLogger(wrapped); + } + + /** + * Logs a message with the specific Marker at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param msg the message string to be logged + */ + public void error(final Marker marker, final Message msg) { + logger.logIfEnabled(FQCN, ERROR, marker, msg, (Throwable) null); + } + + /** + * Logs a message with the specific Marker at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param msg the message string to be logged + * @param t A Throwable or null. + */ + public void error(final Marker marker, final Message msg, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, marker, msg, t); + } + + /** + * Logs a message object with the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message object to log. + */ + public void error(final Marker marker, final Object message) { + logger.logIfEnabled(FQCN, ERROR, marker, message, (Throwable) null); + } + + /** + * Logs a message CharSequence with the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message CharSequence to log. + * @since Log4j-2.6 + */ + public void error(final Marker marker, final CharSequence message) { + logger.logIfEnabled(FQCN, ERROR, marker, message, (Throwable) null); + } + + /** + * Logs a message at the {@code ERROR} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void error(final Marker marker, final Object message, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, marker, message, t); + } + + /** + * Logs a message at the {@code ERROR} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the CharSequence to log. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.6 + */ + public void error(final Marker marker, final CharSequence message, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, marker, message, t); + } + + /** + * Logs a message object with the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message object to log. + */ + public void error(final Marker marker, final String message) { + logger.logIfEnabled(FQCN, ERROR, marker, message, (Throwable) null); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param params parameters to the message. + * @see #getMessageFactory() + */ + public void error(final Marker marker, final String message, final Object... params) { + logger.logIfEnabled(FQCN, ERROR, marker, message, params); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2, p3); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2, p3, p4); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2, p3, p4, p5); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @param p9 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + logger.logIfEnabled(FQCN, ERROR, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } + + /** + * Logs a message at the {@code ERROR} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void error(final Marker marker, final String message, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, marker, message, t); + } + + /** + * Logs the specified Message at the {@code ERROR} level. + * + * @param msg the message string to be logged + */ + public void error(final Message msg) { + logger.logIfEnabled(FQCN, ERROR, null, msg, (Throwable) null); + } + + /** + * Logs the specified Message at the {@code ERROR} level. + * + * @param msg the message string to be logged + * @param t A Throwable or null. + */ + public void error(final Message msg, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, null, msg, t); + } + + /** + * Logs a message object with the {@code ERROR} level. + * + * @param message the message object to log. + */ + public void error(final Object message) { + logger.logIfEnabled(FQCN, ERROR, null, message, (Throwable) null); + } + + /** + * Logs a message at the {@code ERROR} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void error(final Object message, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, null, message, t); + } + + /** + * Logs a message CharSequence with the {@code ERROR} level. + * + * @param message the message CharSequence to log. + * @since Log4j-2.6 + */ + public void error(final CharSequence message) { + logger.logIfEnabled(FQCN, ERROR, null, message, (Throwable) null); + } + + /** + * Logs a CharSequence at the {@code ERROR} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the CharSequence to log. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.6 + */ + public void error(final CharSequence message, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, null, message, t); + } + + /** + * Logs a message object with the {@code ERROR} level. + * + * @param message the message object to log. + */ + public void error(final String message) { + logger.logIfEnabled(FQCN, ERROR, null, message, (Throwable) null); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param params parameters to the message. + * @see #getMessageFactory() + */ + public void error(final String message, final Object... params) { + logger.logIfEnabled(FQCN, ERROR, null, message, params); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2, + final Object p3) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2, p3); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2, p3, p4); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2, p3, p4, p5); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2, p3, p4, p5, p6); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + /** + * Logs a message with parameters at the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @param p9 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void error(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + logger.logIfEnabled(FQCN, ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } + + /** + * Logs a message at the {@code ERROR} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void error(final String message, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, null, message, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the {@code ERROR}level. + * + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @since Log4j-2.4 + */ + public void error(final Supplier msgSupplier) { + logger.logIfEnabled(FQCN, ERROR, null, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ERROR} + * level) including the stack trace of the {@link Throwable} t passed as parameter. + * + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.4 + */ + public void error(final Supplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, null, msgSupplier, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code ERROR} level with the specified Marker. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @since Log4j-2.4 + */ + public void error(final Marker marker, final Supplier msgSupplier) { + logger.logIfEnabled(FQCN, ERROR, marker, msgSupplier, (Throwable) null); + } + + /** + * Logs a message with parameters which are only to be constructed if the logging level is the + * {@code ERROR} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param paramSuppliers An array of functions, which when called, produce the desired log message parameters. + * @since Log4j-2.4 + */ + public void error(final Marker marker, final String message, final Supplier... paramSuppliers) { + logger.logIfEnabled(FQCN, ERROR, marker, message, paramSuppliers); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ERROR} + * level) with the specified Marker and including the stack trace of the {@link Throwable} + * t passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @param t A Throwable or null. + * @since Log4j-2.4 + */ + public void error(final Marker marker, final Supplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, marker, msgSupplier, t); + } + + /** + * Logs a message with parameters which are only to be constructed if the logging level is + * the {@code ERROR} level. + * + * @param message the message to log; the format depends on the message factory. + * @param paramSuppliers An array of functions, which when called, produce the desired log message parameters. + * @since Log4j-2.4 + */ + public void error(final String message, final Supplier... paramSuppliers) { + logger.logIfEnabled(FQCN, ERROR, null, message, paramSuppliers); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code ERROR} level with the specified Marker. The {@code MessageSupplier} may or may + * not use the {@link MessageFactory} to construct the {@code Message}. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message. + * @since Log4j-2.4 + */ + public void error(final Marker marker, final MessageSupplier msgSupplier) { + logger.logIfEnabled(FQCN, ERROR, marker, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ERROR} + * level) with the specified Marker and including the stack trace of the {@link Throwable} + * t passed as parameter. The {@code MessageSupplier} may or may not use the + * {@link MessageFactory} to construct the {@code Message}. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message. + * @param t A Throwable or null. + * @since Log4j-2.4 + */ + public void error(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, marker, msgSupplier, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code ERROR} level. The {@code MessageSupplier} may or may not use the + * {@link MessageFactory} to construct the {@code Message}. + * + * @param msgSupplier A function, which when called, produces the desired log message. + * @since Log4j-2.4 + */ + public void error(final MessageSupplier msgSupplier) { + logger.logIfEnabled(FQCN, ERROR, null, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ERROR} + * level) including the stack trace of the {@link Throwable} t passed as parameter. + * The {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the + * {@code Message}. + * + * @param msgSupplier A function, which when called, produces the desired log message. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.4 + */ + public void error(final MessageSupplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ERROR, null, msgSupplier, t); + } + + /** + * Logs a message with the specific Marker at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param msg the message string to be logged + */ + public void debug(final Marker marker, final Message msg) { + logger.logIfEnabled(FQCN, DEBUG, marker, msg, (Throwable) null); + } + + /** + * Logs a message with the specific Marker at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param msg the message string to be logged + * @param t A Throwable or null. + */ + public void debug(final Marker marker, final Message msg, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, marker, msg, t); + } + + /** + * Logs a message object with the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message object to log. + */ + public void debug(final Marker marker, final Object message) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, (Throwable) null); + } + + /** + * Logs a message CharSequence with the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message CharSequence to log. + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final CharSequence message) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, (Throwable) null); + } + + /** + * Logs a message at the {@code DEBUG} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void debug(final Marker marker, final Object message, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, t); + } + + /** + * Logs a message at the {@code DEBUG} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the CharSequence to log. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final CharSequence message, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, t); + } + + /** + * Logs a message object with the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message object to log. + */ + public void debug(final Marker marker, final String message) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, (Throwable) null); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param params parameters to the message. + * @see #getMessageFactory() + */ + public void debug(final Marker marker, final String message, final Object... params) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, params); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2, p3); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2, p3, p4); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2, p3, p4, p5); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @param p9 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } + + /** + * Logs a message at the {@code DEBUG} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void debug(final Marker marker, final String message, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, t); + } + + /** + * Logs the specified Message at the {@code DEBUG} level. + * + * @param msg the message string to be logged + */ + public void debug(final Message msg) { + logger.logIfEnabled(FQCN, DEBUG, null, msg, (Throwable) null); + } + + /** + * Logs the specified Message at the {@code DEBUG} level. + * + * @param msg the message string to be logged + * @param t A Throwable or null. + */ + public void debug(final Message msg, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, null, msg, t); + } + + /** + * Logs a message object with the {@code DEBUG} level. + * + * @param message the message object to log. + */ + public void debug(final Object message) { + logger.logIfEnabled(FQCN, DEBUG, null, message, (Throwable) null); + } + + /** + * Logs a message at the {@code DEBUG} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void debug(final Object message, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, null, message, t); + } + + /** + * Logs a message CharSequence with the {@code DEBUG} level. + * + * @param message the message CharSequence to log. + * @since Log4j-2.6 + */ + public void debug(final CharSequence message) { + logger.logIfEnabled(FQCN, DEBUG, null, message, (Throwable) null); + } + + /** + * Logs a CharSequence at the {@code DEBUG} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the CharSequence to log. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.6 + */ + public void debug(final CharSequence message, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, null, message, t); + } + + /** + * Logs a message object with the {@code DEBUG} level. + * + * @param message the message object to log. + */ + public void debug(final String message) { + logger.logIfEnabled(FQCN, DEBUG, null, message, (Throwable) null); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param params parameters to the message. + * @see #getMessageFactory() + */ + public void debug(final String message, final Object... params) { + logger.logIfEnabled(FQCN, DEBUG, null, message, params); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2, + final Object p3) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2, p3); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2, p3, p4); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2, p3, p4, p5); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + /** + * Logs a message with parameters at the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @param p9 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void debug(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + logger.logIfEnabled(FQCN, DEBUG, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } + + /** + * Logs a message at the {@code DEBUG} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void debug(final String message, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, null, message, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the {@code DEBUG}level. + * + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @since Log4j-2.4 + */ + public void debug(final Supplier msgSupplier) { + logger.logIfEnabled(FQCN, DEBUG, null, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code DEBUG} + * level) including the stack trace of the {@link Throwable} t passed as parameter. + * + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.4 + */ + public void debug(final Supplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, null, msgSupplier, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code DEBUG} level with the specified Marker. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @since Log4j-2.4 + */ + public void debug(final Marker marker, final Supplier msgSupplier) { + logger.logIfEnabled(FQCN, DEBUG, marker, msgSupplier, (Throwable) null); + } + + /** + * Logs a message with parameters which are only to be constructed if the logging level is the + * {@code DEBUG} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param paramSuppliers An array of functions, which when called, produce the desired log message parameters. + * @since Log4j-2.4 + */ + public void debug(final Marker marker, final String message, final Supplier... paramSuppliers) { + logger.logIfEnabled(FQCN, DEBUG, marker, message, paramSuppliers); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code DEBUG} + * level) with the specified Marker and including the stack trace of the {@link Throwable} + * t passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @param t A Throwable or null. + * @since Log4j-2.4 + */ + public void debug(final Marker marker, final Supplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, marker, msgSupplier, t); + } + + /** + * Logs a message with parameters which are only to be constructed if the logging level is + * the {@code DEBUG} level. + * + * @param message the message to log; the format depends on the message factory. + * @param paramSuppliers An array of functions, which when called, produce the desired log message parameters. + * @since Log4j-2.4 + */ + public void debug(final String message, final Supplier... paramSuppliers) { + logger.logIfEnabled(FQCN, DEBUG, null, message, paramSuppliers); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code DEBUG} level with the specified Marker. The {@code MessageSupplier} may or may + * not use the {@link MessageFactory} to construct the {@code Message}. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message. + * @since Log4j-2.4 + */ + public void debug(final Marker marker, final MessageSupplier msgSupplier) { + logger.logIfEnabled(FQCN, DEBUG, marker, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code DEBUG} + * level) with the specified Marker and including the stack trace of the {@link Throwable} + * t passed as parameter. The {@code MessageSupplier} may or may not use the + * {@link MessageFactory} to construct the {@code Message}. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message. + * @param t A Throwable or null. + * @since Log4j-2.4 + */ + public void debug(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, marker, msgSupplier, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code DEBUG} level. The {@code MessageSupplier} may or may not use the + * {@link MessageFactory} to construct the {@code Message}. + * + * @param msgSupplier A function, which when called, produces the desired log message. + * @since Log4j-2.4 + */ + public void debug(final MessageSupplier msgSupplier) { + logger.logIfEnabled(FQCN, DEBUG, null, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code DEBUG} + * level) including the stack trace of the {@link Throwable} t passed as parameter. + * The {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the + * {@code Message}. + * + * @param msgSupplier A function, which when called, produces the desired log message. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.4 + */ + public void debug(final MessageSupplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, DEBUG, null, msgSupplier, t); + } + + /** + * Logs a message with the specific Marker at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param msg the message string to be logged + */ + public void echo(final Marker marker, final Message msg) { + logger.logIfEnabled(FQCN, ECHO, marker, msg, (Throwable) null); + } + + /** + * Logs a message with the specific Marker at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param msg the message string to be logged + * @param t A Throwable or null. + */ + public void echo(final Marker marker, final Message msg, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, marker, msg, t); + } + + /** + * Logs a message object with the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message object to log. + */ + public void echo(final Marker marker, final Object message) { + logger.logIfEnabled(FQCN, ECHO, marker, message, (Throwable) null); + } + + /** + * Logs a message CharSequence with the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message CharSequence to log. + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final CharSequence message) { + logger.logIfEnabled(FQCN, ECHO, marker, message, (Throwable) null); + } + + /** + * Logs a message at the {@code ECHO} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void echo(final Marker marker, final Object message, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, marker, message, t); + } + + /** + * Logs a message at the {@code ECHO} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the CharSequence to log. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final CharSequence message, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, marker, message, t); + } + + /** + * Logs a message object with the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message object to log. + */ + public void echo(final Marker marker, final String message) { + logger.logIfEnabled(FQCN, ECHO, marker, message, (Throwable) null); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param params parameters to the message. + * @see #getMessageFactory() + */ + public void echo(final Marker marker, final String message, final Object... params) { + logger.logIfEnabled(FQCN, ECHO, marker, message, params); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2, p3); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2, p3, p4); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2, p3, p4, p5); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2, p3, p4, p5, p6); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @param p9 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final Marker marker, final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + logger.logIfEnabled(FQCN, ECHO, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } + + /** + * Logs a message at the {@code ECHO} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void echo(final Marker marker, final String message, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, marker, message, t); + } + + /** + * Logs the specified Message at the {@code ECHO} level. + * + * @param msg the message string to be logged + */ + public void echo(final Message msg) { + logger.logIfEnabled(FQCN, ECHO, null, msg, (Throwable) null); + } + + /** + * Logs the specified Message at the {@code ECHO} level. + * + * @param msg the message string to be logged + * @param t A Throwable or null. + */ + public void echo(final Message msg, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, null, msg, t); + } + + /** + * Logs a message object with the {@code ECHO} level. + * + * @param message the message object to log. + */ + public void echo(final Object message) { + logger.logIfEnabled(FQCN, ECHO, null, message, (Throwable) null); + } + + /** + * Logs a message at the {@code ECHO} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void echo(final Object message, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, null, message, t); + } + + /** + * Logs a message CharSequence with the {@code ECHO} level. + * + * @param message the message CharSequence to log. + * @since Log4j-2.6 + */ + public void echo(final CharSequence message) { + logger.logIfEnabled(FQCN, ECHO, null, message, (Throwable) null); + } + + /** + * Logs a CharSequence at the {@code ECHO} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the CharSequence to log. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.6 + */ + public void echo(final CharSequence message, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, null, message, t); + } + + /** + * Logs a message object with the {@code ECHO} level. + * + * @param message the message object to log. + */ + public void echo(final String message) { + logger.logIfEnabled(FQCN, ECHO, null, message, (Throwable) null); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param params parameters to the message. + * @see #getMessageFactory() + */ + public void echo(final String message, final Object... params) { + logger.logIfEnabled(FQCN, ECHO, null, message, params); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2, + final Object p3) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2, p3); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2, p3, p4); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2, p3, p4, p5); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2, p3, p4, p5, p6); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + /** + * Logs a message with parameters at the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @param p9 parameter to the message. + * @see #getMessageFactory() + * @since Log4j-2.6 + */ + public void echo(final String message, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + logger.logIfEnabled(FQCN, ECHO, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } + + /** + * Logs a message at the {@code ECHO} level including the stack trace of + * the {@link Throwable} {@code t} passed as parameter. + * + * @param message the message to log. + * @param t the exception to log, including its stack trace. + */ + public void echo(final String message, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, null, message, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the {@code ECHO}level. + * + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @since Log4j-2.4 + */ + public void echo(final Supplier msgSupplier) { + logger.logIfEnabled(FQCN, ECHO, null, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ECHO} + * level) including the stack trace of the {@link Throwable} t passed as parameter. + * + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.4 + */ + public void echo(final Supplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, null, msgSupplier, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code ECHO} level with the specified Marker. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @since Log4j-2.4 + */ + public void echo(final Marker marker, final Supplier msgSupplier) { + logger.logIfEnabled(FQCN, ECHO, marker, msgSupplier, (Throwable) null); + } + + /** + * Logs a message with parameters which are only to be constructed if the logging level is the + * {@code ECHO} level. + * + * @param marker the marker data specific to this log statement + * @param message the message to log; the format depends on the message factory. + * @param paramSuppliers An array of functions, which when called, produce the desired log message parameters. + * @since Log4j-2.4 + */ + public void echo(final Marker marker, final String message, final Supplier... paramSuppliers) { + logger.logIfEnabled(FQCN, ECHO, marker, message, paramSuppliers); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ECHO} + * level) with the specified Marker and including the stack trace of the {@link Throwable} + * t passed as parameter. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message; + * the format depends on the message factory. + * @param t A Throwable or null. + * @since Log4j-2.4 + */ + public void echo(final Marker marker, final Supplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, marker, msgSupplier, t); + } + + /** + * Logs a message with parameters which are only to be constructed if the logging level is + * the {@code ECHO} level. + * + * @param message the message to log; the format depends on the message factory. + * @param paramSuppliers An array of functions, which when called, produce the desired log message parameters. + * @since Log4j-2.4 + */ + public void echo(final String message, final Supplier... paramSuppliers) { + logger.logIfEnabled(FQCN, ECHO, null, message, paramSuppliers); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code ECHO} level with the specified Marker. The {@code MessageSupplier} may or may + * not use the {@link MessageFactory} to construct the {@code Message}. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message. + * @since Log4j-2.4 + */ + public void echo(final Marker marker, final MessageSupplier msgSupplier) { + logger.logIfEnabled(FQCN, ECHO, marker, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ECHO} + * level) with the specified Marker and including the stack trace of the {@link Throwable} + * t passed as parameter. The {@code MessageSupplier} may or may not use the + * {@link MessageFactory} to construct the {@code Message}. + * + * @param marker the marker data specific to this log statement + * @param msgSupplier A function, which when called, produces the desired log message. + * @param t A Throwable or null. + * @since Log4j-2.4 + */ + public void echo(final Marker marker, final MessageSupplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, marker, msgSupplier, t); + } + + /** + * Logs a message which is only to be constructed if the logging level is the + * {@code ECHO} level. The {@code MessageSupplier} may or may not use the + * {@link MessageFactory} to construct the {@code Message}. + * + * @param msgSupplier A function, which when called, produces the desired log message. + * @since Log4j-2.4 + */ + public void echo(final MessageSupplier msgSupplier) { + logger.logIfEnabled(FQCN, ECHO, null, msgSupplier, (Throwable) null); + } + + /** + * Logs a message (only to be constructed if the logging level is the {@code ECHO} + * level) including the stack trace of the {@link Throwable} t passed as parameter. + * The {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the + * {@code Message}. + * + * @param msgSupplier A function, which when called, produces the desired log message. + * @param t the exception to log, including its stack trace. + * @since Log4j-2.4 + */ + public void echo(final MessageSupplier msgSupplier, final Throwable t) { + logger.logIfEnabled(FQCN, ECHO, null, msgSupplier, t); + } +} + diff --git a/src/main/java/org/qortal/at/QortalAtLoggerFactory.java b/src/main/java/org/qortal/at/QortalAtLoggerFactory.java new file mode 100644 index 00000000..19cbb3d9 --- /dev/null +++ b/src/main/java/org/qortal/at/QortalAtLoggerFactory.java @@ -0,0 +1,24 @@ +package org.qortal.at; + +import org.ciyam.at.AtLogger; + +public class QortalAtLoggerFactory implements org.ciyam.at.AtLoggerFactory { + + private static QortalAtLoggerFactory instance; + + private QortalAtLoggerFactory() { + } + + public static synchronized QortalAtLoggerFactory getInstance() { + if (instance == null) + instance = new QortalAtLoggerFactory(); + + return instance; + } + + @Override + public AtLogger create(final Class loggerName) { + return QortalAtLogger.create(loggerName); + } + +} diff --git a/src/main/java/org/qortal/at/QortalFunctionCode.java b/src/main/java/org/qortal/at/QortalFunctionCode.java index 0ca9f5d6..cf6b1cfd 100644 --- a/src/main/java/org/qortal/at/QortalFunctionCode.java +++ b/src/main/java/org/qortal/at/QortalFunctionCode.java @@ -4,6 +4,8 @@ import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.ciyam.at.ExecutionException; import org.ciyam.at.FunctionData; import org.ciyam.at.IllegalFunctionCodeException; @@ -30,9 +32,9 @@ public enum QortalFunctionCode { byte[] pkh = new byte[32]; // Copy PKH part of B to last 20 bytes - System.arraycopy(state.getB(), 32 - 20 - 4, pkh, 32 - 20, 20); + System.arraycopy(getB(state), 32 - 20 - 4, pkh, 32 - 20, 20); - state.getAPI().setB(state, pkh); + setB(state, pkh); } }, /** @@ -64,6 +66,8 @@ public enum QortalFunctionCode { public final int paramCount; public final boolean returnsValue; + private static final Logger LOGGER = LogManager.getLogger(QortalFunctionCode.class); + private static final Map map = Arrays.stream(QortalFunctionCode.values()) .collect(Collectors.toMap(functionCode -> functionCode.value, functionCode -> functionCode)); @@ -77,7 +81,7 @@ public enum QortalFunctionCode { return map.get((short) value); } - public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws IllegalFunctionCodeException { + public void preExecuteCheck(int paramCount, boolean returnValueExpected, short rawFunctionCode) throws IllegalFunctionCodeException { if (paramCount != this.paramCount) throw new IllegalFunctionCodeException( "Passed paramCount (" + paramCount + ") does not match function's required paramCount (" + this.paramCount + ")"); @@ -100,7 +104,7 @@ public enum QortalFunctionCode { */ public void execute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { // Check passed functionData against requirements of this function - preExecuteCheck(functionData.paramCount, functionData.returnValueExpected, state, rawFunctionCode); + preExecuteCheck(functionData.paramCount, functionData.returnValueExpected, rawFunctionCode); if (functionData.paramCount >= 1 && functionData.value1 == null) throw new IllegalFunctionCodeException("Passed value1 is null but function has paramCount of (" + this.paramCount + ")"); @@ -108,7 +112,7 @@ public enum QortalFunctionCode { if (functionData.paramCount == 2 && functionData.value2 == null) throw new IllegalFunctionCodeException("Passed value2 is null but function has paramCount of (" + this.paramCount + ")"); - state.getLogger().debug("Function \"" + this.name() + "\""); + LOGGER.debug(() -> String.format("Function \"%s\"", this.name())); postCheckExecute(functionData, state, rawFunctionCode); } @@ -119,7 +123,7 @@ public enum QortalFunctionCode { private static void convertAddressInB(byte addressPrefix, MachineState state) { byte[] addressNoChecksum = new byte[1 + 20]; addressNoChecksum[0] = addressPrefix; - System.arraycopy(state.getB(), 0, addressNoChecksum, 1, 20); + System.arraycopy(getB(state), 0, addressNoChecksum, 1, 20); byte[] checksum = Crypto.doubleDigest(addressNoChecksum); @@ -128,7 +132,17 @@ public enum QortalFunctionCode { System.arraycopy(addressNoChecksum, 0, address, 32 - 1 - 20 - 4, addressNoChecksum.length); System.arraycopy(checksum, 0, address, 32 - 4, 4); - state.getAPI().setB(state, address); + setB(state, address); + } + + private static byte[] getB(MachineState state) { + QortalATAPI api = (QortalATAPI) state.getAPI(); + return api.getB(state); + } + + private static void setB(MachineState state, byte[] bBytes) { + QortalATAPI api = (QortalATAPI) state.getAPI(); + api.setB(state, bBytes); } } diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 4614c81c..37ae1bf1 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -506,8 +506,10 @@ public class Block { // Allocate cache for results List transactionsData = this.repository.getBlockRepository().getTransactionsFromSignature(this.blockData.getSignature()); - // The number of transactions fetched from repository should correspond with Block's transactionCount - if (transactionsData.size() != this.blockData.getTransactionCount()) + long nonAtTransactionCount = transactionsData.stream().filter(transactionData -> transactionData.getType() != TransactionType.AT).count(); + + // The number of non-AT transactions fetched from repository should correspond with Block's transactionCount + if (nonAtTransactionCount != this.blockData.getTransactionCount()) throw new IllegalStateException("Block's transactions from repository do not match block's transaction count"); this.transactions = new ArrayList<>(); @@ -540,8 +542,10 @@ public class Block { // Allocate cache for results List atStateData = this.repository.getATRepository().getBlockATStatesAtHeight(this.blockData.getHeight()); - // The number of AT states fetched from repository should correspond with Block's atCount - if (atStateData.size() != this.blockData.getATCount()) + // The number of non-initial AT states fetched from repository should correspond with Block's atCount. + // We exclude initial AT states created by processing DEPLOY_AT transactions as they are never serialized and so not included in block's AT count. + int nonInitialCount = (int) atStateData.stream().filter(atState -> !atState.isInitial()).count(); + if (nonInitialCount != this.blockData.getATCount()) throw new IllegalStateException("Block's AT states from repository do not match block's AT count"); this.atStates = atStateData; @@ -1182,7 +1186,7 @@ public class Block { // Run each AT, appends AT-Transactions and corresponding AT states, to our lists for (ATData atData : executableATs) { AT at = new AT(this.repository, atData); - List atTransactions = at.run(this.blockData.getTimestamp()); + List atTransactions = at.run(this.blockData.getHeight(), this.blockData.getTimestamp()); allAtTransactions.addAll(atTransactions); @@ -1192,14 +1196,16 @@ public class Block { this.ourAtFees = this.ourAtFees.add(atStateData.getFees()); } + // AT Transactions never need approval + allAtTransactions.forEach(transaction -> transaction.getTransactionData().setApprovalStatus(ApprovalStatus.NOT_REQUIRED)); + // Prepend our entire AT-Transactions/states to block's transactions this.transactions.addAll(0, allAtTransactions); // Re-sort this.transactions.sort(Transaction.getComparator()); - // Update transaction count - this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1); + // AT Transactions do not affect block's transaction count // We've added transactions, so recalculate transactions signature calcTransactionsSignature(); @@ -1408,13 +1414,17 @@ public class Block { protected void processAtFeesAndStates() throws DataException { ATRepository atRepository = this.repository.getATRepository(); - for (ATStateData atState : this.getATStates()) { - Account atAccount = new Account(this.repository, atState.getATAddress()); + for (ATStateData atStateData : this.getATStates()) { + Account atAccount = new Account(this.repository, atStateData.getATAddress()); // Subtract AT-generated fees from AT accounts - atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).subtract(atState.getFees())); + atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).subtract(atStateData.getFees())); - atRepository.save(atState); + // Update AT info with latest state + ATData atData = atRepository.fromATAddress(atStateData.getATAddress()); + + AT at = new AT(repository, atData, atStateData); + at.update(this.blockData.getHeight(), this.blockData.getTimestamp()); } } @@ -1568,15 +1578,18 @@ public class Block { protected void orphanAtFeesAndStates() throws DataException { ATRepository atRepository = this.repository.getATRepository(); - for (ATStateData atState : this.getATStates()) { - Account atAccount = new Account(this.repository, atState.getATAddress()); + for (ATStateData atStateData : this.getATStates()) { + Account atAccount = new Account(this.repository, atStateData.getATAddress()); // Return AT-generated fees to AT accounts - atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).add(atState.getFees())); - } + atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).add(atStateData.getFees())); - // Delete ATStateData for this height - atRepository.deleteATStates(this.blockData.getHeight()); + // Revert AT info to prior values + ATData atData = atRepository.fromATAddress(atStateData.getATAddress()); + + AT at = new AT(repository, atData, atStateData); + at.revert(this.blockData.getHeight(), this.blockData.getTimestamp()); + } } protected void decreaseAccountLevels() throws DataException { diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 5d4c50fc..bc9909d9 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -471,7 +471,7 @@ public class BlockChain { this.unitFee = this.unitFee.setScale(8); this.minFeePerByte = this.unitFee.divide(this.maxBytesPerUnitFee, MathContext.DECIMAL32); - this.ciyamAtSettings.feePerStep.setScale(8); + this.ciyamAtSettings.feePerStep = this.ciyamAtSettings.feePerStep.setScale(8); // Pre-calculate cumulative blocks required for each level int cumulativeBlocks = 0; diff --git a/src/main/java/org/qortal/block/BlockMinter.java b/src/main/java/org/qortal/block/BlockMinter.java index 3adbef3d..2026f5f7 100644 --- a/src/main/java/org/qortal/block/BlockMinter.java +++ b/src/main/java/org/qortal/block/BlockMinter.java @@ -337,11 +337,9 @@ public class BlockMinter extends Thread { this.interrupt(); } - public static void mintTestingBlock(Repository repository, PrivateKeyAccount... mintingAndOnlineAccounts) throws DataException { - if (!BlockChain.getInstance().isTestChain()) { - LOGGER.warn("Ignoring attempt to mint testing block for non-test chain!"); - return; - } + public static Block mintTestingBlock(Repository repository, PrivateKeyAccount... mintingAndOnlineAccounts) throws DataException { + if (!BlockChain.getInstance().isTestChain()) + throw new DataException("Ignoring attempt to mint testing block for non-test chain!"); // Ensure mintingAccount is 'online' so blocks can be minted Controller.getInstance().ensureTestingAccountsOnline(mintingAndOnlineAccounts); @@ -372,6 +370,8 @@ public class BlockMinter extends Thread { LOGGER.info(String.format("Minted new test block: %d", newBlock.getBlockData().getHeight())); repository.saveChanges(); + + return newBlock; } finally { blockchainLock.unlock(); } diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index 1ee78f2c..2789fb3d 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -1,7 +1,10 @@ package org.qortal.crosschain; +import static org.ciyam.at.OpCode.calcOffset; + import java.math.BigDecimal; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.function.Function; import org.bitcoinj.core.Coin; @@ -17,9 +20,11 @@ import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptChunk; import org.bitcoinj.script.ScriptOpCodes; import org.ciyam.at.API; +import org.ciyam.at.CompilationException; import org.ciyam.at.FunctionCode; import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; +import org.qortal.account.Account; import org.qortal.utils.Base58; import org.qortal.utils.BitTwiddling; @@ -29,6 +34,23 @@ import com.google.common.primitives.Bytes; public class BTCACCT { public static final Coin DEFAULT_BTC_FEE = Coin.valueOf(1000L); // 0.00001000 BTC + public static final byte[] CODE_BYTES_HASH = HashCode.fromString("750012c7ae79d85a97e64e94c467c7791dd76cf3050b864f3166635a21d767c6").asBytes(); // SHA256 of AT code bytes + + public static class AtConstants { + public final byte[] secretHash; + public final BigDecimal initialPayout; + public final BigDecimal redeemPayout; + public final String recipient; + public final int refundMinutes; + + public AtConstants(byte[] secretHash, BigDecimal initialPayout, BigDecimal redeemPayout, String recipient, int refundMinutes) { + this.secretHash = secretHash; + this.initialPayout = initialPayout; + this.redeemPayout = redeemPayout; + this.recipient = recipient; + this.refundMinutes = refundMinutes; + } + } /* * OP_TUCK (to copy public key to before signature) @@ -172,7 +194,8 @@ public class BTCACCT { return buildP2shTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, null, redeemSigScriptBuilder); } - public static byte[] buildQortalAT(byte[] secretHash, String recipientQortalAddress, long refundMinutes, BigDecimal initialPayout) { + @SuppressWarnings("unused") + public static byte[] buildQortalAT(byte[] secretHash, String recipientQortalAddress, int refundMinutes, BigDecimal initialPayout, BigDecimal redeemPayout) { // Labels for data segment addresses int addrCounter = 0; // Constants (with corresponding dataByteBuffer.put*() calls below) @@ -185,10 +208,15 @@ public class BTCACCT { final int addrAddressPart3 = addrCounter++; final int addrAddressPart4 = addrCounter++; final int addrRefundMinutes = addrCounter++; + final int addrInitialPayoutAmount = addrCounter++; + final int addrRedeemPayoutAmount = addrCounter++; + final int addrExpectedTxType = addrCounter++; + final int addrHashIndex = addrCounter++; + final int addrAddressIndex = addrCounter++; + final int addrAddressTempIndex = addrCounter++; final int addrHashTempIndex = addrCounter++; final int addrHashTempLength = addrCounter++; - final int addrInitialPayoutAmount = addrCounter++; - final int addrExpectedTxType = addrCounter++; + final int addrEndOfConstants = addrCounter; // Variables final int addrRefundTimestamp = addrCounter++; final int addrLastTimestamp = addrCounter++; @@ -220,136 +248,175 @@ public class BTCACCT { assert dataByteBuffer.position() == addrRefundMinutes * MachineState.VALUE_SIZE : "addrRefundMinutes incorrect"; dataByteBuffer.putLong(refundMinutes); + // Initial payout amount + assert dataByteBuffer.position() == addrInitialPayoutAmount * MachineState.VALUE_SIZE : "addrInitialPayoutAmount incorrect"; + dataByteBuffer.putLong(initialPayout.unscaledValue().longValue()); + + // Redeem payout amount + assert dataByteBuffer.position() == addrRedeemPayoutAmount * MachineState.VALUE_SIZE : "addrRedeemPayoutAmount incorrect"; + dataByteBuffer.putLong(redeemPayout.unscaledValue().longValue()); + + // We're only interested in MESSAGE transactions + assert dataByteBuffer.position() == addrExpectedTxType * MachineState.VALUE_SIZE : "addrExpectedTxType incorrect"; + dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value); + + // Index into data segment of hash, used by GET_B_IND + assert dataByteBuffer.position() == addrHashIndex * MachineState.VALUE_SIZE : "addrHashIndex incorrect"; + dataByteBuffer.putLong(addrHashPart1); + + // Index into data segment of recipient address, used by SET_B_IND + assert dataByteBuffer.position() == addrAddressIndex * MachineState.VALUE_SIZE : "addrAddressIndex incorrect"; + dataByteBuffer.putLong(addrAddressPart1); + + // Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND + assert dataByteBuffer.position() == addrAddressTempIndex * MachineState.VALUE_SIZE : "addrAddressTempIndex incorrect"; + dataByteBuffer.putLong(addrAddressTemp1); + // Source location and length for hashing any passed secret assert dataByteBuffer.position() == addrHashTempIndex * MachineState.VALUE_SIZE : "addrHashTempIndex incorrect"; dataByteBuffer.putLong(addrHashTemp1); assert dataByteBuffer.position() == addrHashTempLength * MachineState.VALUE_SIZE : "addrHashTempLength incorrect"; dataByteBuffer.putLong(32L); - // Initial payout amount - assert dataByteBuffer.position() == addrInitialPayoutAmount * MachineState.VALUE_SIZE : "addrInitialPayoutAmount incorrect"; - dataByteBuffer.putLong(initialPayout.unscaledValue().longValue()); - - // We're only interested in MESSAGE transactions - assert dataByteBuffer.position() == addrExpectedTxType * MachineState.VALUE_SIZE : "addrExpectedTxType incorrect"; - dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value); + assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants"; // Code labels - final int addrTxLoop = 0x0036; - final int addrCheckTx = 0x004b; - final int addrRefund = 0x00c6; - final int addrEndOfCode = 0x00cd; + Integer labelTxLoop = null; + Integer labelRefund = null; + Integer labelCheckTx = null; - int tempPC; - ByteBuffer codeByteBuffer = ByteBuffer.allocate(addrEndOfCode * 1); + ByteBuffer codeByteBuffer = ByteBuffer.allocate(512); - /* Initialization */ + // Two-pass version + for (int pass = 0; pass < 2; ++pass) { + codeByteBuffer.clear(); - // Use AT creation 'timestamp' as starting point for finding transactions sent to AT - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(addrLastTimestamp); - // Calculate refund 'timestamp' by adding minutes to above 'timestamp', then save into addrRefundTimestamp - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrRefundTimestamp) - .putInt(addrLastTimestamp).putInt(addrRefundMinutes); + try { + /* Initialization */ - // Load recipient's address into B register - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrAddressPart1); - // Send initial payment to recipient so they have enough funds to message AT if all goes well - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PAY_TO_ADDRESS_IN_B.value).putInt(addrInitialPayoutAmount); + // Use AT creation 'timestamp' as starting point for finding transactions sent to AT + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTimestamp)); + // Calculate refund 'timestamp' by adding minutes to above 'timestamp', then save into addrRefundTimestamp + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTimestamp, addrRefundMinutes)); - // Set restart position to after this opcode - codeByteBuffer.put(OpCode.SET_PCS.value); + // Load recipient's address into B register + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAddressIndex)); + // Send initial payment to recipient so they have enough funds to message AT if all goes well + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrInitialPayoutAmount)); - /* Main loop */ + // Set restart position to after this opcode + codeByteBuffer.put(OpCode.SET_PCS.compile()); - // Fetch current block 'timestamp' - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(addrBlockTimestamp); - // If we're past refund 'timestamp' then go refund everything back to AT creator - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BGE_DAT.value).putInt(addrBlockTimestamp).putInt(addrRefundTimestamp).put((byte) (addrRefund - tempPC)); + /* Main loop */ - /* Transaction processing loop */ - assert codeByteBuffer.position() == addrTxLoop : "addrTxLoop incorrect"; + // Fetch current block 'timestamp' + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp)); + // If we're not past refund 'timestamp' then look for next transaction + codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelTxLoop))); + // We're past refund 'timestamp' so go refund everything back to AT creator + codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); - // Find next transaction to this AT since the last one (if any) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(addrLastTimestamp); - // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(addrComparator); - // If addrComparator is zero (i.e. A is non-zero, transaction was found) then branch to addrCheckTx - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrCheckTx - tempPC)); - // Stop and wait for next block - codeByteBuffer.put(OpCode.STP_IMD.value); + /* Transaction processing loop */ + labelTxLoop = codeByteBuffer.position(); - /* Check transaction */ - assert codeByteBuffer.position() == addrCheckTx : "addrCheckTx incorrect"; + // Find next transaction to this AT since the last one (if any) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTimestamp)); + // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrComparator)); + // If addrComparator is zero (i.e. A is non-zero, transaction was found) then go check transaction + codeByteBuffer.put(OpCode.BZR_DAT.compile(addrComparator, calcOffset(codeByteBuffer, labelCheckTx))); + // Stop and wait for next block + codeByteBuffer.put(OpCode.STP_IMD.compile()); - // Update our 'last found transaction's timestamp' using 'timestamp' from transaction - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(addrLastTimestamp); - // Extract transaction type (message/payment) from transaction and save type in addrTxType - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(addrTxType); - // If transaction type is not MESSAGE type then go look for another transaction - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrTxType).putInt(addrExpectedTxType).put((byte) (addrTxLoop - tempPC)); + /* Check transaction */ + labelCheckTx = codeByteBuffer.position(); - /* Check transaction's sender */ + // Update our 'last found transaction's timestamp' using 'timestamp' from transaction + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTimestamp)); + // Extract transaction type (message/payment) from transaction and save type in addrTxType + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType)); + // If transaction type is not MESSAGE type then go look for another transaction + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrExpectedTxType, calcOffset(codeByteBuffer, labelTxLoop))); - // Extract sender address from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value); - // Save B register into data segment starting at addrAddressTemp1 - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B_IND.value).putInt(addrAddressTemp1); - // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp1).putInt(addrAddressPart1).put((byte) (addrTxLoop - tempPC)); - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp2).putInt(addrAddressPart2).put((byte) (addrTxLoop - tempPC)); - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp3).putInt(addrAddressPart3).put((byte) (addrTxLoop - tempPC)); - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BNE_DAT.value).putInt(addrAddressTemp4).putInt(addrAddressPart4).put((byte) (addrTxLoop - tempPC)); + /* Check transaction's sender */ - /* Check 'secret' in transaction's message */ + // Extract sender address from transaction into B register + codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B)); + // Save B register into data segment starting at addrAddressTemp1 (as pointed to by addrAddressTempIndex) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrAddressTempIndex)); + // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp1, addrAddressPart1, calcOffset(codeByteBuffer, labelTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp2, addrAddressPart2, calcOffset(codeByteBuffer, labelTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp3, addrAddressPart3, calcOffset(codeByteBuffer, labelTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp4, addrAddressPart4, calcOffset(codeByteBuffer, labelTxLoop))); - // Extract message from transaction into B register - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value); - // Save B register into data segment starting at addrHashTemp1 - codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_B_IND.value).putInt(addrHashTemp1); - // Load B register with expected hash result - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrHashPart1); - // Perform HASH160 using source data at addrHashTemp1 through addrHashTemp4. (Location and length specified via addrHashTempIndex and addrHashTemplength). - // Save the equality result (1 if they match, 0 otherwise) into addrComparator. - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.CHECK_HASH160_WITH_B.value).putInt(addrComparator).putInt(addrHashTempIndex).putInt(addrHashTempLength); - // If hashes don't match, addrComparator will be zero so go find another transaction - tempPC = codeByteBuffer.position(); - codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrComparator).put((byte) (addrTxLoop - tempPC)); + /* Check 'secret' in transaction's message */ - /* Success! Pay balance to intended recipient */ + // Extract message from transaction into B register + codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); + // Save B register into data segment starting at addrHashTemp1 (as pointed to by addrHashTempIndex) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrHashTempIndex)); + // Load B register with expected hash result (as pointed to by addrHashIndex) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrHashIndex)); + // Perform HASH160 using source data at addrHashTemp1 through addrHashTemp4. (Location and length specified via addrHashTempIndex and addrHashTemplength). + // Save the equality result (1 if they match, 0 otherwise) into addrComparator. + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrComparator, addrHashTempIndex, addrHashTempLength)); + // If hashes don't match, addrComparator will be zero so go find another transaction + codeByteBuffer.put(OpCode.BZR_DAT.compile(addrComparator, calcOffset(codeByteBuffer, labelTxLoop))); - // Load B register with intended recipient address. - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(addrAddressPart1); - // Pay AT's balance to recipient - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); - // We're finished forever - codeByteBuffer.put(OpCode.FIN_IMD.value); + /* Success! Pay arranged amount to intended recipient */ - /* Refund balance back to AT creator */ - assert codeByteBuffer.position() == addrRefund : "addrRefund incorrect"; + // Load B register with intended recipient address (as pointed to by addrAddressIndex) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAddressIndex)); + // Pay AT's balance to recipient + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrRedeemPayoutAmount)); + // We're finished forever + codeByteBuffer.put(OpCode.FIN_IMD.compile()); - // Load B register with AT creator's address. - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value); - // Pay AT's balance back to AT's creator. - codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value); - // We're finished forever - codeByteBuffer.put(OpCode.FIN_IMD.value); + /* Refund balance back to AT creator */ + labelRefund = codeByteBuffer.position(); - // end-of-code - assert codeByteBuffer.position() == addrEndOfCode : "addrEndOfCode incorrect"; + // Load B register with AT creator's address. + codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B)); + // Pay AT's balance back to AT's creator. + codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B)); + // We're finished forever + codeByteBuffer.put(OpCode.FIN_IMD.compile()); + } catch (CompilationException e) { + throw new IllegalStateException("Unable to compile BTC-QORT ACCT?", e); + } + } + + codeByteBuffer.flip(); + + byte[] codeBytes = new byte[codeByteBuffer.limit()]; + codeByteBuffer.get(codeBytes); final short ciyamAtVersion = 2; final short numCallStackPages = 0; final short numUserStackPages = 0; final long minActivationAmount = 0L; - return MachineState.toCreationBytes(ciyamAtVersion, codeByteBuffer.array(), dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount); + return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount); + } + + public static AtConstants extractAtConstants(byte[] dataBytes) { + ByteBuffer dataByteBuffer = ByteBuffer.wrap(dataBytes); + + byte[] secretHash = new byte[32]; + dataByteBuffer.get(secretHash); + + byte[] addressBytes = new byte[32]; + dataByteBuffer.get(addressBytes); + String recipient = Base58.encode(Arrays.copyOf(addressBytes, Account.ADDRESS_LENGTH)); + + int refundMinutes = (int) dataByteBuffer.getLong(); + + BigDecimal initialPayout = BigDecimal.valueOf(dataByteBuffer.getLong(), 8); + + BigDecimal redeemPayout = BigDecimal.valueOf(dataByteBuffer.getLong(), 8); + + return new AtConstants(secretHash, initialPayout, redeemPayout, recipient, refundMinutes); } } diff --git a/src/main/java/org/qortal/crypto/Crypto.java b/src/main/java/org/qortal/crypto/Crypto.java index c940c6ab..6c1e7ba9 100644 --- a/src/main/java/org/qortal/crypto/Crypto.java +++ b/src/main/java/org/qortal/crypto/Crypto.java @@ -59,6 +59,18 @@ public class Crypto { return Bytes.concat(digest, digest); } + /** Returns RMD160(SHA256(data)) */ + public static byte[] hash160(byte[] data) { + byte[] interim = digest(data); + + try { + MessageDigest md160 = MessageDigest.getInstance("RIPEMD160"); + return md160.digest(interim); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("RIPEMD160 message digest not available"); + } + } + @SuppressWarnings("deprecation") private static String toAddress(byte addressVersion, byte[] input) { // SHA2-256 input to create new data and of known size diff --git a/src/main/java/org/qortal/data/at/ATStateData.java b/src/main/java/org/qortal/data/at/ATStateData.java index aa8cf4cd..1ea9e527 100644 --- a/src/main/java/org/qortal/data/at/ATStateData.java +++ b/src/main/java/org/qortal/data/at/ATStateData.java @@ -11,32 +11,38 @@ public class ATStateData { private byte[] stateData; private byte[] stateHash; private BigDecimal fees; + private boolean isInitial; // Constructors /** Create new ATStateData */ - public ATStateData(String ATAddress, Integer height, Long creation, byte[] stateData, byte[] stateHash, BigDecimal fees) { + public ATStateData(String ATAddress, Integer height, Long creation, byte[] stateData, byte[] stateHash, BigDecimal fees, boolean isInitial) { this.ATAddress = ATAddress; this.height = height; this.creation = creation; this.stateData = stateData; this.stateHash = stateHash; this.fees = fees; + this.isInitial = isInitial; } /** For recreating per-block ATStateData from repository where not all info is needed */ - public ATStateData(String ATAddress, int height, byte[] stateHash, BigDecimal fees) { - this(ATAddress, height, null, null, stateHash, fees); + public ATStateData(String ATAddress, int height, byte[] stateHash, BigDecimal fees, boolean isInitial) { + this(ATAddress, height, null, null, stateHash, fees, isInitial); } /** For creating ATStateData from serialized bytes when we don't have all the info */ public ATStateData(String ATAddress, byte[] stateHash) { - this(ATAddress, null, null, null, stateHash, null); + // This won't ever be initial AT state from deployment as that's never serialized over the network, + // but generated when the DeployAtTransaction is processed locally. + this(ATAddress, null, null, null, stateHash, null, false); } /** For creating ATStateData from serialized bytes when we don't have all the info */ public ATStateData(String ATAddress, byte[] stateHash, BigDecimal fees) { - this(ATAddress, null, null, null, stateHash, fees); + // This won't ever be initial AT state from deployment as that's never serialized over the network, + // but generated when the DeployAtTransaction is processed locally. + this(ATAddress, null, null, null, stateHash, fees, false); } // Getters / setters @@ -70,4 +76,8 @@ public class ATStateData { return this.fees; } + public boolean isInitial() { + return this.isInitial; + } + } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java index 59955bb3..dd85a241 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java @@ -1,11 +1,12 @@ package org.qortal.repository.hsqldb; +import static org.qortal.repository.hsqldb.HSQLDBRepository.getZonedTimestampMilli; +import static org.qortal.repository.hsqldb.HSQLDBRepository.toOffsetDateTime; + import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Timestamp; import java.util.ArrayList; -import java.util.Calendar; import java.util.List; import org.qortal.data.at.ATData; @@ -25,14 +26,18 @@ public class HSQLDBATRepository implements ATRepository { @Override public ATData fromATAddress(String atAddress) throws DataException { - final String sql = "SELECT creator, creation, version, asset_id, code_bytes, is_sleeping, sleep_until_height, is_finished, had_fatal_error, is_frozen, frozen_balance FROM ATs WHERE AT_address = ?"; + String sql = "SELECT creator, creation, version, asset_id, code_bytes, " + + "is_sleeping, sleep_until_height, is_finished, had_fatal_error, " + + "is_frozen, frozen_balance " + + "FROM ATs " + + "WHERE AT_address = ? LIMIT 1"; try (ResultSet resultSet = this.repository.checkedExecute(sql, atAddress)) { if (resultSet == null) return null; byte[] creatorPublicKey = resultSet.getBytes(1); - long creation = resultSet.getTimestamp(2, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + long creation = getZonedTimestampMilli(resultSet, 2); int version = resultSet.getInt(3); long assetId = resultSet.getLong(4); byte[] codeBytes = resultSet.getBytes(5); // Actually BLOB @@ -66,9 +71,14 @@ public class HSQLDBATRepository implements ATRepository { @Override public List getAllExecutableATs() throws DataException { - final String sql = "SELECT AT_address, creator, creation, version, asset_id, code_bytes, is_sleeping, sleep_until_height, had_fatal_error, is_frozen, frozen_balance FROM ATs WHERE is_finished = false ORDER BY creation ASC"; + String sql = "SELECT AT_address, creator, creation, version, asset_id, code_bytes, " + + "is_sleeping, sleep_until_height, had_fatal_error, " + + "is_frozen, frozen_balance " + + "FROM ATs " + + "WHERE is_finished = false " + + "ORDER BY creation ASC"; - List executableATs = new ArrayList(); + List executableATs = new ArrayList<>(); try (ResultSet resultSet = this.repository.checkedExecute(sql)) { if (resultSet == null) @@ -79,7 +89,7 @@ public class HSQLDBATRepository implements ATRepository { do { String atAddress = resultSet.getString(1); byte[] creatorPublicKey = resultSet.getBytes(2); - long creation = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + long creation = getZonedTimestampMilli(resultSet, 3); int version = resultSet.getInt(4); long assetId = resultSet.getLong(5); byte[] codeBytes = resultSet.getBytes(6); // Actually BLOB @@ -108,7 +118,12 @@ public class HSQLDBATRepository implements ATRepository { @Override public Integer getATCreationBlockHeight(String atAddress) throws DataException { - final String sql = "SELECT height from DeployATTransactions JOIN BlockTransactions ON transaction_signature = signature JOIN Blocks ON Blocks.signature = block_signature WHERE AT_address = ?"; + String sql = "SELECT height " + + "FROM DeployATTransactions " + + "JOIN BlockTransactions ON transaction_signature = signature " + + "JOIN Blocks ON Blocks.signature = block_signature " + + "WHERE AT_address = ? " + + "LIMIT 1"; try (ResultSet resultSet = this.repository.checkedExecute(sql, atAddress)) { if (resultSet == null) @@ -124,7 +139,7 @@ public class HSQLDBATRepository implements ATRepository { public void save(ATData atData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("ATs"); - saveHelper.bind("AT_address", atData.getATAddress()).bind("creator", atData.getCreatorPublicKey()).bind("creation", new Timestamp(atData.getCreation())) + saveHelper.bind("AT_address", atData.getATAddress()).bind("creator", atData.getCreatorPublicKey()).bind("creation", toOffsetDateTime(atData.getCreation())) .bind("version", atData.getVersion()).bind("asset_id", atData.getAssetId()).bind("code_bytes", atData.getCodeBytes()) .bind("is_sleeping", atData.getIsSleeping()).bind("sleep_until_height", atData.getSleepUntilHeight()) .bind("is_finished", atData.getIsFinished()).bind("had_fatal_error", atData.getHadFatalError()).bind("is_frozen", atData.getIsFrozen()) @@ -151,17 +166,22 @@ public class HSQLDBATRepository implements ATRepository { @Override public ATStateData getATStateAtHeight(String atAddress, int height) throws DataException { - try (ResultSet resultSet = this.repository - .checkedExecute("SELECT creation, state_data, state_hash, fees FROM ATStates WHERE AT_address = ? AND height = ?", atAddress, height)) { + String sql = "SELECT creation, state_data, state_hash, fees, is_initial " + + "FROM ATStates " + + "WHERE AT_address = ? AND height = ? " + + "LIMIT 1"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql, atAddress, height)) { if (resultSet == null) return null; - long creation = resultSet.getTimestamp(1, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + long creation = getZonedTimestampMilli(resultSet, 1); byte[] stateData = resultSet.getBytes(2); // Actually BLOB byte[] stateHash = resultSet.getBytes(3); BigDecimal fees = resultSet.getBigDecimal(4); + boolean isInitial = resultSet.getBoolean(5); - return new ATStateData(atAddress, height, creation, stateData, stateHash, fees); + return new ATStateData(atAddress, height, creation, stateData, stateHash, fees, isInitial); } catch (SQLException e) { throw new DataException("Unable to fetch AT state from repository", e); } @@ -169,18 +189,24 @@ public class HSQLDBATRepository implements ATRepository { @Override public ATStateData getLatestATState(String atAddress) throws DataException { - try (ResultSet resultSet = this.repository - .checkedExecute("SELECT height, creation, state_data, state_hash, fees FROM ATStates WHERE AT_address = ? ORDER BY height DESC", atAddress)) { + String sql = "SELECT height, creation, state_data, state_hash, fees, is_initial " + + "FROM ATStates " + + "WHERE AT_address = ? " + + "ORDER BY height DESC " + + "LIMIT 1"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql, atAddress)) { if (resultSet == null) return null; int height = resultSet.getInt(1); - long creation = resultSet.getTimestamp(2, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); + long creation = getZonedTimestampMilli(resultSet, 2); byte[] stateData = resultSet.getBytes(3); // Actually BLOB byte[] stateHash = resultSet.getBytes(4); BigDecimal fees = resultSet.getBigDecimal(5); + boolean isInitial = resultSet.getBoolean(6); - return new ATStateData(atAddress, height, creation, stateData, stateHash, fees); + return new ATStateData(atAddress, height, creation, stateData, stateHash, fees, isInitial); } catch (SQLException e) { throw new DataException("Unable to fetch latest AT state from repository", e); } @@ -188,10 +214,14 @@ public class HSQLDBATRepository implements ATRepository { @Override public List getBlockATStatesAtHeight(int height) throws DataException { + String sql = "SELECT AT_address, state_hash, fees, is_initial " + + "FROM ATStates " + + "WHERE height = ? " + + "ORDER BY creation ASC"; + List atStates = new ArrayList<>(); - try (ResultSet resultSet = this.repository.checkedExecute("SELECT AT_address, state_hash, fees FROM ATStates WHERE height = ? ORDER BY creation ASC", - height)) { + try (ResultSet resultSet = this.repository.checkedExecute(sql, height)) { if (resultSet == null) return atStates; // No atStates in this block @@ -200,8 +230,9 @@ public class HSQLDBATRepository implements ATRepository { String atAddress = resultSet.getString(1); byte[] stateHash = resultSet.getBytes(2); BigDecimal fees = resultSet.getBigDecimal(3); + boolean isInitial = resultSet.getBoolean(4); - ATStateData atStateData = new ATStateData(atAddress, height, stateHash, fees); + ATStateData atStateData = new ATStateData(atAddress, height, stateHash, fees, isInitial); atStates.add(atStateData); } while (resultSet.next()); } catch (SQLException e) { @@ -220,8 +251,9 @@ public class HSQLDBATRepository implements ATRepository { HSQLDBSaver saveHelper = new HSQLDBSaver("ATStates"); saveHelper.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight()) - .bind("creation", new Timestamp(atStateData.getCreation())).bind("state_data", atStateData.getStateData()) - .bind("state_hash", atStateData.getStateHash()).bind("fees", atStateData.getFees()); + .bind("creation", toOffsetDateTime(atStateData.getCreation())).bind("state_data", atStateData.getStateData()) + .bind("state_hash", atStateData.getStateHash()).bind("fees", atStateData.getFees()) + .bind("is_initial", atStateData.isInitial()); try { saveHelper.execute(this.repository); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 4f88842b..562f5030 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -975,6 +975,11 @@ public class HSQLDBDatabaseUpdates { stmt.execute("CHECKPOINT DEFRAG"); break; + case 71: + // Add flag to AT state data to indicate 'initial deployment state' + stmt.execute("ALTER TABLE ATStates ADD COLUMN is_initial BOOLEAN NOT NULL DEFAULT TRUE"); + break; + default: // nothing to do return false; diff --git a/src/main/java/org/qortal/transaction/AtTransaction.java b/src/main/java/org/qortal/transaction/AtTransaction.java index 15ae12e3..f55dfaf8 100644 --- a/src/main/java/org/qortal/transaction/AtTransaction.java +++ b/src/main/java/org/qortal/transaction/AtTransaction.java @@ -178,6 +178,8 @@ public class AtTransaction extends Transaction { @Override public void processReferencesAndFees() throws DataException { + getATAccount().setLastReference(this.atTransactionData.getSignature()); + if (this.atTransactionData.getAmount() != null) { Account recipient = getRecipient(); long assetId = this.atTransactionData.getAssetId(); @@ -211,6 +213,8 @@ public class AtTransaction extends Transaction { @Override public void orphanReferencesAndFees() throws DataException { + getATAccount().setLastReference(this.atTransactionData.getReference()); + if (this.atTransactionData.getAmount() != null) { Account recipient = getRecipient(); diff --git a/src/main/java/org/qortal/transaction/DeployAtTransaction.java b/src/main/java/org/qortal/transaction/DeployAtTransaction.java index 8995b626..75d51dc0 100644 --- a/src/main/java/org/qortal/transaction/DeployAtTransaction.java +++ b/src/main/java/org/qortal/transaction/DeployAtTransaction.java @@ -13,6 +13,7 @@ import org.qortal.account.Account; import org.qortal.asset.Asset; import org.qortal.at.AT; import org.qortal.at.QortalATAPI; +import org.qortal.at.QortalAtLoggerFactory; import org.qortal.block.BlockChain; import org.qortal.crypto.Crypto; import org.qortal.data.asset.AssetData; @@ -212,9 +213,10 @@ public class DeployAtTransaction extends Transaction { long blockTimestamp = Timestamp.toLong(height, 0); QortalATAPI api = new QortalATAPI(repository, skeletonAtData, blockTimestamp); + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); try { - new MachineState(api, deployATTransactionData.getCreationBytes()); + new MachineState(api, loggerFactory, deployATTransactionData.getCreationBytes()); } catch (IllegalArgumentException e) { // Not valid return ValidationResult.INVALID_CREATION_BYTES; diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index bc8974f1..6329e486 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -1020,11 +1020,16 @@ public abstract class Transaction { // AT transactions come before non-AT transactions if (td1.getType() == TransactionType.AT && td2.getType() != TransactionType.AT) return -1; + // Non-AT transactions come after AT transactions if (td1.getType() != TransactionType.AT && td2.getType() == TransactionType.AT) return 1; - // Both transactions are either AT or non-AT so compare timestamps + // If both transactions are AT type, then preserve existing ordering. + if (td1.getType() == TransactionType.AT) + return 0; + + // Both transactions are non-AT so compare timestamps int result = Long.compare(td1.getTimestamp(), td2.getTimestamp()); if (result == 0) diff --git a/src/test/java/org/qortal/test/apps/orphan.java b/src/test/java/org/qortal/test/apps/orphan.java index 58853e55..f847f98d 100644 --- a/src/test/java/org/qortal/test/apps/orphan.java +++ b/src/test/java/org/qortal/test/apps/orphan.java @@ -13,17 +13,23 @@ import org.qortal.settings.Settings; public class orphan { public static void main(String[] args) { - if (args.length == 0) { - System.err.println("usage: orphan "); + if (args.length < 1 || args.length > 2) { + System.err.println("usage: orphan [] "); System.exit(1); } - int targetHeight = Integer.parseInt(args[0]); - Security.insertProviderAt(new BouncyCastleProvider(), 0); - // Load/check settings, which potentially sets up blockchain config, etc. - Settings.getInstance(); + int argIndex = 0; + + if (args.length > 1) { + Settings.fileInstance(args[argIndex++]); + } else { + // Load/check settings, which potentially sets up blockchain config, etc. + Settings.getInstance(); + } + + int targetHeight = Integer.parseInt(args[argIndex]); try { RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl()); diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java new file mode 100644 index 00000000..67968af6 --- /dev/null +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -0,0 +1,403 @@ +package org.qortal.test.btcacct; + +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; +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 org.ciyam.at.MachineState; +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.at.QortalAtLoggerFactory; +import org.qortal.crosschain.BTCACCT; +import org.qortal.crypto.Crypto; +import org.qortal.data.at.ATData; +import org.qortal.data.at.ATStateData; +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 com.google.common.hash.HashCode; + +public class AtTests extends Common { + + public static final byte[] secret = "This string is exactly 32 bytes!".getBytes(); + public static final byte[] secretHash = Crypto.hash160(secret); // daf59884b4d1aec8c1b17102530909ee43c0151a + public static final int refundTimeout = 10; // blocks + public static final BigDecimal initialPayout = new BigDecimal("0.001").setScale(8); + public static final BigDecimal redeemAmount = new BigDecimal("80.4020").setScale(8); + public static final BigDecimal fundingAmount = new BigDecimal("123.456").setScale(8); + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @Test + public void testCompile() { + String redeemAddress = Common.getTestAccount(null, "chloe").getAddress(); + + byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, redeemAddress, refundTimeout, initialPayout, redeemAmount); + System.out.println("CIYAM 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 recipient = Common.getTestAccount(repository, "dilbert"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + + BigDecimal expectedBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtTransaction.getTransactionData().getFee()); + BigDecimal actualBalance = deployer.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = fundingAmount; + actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); + + Common.assertEqualBigDecimals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = recipientsInitialBalance; + actualBalance = recipient.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipient's post-deployment balance incorrect", expectedBalance, actualBalance); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + expectedBalance = deployersInitialBalance; + actualBalance = deployer.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = BigDecimal.ZERO; + actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); + + Common.assertEqualBigDecimals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = recipientsInitialBalance; + actualBalance = recipient.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipient's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testInitialPayment() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + + // Initial payment should happen 1st block after deployment + BlockUtils.mintBlock(repository); + + BigDecimal expectedBalance = recipientsInitialBalance.add(initialPayout); + BigDecimal actualBalance = recipient.getConfirmedBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipient's post-initial-payout balance incorrect", expectedBalance, actualBalance); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + expectedBalance = recipientsInitialBalance; + actualBalance = recipient.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipient's pre-initial-payout balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testAutomaticRefund() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + + BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); + BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee); + + checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + BigDecimal expectedBalance = deployersPostDeploymentBalance; + BigDecimal actualBalance = deployer.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("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 recipient = Common.getTestAccount(repository, "dilbert"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + // Send correct secret to AT + MessageTransaction messageTransaction = sendSecret(repository, recipient, secret, atAddress); + + // AT should send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + BigDecimal expectedBalance = recipientsInitialBalance.add(initialPayout).subtract(messageTransaction.getTransactionData().getFee()).add(redeemAmount); + BigDecimal actualBalance = recipient.getConfirmedBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipent's post-redeem balance incorrect", expectedBalance, actualBalance); + + // Orphan redeem + BlockUtils.orphanLastBlock(repository); + + expectedBalance = recipientsInitialBalance.add(initialPayout).subtract(messageTransaction.getTransactionData().getFee()); + actualBalance = recipient.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipent'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 recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); + + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + // Send correct secret to AT, but from wrong account + MessageTransaction messageTransaction = sendSecret(repository, bystander, secret, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + BigDecimal expectedBalance = recipientsInitialBalance.add(initialPayout); + BigDecimal actualBalance = recipient.getConfirmedBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipent's balance incorrect", expectedBalance, actualBalance); + + checkAtRefund(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 recipient = Common.getTestAccount(repository, "dilbert"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); + + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + // Send correct secret to AT, but from wrong account + byte[] wrongSecret = Crypto.digest(secret); + MessageTransaction messageTransaction = sendSecret(repository, recipient, wrongSecret, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + BigDecimal expectedBalance = recipientsInitialBalance.add(initialPayout).subtract(messageTransaction.getTransactionData().getFee()); + BigDecimal actualBalance = recipient.getConfirmedBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipent's balance incorrect", expectedBalance, actualBalance); + + checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + } + } + + @SuppressWarnings("unused") + @Test + public void testDescribeDeployed() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + + List 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, BTCACCT.CODE_BYTES_HASH)) + continue; + + ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); + byte[] stateData = atStateData.getStateData(); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData); + + BTCACCT.AtConstants atConstants = BTCACCT.extractAtConstants(dataBytes); + + long autoRefundTimestamp = atData.getCreation() + atConstants.refundMinutes * 60 * 1000L; + + String autoRefundString = LocalDateTime.ofInstant(Instant.ofEpochMilli(autoRefundTimestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); + System.out.println(String.format("%s:\n" + + "\tcreator: %s,\n" + + "\tHASH160 of secret: %s,\n" + + "\trecipient: %s,\n" + + "\tinitial payout: %s QORT,\n" + + "\tredeem payout: %s QORT,\n" + + "\tauto-refund at: %s (local time)", + atAddress, + Crypto.toAddress(atData.getCreatorPublicKey()), + HashCode.fromBytes(atConstants.secretHash).toString().substring(0, 40), + atConstants.recipient, + atConstants.initialPayout.toPlainString(), + atConstants.redeemPayout.toPlainString(), + autoRefundString)); + } + } + } + + private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, Account recipient) throws DataException { + byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, recipient.getAddress(), refundTimeout, initialPayout, redeemAmount); + + 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); + } + + BigDecimal fee = BigDecimal.ZERO; + String name = "QORT-BTC cross-chain trade"; + String description = String.format("Qortal-Bitcoin cross-chain trade between %s and %s", deployer.getAddress(), recipient.getAddress()); + 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 sendSecret(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); + } + + BigDecimal fee = BigDecimal.ZERO; + BigDecimal amount = BigDecimal.ZERO; + + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); + TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, 4, recipient, Asset.QORT, amount, data, false, false); + + MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); + + fee = messageTransaction.calcRecommendedFee(); + messageTransactionData.setFee(fee); + + TransactionUtils.signAndMint(repository, messageTransactionData, sender); + + return messageTransaction; + } + + private void checkAtRefund(Repository repository, Account deployer, BigDecimal deployersInitialBalance, BigDecimal deployAtFee) throws DataException { + BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee); + + // 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 + BigDecimal expectedMinimumBalance = deployersPostDeploymentBalance; + BigDecimal expectedMaximumBalance = deployersInitialBalance.subtract(deployAtFee).subtract(initialPayout); + + BigDecimal actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance.toPlainString(), expectedMinimumBalance.toPlainString()), actualBalance.compareTo(expectedMinimumBalance) > 0); + assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance.toPlainString(), expectedMaximumBalance.toPlainString()), actualBalance.compareTo(expectedMaximumBalance) < 0); + } + +} diff --git a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java index 23c7ab7c..f03fb8b5 100644 --- a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java @@ -34,7 +34,7 @@ public class BuildP2SH { + "mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" + "\t0.00008642 \\\n" + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n" - + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" + "\t1585920000")); System.exit(1); } diff --git a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java index befa1963..e61a031e 100644 --- a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java @@ -37,7 +37,7 @@ public class CheckP2SH { + "mrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" + "\t0.00008642 \\\n" + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n" - + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" + "\t1585920000")); System.exit(1); } diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index 98e2daa7..488de43f 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -32,34 +32,37 @@ import com.google.common.hash.HashCode; public class DeployAT { + public static final BigDecimal atFundingExtra = new BigDecimal("0.2").setScale(8); + private static void usage(String error) { if (error != null) System.err.println(error); - System.err.println(String.format("usage: DeployAT ()")); + System.err.println(String.format("usage: DeployAT [ []]")); System.err.println(String.format("example: DeployAT " + "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n" + "\t3.1415 \\\n" + "\tQgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v \\\n" - + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" + "\t1585920000 \\\n" + "\t0.0001")); System.exit(1); } public static void main(String[] args) { - if (args.length < 5 || args.length > 6) + if (args.length < 5 || args.length > 7) usage(null); Security.insertProviderAt(new BouncyCastleProvider(), 0); Settings.fileInstance("settings-test.json"); byte[] refundPrivateKey = null; - BigDecimal qortAmount = null; + BigDecimal redeemAmount = null; String redeemAddress = null; byte[] secretHash = null; int lockTime = 0; BigDecimal initialPayout = BigDecimal.ZERO.setScale(8); + BigDecimal fundingAmount = null; int argIndex = 0; try { @@ -67,8 +70,8 @@ public class DeployAT { if (refundPrivateKey.length != 32) usage("Refund private key must be 32 bytes"); - qortAmount = new BigDecimal(args[argIndex++]); - if (qortAmount.signum() <= 0) + redeemAmount = new BigDecimal(args[argIndex++]).setScale(8); + if (redeemAmount.signum() <= 0) usage("QORT amount must be positive"); redeemAddress = args[argIndex++]; @@ -83,6 +86,13 @@ public class DeployAT { if (args.length > argIndex) initialPayout = new BigDecimal(args[argIndex++]).setScale(8); + + if (args.length > argIndex) { + fundingAmount = new BigDecimal(args[argIndex++]).setScale(8); + + if (fundingAmount.compareTo(redeemAmount) <= 0) + usage("AT funding amount must be greater than QORT redeem amount"); + } } catch (IllegalArgumentException e) { usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage())); } @@ -100,7 +110,11 @@ public class DeployAT { PrivateKeyAccount refundAccount = new PrivateKeyAccount(repository, refundPrivateKey); System.out.println(String.format("Refund Qortal address: %s", refundAccount.getAddress())); - System.out.println(String.format("QORT amount (INCLUDING FEES): %s", qortAmount.toPlainString())); + System.out.println(String.format("QORT redeem amount: %s", redeemAmount.toPlainString())); + + if (fundingAmount == null) + fundingAmount = redeemAmount.add(atFundingExtra); + System.out.println(String.format("AT funding amount: %s", fundingAmount.toPlainString())); System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); @@ -117,7 +131,7 @@ public class DeployAT { final int BLOCK_TIME = 60; // seconds final int refundTimeout = (lockTime - (int) (System.currentTimeMillis() / 1000L)) / BLOCK_TIME; - byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, redeemAddress, refundTimeout, initialPayout); + byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, redeemAddress, refundTimeout, initialPayout, fundingAmount); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); long txTimestamp = System.currentTimeMillis(); @@ -135,7 +149,7 @@ public class DeployAT { 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, qortAmount, Asset.QORT); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, redeemAmount, Asset.QORT); Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); diff --git a/src/test/java/org/qortal/test/btcacct/Redeem.java b/src/test/java/org/qortal/test/btcacct/Redeem.java index a4907813..ad403cd2 100644 --- a/src/test/java/org/qortal/test/btcacct/Redeem.java +++ b/src/test/java/org/qortal/test/btcacct/Redeem.java @@ -44,7 +44,7 @@ public class Redeem { + "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n" + "\tmrTDPdM15cFWJC4g223BXX5snicfVJBx6M \\\n" + "\tec199a4abc9d3bf024349e397535dfee9d287e174aeabae94237eb03a0118c03 \\\n" - + "\t736563726574 \\\n" + + "\t5468697320737472696e672069732065786163746c7920333220627974657321 \\\n" + "\t1585920000")); System.exit(1); } diff --git a/src/test/java/org/qortal/test/btcacct/Refund.java b/src/test/java/org/qortal/test/btcacct/Refund.java index f22a436a..14752d8d 100644 --- a/src/test/java/org/qortal/test/btcacct/Refund.java +++ b/src/test/java/org/qortal/test/btcacct/Refund.java @@ -44,7 +44,7 @@ public class Refund { + "2NEZboTLhBDPPQciR7sExBhy3TsDi7wV3Cv \\\n" + "\tef027fb5828c5e201eaf6de4cd3b0b340d16a191ef848cd691f35ef8f727358c9c01b576fb7e \\\n" + "\tn2N5VKrzq39nmuefZwp3wBiF4icdXX2B6o \\\n" - + "\td1b64100879ad93ceaa3c15929b6fe8550f54967 \\\n" + + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" + "\t1585920000")); System.exit(1); } diff --git a/src/test/java/org/qortal/test/common/BlockUtils.java b/src/test/java/org/qortal/test/common/BlockUtils.java index 2bc02931..40f31083 100644 --- a/src/test/java/org/qortal/test/common/BlockUtils.java +++ b/src/test/java/org/qortal/test/common/BlockUtils.java @@ -17,9 +17,9 @@ public class BlockUtils { private static final Logger LOGGER = LogManager.getLogger(BlockUtils.class); /** Mints a new block using "alice-reward-share" test account. */ - public static void mintBlock(Repository repository) throws DataException { + public static Block mintBlock(Repository repository) throws DataException { PrivateKeyAccount mintingAccount = Common.getTestAccount(repository, "alice-reward-share"); - BlockMinter.mintTestingBlock(repository, mintingAccount); + return BlockMinter.mintTestingBlock(repository, mintingAccount); } public static BigDecimal getNextBlockReward(Repository repository) throws DataException { diff --git a/src/test/resources/test-chain-old-asset.json b/src/test/resources/test-chain-old-asset.json index 08bb6a6a..6c9598f2 100644 --- a/src/test/resources/test-chain-old-asset.json +++ b/src/test/resources/test-chain-old-asset.json @@ -29,6 +29,12 @@ "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, "featureTriggers": { "messageHeight": 0, "atHeight": 0, diff --git a/src/test/resources/test-chain-v1.json b/src/test/resources/test-chain-v1.json index f3685db1..71d5bcff 100644 --- a/src/test/resources/test-chain-v1.json +++ b/src/test/resources/test-chain-v1.json @@ -29,6 +29,12 @@ "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, "featureTriggers": { "messageHeight": 0, "atHeight": 0, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index 2c315c79..5e71b4d9 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -29,6 +29,12 @@ "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, "featureTriggers": { "messageHeight": 0, "atHeight": 0, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index 625c225a..9bcc4a95 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -29,6 +29,12 @@ "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, "featureTriggers": { "messageHeight": 0, "atHeight": 0, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 088f7d44..81c6760a 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -29,6 +29,12 @@ "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, "featureTriggers": { "messageHeight": 0, "atHeight": 0, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 1ea8bb57..09dbcab1 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -29,6 +29,12 @@ "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, "featureTriggers": { "messageHeight": 0, "atHeight": 0, diff --git a/src/test/resources/test-settings-old-asset.json b/src/test/resources/test-settings-old-asset.json index 587a880c..694bcd11 100644 --- a/src/test/resources/test-settings-old-asset.json +++ b/src/test/resources/test-settings-old-asset.json @@ -2,5 +2,6 @@ "restrictedApi": false, "blockchainConfig": "src/test/resources/test-chain-old-asset.json", "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, "minPeers": 0 } diff --git a/src/test/resources/test-settings-v1.json b/src/test/resources/test-settings-v1.json index 675d0c51..069ab82f 100644 --- a/src/test/resources/test-settings-v1.json +++ b/src/test/resources/test-settings-v1.json @@ -2,5 +2,6 @@ "restrictedApi": false, "blockchainConfig": "src/test/resources/test-chain-v1.json", "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, "minPeers": 0 } diff --git a/src/test/resources/test-settings-v2-founder-rewards.json b/src/test/resources/test-settings-v2-founder-rewards.json index 1751cf7d..c89df187 100644 --- a/src/test/resources/test-settings-v2-founder-rewards.json +++ b/src/test/resources/test-settings-v2-founder-rewards.json @@ -2,5 +2,6 @@ "restrictedApi": false, "blockchainConfig": "src/test/resources/test-chain-v2-founder-rewards.json", "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, "minPeers": 0 } diff --git a/src/test/resources/test-settings-v2-minting.json b/src/test/resources/test-settings-v2-minting.json index eba9a655..9c72c375 100644 --- a/src/test/resources/test-settings-v2-minting.json +++ b/src/test/resources/test-settings-v2-minting.json @@ -2,5 +2,6 @@ "restrictedApi": false, "blockchainConfig": "src/test/resources/test-chain-v2-minting.json", "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, "minPeers": 0 } diff --git a/src/test/resources/test-settings-v2-qora-holder.json b/src/test/resources/test-settings-v2-qora-holder.json index 0248c867..83b23287 100644 --- a/src/test/resources/test-settings-v2-qora-holder.json +++ b/src/test/resources/test-settings-v2-qora-holder.json @@ -2,5 +2,6 @@ "restrictedApi": false, "blockchainConfig": "src/test/resources/test-chain-v2-qora-holder.json", "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, "minPeers": 0 } diff --git a/src/test/resources/test-settings-v2.json b/src/test/resources/test-settings-v2.json index 31fc2672..57eb22a5 100644 --- a/src/test/resources/test-settings-v2.json +++ b/src/test/resources/test-settings-v2.json @@ -2,5 +2,6 @@ "restrictedApi": false, "blockchainConfig": "src/test/resources/test-chain-v2.json", "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, "minPeers": 0 } From b93dca18181c1d89a2402ac9be689c69a32f603a Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 14 Apr 2020 17:28:51 +0100 Subject: [PATCH 12/15] Include CIYAM AT v.1.3.4 jar --- lib/org/ciyam/AT/1.3.4/AT-1.3.4.jar | Bin 0 -> 146000 bytes lib/org/ciyam/AT/1.3.4/AT-1.3.4.pom | 9 +++++++++ lib/org/ciyam/AT/maven-metadata-local.xml | 12 ++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 lib/org/ciyam/AT/1.3.4/AT-1.3.4.jar create mode 100644 lib/org/ciyam/AT/1.3.4/AT-1.3.4.pom create mode 100644 lib/org/ciyam/AT/maven-metadata-local.xml diff --git a/lib/org/ciyam/AT/1.3.4/AT-1.3.4.jar b/lib/org/ciyam/AT/1.3.4/AT-1.3.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..5abe2c77a5b209f52f7ce11f7cdc97e29f871890 GIT binary patch literal 146000 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%0D65IQm6{k!0kIwC(@tGUY!KdUB zK@K-2lc5g;Kl=}^Ijh+ME?p5Lb)(LOl=gg+z)3OY7V(DL8V$mY$>h0W&?3sf`Mtha zdY3Ku5>ecQUeWEsIz&%wt5l#RZ7&;&V`)2VUsVB<>p7yVeo5&b zd{ptzAt*mD;2}oSwew+r?G6=}00uzxn{Z&3zK6_FPCM1CNq|6(He7@g(NvsG0K)*} zIcizL^Y1B*#Vih1O@}t`PsSf4d(N9Yv$5Q^pJ?>HMKT@`6$N5W@(zl*x=4eBI!!s# zNe{9Td|5#~-F~6FvAu+P)e&umX99#Lh$$I80+0`SVi8Mj_2a5@kXb*?j#@qVg$FQ# z?n54n#45It2ZYocfK(?^<1uXrsY^D$Qq7zTH_)5Eky~I8b3mtuxLk{WF*n0Zast7i za`upU>+w!R^b^dD+;Bss6bn|RZRoXA%)m>U(ry5uLj4$6F3WCk0?c88iBrJzJu;iP zCKtE>D%~;@lrwbWlsUk6_|xWH;2~4n3g{-88ba4+8m>(`KnX@$I6uyG!a?oo?BGwD z=80bDi|gY?Xltv_2n?Dbyyp0c%oyu}*Z|IXYKAhnvqWSR`01W{gdbXOe5W$=y+DZuCtf= zGjwe1yN0sQeG0aSk|)g_GEM=Gj5-#N7Dy!G`I(TLQH&`-g3X1Nv~)Hpk;HA`X@}v4 zAN4VFnHL_L6h6~bBGB|}o)GIF7?c#-Wjsi5@3q7l9W>YpN@&EX$NfK z@j$7DBf~LLQSO;?CUxYhw0L5F0==S69dGJL4iG;}!oHa!bcm{`6C`5!A&MQ_E5}eM zJKnHQ=BG8!>LYA)lpq(m&nFPboCgJ#SHx7o^nEt(jYnYLt;Ldkpw1UKneumo1 z#5`gZ_GMM(O4XWHM+B=;7E@Dch9|4Y6@QCTfXnNEX;1SPlqg0h%ZAYWtv*q8#FtP6 z{^|&mfS~rHZ;6sPtF`TpR1R-VAnw~p;o8J!MnHk)S8XNg#+_gUJvh9vflSy#E)6)5 zWUE{R+qC@|xm%`*F4-iE{NuT1`gm3p8}2U~<}VxWKTDwd9uV>^smA{y8VHp?JxukN zga4RkIDHM>)PP7Qa8*OfDvEI)-n4*XW=P-@_PQ2#EVR9;$%^oKQZHpV29Oee&_J|# z!3c6sbq>_RYP?fsD5z*oq|xAhNio%Q8`!YXZK?@&3shNN z%>WZc;s9^}<(k=u!rF|wWK>7qXnuSdLANdicbcQeN#+EqV-lu8GQ=qruu+SAPONPw z+2TIQwiDsBoO(tv6NR_1EwlRdT9^;?*;cGnHrY+9V}iGRSVKol+ubz9_dta+u5Ov3 zE4VktV*#!6Or64m!YjQPT=cSb?F*tevUUCIIJRmD%-sPwC4Iy}oHsS4HxYI)0$fQX ze}bt(n})SHO#$5^Y?f}R1v?}eK@BB%xkUcd1unIQ;CvjRELI@UoXLZ$rtOK2NjQ%k z(o)(mn~JohRVa@c@>1F`uz#{ zaE~$Cx}$)S==d7pEG%G`iQ^wKw{ek|C$NhUfgWb*c|}c3(rpx0 zAUj_godhScXNMdjbr{B@oO^@uSJ!ZOv>7*=j|nn1edN$1<;HozgAh=(&~Az;XQ}j@ zF{`Nu&%dfH!Gi{HR&*a^@wJ{uaMoqvT^v{^ zYCy8&zJW(^HoX2tG2xR6uvTbadGWQKM>01%O}AEf4^7@}n`vjR@gIA5g09HkG+CE1 zX77UIlNM*B503w?(7yHJYc-GNY~!X6n!jtoU5)vtPHy4prnPTaA70^Ij`^qf>@pPM z=>06ht0eLcYc$*wSNG7$w5V!$yI{P0I?;4#Oc7Fae zepTRY`KP#yDzrn0lZ~~3xeWsf{k1iNizO$> zKh4<^e9<_-cO<_f-O(=&YoEhLG{ApvNFe zK@BPbFAm;8`?EsNB29r0MglVq-a+D2+>2W7GWl*j*;!u}EJ))Q%PT)eLGxK~XeMPo2=o2k?rTCej z6mBaPc@Jbx>GM~6h-;yHDo)2_u_#u(hcYs`6SX0^rr9dcl5}&QBlTZrjmc>nY=fk> zY{R8?ZNsLvud!^$XnpIpal(-f?X-O^33J;SNR9X9aRMfX#YHq__-V><|X zJ6L*pfX+NDz`fGNHB`Fust#04J?89y)O>y?9mvPH+%)~*m?;sD(&$`>PqDpFz+JT0 zoIY2O9_l-4|5_+7889 z5FJ9h+CE3%8w56dyVO1#|3Cgd{yhGgz!`w6P+F9A{CgJu%7CfsW3csuR|sQ9mwg>5 zder#!DlIwI@Ws(npa0P_y!dvYIriHve)ij8$MBy7fwaD%nYoRzqLaRpF|mrVgQK~v zjj@rSt&O9TzKxUP|6>l46{Kw!`H-`ux7zHbH=lsSc=MWqTU4+DgNFVD8MXf*l+xU1 z3e_oyZj@+swjSS63PAD=7gQ<-^8w(6ykAiwl(X*(WqmYxnZ`2z`WpSm3P4+D+&>gZ zLE%<5nL!ilq|REeZ;+_J!MkPa65G}K+ltO%&QOOM{-Lvpg@)!u3*Mo>`MCQ-fEPL) z+j=}fnJ)G(qkqe0zI9Qb?z4#&-2^Z_EO1{~*2sD%Qal}MA#4;UM|^uXEQBMOJ=<9* zWs(WOn^)Rl7)iKbTLLlujegdV-i!ghFuqtj!`H{7A3%roAEJ0;(B=jTw+I z1^XTm7-5hSIiZ3EtZ@99wPDK8a5K?QxCU@BAZ!<)Y+o@NF+3s|u3u=u)W+Wi_^`>F zsjI1%FRt(V$4yrNSNq9;m}m;7Ch09Y`^0{?lSuoizIW*48Z@_YZ&2&Y=Q#dd*j$N_ zkv^zj2XeNAc|-b#@&+z&?R_}mqw+4Yi}4{MoWj)0<#&GkLTlZj`l2Ro@WT2-kI{oU zgz!e9Er{|P62kb*^&xJBwF^p3!@r+O5HgEy1r;9{Q3u3R-XR>(?X#{!O?E3K`%H!< ztrEnCOS0&wUHBL$0#5>Xrq{vdM-w@vA}JF9uCPN=b3-*ziJ|&gX=_#eLjo|M6wsGH z1g2_AfHAZI^^ayw_}UG&-;Ur|O+MK^U_h#%$Qa0pc-hJm9ZIH7n%T8_IM-0Gq!VmZ z9JC8mZh+aPWe*rMa@8rWi`!#L(p0Dt+!+UsBpt5*=;(ND}y@O=P%vHwP)LOGQ&INLj#kKiuu$O56e{6BX zrwS?K0svI<0RZ6rx7hk0YLWv&PieXNbW43_@`xmz3W%5wy(KPLIwg(|9}6BDBCbxT ziB4Q6A>Qckj1+v6g{5V2vxTx1NOk9ettCjXA3uLJYO_Z5O0h=jO@wuIbu*3GH~UL> zy3}707EJv8*S#Q@~1t5!!kFWN>7fyH4&R`tv26h;sHv~ zccyW?o^Ov_Ap+3B7!h}7c&wWkt~`e>c8!UdP;KpTnY50M2&`zNgPm>=g1Ywms`m-Z!D~;4vr|Ss*aC1HZ}Vv8a6eDCaq9A{g>LW z+Ml?-JAIuu^7ge6tPE~Qth=grCTL6h7B)8fi#En*EFm_kcd%$o4{Ag^If&clwOJFOLC(TiE-` za18T66u8nOp?CYx8~1LY5U=tgyd%@HZu5sURRg5RpXCvx$X*M zp=Jh6P;w8&XHETpboYX3P5h8DmAt~|X(?YLplJG48Tsf#bSD8&e5((zFyexERqv@q zdaCzqwdx|(?GSw@=WdlgxnF$c?qj>=b6*c}Kz5z`nYpM@WE(2FL-*8nt}mg9%$)uG za4T+}g9kahO|0ew^?m8`T2;Y-_QPo535+HtHYPGQoHZ{ju~e99h59%KghyFlJ3gGS z$bC@-91b%sMlv?2rJYTe86XiBQfqC?Y-%j6&{^=|wXi5ISK;)P2#Xs^Oth^P6`@T< z?P_bbnuKYX!610q#9k*Vs4p#4mzbH6wCCS+f~H`aFEh@XHZV1o=a#hA=eC04RD5e} zJ8e~ThjzIwn`>LK3ChQJG46!l1vmyW_6sAUXKi%z990xm>5$25;X*h}o8MgDJxtyR znTo3?@#ZoKd%}3Ei907K%{+}e{YiD^?=8gOF7vNFIJfENRuyrqR##s+(zid=4B0#IO%rcR%~GGyBj#7 zXg6DnB{2SUR^k^uhXiC3;LS^ko)IK?(#;`5Jgc;{E*6`OF6rg5AVY>2giWzMFVAPl za0`93eeRdoXy5(=;|QN?6!X*n*22Rtsa^WY))w+boX)TIOqZ?T?czf2;v9#%KBJlZmdOlsDZI6 z(H7OxBPRwYRM8P=y;;CiED}GzxacAH?&4cVi#^CTfY6;vlQSq3G!Q?cgKB3CR&RMW z!J=E68-qUcJ;CfOrge>Yo&5fjlYG7%9W zd-c}+IXQZMNn5Mb(~L{2sPW&!DYKUgod3o$fF+9&9u{pd&9QA7ZtlR7xO-z-<-O#E zI<#1}8o1v;Qd+WcaoJs^dWMyF0?G(4W%Eg#?Q#2ad{N;{7RsfGJ_StHATO^N{!Fh? zUa%(|n=ZNE{Y!;=!NVA=(f5Eq5g+K9`W!M>_#Anrw;hC1g>_}ao)8=&y~}Qo$&Y!mdB9J%}_FLy^4m z#vu6e^3Bs0Y0_J>Eh}PNo9F+C(-sX_9*h>Av^7w+wa`%R%O`hCqZWn{GkgfS>dT=$ zrHIc97mI8!JBw&a^NcXS#u4#gd}I0}Yr=Po&FH-!qPIuIja-HOEL>WE`D5zXkIoF8XvYN%V(8XF|Nda2(p2YyLkuKU5;9vZO7*-Zy@@(1>f zUn=}9mh9J4q*;iBbV+Peg4C)&xbq)QJiNRT<56-6iH>p6pwT!9RzhMF!Nb@x-Aa8n zlIS;!6aT#HnYFi+n?qgdcAhzf_{ge*HXgyWMteXI0&Q|p+^rW8aN?7huBsn+EFsq* zE8W~wAFQ_SAVtwjLiY2k%h(`_S8kd4ODH@0O+39Bm2gLg+Ydj(GZwB;gC=MWS#LIf zIjB(-5vtO^x6aMzDKZf*Z58dU73&ctRmD2mr4rhg$NKYoz})dn1br9}wg!#UGK`fy z=~vEd#)<@dsqn|q?QM_J+D?Y0_1~;CE|DLNJPNc3S;wn~>ScV5o+sOMxUb?p5X?`^ zPoChNAPg*jZ^+M3EYl~NPgQ^Ho{mgs^wIq0B3g`_rn7CZYYQl^-;d%Q5)AL~EHYQ| z{^=`-z`bvxzl9xoOc|TlDb(EFs9i1S%e}u2k@g)ZXIB-@j*d)JXl@U$KJMhG#m=7` zRw5hT2bvu^&cva0Nw@beh{g4PQ9RoCCgyhl*h@iv+!=KcouIt|t4#ZOvToS~q=LOB zV*YT<`3uPLLv5_!V6+3Y%&+XeJffX_k6WJsyt)CY%=?Jrm|cLi6atGv5n{%|ZczF* zOSc}yu@5ei(N8U45U{{RJGi<<)svP>kUHjJ`qD-EHH2FkVrUqcUVZOOqY0DodUIGe z`ZU4;J2iT)o%CiNgX}5Tc-gV=owcCuCej{eyN=frrrC19OF2T%f%3d-fqY zd7zBKC4>!P%|W7pXy8OjuW?VUEv2jtj_BNfDQ?*4MWU96*y5wC+uT!@3DJ0zhKQxz;vvtZBE<$r zPa5vMio(ae#>I)K)N^o6M;ce_Mj4I>y`2k=q`mLJq;TejkQKNWvVF}BD7wLJ;qq4k`?!Ic|31m5%=btu{39kl{66=-7423(h9@d51^H&4i8Tz zQ662)4E)u~A{0ib7lhKuREG@^n-Si+uDv-f^;of}&dn+jAegC8BoU1{8G6hL0$y|~ z!i@_QP?WtynBTU|{$5@m(p?F6cySo$I-7TSTcL?t8?ykYzokD|4a0o8JT?1e3-n+-?_dVz9?jQVO3zOt0wbs4n{R6xric!B9g0fiBXcn~;X&L4i&1MuD4|`p}F1bd1*mQ9?ukexXkA04x zCSOeZc{Ywf$Q6U>8Q-ys8jnO3@#(y`7-_?$Rt%dcamC2$xLL@A)9l+v2-WAe_Q*jD z1r@TDs8*;E+k}(_ze9`sY=nPsu96OJm;?8+`7&+ScUDVOG0d;UIiJY%9qg2IypAu9 zM~T9dDfS;R#<>ah@qe_#rnvJ6-HgzuARsRL~dxrrweK-lwCPk%kLrX&_vscDx^d} z*2(V}cH(N^FRF2B4l#|DS69~jnw{6v_BQ2TnK?bbbUK~IlRa-(;JVDz-Ignz*X3!phh@7A75YE&3WJ z7ZuI*esJ^Mv7_DB*2rH&sr7q=BpA}`c} zjvgx{)K)ZPdVFpYmw$Rh!}&k#9G!5wc3f4}4O~owJE2}(A6F96RZ2;19R#(i_!H4< zWd4LAS%eu)lx0#e(aCjbLoHRK_s}RWf4Iic7Sx-efFU1@F$fcB^)kT~{9nd_sH}xj%d{mhNk4Xwem068St|M{k=4t^Mm2oWZ^f*&oA^lnO1$DU%=C|bEo%qvbuuyav}vFYPM{4@Kx7D zL~*dZhuBK&W4)quffuv!7s#fvFEx*_)J&TRb}{>^i{$llbJwC|9lF@^RuB2SI*={E zo-X_5qrOG@qJs|N-NlKGVmccd(H|hUN_Q830)6HwBd=5`wiP+BflYTZU-PS&FaBV~TO`;9gC z7R|-n5+}r^+?lUyL#p{gG$WDX@Z`=6{1Eis0LvJRTnmSCcU9wKPv-p#vQSEtC{Ir)UptrXP+HZz3=3U&&G)_X!|SklqaeW&2#Fe$#M@=%@1!M zn3~a7mxi~dE+B4@jUH~%DpcujYUQiMu*`oZ{; zoe=+BOIuZ&vIoXU-d5C7O!}o$y*V`<=WaL=uBfPyEvw|vP;69Tyu(cV$&%D03C`CM zNfr1id95C)+M)AC%u8y$k}$-gsA6FyBIc&i`=R#Q&& zz*6?vj)b1~<3AA6la5tTc9@e?tjcJ7Bdlumt30+cR@aPE*H=t&X49TtzcCZio(MGN zij7<6m&dv$UGBFsX6Y%7@9Bkxw?|>yV&3mV&H4o0iWaji;f6C6Vssd|v-Cc8lhX~u zbBpC9Fn3c^dev8UeuAwL)<=@hKWM33Nu)AWM5HpEb7Z(-^OJKq!RtuRULdQKW<4D- z#zP|1IrRl&FU^SNrt#=})`|7Xp1j-CwBmg4C{f~IbDxFOhHrEq2XQ4I%Uhbe0NpbGr~Zecb*{kD=}Gj*xnd%JS{>$rU_`0HI|ANIefvJ?Cy!6{ zQYp*igL(-wYVERu>jgoFnX(ufy^^(1uQ0b42+OBt349sCRigymbTe;2WU?1%L3SpN zNROO-VqUCQsV8^AUob%3sL%y$cbKBDI`-U9gWK1673$kt8;=z`Z{q4)VWM){+mH$U}nEMJCBbeoK<C%x6JVsA8k7qu`_+}fe9 z3n6lqLGf-gEp6ADm6;ETO8G2;8R!o*$k z1irDtAEWxZ0@7%T(lui{9#RallM4H!@?#5*PoP0a0nOBz@aIWlluyB<1=kZxjE`1Z z-AGFI$t3M|RE<_Zol%5|PTF|8nXyn6a}3S}hl*c~D6S85ryGylu)aO)m-odFHsAZ{ zKny-`oSPiAm^Hz~XCY%39<5M4aEsgWwQzR+x|gmG?>AtcFX7aDU*asIiNFZ_@0d1) zAIR=xS@b>|Qov>=3yPlOsly6qksu;CWZNa8Xg;|jI8>eI>Fm&G@6pw!SW@~BDUnEZ zTpA{|2)ZWKk)c7%t^78|$i}|yxFy;4+$_y0wU{o=s>3N!sv~JPG;$s+IgChY#Z=G{KGD-15P#DiB0z?kdn}K90yzP+ZAr&=;xQ@f z3y3B;AGRG%@}ZK#IlF<@xKNp)y|?>NrELb_>FFU7`#F>)L%-+VHr#vcTX5=KV#aq7 zbPM|!3ukENMD{~erAr6<9R!?MHJ|ONk;9zey$62aD$}Oy18G($6FBt}vlag^c%ux0BRr@v=Q z^Wxl%=M}nU*mqn00d*sgXvg~j;p1lM!v~*`sT~?G0=P>X$Fp;Uf!H7vYHP6;(%B%< z)?iiRFmemT;}Jl_1=k@(BD$E3kOJk1#7%anDz!zE5h3uEJ>u~r;&(U)USWkTSmF5z z166Dzc{UYs+4q?$ZtRwhEmb26;$8o>dPlaZ2v4w>cTw^9T!r8?vcTFz z2s12C=EFa!rinhq(_-qOAH`9yg6c1NB90)Yw|zmh<{tIYd_Qo(MhqAxbNdDYD6UbK(K6 zj_?;Y-UWJHyqs5}6l!KM=$Gwb`y=55O3x{=sM_l&wS4RS9$lv+p&`K7(FSZK<` z1{xBuYdEe+kihF7IwTtjp?c_HDfrgD*{%%ej-U4?C}Nn$V0aH;Ht5OC6nVC0Hz<7Ze4w4CTim z1OE0iL_;f;Q)nOK&$8Rtle-?ZuD^;~F&uMo#-MPu5vsdZ!6&;=|I&S zG~r5=Mtm9jVAnal=EgU%b5glcbv4;`HjDJ_a-r>P8O?sT{hDHP+{Ke3Z?}C|`tyWK z2gPOy%TGu_Q=iPDl`$s7qhKp|+Uo23Sam0b z2FX`77B=u3aTeX?J1P!O^M|~bVV>TzbJ34eVFj=Tc%Q8uf#M!yi6aLq^Kz87u?+EP z?+*#P#rC*{e$Nn5Q;Z6XMF}c_G?gS zMm;Q^-r;_q7~Aj803>+^quAE52#S=+{(2`Jp|X1&JJxZ|ED`-un>fs4e2Sl!v`n0d z0Gba*$|5{4Nqj-VtS%OqtU^C$x8-$s*NvYot4X&C2R&ZH^5ZPipq_uHU@6Z_mw~zS zOt7`NR$O6To0?&$nw&Et>5sibzIKZ;^D><7SXQC3EW$5M*v$&jc}8Ag=jF%r#mT8o z`foYTM}lc8=BEBH8u(M18e!gF*Ff;!r{j*eRU^pzYSJ^$1J$Gl7YijBHlPO)oRc%0 zk(Ck*69%+)#AaKi9bdT&E0fEAeoDpb!Mwy4{{d}4Ch1IK;+DEO=oWf%K4jL$6@c*b zxT$ErCGJyO^2@Nzu8?i1fg|>AWXBeu?$ds_r$O=qi8ZDWRBr1hMzrTGUI(EQtUvaJ z+<=yd_pP${#hX_sYgB|Gf!jFz)P%v3gGLugZcty)s5YT5$-QWEL@=h5YTdC)!w>9} z-TVW7M7lM*A@B{3wd!{UU`J?P8ea0Aq1kP^Y|D2FcLsk8YoV!qA^a33v?;nxfJlG7 z)v&GV_2mWl6~(tNcJP^-`jzrz8#p%*D&fqOAaeH4DGTljjkKzx-9IgwWzWU zx?$o3Q(A zpi`nysJrV6KCV(<`BXm4DI0A5=aRTaf-P?Ju3e0Im1ml$#}7#~)!M2fsv4?EXjecG z2h2OyQ%%BPeKi+vCk{-I4ETYJxo%tmw5RL2*@@O{f=KS!*JgK7gD>JSmbE-7mg!2q z!akTq_q~&0^1)R|udR-Ihc+}zYW3$5&zps1of)4jw?JFQ;$@L{pyo{ZIe>!pD)i$j zw~`G{oFU$)^yct5qcNBo8#K8VcamS6Fs92T{rs!!T4rN`SgU{~OFF+X>)_fk4g5&` zFVhsM1k90+zWc0i?9RnVs875)aGp6i%$WR$wp0E~I=Enfwq(0AKYscd(gZ zSCm>nd6?P*V;_$*W;ykyM@Fv9pL^;}u5jc6V7)`MaTC&_$_HI4-CXO~(L2;-LAuqi zBX_Cwf0C`grk-ZL{aYYX729%iJY?EI{{Iywqh?_PS*8uLvH!arZKEO&iTc72sF`y$ z2ma7n(Xpt@Q(Jw1ghnP&tPW0s#fp2XBdGxw0Tj#{<>cX!b^?NgcKSaAIBb2okkmf+ zBtQ(G-1g~uhQuLtKmIv5V0u^aog#lr7>b1-ql8pWJvg4c2LpVF7Zb2$PyJ(zmXO7` zdEwi{T}79NRY%?Ok``CX-FPZs+&InhVMDpMD{o@mzbdQ(kVvrDB51v&a8GP7ZEQ+l zKg@OyGz}2Ek7>o^?rt&i#a+rVVH?Qh%|Yic%rnd|ZJI!_d3bhaUU%g8w3`v9SQRQk`bzJFT%g-zw!m}a8PRV?B=$G05sUwL}WyD(tt%c zj&G?H84b8t_OZg)Emn;8?MC z!9ZnP#y4TSl^8XPl4UNm_YYI<^fMgSCw|f=U8l?pbh}4%(TMjG=9Ud=j^bC3p7Gw! zO@5P*xMBq&?1x2tXdz8UB5_JQ3l}o%r=}j19jIAOxSFI@<4*xx7k0CN2huhoMF)zkN0Tiv74!|Dd~A6on2n#pLJm&7;%XKKaAd+ zjBhc{up~*Xh24eYf0~YVGUTI5a1!%1xLQpkVVvTkDB%2!jEcS;Z8w4~Q{Pbf^y1*1 zZ(n{8+p~ZjQEFV>H|uz82Y@`;>RE(I~XBv=zPcyaw~csV2c$Iq!`_>&rV|#L7fNAlMu`#>){`ca>E~|(ueZ;`itnbT*^~xdFAA3-C1y2#H&_NVK-iI;eoH2B7D`7;w(4*$lXM$ z>@V3Dr*0pK?-TM5{gK3ggw!DK=UvJ(Q{M$Vl$*lCtaFAA;ltdL#C#%O6fjFJ`}zp_ zj(-^RGIprP70rWO?HV=VZP#W++Ku?Ekb)=^Lcv9o)Q8Lk8*$rJB$%4V>EtnoU;Ra9W zlPjT$t0@My&?7yLBEKKEqzwSG1eq+Cq$2!#o3m~Q&Hc_(J%4;U(NUi9Jf2T%hm++w z`6o&t#pj(W{j|f3wD`zF^IBTo@ZjG|#_5bmRhKswF$0h)qPnzI5v@ga5v{wbYBKxx z>(0@nwFNrLk*e}SRBg@gdU(w9d4s>!H4LpDx8*9!?ThTcA+G5TSs<1YDVe>u&33>; zY)NFA+@imeX0w;}=Yx453v_{DB*M34c&7_EqppSg(tiTj`h{PHG5_)?W!w4f-M`|) z%0sbR44KJ!h5V5I`)rP|vz(oonTewW$jI5w(fx0N_-^eM1z}-fonUQUVHI3q86{ww zcMIkR64WGM8IW@;2kOQCWDRsWdmm&CY#Un1=e`s;|Jfb?F`)Nn0L^Q zI(UTGuXb99hK{2xC_3Ih-aq{P2OMo2U>Z=%&r5?CGLw%yVhGp3{cjJ+r9m!`1bMh7 z$PeEC-$VZWbmCD0i2a{2f)4gH$pWIkO%E|JR9aU;VZDEzc+x^Q#3ID&tI&3P_ld&x zvqQvV#G`u`k>WeuaYs2PjG!d(ER>Xr`k!1?yz9E^?iL;$y161g`yRDHK@)?h5kHU( zAEuq<($2k)DovK7<}S7dXy?{$)h%j#NI;jqf(s0Ixv21{5`zdN5>OvXzM7JWl6@is zUHx0$Px7xBgAm9Ij|Ew;L;UX#^Y@AeC6FV?hWwvUj%ZaIMRZ{lpJoA%J?>yEWibrY zxD-UV?mR6Kc;tZu8EGZ7ozlIf6tczDeOuYhuSZnj;X_le)H^4a8hK*p30-aJ%Vs(5 zruX-`=YK{16a?GK-4Ta&24j@WQm46@8m{yM@X;G6_i?0m+ zU`3yS9&}%bId!55P<=M}1l{W~j~pS3;&WbwTyQO?3Io7gfm4{_WuBAHCqm}T&Od`3 z?{QKT@DTWqD)DLRX3Kw4=NGd5T5xhP``Q}eXWQ|?H-3P5+JTAU%yDoPz3Qi^CVMhf z;K%^dZ{bmN3LvG^qHOZ@B1Ng-$JSpFO~<`9wKQbvHkKio`IU(uD3%eE7$<*-8OSz^ zXjZ!G9CUN$o3dlcn{(KE1C{gdMNUWc3tCA?Z(}~&XtmmG7e{NRwH(=ZF zN-1fUDP3b%M*x0PVp(^>DC4)gR}Q?D5Q7heLdTgMY0PewS{bVD+D?jAilbtGTpxX3 zcFAX7y9=)m%vO~0$?l69tXaT$a)LBUX9TdA5F9v{)#O8vt9gIrO)Z8lfeyzSq~_!o z6wwd_;HN7O?NDL6K6{ii^Z!9Up<70{naL;s^ooTnbnF)58_y6K6*<5`2Mtg=#v0;U zM0ZOQ(BYFgMD3>1NohpgJdl?nTcF(UxZ#Q#Q(X922l9g(1qTXMbJU#&`Pj6<5B}AP zqy0=p%`u=a40Ed*lulnL15vN)mT(8rS3m~z8$2=Lt$A1n7AHqT3{R$`ze!dVw8f%x z_PTriRdvu!k=mOe47>hM4F8k!k*s#EiU#pupWh2i}^M+qVLCP~WwIE7*MPRW6}nbFbI{ z2LRa`b^dmig|zo6!hi7%E1%uhPn;9vyQ>zUG0TJo2J1pe1Yl z0oIeNK#aS9g)!hk|=Cz{$3igN(qH0!W{5_z@UN~@Sp zs$-NKtaoBqX8l6C=NxNf%c*zvbmQ7!x@gi}!n^kEgjG4N4lhM*P&);edI?z)n+bW) zsVML}MX)&HsaCtiV%cuc-W)Wl3%&B%;CIQcXJ@5n7)0rNK9U@am z=>kcc>9(ub*JWDCaVl*P+c79U#DK?O4)*MA~wEwz@a0VU1aq=*TGrN2e+>REJa~Z2N~}9 z!w~BETjiT$`M;TG7d@C3p2LfqNJx5h+u^vK%fI-&u{BEd7U=}DeegUM=ld*X0HaH5ZSt$0#VVB_#U zljP{~#DNe$JbrrCF(r83F?CHS(CqXDKa34oL(%$;2H!idCA99b4u#j>e#%Lo2b zZNXN4h$P_E1Bs+hi3UKBCQQYpim z+LA3J!=>QHFlbV)G_^ueH%#y)^AVh=aKiSWmIdOr(=u$)=0lmp^D$5%&uDS6J#!jX zI&R2Q)IgTu_ThPy$)NqYnym093Isr#klfOOhnB_Z!3s9nQguJ+QxT@Q(hgglwl!z{gZ-%#30Df-7?%6{;i~5sAOj>^x_yR6syd;TkTp zhKc-`Aq9pfD*!P>QMutt;2t-ohfj?&)(}vIRkq-SR2<~vCo7>U;7rx_F)x@beq5}W z#}*$gp1$CZ0&oHiQ!^D?I;|5yziX;`nP-BR1+hF--Df#N8&5?#lS8DDdDKv^V0hZK zX?Y5S{RiKU$W^{NWz!`AC3f^Taz+c=S88{C#=hF=WryFmzVogzSuJx|eoK6@ucLc% zRyGCxW)czj;+E7v++*JKk?4GNil&pu^E;(}`6$+Rc}y`t%i15}A&dR|q`U;2vuV$Oiz<;{ zsFA$D$S1E~%oiJ(FYn*X%;Wu}+agNg@AflKN3g99B0-4X*8ku@!V9Uqg4C$aL6Z}e z#t|Ly!hkuP;$_T?PmiMHqyp(9cK?b!B0+z_U)a0-PuTwx(2)#@4NC?o-sje1f^K|Z&73}!*wHGMs@@U6T%X--Z)_gPuq3|sVbmgTqo4W}uA z10JTS57+FoB5$z8q)dk0Qe; z;y}4K%*;E3Si}%RNGdxgZFH9aR^i(uLfaPEcumgQIvL7iSP>=avK1=`c13M+sTuAK zq<5`@co3nlhuliYEi}WSu?$(V;rJMI8$xtg6pksMeEhsNn{}N&OLdG3lhumv#Uqb@RtOW_S` z;4H!C}Nk zXae&&O~XLTiMVGQk1m8^suiJkH*Fg76@7b~OjqE+d>m%6Hb9n8(6!r^Lm14Zc&W?7 zAq>=YS@?;vLs!ta&lB5-ar8IA8c=E@F=nkFPWLL3FZqHx6L=-qJr zEB1Z;3_&gb8-w#t0L1^n9!csyu{TV&1C{(c_IulhJ>^V$?5cAIK5BDqktDu`JKjsrbf~Saq0YnY&$1Tzp<5W6ekYh#MqQsFE+38sx7Lmu|@NiRrP{0ly zHn}b4poSERtfjk$P9I!_$-DjFz*t zRqY{|?SYM%a;$R0Or<|48C4bTx%W-Md)1OQEmp4j`&Q-s-ouROFu)FwbaIwSnP0%V zz4PU)62Ih1Scaxn(R564rfW9ZDWY~=q}q1Q_p!wfxDq(1$Q7W!RfRip~4 zpm}`+@GX*R%2jGG`VgBnbCk1r)7UK{hq$mK4LkoQBXnIrT}5&!-4~Y;=GR3*1DxZF zWgb)yQG{8Qkg(GWH#+3Rc85IrU8Dr33N2@`Y=G0-oP=1{{*M$U7QirHkU*(qQzK|J zu%vaPH6q-3Op(ZH)U{mi?v+qT&o-Qpp|S1Vt*=rnHX#L_6w^u-Sds}PO*&@M`~9<+ z)u#gXr%`CF0c70JQ3=7;a1v~?&ndb=Vm}OmCNXB|GP>B|5*z+DI7pVu!R^1CErnrF z!2}S4qpag@pSmDezgFlF^^zIfO@@K_A6h?s|6jBo(G1@8K(=KsvgvA&IR@^`9e|!z z1W#15HWHfV%0`#5U0qBR$S!FCaaho=DDhHLrn~Pp3;{1~p@So6E6aNE! zI6{G;oh7I!0qbA2{{Ihpp8J1+9$*L`PBKmc4El8KLvOA%4}qQn`BMNScNLe=%B_D> zmyKT3l%1aKp~1DvHmx57zeg|2t)0k)ThMpGo+Gp?$#3FVkw7amTuXbTx8X`lv(CNM zgppb%B4Cv|K={p#;SP=}d&%sAmJckoQ9EGU;7Xju_0mf&OKx|(wssJce`h99Q;C~o zzwLOj$F5%9LYMs-%8CO!Ch}W7Wc+W7lOt8POrJ`VSHm*naM0hE{}=SfAAAW1m{;!U zVNzOmv;F(3jP&P=LAuDubk1-VEosJS(O>0~W%Y`(&Kw3NO2+haS8NwfL+Iz#gFR#P zNf7H@=Mrdn#+2WScN`rWt}5||@`dTBL7-28Kp%*93a{NHtF~QjJ_3PW1RgRTOTs_c zfHJE^W=2n#sjVZRfTCr`0??e2=b_Cf%|gi`12b<>{1=Nu8suWlEXIVLYwMo=kHuNW zY;d8!DVZ%|_~@{Td8vU*$+2rMK9X8=C{eZ>1`EV`Elmvbo=~q=GR2AfX5lFj``Z$n z@XX*X;NU{Jw)+02E5)XC$mC5CqV?5>bK=LcN6@;w z&G&Z&t$R<tg&6>81ywxs>n>>1@B+Qeq0g#%{J*VzCHWQH zOI)eEt@^C^5sn5$;@GMJMHSv~XUaaS{&%y}gr&WYHG^-P#tEJPK@&5aNxYOLi-o)3 z>yYzI`YI{VfJN3E*|>R9bu=CF!2 z=a`A(p_QELT3f9zv9t7NQQp2>9*DJ%rWl4;`#F|Im!imacI%3KQ1}_~DOMtYE&RI7 zar8QAPT*tPvUA(9>gsL#dR%aOx&2}totW@~t&R$X84qoqITwL`Co{%W{Ko<;3@n;P zlcSRLJh4>~jZ?JbRx;cxOn*oqv>4bCht3U60=5F6!;hN#MNg2lazh7)F*n^&O<0^8 z#6O4&)=Je`)vHm_#h^$P0XIOddi0@mXnD5piiZLceNR7xgfr$=k>ztGliS#YtuV&6 z%rch|h0=-$QVDa(P)M?mFuw^!i|txf+Xu6P%?o5#R6W#G@lw{TMvvvcr%BmCPao(s zXXB>+=^6QSkXfLJUd1Hxhh%;ySP}tgHqy1?Vg~7hsp`)4*IvPyl|brfpR0F{ts*G19>_ZN^o6z0oJ^k;i45gfEC@gFCQ-L_8$#7g2 zr(|OcxKtxO>W6$H%JFsIr|yxV&%4TqE__3<5Ymz=+yV^jx)%u^4NtvT&B+=X(s;~x@=OUUw|2^fp3z$Ik+!0Os$`j_0B zG01WQ6BmyQCTsDh2rfi(SliSUFpG?|_MAD*`1X=)@vki-#?-n~>LLw8`8s(Y|B`#7 z3mQJK!DQ`#YhgQ6mVUS0-yY}80pf8~|M561|F_5aee@q52Vb^shB}!#?{AOu>i>_& z!Gd_4YkC;0w%t?z{0TDs`67_63vwhx?zz&9(_(TJljXBYA#y)55i+J9yHdS)(muYZ z7R(o;Pl?Ft3jRy(Ro=sNY#nX~=exx;qH@&KO6AMTa-H&0tf18!VwVlrD5uuIvqv$L z;->U#4J9(qYLS>ZdO2Exj;MtV)(4sO1ldf=g6i%2-6GUO;y<=Ew|>d*rj}w14lx5& zg0e|m>mHoPBC`N3Z!3rZBLbF8NfA40d7W*P76R0oVvvgH$E08{g@|hG$e8bmpfg|I z@TEn)y(@~4Ew|9-(wpzqob+vr^NVX=FmVb4PyRfb68p<{=?>2{fEu*LOi0F_>vJgx{U6ou=!^R=5{C;*ABKRFCc6y zgQBy2^i#dMbgK54Uf_Qe6xOcbRq3418>pyiIyQmGZ^0({@@hx$W+KS5?=gO$yp%n`L_zZhN7oR6a;+2|LIK2 z^LI}nTJ`*|3cU9!VV4T+ENxgADb%dWmeM6$3QC^;QjrQ;(g2E4^Wv$)uE*+{4Z@4& zleA&MWrJ^$!x{DTELs=k;&Mxa-A3o~^Phuf#dkHe37>^b;o^ij{c-lUM~>VIj0F8* zaCos?_)Ws9ma3~XT=@=!#M%UK$u`q9XWm9!Y+Of(O0|>ApPe{NeOzEr zQibEh#P7-R;!%{DK!PYeNnm~rWs-Yu?TY0R1m zL64jv&Y61(T7}-_-N3+p9S!b}l^8%8@^yTB_L8cgoi>=n1p!mqkAk0IOuF-Z8GeUP{d~VmCLr-G ziw!I$Ik{iQoaw)XJ(=i003=E}owmVi3_?6ys8CmY_z-p1O{wju{E14bnkCAZ*L2>J z{TFEDG02GH=x5vSKNP3mJ~a$qEMlRSGV!_brK`oUFSQ(vSvWk<%`q(-6BTR|l{Dh0;2Cnl z83bh2tx=2dAQ)&T!cZw8+iBm7g7*=LbswY)&1??;t$EC(+zA?kkQ@UcndHCiB{Bbl zvf7`&0=EV1=#H0$LPb$v#6A=m^}`l~K@UuzrpieKB%l}?F1i#JS*6}ZN4?4T&J2p} zOuZ@(J@X23;#j73c7OZjevsoib?o!!^$xn1ww@#i70HP35(UZ)e;^=A9EeaS6H*(r zhmvC;KBNkq!ht#tF}aWjRsi~<^s9g+XUt-bDaCw1RTuqr6D`%N;ospI%<2^hOFJ%HeQk;q5Y=p{p5o$|36wMP&`Ja& zMyyLTx^j=iO)7E7R<#*|mP*^oM}P@l#+RKCn>N-QRpd3>rUZ~eTMjLD$A(k+ju^-B zm+N+OysBkJrum?L{o!gQBv#k;ZBm}Gct3%TGxMir5?q0$h412h;#ejO6OH;ZHyQ_$ zuHocohm5|WMW|xT)J6;e*iz!PG03#`Tm_8;YRu43eGWe)UM_IH~?PP8@nDt-`9t;E>D ztIoReXM124P=6quKh9iKw5pHJU0>WHb^a&C&PP0})g^p1UI;VaN$kk}g+AxE`SGJS zLXu5s`PCWG{?={9%pR=`1<5Pzz)q;*64I=Ag^Sb;binoHWpIr_uEm*X($G5+KO?{l zw+KuBCTEC146*nb3OWbkHw#5(o4FrMcaX{5j0@lPezn^)!bWBrxqIgQQ#cj{HX=%M zM&(_>=)@qqBYN~m?lQ6BobyATi(y~V3m8ZAzAk7BJ=*Llp@`4uU78JiHp|J8Cx|5% z)2o`y(Li1j&;s8hyi-n0Z3`7C2r$hj(;%2}4<|CO?9mMS?|pDaPaEI`WQ2kVl5zLn zzX|`@R{bZklM!LLwunMS=gIUd^-@t9Y@-fs*k^J-*nl7vtDhdrYvj5KUFqw1utP^t zlZOx3Pe?COKA%h2TweC0dPW`iza`GTLpq%_zF%|7^_+wl`}*f8->J6`_SoX+4xMJ( z-fJwVLvXNy^HUv^DmanbIsUP7QAP!k3q+DEWq)$TSboZu3UM%75s#rj@~w2`anp># zOpU8-HG{`cbnWrCOv~4cM-$SKnv1Pg$1Y<)4a`imZf~!PnHh%y z%Tv{^OIUs!URe=s%#cwHiWr7DwpE#Nb{Pt&54?#s;s=Yl@UqHzb6CN?FoxWCZYh4^ z)oy&#S?l#;`pdJIfN@o18O~zk)^zR+OA>cAr*fl2O}uWm_iK}j#ids+6ZXr=CJDr3 zcrml1%TOFm^Y--FJFoRXm;$QCZjQl1uVV3O~;+tFww{1jzE$Dq8 zY=}`$CPdZ8C?|y!d=f6^`QszD7_vAnv;nzf*kIBW_zjIO;_X4BT=ywQ2Cg(L?2cx1 z(%~PmEjJ@^G?cnzX*Iq#Lz*ykqO2s0>;7=8K5Kh`;(SgzdemczaUt)u16fMFI>gI5*TQ zJfBIKo0$by+?!p%rjny{UnZdBcTx55r&xRo?F8HCip|vC!a^e)?q-4cqT#W*;Qrq) zGOw@r^1e%39QoRCoAUhqiywdn&{6q4LtJ7KyjYr_tkp@zRM(VmQgDzAoafQwdV9G% zpt_B4yqe7TG95)5i6^|YZE21m`SMf*jw-h7zX4M#4b2}pO09DE2FovCGe_Rh4io6D zS4X~ku$s3C9=Wh=GN2Py*9M5%4Ma<98L!R5dmLUD)S&R*^k4%H%sGT})0hPcTSAyh zU*A1PpM1QT6A*#V^S^c$k`(OUgkpSRDQFEE!wBCvX%+G0RY}nx>Eh~)j1dTO{V2Y2 zMW%0MxI(H=L3CYpZ0us-cf#y$JME1k%8`UQ$4|-!X>?-}po4dnayJJ=0{-p@NSJin3PUTqA@EYBA+bn7eJNLPY zGSK^s&MovV-HsxT{A@jqY@T`0GqqD6@**w*ik@b|SG(~oq8|2+G@_nzxSw9;Np2Fn zDGgOD+e@||u<&DPmK&Kqf&qV6-=P2X?4z7)D_aZs;*|U!zc~NtU`MO!Lp&gcz+8$g z1Km6+HOz+(Ma3fTg&wG(W8U&hQSMpp*7k5E(8 zDz{ewJotR#n5sGSwpoPk33zW}b~u^dyi}%n7yO)Yt_wN%;|J<`u5tW)T*HYoNksd& z4iMzZEm4V%R2hCP{1_5b5GRoiy6r??bymTHbT{TB#SzV|Bz@VUq!gm&axG;m293M3QUep1VzDr#?T+M!nS4 zTaS4u3W#^+Z6HlOdQ2tz-_<0|a^lWK;opJ5;%GK2A$#-GGtF@(2YxbZs2q?U$x%K z-&$Z^aPL=2Kb@}8G3KW!cLSVmv<7$jsCKk{5t5J`&hl9&8U*Po=7${wL$&Mq5p3Yo zw&o45ou+)b#aLH}X}pF2Ld{_-CgW;P?YBh^TQ>CL1|_C1-aZdW3dH+IjREie_)V)h z+CL#JnXNpd6mv+S_Iln-uSjx6^y%Mt;E>d>k&zeR{Ml8N(sjO~`+9}Z&GrDf9Or!z zCxOc%FU10Idqsh@H~^!ZLL0k(K4u=|`t2o3{o9lt7v4*BSl9OoE}(RplqAv-bJD(o zo7GNZu=BFd&YU%5dhkb>%RT>E*`>; zgvAxa9~!=Y_m1kn^NFH%Hue_QCXRo*MHeM`S@35RZ&jn_p>m$z(jPuW7{m}d&VH^_ zs-$+iY}6mFr&wm_!0YST?<3lbMdLaWM$HCyZe8wA3=bcy^7$d{Lajn|e8{(MnB_p9 zR47?sKrH@hqB^29u957$?bx2gRR6tL8>8VdL7iHWs0Mzy%O-o<;a5gYQ_Jk=e&-ze z$1@#tXYi+$WA};Fp(WzL<{|WtzHTwPg}gco=;%uVvV#;LH?W|dF42cOcuC)rpHsZm z3u7Him-;c3L$wu_M*_nZkLS?v&u_qzcOXATY7%SWaP@$RG&m^|(h_BssMeoOKvz=J z_(-Nc>WS`un>$N4)p3}FB%sY~kes^B|4iZaSEDR04cEugmjWZI;5CCkP#le6o@yVzz0Up}LG1ed&0B9l|HP(HR% zevYi`{JDFU{5b8d%_gM-9GmRvhqJP0)U^wAEKg;9x9`zKvDw;KtVu*yvl$y>ZcAC zB(kWTq$netWa%|B(joF9{-y9aYu^w7LQDsZep{mDYjL0t|0ISG$+k3trxyy2V|esS zLRcw9;~4zP^ED}>)Dm6c_=d0c02|uf==a+Duc(*m2+t;J%g;685A8u8fmWw~8c4fI zw)~=SJ>TR}erym>*oy9`N z&PS_FK?|A;z51F&wx>&Knk}M!H4eJ^J26j6R+6xeug1p1)k6yt1VJ^wL5RKYdn8Po&Alh@DtYMM1SZ3;o=<;zca&6S6*Q$`8UTo6{ z&~@WrIx5pQL!n|Z*9d4*UwyKw6UJ(N^iRl&QfNkH?xMk6Y(c(r`5mrN#U_C>lQ2EY zkVil5ti)HDJlyD}=)NQ;wj=P9qRsdzBPtBbY7R}AGGfeWs7Ww;hH{kDYEgVgxj~{g zgT5TMhM1Ycrl|KMIstrU-$+%R9*CgQ%x)iSroY|}Ji=50-wqc_xSuMk))1VWQKnRm zBmh)(c|ZL|P2`V1jupG{VY3_<#rQPE$VZ;XUcvG+u{f!5QN=`T#}seO?_GZUD?HK&W0~kffxmcV{Bl)IPzhqLwh*YZ_ufbgs{p9E) zWd4YX`-elVJZFZ9sOP|69DpIRq&Qoo@l10-A&Y05^rBqR0?`a)Sw+@S>d0V1K=t9f zOn7e%X`2V4`kz_}RTqd_okML}yY&Q=LyQyitiXdCJZRHM%=1%(!cbaTW5AHSgd;)o zHD!88l~iw&|0v6pZG0mm+Rnr#eC@1WN}oCr(TOgX%fZiDC;(nyc-V$P@K~lWgOMS1 z!0OHW25qxw!MwcCb;B-1pMcy1Sycp$>Cjvnsm%jK=V`Gt7F#rR|BXT_jJ2SzN@+s~ zt~1zswA$|@E79&VL$+d-ZR(SAsWKV(q+#ctWnp^yYb<0;K15*_2mIP>A@wQUyng_H zIISead_na!ED}PZ&Z>u{kx4C)=zF$@b9!Pze2wU#aU-+EsJ&nfmO*Z(6Sn;0iv83P z9h4hQ?#s1UvPsaY+-GGk*7tn*zJ0s|Ux99t#I>58)+H z`iTk&P;AdNua`g@_VyL=Zg*d&EKh)Im=?y<$w`fNDK)cDrtftPzXH>Fcfa*ZpZ^-Z z6^40~(Z>8tS4_Z_9ZYsANLSeUO&O&Ppc@>(IQ0K9_LWgtZtd0^5R~rjZlsYC>F$(L znuqQbknWUjknV1f?(S}t?#}N<-TQsFXP@uv;cyK9I-a@aHRD?AR$wfb2i0g@oFH2% zV-VmFN5&&lzG8_ukIjeEbU{}#YRsgZ7yNE$)Tlidks&V_H&|qb$t%B9gm`Trlx)Y{ z2T^q2Ex#toUGz5iY#bL-i^dGbNyBAm&T)D4&jaqbq=aJ7^ zZ*=7K#4T#WlA}wWYB-)QLbXvn--3Qqz+*-2*SKeN&{Fp$%nvB7@$Yh;*NY(Xqn)6& z5pU8u!J5u-RQ8z8ZOE`gV}urlbR**WpPf(?msU!C7MW_O4git$O3JQW$)J%nq$I&@ zjK2T!5jH{R8~7fNv%?#-L>p#<_=;K?=K`O-%ZAq|6rO7c(n#Pk@jqSDRkXp3kY zv?u;h`W7u*dMVxqR@nTs z+$W>hl8mJ?LDwBBgQU-)iWU9yqqZ}N-9$l$WUs={6rgQ`Ws$@BifDMBgPt9n3okDm z_%-&r?()XRdWoJ>=Q8dWomORW1%=fjuxZfh#)`^~a_L`}qs>IIAq=$F>*HFzbh}&j zPpBh=J^DkQQ0NcDx6@GKX+FMZu~sZ^F^R%0+KOYJE7!r7R`TSv?^<(FXq&^r*~_Ko3`7juv%^mJ?WG6NkykJL3D z$~W9L<#gBC1 z>g3zWQ+Rf%ZDv#Ddt@a9UCBo;V7x!@2Gt7=wJE@=q04vDv@6uS2o|UE<}aXK=!Hbu zzJ3Mm;Z<=-X3gWPn@kx7OVz~^J60I5Lo!^uZcdgPKUg{Rk{rUaZGnC+1|&+uf|qe8 z+#3lMdB!>Cisont3vyI*vqjO}t`KEh@sw0NH52J(x%J&fE<&H^;JQkAHGCUs9~GR= zq1Isb40f8!+jsbyRLuz_%qxn1`eB}9U3TDdUCXJKd?omDlx9S|Ic{f#--yhmt0{#? z+%b2eZy6dyx#oQpv7cg9NscOD0OLO87^)XSB2(P>DsUYwHm@anm}!T({aF;1IC0*M zgce>UUEo*h==_$G4`KBTf#}pqc`Y#{Jw*~;WUBcsQz$)}X`P8^(*-Rihh2KRQEF)d zp*st!%}y42C`k%=%5KDmUEcQMlewemHx@??J;sU11x$hT)Mmc zg)OSiymzZ%WbN8t;gSpVu)1`nI76b!c5u2+$`biP&~=pYL=4no{K_ecT%fB{|5#&3 zCer5xyJW0EN(U_ow6cU)y@Fm7a?w{Eb9x?=8}1&=ly(8eY)!63&n9GoqzM{?&NXR&}u*Gzhe>=r$O8*H94d+0l~EO4c)v?ZXh~MiiZS{a%iy zHM`I>yS#3`As}#t{JURd4*&jW1BmAfz%%|20sT`n>pWf55q8ouuzLE(Pf6$-IV1zz zM(`bZlcA0j&DWMjW$pcbuiY0G4%aIX+8RQKZxX8ejiiM92Rc5(C zhwOW*v@Fi3;nuyYR2wLJg8cCPZ6yRRY!5i_uC(%lQQI_Uhlr53HtgA&?!|)X4TkF? z@nhCTsRk=FEoz`8d`Z zU4j4oXUo+}tgiut`zr94^mhTTqcf6}Sg1Doyp82+_Kt}AddEB%F_Th8d8mVCT z=vqb9jCzmr>ptk_&{D5h-*N#g3y=FSbMit3a8be>$`(eQsuE3B-@U=Pz2d=?)5y=q z2U1t+?Dz(A_*0oS4S{nHbyRDu>V`hDgkcg%w3tDkZ#?+1?%<0qD94x}U(mCr`;3Yx zG-7aB%0x~jPKPoa{r2A`w{d}X2GIHlpq1wL|F*b=gN_+UpI=|!#=zG0H^7EQ4oP%8 z0k$fCEU)-t zS0kHYN0z=HzE9B%H@*W$GSnc&K(Hq#jNY@}7Co__4!w@!{=ge| zsqcB3Ly(HGo5o75Z)L#XMP&ScU@l0{%jaj?0biA``oy z8@agZm*0%%G#L7zHSpu50ry*pfBymhycGEKG1xyZ3ArgQC;$~DFGLa@bm23*sTYyK z0pf4KgEF7d7rihj0O#@Php_BmHpBRYZ{mn?Tv=4n#Crwi9&K>w(=G%qKBd`kn|$z^ z_x5&V}Z(S@NBCj2K6qotc3sf;LO1yyt$zNu~IwirOaDEA&&! ztJfCw>ct0`IB+7u1TcP)iQj`$^4y5Lc-==y2GpdvnxhI^!BQDqP{?fbf0~7 zdB~zKqn}43z4l?22URAkw<-2;f%X!vuY|aeu`& zH$66ri$-1!k3QBsa)yiSi5iwbe`kenE^tw*tKGJVvD{>hTsuLnQm&iJ-g-!BOI~x^ zAb#)I9*TcB7^6hMkyU2LS}{l}4v8(_p!e=2Jale{V>s5ghL!DuwsI?_`cc`zy_gLh zv3h~2tlc0Y_bcCJ@fKc3WdEfZep$^|(&7iNSEMq^%YDe)9F8qNKCnWKQf=3WUXbmp zSi6%L;X172&KlvfNts4ve#E0w4`y6{wb&Z)tvsTQ3hEpbB7=WOzbN80%zl6*n#@0* zn!c3!=;e8crOATP(%y^17lGCKMTvuL3JslI8jXePtHg7s$nkEi#R>Ac-u2hS8xyZ6 zUL=tSAs|wnlf8uxO3buZKOI@ksObV9Gzk{Je)E-SiHDup`S zZY(S^Ax8l)@Z4#P`y|72_nq9kAj?xSJM+SfhLE2paQ`?`WYCjF3cGMcrBM= z#qqfM<8}1$a7`ozJ$K*UqU!|~JA#kOw^%PE_F0C7)$lwSmeB3F-EAR=cJ28~J z=ZQM`t4-gH77uLZYLaM<^c&}mS;K z1SYJ@*X0 z@57UU`p)Ch1w+9E6SD-zO`4TyN;hpq7_Q;>Y|UaaxP?{TfU(%cy*K9Os$>fH<X40g=vOwKe1a0#8$QDnBERL=t@K@>5@05XIpZ(`0dhv35(za<-Y>iuPZpegX~!+ z;-ouK7fIspAgiq$I-pzEOE6}AI0E}XwUJ^@O0Vt^HCKNL!0dbJ3ZFZ;lV|ZSkQx32 zSyP}_APE36S}g!%#Fk&a8#V1&&oOW)ul74sj~U~ktHX&5@gc*p)X;YEPx4~45%XP$ zu^8PVT&nX}RrAQ<6l?%@>;)x|CwU0`ygCgQ5a$hg?mHU|O8U7Y?V@)jhngDR8i=J@ zza+7oe&2iM)Y{0#7Zv0Lo{{;+uNcg|Gh+Wbqrl$hG{c=CSuFC#;NkJkvTl7!-Z+gL zpC&864rMB*WP}U9!ceWrD+mR?dV^1ev}m&MqX%9|B#Nbi&bh7VSrr4TJW2hws3)m| z=2GpP&aFK!TxPyx&*@(vJNXl2A#0)p5V@Y zpQRCW3@Z8vle5LpKI&kXqelSC(yL42J~Un!KCF{Uf?XGeXT9~}u$Q7+!t`ClRajst zBLj*47NVKF&v(7d#nz!$u&}jdNE#Y-p~AXanYH}0%u9~C-RO&<_6?b?`29D^W{&U=s-0F%oYMg+ zgU2pv0I19bn7V-j@O;J#VYh+6_e}Rv#JKv;vkkSEXjsTb>M$cY3R(1BcvhDR{zGMd zJW<)5oHBR&wH!C53_xYfk3XpFgsN&JsO-xvCg?-;L_-+RvD_Ldz<$c2bvoLz=MjvhDrzintV7T(R+} z-Ix6SI1qlU;{I7vE`@69>ciOen;c~$<;s2sXm7z5;xijx2ri5zggMg<5(Nidu}PM` z=JIzA(qHAW>C7N)B2B`gEq3Ey=cZ_BuM3FC;5$krQR)_|_e`?Jz_8FEuBoxY4eo3Z zvh*%9XgKT)p{>K+wG+P7m4=yFr>crgEQ$IZWx}fw%l~d*{<>h|reOZGxZ{4XrNT}F zXGPQAi8|ZhX7gA%bvuOcd--wN15~2fR(&&_er;sVI8y@-8}w;Ih$B z!>?qe_-(Ac+5w~S`KlTt-E7Iri;aiK7l>DpqKV|(R&_IL8HJmX3ZHm6bQ$SZFm;wT zaBuzTowS{$QfcISP#gt3ueL znsk{$slbDElZZr!IlSlwYH6*0_!LK$JDANqv_@jq zFvIF_r{8z=pS#EL%TnB3_~_J6(ky(kGFO-Bh#Z8EdtU`{9X#B)AT|+I_)_=@Wpv`t zvy@#vD`?STR0-SwT-He=>gZSWk>cA{$s0unyKkrj5U{;^6wdVHrU48(2_&GpAaLuv zCoXF%NO~7>6P8dqI!wB`g{X#>!A9QCrYT#&XazwVSnZjlMn28vV(Npw-4erCvf+@a zh`uDwimfT0IjT<6uyjZGclSFFzH;#IsO;B;jz2-hU`LCK4)qFMXB~D&JHo3pki}0w z?3Kc3)&PJpRR)b}U^@)s*a(=H?nlqBK8JjF2sYc=lD0VH9N`>szW@2M$PrmEtA&x( z^WC?LUCnR@pYuLveIAT(U%M&!&{w8ewG7n!E{lipHE>#A6wO(2{IfB)LI%%*6x=zP!m;hhg#xM(C%`Flv69=k=^CLJ(S(x)-2=EA4yzN5E`@7 z$D^&JJUM3!#vIbFB>iRVe(+pUt1FWuuHh=fj8n@w>X%sGhNt^r?hw3FrLKmUIuxu+ z^+P+R#5v(bn7P%w)(|#_xev{Vg(XW`sJSMw81=0D#(k+21 zz?XGn513gEM2#5Mm>9$Haz4#~H$!ca#8O514fLY=2OWi(p%hgdLn^K?-J{RH^`W~5 zOCn*V6sq?s6vu!n(cvs-x5B6GY>==FEdyWTK%l(H+x)Ci;ql@tdCrf}`2+usJJ zyQl~P^}mv3zphXGiL$>_neb%9D?PDqFGwNzLg)+wDIC96C^tHQERj8814TBTdwc}; zC@gAw{lbgT5)-tcrh!*Zvu4&ED*FJ9 zPL4y<<5ASDmahu-_)49Wd^x?{#HePARx9tblubuCxW*n=f#vDv3tWK9fK+(~5rp+z zz~NrcZ(9(#pSNJ;V;Bg37m7ef&>!?3HQe3|+sP|8dYODb2P7+mtGe+aqHOUT^b`D#zpY-E6s%HiT7kI-sD&ZZ!R)Vhbf|}Hg_aP zGrB(#OOGRiVwS05R8KhRfe&Oy%AczWX&bn*ArJQs2F~+lexTPRD+aG_OdwM z8ogF8rC>{@NPJBK&v#m3Y+wKP4Zlqc@za0DX20$${3*;J78vMh(aLB9x}M~}@R)HC z6Ir!S6r@KqtDncj{%DDXQPR@e&K~A5<2&FjX1Q+e1rhbCJ+eyML%;xx{D9dJ-pIJv zx&_Q-S!%Z%kJuXV2?Y#A5L};XiQSZ`-`A%5)U=a|9)sLPIZs+{0A}u*#>`mCU+A&E znX3kM;xp_$yO&dLd=h3r8uFJg>wjg}3b5I@oMxbe zARx>_)L4o%1^%bU%qyEuApPMnBNvYVVb%x;v-Z6EoF|X@Lzr#6$tzhgjORqlnE!l3 zA0N+DuCs>BE*(v8wpJcUiBWSLJ>HlF$p1j9j(IHhtsnA#i&veqL&-GBWXUL=`yJ&% z96l6F>d^wKU-q4Q8-YLVCmWm)eTTAV9r(SPcFS`DUa^`UigtNS@5LhRgw%^`gR-YI zr+X<{Jl(>I$L%QlV2t%*4@lt50X75nyTjpfHvVEW&%$05|1f~fbc?Efv01mX59_@r zg7B71fDQc21|8jEtBslY8ih)LeateH4rha9SBPmHJ|N5-b)`{Me+V-R%G2k6pP8E= zmc@T3%zj-Ca8uBDS`2&0F>9?0w&Xa(lmJWV=eDE-E2!oGX|n2Jz|XCP6M&`RD{Aa zws8LpA|}gQ{@nGtG^f&bWdrW0FdMi0gqc)T>?q2`;NB!QEM_-?BkiH^ERxhWc~lWP zrU8V^`nT?RCR%GvrD-sf3R&**SA37AB~JEu^BXd8Fd&)|q3ZnI$}rI)?AVv-nL>j5 zSK(7wwC6OdgrcTdok>b+_mjwO#jw)R;ubgZs{!V!m*(!1?t+Nk#yjK3cM*Q&lu9#v zy79}H8S@!WC}S_TSOa61Qlc|k&j=L7AlQJ;cI0ma&7;cuO);5`>VQAyxG%d_+4u=1 zc~xrp%~d{`Or57BgA;gWdkhY$y(c0XaXivjn3AU?!|y4{u>Vhz z5db6^EI^WB0VEj=Ka&iV-;<21(N@Ny!&f!-%V*j1`Wt};X4qDNa?l; zdWT?lY{tG)Xk(zOVImpoV86wgoz@BZLx9YHQM3M_k} zAGNbnurr}-UjVH20jymOMe|nPtPQNz_juA+<4l^O+GdA`haBl(q+ADu_N+3L z1T_1RDB&@^TUZmvKE&*XjWr+oM-yq9)^K_DtsL33qFeS$45cPC58mpJR%nYevZ9-* z4*Hsw`hIDB$E^>dcEWJPsJAS<_mux+!2O<4Y6jhXdX1mRc;W5Q-Bgu<<#9D@-1hcp zb=H18OuefWQHNY@TS`P2%O~QNVYimpa{*P9h?n|Xjcx0m9tp(d2awDf7}E=jIb{Ok z-+J;ty^LRvGAhujT%=JX2cGljMN{_d?vFI~t6@o}8he9(_p$&cCh_a3qiAqeXD}Q* zc{}4$)NB@sZBne7>cgAoOh_+XGAsi@8f@j)TXU{K*j|J6u^W(`gGCVrrJZtol;E6# z&5WZV82BS_A$kHmS)Oz`aGQ+Ma{f(;C1{2197lL8Lf84%xQvlA$L(*xDxtnba2zQF zs`j!}rFCsu&+ZV|DPPlUMxMT-l;PG|BgY8sB|7%dSAivvrV-Su_Ep~>wP9qb&L`;S z*2xc)mqX2tdI_G`Z<@~{Hkf3>j)QLSNkc)(kCwLGNKA#n(fp;GaSoixD}(N>R9MO@ z?6Y$@6=qZEgzHpAe!ple6 z#k;9sW`&~kU$Ub6OV**RVHtNRPl~AHg3v9wx#UD$C>ve~E*&LZ>{za!U0m*G8X{KT z5L7{&ljttI6Rm#Gnxw484<+9j$m+SYve=Fvde!Fe@J!=#1-{BCbjqe{a?kVr6VmZ3 zA&7fy$t$ng*+3fH{LRZ!=I1k3yakJG@QY+*+4dV4$_swZi(v)Lf-0ez3S_RCw=QFt zb4qEqq>|uqpio7nPv8WycfWP8(T^J`m;dh6{MzvcPK^R!nzfz_K(GnO7m~bidI?$l zLVFjIN>~wgC)yKx1!3D$L~vYO1}V+yxb3*jy6N7Z1!4GV^*KDWtXjn+~Vo)Bt*Kx;Q>Jb!$p8gr90h<2LpUE?HCQxuf?09 zKCY(xIRt#o{84Y+MBu%}%r}%^6(0ez9f#_zu^eVQc9KaGf9(bCiA0>tCe+w@EMc~{ z47Vid#duQqjGbQj4xbf6&33BM6xU8Fj7}DIN~zAv!>zelBgiu*1~J zzPvmt((;)%zj;h8vsu991FAp9<$Dp-JuB2AMV&_UGeOF00{>&rin{=~*JA=6-6Som z5_MX_F|CmZJ4#P-)|IAm`lhpb$eR zr#mJ$HJ}{}t^bw+z!&+ukORl&E6~mSAH(Laa%LAOXRvrDEE=myKKkz9Nw-}iBt(xELmwl+LXpbhoMn=MP18#;JE*k1ZbGDCy8bwc*`ToJnPUL zIPc(xp?fR+@9f7d>4UJnYa{IyL3W>ZYZhpDl_Fi&{(dmuX5B~ ziL_Xy{aU@F5=v~}-yEM>Ng?RaD!5$wy!@p&6&f?beI82%A=4%lKiMwtT9ZXGG-d2F zKI(^+Hd_W~^Kl>xATSt@)eKVCAUAHt(_^7aArx$c>0Jq-6!)y9F7Hkt zg@Or|QrcQZ06t30T|%>X_sT=i-di81Wdjn~UhiG6wpBZ$EfGn*gg`EyP3rU z4Nazr?m#;Pk3Lnhs^!k55=+O9kGYyI^Fx^1{I1UVctfP}&iJfQHFzSW-<-G)@x~bY zj#-7;{H=ydQvn>bOof@HD=Kv&=$w2jm-%D#mV;Pcd$CP+Su)2-e(8HcHd;y3M736K z&Gc$G!Q-v8DK3m}5rPV7-?mw+7*> zBPup&wkhNL`kJaun!(PL3l%_e;p%JT*bCE^towBRaIY71r;HM%fNz_Unt&DZoR3xXNg}FFp67{QjW+;%&d6An}pCW?sctMG_Vx z@gvTgniUSlL}PmA|0*OJ6sLXGz`Xs=JzTEIUD{T7)tB- z)r-ZL*oN2#TD~>n2ow4*iLJtZpk^G8;wZ({NLFWG$(_lu!?IM~HruUHHgS0A!%IUA z8e%uKSejX^P_f&U3FJ*RmK}WI%M^?k`WTsw1|zvj`+?qS8$RBGkpcRHx8+>_#jr}X zNi5y8;DUvebU`*zoAHY?DEcb<+$b&j*Ua#bD>3HH>qfHi++ZEYr?b`amLII`n66%Uql#~57;_xY9S`;=~ryBIvow$ z&#O#DH3rR<9WzjCe%H)J*^AR_WfURuNZD$q8V`6M(wH{dHp0rtbPl7-a1Tpzov<=? zk25x;)Wg7aX{h~8TX0S)f1+_~-aGy9#fDypOYpAkT@l3nAXmUErYIhK!wY#s*@+E2 z+1!A!K(HqG&|~Yfbsh#JzEGCBfMcTX&lrxu7*4T;HebTQLVmjC+2AI9)E$I)5Dmb} z_5%&Fvhf%_yjXc_5-^gTmhm!0f~O~e^>T?%f~jvvX@tS$a{MKZ?oC*yCXp`$lLE#> z#2y>m8W*UgX973Ni3KThtn^bMt!KnB(!E)LJ71=Uu9#glX&TveVTf^r$DG-lH!Z45 z4ZIg{edXHcx7$e9(PVrVB!m%s+_o^+&*jRcP7uXkk4&JWX!7oP^(RW6L<*inB01N) z_|qcyV!`wU$cf;R}4p)}9=y{6f=#Bf>78?}K#sH1@B{vB!tdGYQy7k&+9Hv2DU)?Z!klMVkL zGmHP{%!)-7C$jlm{%B<4#jJSI&DzELXsI z+B@jR>tfo>iwvxvJF?XIX9h+EG7H`W0!BPqr+n6>OST^9;i?ya9XKiXbkWL-avEUR;1WEFe|+FnJV?d1j9-V$1RzODJN+t^E>hgMQ& zTFhW6%MhJ06d`2Qob6P2TLj$MIeY&7S%kAR= zX0j`cDQiokr;&C4pOMx5Un5ITC3c#%{G(&t8&hn*Z)1G8yK3=*2z^(qvbh=dU~#>l zJO_F|`CS}&y(%|vK7kH=Ddz5X+#?w4u*&Ov#n?4~t{M8d%nCl3X#sHIktUwQP;;>$ ztw3~DYtn8z5e_}F(BN`R907VDxc<#g(55fvko-Gg^jFpUk068mUqGgcBM&%mFyJX> zj)niBicoomNkX}?7rWFkF0J^ypZ}?f>c^EwurY#*Iqsqt2;#IFi@{YLOot3l^Coo| zcOC-Y$D7tG;0fL9A>*Csg-7_t9$J-lK&Y;Wrf0ZV^uA}yM&n=F(4pFD7(ekY*{?fv zUisF)9Gdl6^PLGGiX483qJW>G2x7VPHd2WU4Op;Z-V50ig{$`@EO3#1Wr=Il9*EW^ z8~o^arE1FORELcrXL#vne%)&`xipi&ExL+Zf3d97VA31J_E3BGJ6j^@@ z@az^qMQ^CRr?ToSDTL+V8(tY*B1-VHo zCDFxRJj4=|(g=E4%Hv-;ut#PlOuzGTr-2X)wD9*aS)@^g8E_^|yMei2GBf~5qrzT& zl&p%?F($aLED!VL+>8j3J+;G8$F%b0!LAG90U0Bf2A~3}=)2el{u7oEK7xuWh_v30 zYqXbua}8^6ho|c{gXQ>-Dk4|W>?+2Zz+MWAWM$~lHhxjPD06`M_b^d${ZW_r-y!U; zn)e^2x3ca(OYd?(caxnw?L{SlJ{PMye5y}A9K0^!b5DKuhf`QFz+rK1Zbb&ie;~3r z`TXLr?A^u_^bXzf^RP6Vg$?%^i-FPe#%jY#XQdrRtn*bN#=&aMcX*uu!U%pK?5Tn4 z5L8_fO(%8*8aOt$cRuz&1DC!lLHnYyX2X5cElJXq@&bPwszm`Hv8f*%&Iaa*3kye~ViTG?xQfr-6ioLm9T zIE&z+BT+6}*Y~c&DGJRg3B@#-Q#ZY=SIGC^M+di2&^{D?%Av#|mb1pKjU>-;;kyS( z3CHrBB&sp`3nVHF#WX!nw;%LEJ4FFSs+v=f5uES9KS4~Lqe4OpCn-Y@OEhOHAx1$LrRS)=_qI>&0x`;Ew2NUkBf2@&7Udme7C z=?2B&*5v~n1{<7czQ!IaR{h4v+iak!1W0BU$Kw09N<_x%3YtfeXzKSDe|!G}El~Bhw^MRI%qXpTyrH^C7c9gCbwXL7QT4;yT45<@oEyH=5BM+oTCJWQQ( zI;nmz+xR_>@a5(O-O~Q|s#B~1IUwBFjWJdS-#uG} zK=4eKl}wy**zt%+_fW87eH+>3k$#Y3=@|DhpEP4mRk2n%%L@;DpQW6rx@B37fAbbD z0q}VSV{EC`RdS2hNnDHX{9aJ1*PL>TN)uNXvl*Z5P6dtTTzp&u2i=N|LY*l%yAxv$ zbn$sL!#CM+p>9HcXcOj=44~%IkAQcwlhT74d($8^-s^(R*(u)Q#bccZ$9p1Qb0=|) zPeO>~79NkX;TeDRi4HZwEj!&3?SVI%)gzHLyf7Y~Af|_HP(CZ_Tlt&vV6vXx8lms! zAJ3sIpyFxnR6de13D?qSE81zyz56@KDRi;s3I96`{#5|~s!VFlchzCkX%$*wOuj{A4grSA|7=K-Sc*{ECdz9u=EYqyBoFGej6B( zO#?Gaj}^j`ga(!j$PP%;-hc^b$3|Bbt$F&P`^}dD};1~wkp)^p{)w05easb9x(aStx17~4hXG-IyAJUt;&9o)GfMKz;o=PdE zWz1<&YZhb)V^{5?+h50C5c)Y7=u|IS(d`Ax1|qG~P+Yz(*nJ zbcIUI2bW|r-X`fzoLoOFs-)ZuDVK#~Ey2C`0_%y5V_FlOXA@=c1INZVz*0-Hp5Il= z(WdQsDXC|cpE5qW5?j%>T8|)nxLx8yPFPRg&TKn{;6di_#O8=S3!TiB-w?%j?BLT1 zf42}w(&Evk?gy2DjI^$Z#41KIx&{#zgzvD89)jVQam1Z7d7m4xVfU^+`Ci@ zROR!&uM?UF)2#w)R_h-!B1za1Evw~^mN5#+{8(BiWrU23?vFMO78V8?B*GI@6~l2U zCGn=}n~5NYQ_hfN>W|Z6XcpXpXg@@j*JVZ0^9afYFSCq3OBweFK_5y*A6n-!^cFR+ zhOmv!&%*mg?dZAr;u>SS2|InceZMs)UKL_F z+}_-O%DDzW&S7B$52@by{E&0mfSfA~71_v}G-Nb+CApxrV>wTsZcVoO7V|fTb{$D$=M6hDWXOl{QPLGg^C1z=$?nz;W(ckf3AV z&Q8aK?a}jk|Gdm)yDm~=GP^v`9cs63)zlQA_ zVAu+!vR3(Hb_Sa7#$1Yh5ITYQDd$%DI>M>78P;s@e0^!vLgp|^bOV)O?bv&3q;@5W z>Viy}a#$J&NI}Wrz_c~_pVPKbXWHYb8~ztb>R;vXKY-^X5%SIMgLRLz4LH?00&k=^&`7O;Vg9NgEi_>+VIh&qoty;$7MBFVq~8@gX&%U z0>P!L!_(eg;S&cdtruDcb|d+rpnrLx`ky68pY&u53aTd0ghS3V<@oUnq-o0ZfAg zU>Z(d}50u?LC8^QoN>54Zebvn8oosv3!pZEwKoiI(lui6V+2Rw* zZfno<0hBG#7i=6{P$;lY0Glqd27geG?SGW>)%Yr*!)dvxnAW-s`sqt8Ux@;~bbXAk z(4KCP*gxy_&;&?ds}=G@f0g7UD}$$rD6FPfb(!=Sxet-?!Krr~;R7WipUklhbeugJ zM#+2mN_x#MlzfaWvk3EqY4lU&zN?x1tyDjDCgbw<5C}aM!99H-z zlxe*AVM~$zSCnC)y8ZD3Wq;-M|3zj0kk{7%c|9{7M$(^oee93Co)xDjoBdZ_KXKlr zvv?SA6yV4sfRX6g(aeshWl!kow^xtsJ$2-UJk3iW41^y;M0-EO4~G5bAM2qK&gO;c zdSWRyPbh1Pz(0z(-2Dq>CgFQMaP|BE$^b5N9PvSw^diK3JY2<&Yhnj+t;&QmgTxtx zi?dOxw8P4=V}~O%-;kak5l;Kipwn(zc7Muh)rg)w_$#l60rGm+|K#-!ZauIhJKdfU z&8tBAAt!Y5_w+-qE9d{4e%O<8{766GjQ^f~JpN2SxE7Rp?o(*~8)X6ih_WB)$KO$A ztkp^mpp5x{M_G;&u(rnq13Oce2XL9*UtAUhSX21W{16KhApDqzh0lmu=)3>ziXf=2 z_=@abaT)WUW$KSbm){dM*gU3R1M}sNMVG%4wzm}8C{N)B32NwZlKC%ldOCDn@{`J# z2n-^-i{UKv=!3cIlG=Fxq%y7T0wcKKu5YyN{pR5VB})pPw2f0DpRF}bSg_b00dvZx zMUC*;UG$k~(*|0eFOH+3;bZAHcVNbXP(ua6rjw3FiK$>;7!X%0dGpr2V5%K-2uGm zg5{P)gp?LnDxm@gg3r!1;7$EWlwAMxri(p4ylIV;nTFFxe>mUMCvPgjQY2&5fr5I? zeE}->rwu2(uJ_d3LV^6@ju8_F}3|xdbitROIzJ^q!X4 zlQ&gq<@nJFE&MHDBX0aCx!KohuqiI+>cr2^RIBYA-YH7P*Lt-vIxQRCm`bfU(&BiLoKoVt;{ud1I5!NspYyeb+bmKR#}`Bl zl9{ed16}>;#`oI8KT(P15EN?Q!LOxTJO(0=|vj8wsu`N%=!6&zmGUAOk^H zvXhfVfB3}KCDCDXeOo5R<>c?fHUiJ2@LEAODNW4OJvH%v(Vum%&-*EbT8M72nIJd7r0zhm^7?dP*s-nP69Uqwn9`?p zfx`Aw0<~pAF)-+5Qq{CRdT1Kihx;GT5e}eUliz8titENcNHpbaIaL0BkJi>#X5n8V z@UI?W;i>z6@FS?9mjo4NlR)yMUwlM^p-})XCI*uGCPismacFfr*f%ch4U@c%4fOAX zC2aL)eyA*-Ehzi6_u+B-R`wSFGh*MsnOgI-c041H`Sn>sJ73rKMhp-$eb1_je`8KmPk-57 z5`p(}mHFB<{eiPrXH4wI1&!aeCnAle3c`nlh#WoIZ$ENgn_>F2_P3vinKl^+C-MIR zAohYRwt`^Kr}Gv7fDArsk_w0D4{59Dz%VuES{F&<0s)Kg#l<+2D^gA@bXtPKzAiLJ zgr(FfII#mOvh3o%wfW`UzQleK-$8j1iw+-Vke=t@Xu6Dh2%38vDG%}|;7J{LYbfpgh~in`1o$9*B$!cn z0n%M*ePrUMk9fbrkwuqlRKpgcGi)75Qm~K*@mM8&I^Vak-QWXKWJ6jMLu(iq%JM!I zdVwV9{dx(ysxSQeY9t=H!FIJ4VeJZzZ#^meoEwx+6EhcNf1k5gn8|tj00+MNKUe*j z|124lG!!S~kRA|xpiDk`De)g*R!2+JLrcheDv-@)%3u>XacB9ntD{hG3OlWLJ%0>i z31E4B1@YnRd7!HTR36c%grU^t4@Qn(q}}hYk5OCkmi>|2K2d~_Vs}5I=rHbz4%>!0 zS-;xbD}t7eA3J6Zc{`Zj!9t!&%Ty{P(c=|{36}x6Q2tfYuSD$vf^W5t*)gcE>C7xQ zo>|(~)h?J{vJtXvG_e}LTz+l=d6{B``cpRVu6k2b7q3RjNN6J6+-q+guCSwVgM(YI zFt@iev=sEW0f*%EQY9*QlJg~>BSdoy72x2XH)&?r88Z7^*7sbFTXaP}+k}35jOVet z_+)1yjqD#acvuF1?Q>sa#!-ij9UyUnn~5b znM@NokQ%|GU9~t3&&a3}5!?iIfS?Jpt#P2+*GqdOh_xn-W4dyaLgE^&A$-+AB>nF4 z{u0^2O&_{?NWX6QwE?z7Ur+X`T_=P0$dX*K@LA=$MBd6?4VzPTSyV71t)@lDUmuI# z0ZaDymT>x!EM*y;8sy8wJ)^d`pB~$%htt1wtCEK&V}81N=MWPoS4Q%w^D#zisG%w! zj=mZL$38*(JGUYsu2wFcqjz}Eav_uRB1m_w5pY4^L1;OBym_-rQM|Ly{}M;+P>@e257&L- z?fol>1_3bO3bRbu=3P&@+2l-Cr6 zBJxa8pWEE5Lm=7NHiX^>uD)*WK$|M~h)MeUHS(93a47%su>9GV{CQZi;3xlp%n#C3 zo>YhssHYZ&l!$yE%w^!Tx*tI>Z!8|aiMAGqNqEOSOayHAAs&qpk{yU+r`se9nqBOD zllIMIo@+wbs`s&XUNKuSeqr8qDjkCD*26 zDil)hNe+vHTjK-{3$0&?(*fzqMjwo$M4uOMSfbgbZEcOc`3LHR+eTw6@yk_~=1`Wo zRth4rNx!Q%RkQ(zMHV|KE2Rts1V%(?P}!PHYd~JF_U5myE2K&>eO`x@n1n2O7Gn*KYWo zZ?aZZcqQo;Oak^XZAH;po+a291?{4;I1>+8L}q}buWWn`b<*+ixwLWP0lE-|z^23Q z)lEnrc4ot;mS|@=f}$0=u0G#ogfChz_QX4ZnvQnL?R4G5cCpYs@tD2AKAX8V0ajwo zP-YBfoS>i6GRxe(o1IFQOzIz1x6F`N-Lh|B2|dnG?ys2arpoix9YcL>{`Jir6pE^J zHEwZGMHd7$M&ZIjyI#N)gQFRTvqcV!#XB9tQAvSmnDO|qz5nADL&zR@FAq%(oM{|&R0;23Rr{k}I_%{b zmhn>7R1@!(aKPE?GgQBZr@g-L&&gQWuLU}oaL)CNb4@7Q{omzQM@Evec2&K^31*K8 zN4^fzh?DL%P#iE&+;u*88_mCwuPS4*t3(r-dxkMfHa&+XVaBy(QlYZ&R@DG*)pIAY z3fO}blfdoN+W|h?!2`|ZCIPI&u9NwD-J)P2n3;5wdXfhkRLcXuba2X_q)!6m`r-I@E$Jnx(@XMV$~t5$VacQw6PrHTjj+=or% z?Pc+~!h}p>zhsnX@W%oU3EABY?p;I}ofzw`?por0xVl+$e>bK0y?3CCPTetQ`+dI% zC_aoG!_0r88OP%>xJS6`ML%0|I7Mjc_-F*dt4Y1WwRI7T32(a&eWayJ@QgB$6pLVl zGzZvlHl5_BAx`*oC?s;%rP*T*?H`WU9cdzV?!+7E6@B6x4=G`7!3n2(7W|&IMm=W{ zYL;?Vp;WIC%1H!wRESg-LQXVpRtj@q4^aW)(PJ59; z#e4QFAwD`;dx3{;+KW@2zSaW{iyqH;*FX)U(nT}wnTsJF`y#gXpsWJ)F+#2~VGAx{ zM6H96Xnn%NH`qSVGTr|qgn45qd-mm0)6mOc@Db^_g(<2s3<)7dvF-=cXO_=-K*i3} z*5mfK3~bG`k*q8xaz^#H25;RqL20|V#g@;ixP+{HW%fXkH{d5~FDvI*uWMwg?7SK^ zeMW{AP}VE_m8Ys}Fq4QH>RRmxL;nvRT`|7_lf19EvQd><^^^5W%pq(m90y^6l!&Du z=ilPL5tI>C5Me|f?@B$b13khTOGzp9Nf`A_ai@oAZCk$bX_dG<0Ic z;voETlLPchDagPZFz|WBRTSjDKWy9oGzMD!jNWJo0REba=n6JG`Oq<%#(THB?rnbX zugRO!{Pe)E4_cMGB>kk|lSkwj_!MIhX*l{nj#D6HiVnOzX$TBpd`k*Q8IP9mqK2-- zHF`=zD5Z(FZXp)rLHt+*quC1C59UYG-)Ko=?r`KqP&&c5g7Odl`3F~P3+8}d1WQk3 zA<^@bLg=ll2$1IbHRoVlm4I=j%Le5{iaTn=|Nn3mabG8BggMkMpm-kg!n#k9zCI=M zW_KxVt(Ae?RLDzAPC(yO?y8yl%MPex_yA9X$9B;7t-e_ExEA=}s7!{mNn-S5QH@u} z$WbTtB%dl;&2xGGpA~I`lgsp|4gPomg+-tOK zzAPk`>#^WC=laG;SIdFL`^gAiKl;_;SD}-?s(nafw|T+XW#abACI`zc_JhofLPFsD z+8(cS)NZ9s5bjt$&ijZ{7R1SZ*S%prZ1~jviRE<46Fx5t1jz(j zlQ^!{{#Yk2b9=LI#Dh%zhD?QDTC@vULMdkRgEK8j88;1uIXRKp z0L$9qaoSa}wu+yV9Be22aPEc#lD~X8Wp)f){AgC^a*Fl$3DLaoGqqA+V4?q`Rn78m zhbA7})>J|reuM0quGbbUSGS`nwG|qZ6Ace46t1N9p|%3*E2NloXuFM`gV*;im7Wb{ zpMU&W;7b#(#VX2p=CLrJ{9-=I4L+g!_WlRSV#3jI7gYTS+;xCeZ{&||Lv=;ei9u$f zVF;4ugVty*O4ItJyPuL++GDnumtL#OTb5Z> zuRbr7Q*XunDq}THmlv!ZtFCxamVx#^*!LK`!G~UZC`5)I!)z8_;)p&Bzn<@tjzi^F zu8M6@NsHxE?dGBK%(KxPS)#bz$aTZpB1Qb@iXZ?E%I-9B>d?U}+ zjMFo|TuB_Kf+hD?HE9NulRqXW-pG)%mR`A!X7}SVHyNk?elJIituIwKakQAeM%Q%9 zuVm5>oXcsSXj^(&GO9POA-bb%ZPPae^6Vu(98gc$_x2TWCDZZs)DJR#tpj?HX<}hQ z+XM`2Qbg*zKtDsh;#0`F49d%A1zPMrcXiyb!SY*YAAX@23NTcv9Vneb7|_5m@U=2Q zzK-5s>L*fw?K9Iq0}c@x?w3dm&&Ijj;vdQI(hf#CVwZy6H{XOx62dqZRhbw8tZtjD zDn4h4)@<|Sf3|Q4WH0Qqn2gi3qqxH256~A9Ma3F`aumuTzAAavBWrzj7PUf=^76;* z8er-Y2I-q*ZW_jK0%ghzy4JP4xg!l#S1oK1cPptDq3oP^Tb838PC1Aw}G5!kxT{l{&eou-`_vJZpu6x7v{D)}~%Tz{m*= z#JUrOi(H_WAb{COzK%8>rbpGKivSmjp9GUlQ!bUhgS9%a%5#-cW#?qCEzv|R4*P?=n zhM&PKkaFi|r{7RKD4XcEkO?FkM6%c#=gZfBz(mDBu0XK5ODg3F*gw;rB-0>`C*;6n z6!ziIYyRp}<^TQQSedZl06C@lgpFe7!}KM|KG3v0+NwifW5|D&;7*j-vDD`!`;pV| zhyF)=7=5F;TpYnmkWqwDM&3ki|0Q+?M1#BDdw%~n5Hz;kV0E#4Z-(3JIzJbZf1Y~W zz|n`u^|4c3p7?0L;QNo}QD{R{ z2>DT%S090yA&z{ou84f1c>N?9WNui#LDZsQ3cJi!mvAYG{dKYeZ_pyXh&Oo0q+>Rf zFC6b}6g+Wo&GlTgKFk>U@iE+J%=54aZ&Z(5p=HQKI6V?GzOmE?P zHdTYZhUwt~4?-_-W+9vS`gppQl*hk~9GZ$rBk=!E*kk>VxAWJm#I3kiQLb35Q(1np zvQ{ou5)izKu?QY(ee79t!ChOZmvOVmTQD#)HrWaM)I(&(SAt5Xzbx1NFlpA!eK_%a z|8T|&&i#-wL{t!u8)OlQHVP)0x=b`m>NVAjFyaRx`5!f(Eqh5AQs{UyP8F`i$&Xjs zOu1ui)@tR>fkPd6pZ!S|op z__0HYs0_a4y&?fxu|#HiSU=<4Lcr=Xb($_4Q!#&L?d&TSd@QETrM%p#?dfY;9-OE4 zSeVS|2q_^!jX#-x(*b^WZRSX29)Vzm(K1zE*ZTuL;aimu)K(4U<1FBmty*T;Ir7Ikb+@&3-oEfQ3U2+kFt>z5`vt)EA-B z7&QHUUr~>!-)Ot+o5M#JCMA*(J0Bfq#FTHD=mm_GeDG*Uoz=u)8YG?51`e1M;3u$a zC5O#JL#Tg$z1$Y`g^Pg^AROHFw?Is2q@behb?O-Xo3tb95McEKgU|UNe_+;sTRy5X z3K&M1{03IME`Z#xp^~1ZrSxK;64h{NWs+n}-NahMJ7&Z)$uz42|5o;yqs`V!lva)z zOWl7Z&v=(0HB-67uQ?l7oLBr`D9E@+CD5N2Ze1*03^a%tQ5kNvE@KGO9-sy73~{A4 z-ux^9(SJPa>n;FG&o@4>6ZiR0Ed1Gq5!ruuY;%itN|_ zVHO-U{u&^;^CDpi>%x>ujB3UtN<5THS>Y$~Oq-~1G)93JTX76qacn_&g_p2{Fy&YXr-R{c<*6p1#)wM_-Qb!S2Y`s0(?g9x_zOf7f0H<0P!$c}Z2 zO64i^WnTZbVFWuonCSbzq4(cr5_o4CLkUx_q>+FJQBhEIuROmQliDdxT1kp#ARfG! z&v)qt0J#-Tu(4X8zV(R)gnjza0L~f-B>EwNdza0zoWCsPw$xp<((?PT=Hk;ky4|#B zsB>}$B5G_a>^V9X9`j3Le=Sq?2`djORD&i09eyl;e3JesMp~6rNjyF6lpD>1ab0W5 zCRQl+I;2c)iz)A-n?t9)C$G~t@#NQdo3YjE+s}&`L#5@b-CvblK91AQ7hdQTMyKf3 z#Iv7qUUSi^xsf`^nsp$vD$q|HLK{=oV=J~(!5_IEkM0KGbA^ZU$v|iCUU0m4LyWec zR0uDy#4IY%vnqnY`|<=mRTTf=wv^0PF)yVhWwt-Qp6j@bPS12rL_;%wIPfihRntl{ z+h=2y3`-yTh@!=_k^JPl)-f3UYKH)We!gQf&P!RFesc>0n^!vf5X&WkhJ(k(EP5HW z4M_h<+i?v>OF|eI7b{Js+E8-H7)`o{5hJO76G++-8%_30sK$$#x2nL&ZRwUC(Ttk9 z_fGnaPTU=?ED&+NJiK+IhS`G<5n}$?pAI#gvckV&6Ap}v`1{YBx6hwX8mJkY1nNa) zSY^vzJ6*4SW-%Yo%*#gYxn7>ZVI3xh{0ou!4a=wQ)BBU8ugx|3*%EuQKAgIZI1=BvhY-AflqpE zgLNl&c7j{27)jdG#>M*0i0AtAzaqb&`C*;hl-c zS7>MyEI#y5hP344D%e{}c*gxYS+znnX)ONXMrmz7G+2vMUyE>-DM|#ac+}CO*afu#Exmoj%+r^cO z-dIyvN8(Agy3i41IJR3eQS1px8&R)wd<5_r4Y%QEvo0J+HJ>jK8trhaTjXC9XZ+=% z)5FQ>Dm0BeCThpP_X?;*KqL`n(A-!y9>s=wVubh*A}LX!)g!^y{xcMMwlsx&v=^Q3 zCM{6HPRWlVxIw?owbD(7i038%dwi?`(chLD4Ry%hlecm)B13s$;9~@vH@PXTHc7QC zoU6ZByJorTXZcIN;d~O)DPLO3&r)LEBYJVeeVAv{VRyh=9 zdQ+NKypt67E&5)g(N|c9MZF+XOD0weWf=2HU~VlcX+ z{~6u?_PG8`EGePB%CFeTXzk~#qN0ktVt&?BZ!R*qepoK`Fq^}gzD_pdS_ya23yju)Z*EAL{+?2w9;(Bd-HNMFsD zENcc8@KnE}(msM9YCRjbPbUqquE`e)N8RkQwL+fCb>Q5xW(AJ;4SD?AzD{XHvSnPS|B73kps$ zO|X#Rmg?jVG^4Lcf2J);F6p(;{Pyl;02$)&xDh(ZPcS3(EN=l(`Nc+a2?iB>Kf}^Q-d2q-?wx^H79lpB&Sy9YTmO3*y!ziGZ={uxqn4dGW{? zFmrKSa81$g#uW){>H1x%=+;hPhYF+#tofvw^E?vywmsOCC7v#9Cea8&mlOx_Ex{Zg zDxBjHgXS2TT56*0lZ$f&C$qzSs-X|kP zAzIlXUL4a}R4?>x(i&iSss7-14VpmkbsM%^WP7-7vj&jDPfF!920D=@Y}PJ#-t*E)?eoPbpem~@iDKu zIq}73S92wf;%4mh+m}m8Yc1}J)p)lE9xKZ-wW406bOqjg=phD4v0zZ=RZXe0mX)Tz z&&&!L)@JSyN3^Z$NHbF~@EAk{-Ms4J;SiX0h2VpiXx`tT_o;AhS-E}x0->-)t&~XR zjY9UeNRdTps9uWm>kol`Nbsybd_PUU2pvMCfeVxiG#J;ZJcqn#Jls>O5626r!*B!XSWIHc&5M4CUGg84MCkuSg-DS+BbH(`JUAGptQY6FBHWStxS)_) zNENC8AkGf#bp#CTbH#+wj^y*2_YcT-YB(an?T`YBK0b#m)xnHCJnOj3{Pg=3-7Si! z4TlaBK~{ws297_hB1AvlDneAj3!OT`FkqtTHWv30k@C5qs`63S#x(&P&W6bu#fp`T{h>zM*e@p;{Wr0{w0P( zS}2%&S&vNPV=Hl1A#A-OIw zURlL{Pqu04{3>rEaa`<9HQiAG@sfJLX_WsdtTPNEoMPi$z!M@0U=bjVc!G$cgbB_$ z`-8>|>{Nji-W6Br?5LZ=wT%vvX;KZ#S4vT2bZAPkiCN8sLC#wnoAQp1t9Bw7kI)HG#&?rBS47GxL`oO9t=mv9TNa0 z%4^?=BxLaC-}GLVgZf<}k-T=b_qZGkD|74*vv?a(0SMEJ!> z1d(19)}%JAAq1{n)-qY~i2n*(wMqwPD1Ksxln1R-PVX7j(3&%4_^nau>l}FMrsI3> z-x>>$uOYh%{~H?r;~D+?TmgbnTm;PiIW)PTX&YHC6m_{Vl?k-I@VrM0}VnYqibS^+H+MGa6*f-Bprg$D0fV#t#-zHZj=XZ@J}s5MdrKbLOs$( zSGe`7>pghaZ<38G4mOxIP=L``HZzR`M&mdbjm9c}(5Q9wEHdt_Kk{wmUrG%f9m<^ zLrhU}@J%&=6=!Ked9pTE`Fid{C8@UIX}qecWe@&aI8O?=xiy}WC8xZtW?kv>xq@RY z07_l*PlJDHj2^v?|AQq&EIFAZ9H=DD{<=SMvkCkHpO=1`B~W#->hfWOGq$HDp)o)sp| zBza96YRkm0{iq1sSq+{i+_{A~Sbx43U6mXb5bTV~e}vn`gua4?95^&|_z2_3K4!9m zH+vMl%v6VQ;kqsuc@Ha2{o!p}uF&6y;|W=>vEATX2_ssddSp;9P@Yx@j7h1A5YcM| z>i$HS9ookM&dBhKRJ2dA7@v5h$8(aBhXF%vF2)$Cg@_Zy4hdsjV(Y|Pwtc)xzpd7z zKJ#UJ#}(m-+{!7bhR@j|J;VHWv30|9PWwN?@!w99^8a-TK6vGs#RgzQ!+Qw%D^nNI z1OMIOzwBB2f8;pI_-}H24GU{wWIE?Z{j7AnS6SV3m(A{-<1Fy@cn{YD`FV&rv<+}j z*Ar{5X*$BRZlP&B!a^qz7hx<$TyM0Jyom|Oso_rNIeWI(Ly z7MHP>k7l$F@n&50=M;jiMbkZ+vySGLFUg-!C0#x$q-2cw zC~+$l*HCu~yj`I1ft#&fD!j9wXUSaYn`jn11$VVHntJxg)#pox8DG^FcBM1+$5J*# zly_b6DxHkWk%v2}t_x41FVd~ocB1f)s||MMh%>@%n?J_G6z(lgB^@NR(FF~<4o?0f_DD1kfujXJI$ ze9`U?Ge!zwr(!iH_ui#`@x7f6LTwecMnMDTGj*k=OM~LKQBdsz%Z>VA)r5S60zB|Y zs{3K9VFX~RNy1*gMxpV3*B?d-3X3rb;JZh#hd{z)O>;jgSa^Hnf7A-H43-Mg615bQ z64gSS4v!0cu`-MNOx$fO>C+!{AI*IQ34PU2(X``fBPI(Rtis6XW8-GZ=#{g+!@fe@ zx5L6lCb=d5%rrnt$*BZIYcs2kwBVYqggeDGdNFksP92z-%1Ua%#fVnaL2x*0?PUR0b2HgXO{>6=@{=yVD$o!37e{o|F zm>a=GJ8&#NM*UM*_ot#rWxUWD0Ol4GL$`_gv!MR-DOHt&zV>?zNyp;>3T83fRJmP> zB=3k;q3*RH_r6osR{T{DzB}RZcj8C*C8b;?Ytnz`#^3Xws{Xj}|6Ihd{l|d%zpuKX z?K3)do2BBpzM`;TvP^(LD_mr!fmh=u|Y5ZHiluxOM)jKNLD{S3mwsGtB(VdzvJ{3kH-60v&D)gyP;73;#I1UYKIJg`QQr+q z;W~^2U73DZmb;o{R4O?+|B`FVd+xo`P}F<}oSLb=sJqd%DgwsxByEK1 zjvq;~hPr17-ZY7wbA?~x4_XY3rbS1Eaq+RiNGw5|0&w8cAnG7a)sZl{ZlFzL-P>y$ z{+2%XS)%sjp3Jeoy8wM9(Xe{e5Y?`zTm*YiuI7mK1C@;p5Ma^BdXxu8;t)7bGJE?@ z-OEaue$&~448)eZcA`q+S!cH&Lhelw;@d80%aHN5rq!ICPCpJ|+ zE84gTsXL2dawedUso!@Pn!IB?;+^i`hz8D`JpjWV-h_j3?;~fwM3*~#Zee}?TLp9b zMfmYS^wz8b3EdAw=A8z~%p@?~VMZYCzWo*&l~K0$h* zY*|VEfJAcKE1)xmELx7u&o@>X99H^l6`EJRQx~oP0HVl^ z-!9_Dr>)*S#~!w?TOX!pn`oa2H)(H-HUossybYKl7i4AFV7mCXKi_mQXbssk_jb^q zr(XM5BYG0dnN3$DlV>`mbX4c=>V9`y4?s+t4#{1Z}gYZhf)rO9;tki(bt-+EBxovk@X6%OUF_&higJ#!T0Rwiyx% zxft*K=|rf4Cv%XB@WRR1$>Fpb>G6F)jHjZn{57VzFbnVj-|abM{!qYR?gJftK0!D>3>X&Ly2x zi;Z#h){3CZ5o$~>RJ`%$3+VCEq8)V?W_n5xNq(Fn|4ad%HM#}tR#5*Xwc?b7;G z?ZUaaoJwD*hT97|fBUtyIvmS07h)(i!i?$IVN?Pq^qA<&12A31n#{8bWm{nGs7FG- zZb4*Cv1Q!IM%ci42&kiA%*5$WYQAi4U4sL@UHDn-r^_4E&k5y~i_x0ms=DoGh7dp5 zrvMvtt`YS3E$)uRxSTu;1eh^hd(RZ%M7}q#HsYk6nzZYA&#N8KRw%D07)KdheRL5? zK8rRX8m+^suiDY`BEg9L0{>!p)8h~&zZN=;GL4gBZcZtTu}##gf3Ji=Mv4hd?%yJ1 zASyYu%@glONT8BB6k78=7Td#uIfUt$x{&&Vn&B)4xq+{v)dQ5Y0Gq%ye4Y;%t~^K{ zby6ZeP9t=0VZYD|;um@T7n*|c0>(Tk$Ndjv*V{{>{J39erC^8sJcW9Wx`ug@K3?Tj z$HVO7<&U{WQYWOU7RM@^$10iEVlq`t8=9g!j1oUS32p*QMo7Tu!l{>^ZXnoUJBqLhq!9`6c2b^*m! z39L328>CTjL?OEcERGQ6(=o^2y)=Ky{bD^!5%R0sUJ8{)u%NV+PB-%HZ-!n^MGu)h zxR)jfjtm6;Ju>jWZrri`W$0z8ohYxVqrQ@T9*2E@u($(*EV@!M9gr zL%iy6J3hsk;0G#f)pZg1j}beX%FKcL=CVd&iYU-cG^L&4?0m{ff_1nFni`jyy&6hB zRX`9DG&ytzIEaauG2eqsD8?}5adk!_*n&*pUK03mcb{(6>NheR^A-eV+1CwM7 z?zIDa$z4=5EM3x)(u6l2&1CPy$Hjv?2;IqUKUg`8R+@N790T3;)VF22A%e@8H*A<# zTV-P1oiODuz0OB=+lDr?ndUH3>kxTgvgK-8uKIbF`1SoP1oM|M-Y(nhQt-T7dn=b+ zsuq(`T=j7@gPyvaTgld2Vc3f1u~0uvw|~tU`&p}TI4;DTL`K#0t~E$qdHyb<@_b37 zmlM7j6Oq~@|A$Wbf|iG>S(7Q}$E+GaJ{r%Y4gnVG=t_N2MzhR@+MfObgUb05HA;o5 z0uukdWWS1{e@3`W0-oDt7RcgWzu);e*WXAZFuEQlnjZ;ENX>}KBhQGaI3OJcp&7)Z zFWq{x zn#vfA4!BN3e{1yJ5#6YjVNKiypenjc+{@tk0x{v^`wGQvy<^xe^Y3j=uL#XvCbrU2 zb(^5pPF>Ol1^y(7cpq|Qs15s(r_*Pwr+<^l39<{#kwu9@A@h-KkrO?7-?*)6@I|4N z@)>5<#-Ji)64pvco<#nZNmv*qCwHo)AdiT1W>2Lh6uuDh9@8lQ!xiodF^Y1O-FGt9 z5EatlSk}DBVvHb`Z4WL&9&s*=u#gxuZvS|tow1RrP{M+TFom-Bu93;IY=mJF3b`+# z&52$Ciw4ubF|?JxUyMY{-g6FQySWX(2_Ayi#?Gmltw?Snl=``O@=b`Mbi%n>beOsOX=WsXz{QcAoc@51kca%_O@ z8iYy5a-mLDWg@3eRd<;BWN`$w8Q*^i%^j-(Hpm2p%u5Z9CLV(`i>9Fz62D9yy6nQd zR1_|d12bY?kp8=j+d*PN-UbU6nh*a-SASW!e*_Dj23WAb)5ZRRR@Mo_v$|1M#1OTe z6}Co#O~MStBD*SLTyhPzHB4#fuzY_1LjRJ9vYL6D`Px{&yVH4yRk(W&C&+S+0k!sORKT19mv@ZvJvadntiMPifqC z`bvXpRcVC2#N*2b&ZtsqKwh(4225!_H@$Prm`)L1mGU^I?yxuf;w8I?WmltmX=NeN z#Hgl*qKICv?dK6oDi6>hJV#xTAwDmBH`<<#PstDOUdL0W^%wgj(^MV}bbEct84Jf6 z>!GQh0flk2J0AQN_7g#kCN8nLwR{F(h#n}cVkm9#SoxP3Pt8=F4E~5iRT+21x}9h8 zoBNLhW4qxRfD@zQXl@#dDtt!w4|B=awPdnpXS>`J96Crnv=HozaNdwg1}&U4rbM}d zQKnc)fjrkWxw+2v3BCt+G@j~uGE;;AIu==jcg+gQ_F4ydN5&j0n%M<5Z17hJM|q=A zwv1UnVW%Of=2$Tj3c8ivrVMLzEAGNzM&Om{|UWBTXP6891dfOS;0S z@55qtMXrU=mg#w*rCONzS-l~scO#HHL5#aG4<_9h_pJV!KiDB~Jvk_H1O>z|6)5&t zwuvNmB!*i#@OePj7(m=NM!;$$@-Xc2hG%tp+Z+# zDOa*Wq{1SY*8MM`3VLals}|K{;ef7wQ!F(96p7cr-R!+px{XK!>zS|rtY`jm!T*01 zO+sy_I!)d5khTp(qd&Hl9L?+{&|lC-q|Da$*sM(oo1^2n`<)QK-mz2le}Osrb$MDre^;UJxG*vPSQyS<=r`rA0a7RR@v z@7SmhU#jR3bo(|EB&q}p*#`CC@{A~M>F#{b{YFDr?okM=0GEf1r5Yi}N+}6gYwj-0 z79&DO+&_Pn2TAst({D8DrB{=0FPyoSzpyMAWj~f}+uqvTqZ=@johO}7GJKDei?qJS zQ($wbw3@D~7RoKp1GU3I;^T3UTS(8w!Xq??7lTS1R9o@*?(v#yzdJm9TS(pRt1}LU z6b~oG)<{0PH3~Qm6fWu~k?iPOVR4BowggerQaGM5A$D559=~Lj3&`Qf3;vv6RNmTR z8bh>@PgMl{PQN^8(?RKa+tNr%ni6iO(P9fsmLHAGsx_>CyIwXv`Or4R5)!oDt<&dCN?S{^k90AAkPT6!4PGS1?> zK%aUV{z9GsXbH_YM&FZ&781ZX2wWCyL(Eztw7Q2OG~rwk<_{S3J?#YoYcGxyi8s!t zFGmOyeF|G_iQ;_Nu;j#kqkbP&p5Y((op-R1$a9Uc<1NVt+qy@xw3+pKzCy^xbCyR+ zq%f#_zR zH8c%G7i#O|={wsdnHBu$n zk?TJ8T%1X076Qn42?dAcOJi5sI;!eZ2qc<@Q2GH49Z5I`DTc*Q&?<%u#4JuZ8Wysl zypkeLfMmv!oK!9+7Q7vePm^^BnD{!LxDKr0s%T6YVm7mogi|lL?D`$~* zr#Jebjx^=1dA)v?qQkV{K|T?O6!#~WEHwa-cm1F%e}Pla$rPc*K12RQ@!KToM%~Aj zWwghag%83P9b}W_3dps6dfA_<%N@H!(%5v*!+k_30GlkUeU8E3nf6n9Nm`n>&n>L` z96fX@Jz$_%_cB}>JoT+bvC-JBXr#vuii85DisH?tk9fK14!n!lNiFj1G%Dk4k=vah zp3TD6sO#&6(;Yc1dn*t83(M9>5SXIa8`(M|?%37`+d)WD=Tg@KrFR;yI&iqfF$;Y} zn97Kng(vDesA4;{@NnPq;3SqiKn&=Z<40pol1D^Lvf1o|jbbI`3Ud!Oy)Je+;FJ^)1R1buR$M3e%X)5ty6jUPv+D@-m;vo_(%w_o4SrA zmj>lor`dw?On)NVt{m`1l?|Jcw~>rO^q;~V!H|8Awmlnd-dCwAI=NLIBK`vJ%jR$B zkuEP6bOhPBa=SP0VaRt+x=cD{ooN|bnd=j25t<_=xVo)1!2b!4!5Bh>5G;6VviiuO z!g9m|d=#PF(&UY1tG*W>BIoWwn25xnjX>}Adi;3Tt4l@b6{#qn6;bg)@r&SnD6W7p zP-3F(0&N`n%h1S1UCB<6%k7c`6jLNn8q;eeFq0g4j1rbM$5{0N5hrnqxyr}C@S6M} zDP)A&Ag|OXBL13vY>d;AX3O({?S#Zw1O%P=y9*fJTuP0<(u=^+wc&cb>%r(Gq)%AC1wm~}ql}8UG z?MF#(6fdJvX`ffev&qCuzrt#Xnf)j_%K7h7D1opTHWRFL{Qp_${55@&rFN!*Z-)A| z5w|DIrB5qKXG^-6{>~P@sF9-SnxR*4khtl`TK~S3)4mhVd2out?}{%0M488t(KWl4 zO;M3xLU^M(5pZXNMmS2HI6ZCXm^y!TyIXm>sWJHS!2zj*`PL8!vXd2O#u9;<3z&dq z!56`{XWQjDfJ(tw?V`TLSIOSu20Kt zbWxLJPi&d#(AhFIWHPii+a))&>DsY30ZG`*u-bxtciLTx)wEBZnKj$R7Sh^VO=uc5 zGfY19#p=uuz*$eSweg!V4R5>fWwe|+Y0tT9v43B`t+cSNwoB`~d6=oI47$W-dcEt!ZrBoo)jDf9IH8r4ft z@G6RTm?LOqcBOq$m0eo9myfbxU=(m9RyrU3GN9WCpRJz8G=@z;yO2f@wFHHY*6s2x zC1{QkEO&Syz+-hGV7X)4>{|<#J3;}m)r6#b(*y2GHKJ}y z$Lj+2SMYKc28mq=Z@r`h3k_L|%{7tbmLS||)#I$cNxG&M_?3GQcF}AOf57Zw;&gP} z`c?6r{R*O87nQFcGMUI{Xi5d4@j-WoLBwar~aeVVY_U(uQ>?et*Q@w zl7L2yNxzWGkh#{9oQi*pq0XQ{bowo*|BPO57Y@D%KVK7KHae&~K|CjWv+n1vi_p#P zuv?P0UmvCnm+OFov?aB(do*`a&(oW)BJ~w8s3KEp*TOn+;u5Wc{1@W|o$~q=d%SMO zt#5O}lwj7O`AU_frF5~SmS~#MG#+}XcS74dGm3&fQE_i}$xOMY-6qR;pVzC$37Swf zEQ>Ts*b2v5(L(Yq>+vPyj4t39E-$w?=2C7Iet;^Gfm~Lm{EYg5ID5ywRfoL%3#pTc zoRCg{Sj)FVo@{miSAO&dvT>}nRcwkB~om47bwG z72WS~BvE`Sa6-jL;IA#X z3LVQS(0hgGx=uMt`8E{$j5M@o&&=>8#Ph+MZuFC(+$$RS17+$p>F2Re{c%J)CT-^$ z#X*2oD^|T8+Mq5RT1lW-(}_fOIR`AX3|AmYyx6v46vGbsijo*!Q)dVpLxeU4XCzvA z=8tN_M3yMy4;sgIuqI^Rfw!p;9JEL34syM&QoR-MECS!rp$S!bE=l->)TUR$@%M;D zjoL%I4>%tv^zY1!*@k?jP>SFoM7QYTRy>CPwjIjF%(q1i=C*==7CV2Xe)PsfUgzu%~W>*ah2g_5wlWS$1n*Y%{Uvmb(J$+a%V|C4( znK{1y{Zx8r$HGRHYvSf~(D|Fs?{K|i>eb^Kvxi-^A26U}BTw0j9el(Yk8_po;RJR&^>{I` z+>z$V_`WHV2=J*;*J8Te2ghEd{~|E5zs|z50&>e7Hlep5QMV)sv{-W?YA><{cWLiN zlS+2RHk?X>qw75QM>qDo=KjbXv%QF$rCG#RetnzS6S{F!JBN%VjjG~fWn!IHxiuGQ z+b4d|D`>y9LM`u;rZ?4XvCL1^=UXV;^XpqFmFyF>pM^(G8if({z4|3%ht&+Gk>L)y zMy!!dJq}c@wyhsVI~_jiqtSn$Q9tqV7h2KJBX;ZJ>tMR!3tcSB%^xRhaQYFRbZ}mM z3$QL?MxuStmD|Q;$9&U)gRWSfuNZF%7c*|^{;i0Ew31tC-cq0iigS)7&Lmj5`_w7_ zRk)$~!S67#v=|tuZNJ?!EfW10$Y<{%qTwhZrZfl@b>7N^=hbS-ZmU;`@5Bhhv#Xc< zT`8SirA7;xUPr%GtzrU|4pkhVO&^KTP_mz5v!rt{YF{R(B|IL4Qd_jmV2LE`Uf;5G zjoYU=*bG8(H0YPO*X*bKf#*gxqqIm&)`Bp`d9}r42Ke+|-e!Dcdb>`u*7&U6IGKQ# zfqZd@Y7x(U0_}HpUIc)g!y27#MKjpww(~~t@JjYZt9kLVz(PJLuLHP_bgrtC{dPA% zwpSmM=`5ffR!>2nUZ%3TqEeka@agAq>X!Z$3v#xT>QVhli>Jt@{+{cQk#bpT zrWyL4-qnB{PbF_d<99+;j%wth2E719#&`6Fj z${9UCpUev5z1KTiCu^6_BbGABpEBhxJ48)tqknV-=ZZZGS2=2bSHlL8S@zZLjfB2p1MMb9WU023C{M)b^?d5licnyi2L{wM#929WP8a`%EMg7fC<^juW-@WDxrv2C^ zvDcC(?w_K2~1rqsnbgZl_c{29zqS4%8rK@iCJ1Gl0ckrj?F_oFNPs zLei-rwLm+tHHH>*P{PqaCGS~E?p{q)RadR9NAG(Y;@ z;EpX2^F0)=?>n=dqSwDi)5%4Kp!@_A&E-FfsK2Jvveb3#!3Oy2 z{u(5>zDec0XtgsMN8Q@#@9=h+S+dI1Oma` z-QC>@?(XjH!94_r;0}QVcXubaySux)1-?a|XYcoWchx!jRPm>~XU}e`uD;irw|~>!fK76bE107n)Drv8Y)+Jllj60KF-g9dX6@BE;_yunWcwrFZLQ3>|#j)3Re=} zC2aBa1D<%ARagfim{b(c8|kaSH1xyOc0u#Uz9o}xJan3HsDJDt-YYwx&nFMuyNE5o zmap>I0tYv-4f)=XVECGXoF;oM`kqt=COOkY4`v1pEk{I+_nQsZfed4#9io(q!F|@f zIej)%+-twZAm#M8%bzNr%{9ut(P3s$Q70bI0v4qezUbxzo%M-Fe=qad1lXTRS83} zSpq2pKt>)^`!Nb;mQp|$M|xAFZMU=iu+nws{W>ETI4*b?*9SBV!z9P4>^S0p=18PL zF6rfQ2js*^C~Mrv?V{M_SSa)isLz^RSz$|Di4`SVaHmW-zwOMKaaKXJS_vg4^c`FV zB}(#B?U%bngF>CJk#0uC-8ywxU}JuE(>_xskw^X$ToRsb-VJ|SqAAm z{>XGpt1~gDgY^CGxzlv_>Pe_{NhhJknT9pRMv?MjW2$thXKS9*kYPo_$&PYwP5Q&x zS$CeaeRw=xdZHrq^H3HeS@Egtt|GU#gh|^EO`or+Wtvx4kvg%n$lO}+;-6zXbD`GIYJ-^|MTt|k7FM7 z5X$$%EkVVi9E+f;d|Su+O;3ba7mkoFKYUzBp1`~>o~aE@x0pBr$@gXs@xWT}5>1f# z;PmeLwp4H+fGc$tVK888!_6FZLpq0;XwFhQLTsB0OHD1aj4(B{$H*I>pbqLpvV+ym z-b2f9XO-%9+!B=*lNK++%F5gup0@Sc_6gzSEW45YHpFHufL~y5CX#$pS$ED1zMzOA zBDi_~=d(#VU`WN)F0A|jRXRAguiEy@U`LB2;f&#@SCr!*Jcg1__qR*r-S{7b@p|$q zK0UFQA5W>;4MZMr&!~LCIaUZ!G%b{QgGmS{B96onT@VFNs5~U{CS9?-Nx}`k5aD+@ zbTf!GjNEEbOFIy%p= zI;@q7iKedbV3(gUKHP*B zad9hCd$^77Ka8%lj&(EqfIVyBK(+jrjRJpdpj6U%vryzs^7&vGAV?YJ_28EZqNt3L z8KNIZC@o+%c-|522${qdOP0P3eqr`Nzyug5KFe{P%3zu+n43Ni}Q1+!8ncYrdZ4R=W@-0{*H?x zk)bM=D{h#9*mimhkHJXtx~#_xh`NUisf@NNJ4e^xkC9wIIOD2z17L!7+=(a}0mouo zu^}IM+WAp*U3z;B^>7|7uD1N-KC``Wm_p)H(fCms@YY|Ty7Uh*Zuc7e5Nv zRFSCHe`uxUgsT-)&KPO2#M4io$B&}&Ls?huQfzo=ZRG^}sgiS&lrl6h z#5}$qr_>JTVb1mwhs%ao23}uU8ZK%(wsG3EqTC@~;EY8ibBoE z4G{v)m{q6x;v#XZ-w-9SNVz5EF@$}4>eLl>s}T-k|`_mlbLydy5y-kjLtExOn@ z5uR%6&GciZf!yS$NM=f&=k3z(+vEDBwYSuOiV$bq^yJA4geWJTphDtc3tZ;plTPGe zi+-zQGjV$E3}Q75(cwvQpUx|~yP}6G7@Ts=3K}c^`o3tI$KbD-_1kWS>_zw?KRe;I zFRzol(waSa?57`=Q{q|4jTvahkOd}ofWR>hg-d_SRhPDNkBp&b)9r=H3*H`{S_VCR z@pOCkEGJXOeOSD6kBKNA31Pozh4nzsy?Z{Pyp7;0b>!QN-ogrEiBr<6SFlxw0qB!) zxOyw#(E4XeD3h6=4%0{@3?87UIxNaSLP~9Ator-c9^?~7TPUYSApPIONS z`r$HSkaKfb{P}qTEZ7+*kAyNtV*RPU{o{?)f{h}-vG>20ZKU|^0UJerbx6@zx|yuA zCbFQw&}>Bnp@J5x8(}m{_@=(rGISYZ&&Gf62-ht$t(O#_Rz>~%8&rnXYIl*8n4U7B z@z4lEl`_GQpdrim=<)U<(Y}avF$nSse8DlEdm3ErJJ&$Z5o}ZNwN#s0?)%ilgeBAc zM(umDpFPy~FChjU%cBX48=cNrtgd;@jk$YLsg0>EZ152;(T7t4@G@}mLitd@F3#TT z;rV<0&kQ%9$5uEdX|;^qw^w=A(#aJ>E9Jm8PxQNq@+(KEu;7{X!4gv(@bDjUQ- z6g1q-Pq?_(G9;tXJVJ-qlSv6qk0OG7pR$^BHE**t(Yi1c>IskFL{(Tcfa5~9kX3O3 zGLuiX!*1|*Mn4Yk1&$Ad?X&}xRh_I-+W)ua*ZbPR@D*VA+rKmXmwCuvMYsmG8zN8< z<>3;P6)fi`ktE_TD4~?E!|@w_=zB%zg%6Re{nCt*za!e>vJ<)r=$Vpl*UNzRz}Y-&=k|{gAuZvxWH-bW-w3SzcR{j zZB!dkGhTO_5iC?*96Dhl348I=->2lxm=8OABAVZLqH}*7Y#Uk8!Q(jeI+gBEMP^DS z{TpJ<+dmLrzCo<{Ux@kGwe-dD907<`4H>X_)^D=#-D5*Q2(vq&UB@nXgPgkHO)ANb zvuBVE#A27BXJ4Vc)??8ZGi7Y&3gW`H6;Y^)1gybhkpI z(QfMDBR88(STrXTX`0Q_NK)jpieUz?xEGdJD}$g)wh~HCKlE1Z5!-~XswGt0gKRRj zYlF2?qPmJy-ZOm+pD367I9#47yv`nPCz{?U1{)cqY@jvjD~uy*S^cRvN&lpqU}~Ho z*$7nOp>$gij}{Yrcub~Q(K4V4m&Ii^qSdE{yR`)$6u(vBgS0K;v^DlRzXiEdv6gAp~g&RcLcna^rcfZI0+d3#%iKohg3wKm%!M@4ZkAXG&c4i|#PKsk#wipmDXKJPn+y3kN@Q5EvICd+_XE^v^v(kIRGdn^x#hicCYf z;Xp5{av4m!^1L$Rb7^yOhhE3yOOCYMZo0=2a_JFlDDyJ(hyVL`*DknPPbO(b`Gui4Nua4*9Pn`I$I4`Gzrn<4Li9$?_3 z*KL{fSRdnWk!73h=ZuWamSGr|$s=W!dgZ$UGef!7ShL+y@^E-Cka$3K6S&Ct^MH2l zaB31kG7P26P?%kET7ab2C%N0sf1G<|=b|HlYB>*DDJ|2kwf+VMT*u<~@Pw4&mzvBK z!UayvlU@(^T62`@wT`wN1^3523aAt$nkww>PYw-1N0dry znH=_E&Maoyo_p1XCz2k7>?Xpb!LCvcg~Ogr!g#_Oa-|54*iQG&t5ofiCKLPF`qlDY zEeF*FSbLn(?i5L+zz%3=c6wAd*#CE_L`r~Ue=C)h|6VHpHv6!V1I#{r=&-4aQNclw z-H}C>!I@A5OhSl}H~R_5v=aO4r5degPF*bQ5PcIve+eV|e&HWX;b?W&oNP9XUZ#Y*=j1&d zI_!s{OWXyD_@SNO*6D=0mvCTRpL${p>8)`g+I!)(t6)@k(wd!5xi$IOdsU_&F-of9 zJLwyFAU+f-p^n*Y+w39R?KBSB1y8C*k~{A%Be6r-IC zKyKgvRru|s&%?9P+ex1UtR=2W5v?Tp>3T7V#JO4>*iKQEm#ojV` znX~9jJGl>sus1-4EiC8|T83euK}~UL>QolPswK91Z3yBRY)eH&&{w`%+x6T)*cQX& z23zXxFzOYUDV?@zk?55X3d^l3q)JDz)YS^uk0v$fY*j z#>ET~uol++uT5Pw>3G(7Ub9)-8IWAsczyTdNBg^y?Bp+gA0cTr4M3vKIs90di2I4m z^anbSgYK!txDHvnq?;aN@UOhmhD_V?c*NMk6C_Rer}RS-mxX>smvAW_gQbIR%sdFB zMTZtyUAg#sM)LjeQ<(Ye1zt^*doF47=CysLGH11Gvls7rsl7AgSCgCXMrUe!R{sj-inls!5ufF~0~?lFSd=f|(ZrZ%eDfZrBR!t;JaZp6P|KiD;F_6S%e zEzuU`j8JGqJcI)=Cg_!*E*Qet!&p9 zvX1wABkrE)k1V2x>uQ$Du17yw zsKxWvE8R6*ySt@@EoXsA>P?*u!*$K~yr}9d7&CN6c(`^mq^u4oe>~V!$-Pt6uc>!3 z<2KWQTh$i${-XKFEUbt1!93Zi2pKQGK4m0#H!pjt@=RY(uw22TcK|v@CE3^vamJ^? z&$z&-vy+1;lT|=9D!m)U&{%GZ%wit~zhBHz)%E~8XdnH-Z(psOu8L$oLj3(ezos79 z5l>v8RzsXOSmUtUU4fv5qNJW4NVD3+U!3LomaLZ z4VnOqe*Skx|FYvyT2Mq5K;e~1^kaxM2o1BQE0Fl~6*?z)wjlLY^Ss0zM}D zzY&C!f)0V>F?O@#9u%m-OdTAG`E)zl(4{q|Ug9hat5GGiO8XOPI>CPssyY;}#CEz3 zRNDi5=cSc(((1979j_m{F;!^$%owN@p~~lX+vxW>^qgBwM}Z}kY-F)EXLQ|X?Lun$ zveF$)L$MZId9GpxlBVUY^ek+^Pvbw3B6~&sfwV5*ccBx_@uwJ1Vs8h3cI}I2f|{-q zG{!>8GU2v7$kbr(2^eOz_^j|pu8Z~pzakRA4*o7KCwwkM{a_wV=i~~uWV~?~JcD*H z63w=S-$z8we=t60#l<_dH&UMV?u&t&+WjHz1v}$6L{LuTVmIsc(9nE0Q~i-{#}aHe zgH2L44Ab(-^j#my;R=Q?H#ubI@m&wcK9J_Mse(4G`4+Od%>IfoNhnpLPvVN{lM_bt zp0@d?4hL7~e5j2g)bVYC7sG5_^9$f=e;7^oaP0>e9aa9g@oci~DaEj;)f`t^JxY$Q zikQ!z^a3-HB>EnUJpFJxYHDeiZe%n!t8gB^qTFDbf_Dogu%Ar2sygJ5LtBWc zzL=hxb!h8{hsb0PHA#uRWq4h$7ih6lS%aThcvdzx`|6P13}A-y4L*_q)bhaA{56jJXlQ9EBoxRde22@UlrwgrX`DfeIGCdHCdi%gtO17606h6^P z`*WrUy7nqEk268?u0BluFxnY0n=~`JXo|?MWrohfw)pT3R7uiCp@8WxdHJrGy`xDD z$Aet{h-EWNMnMD9woS`tqdIx*)gQCdM~>(+oEhCbVI3ZQ55^F@EwFxuP8LeKeP1;- zb+`55;=+A`yCpS_m}Hiv5!U@h!tGzMy8#tzfQO9MiwIc?S;e&~Ax$WH%TlHYr?bK9 zP&1~f`w&FhvOBS(5v5*ujMQ1(_#ZOz#-{N#b2uayzvR;Wfmoi@Do^g z*81;Q5Y6)-O?v(Jp=iQT=?Z4ktTZ+x)?5Q<*HIi>W9I@+UgtBIzVTZND7C0LDYl~- zSLtWkOTeLEA5ex!6KEZbI(@_LP7&>@=&t?u-!`gdbr>B~+RG2Nn)rHY0YiJ*6{Z+= zhqTP!b@blONttVuZ5!A;==K@|3Ci0;lB>=uW(!P0MF?dOL8n>tJzn0wYeN$6kG`<* z_Ka@{q=>b(*-R8M+aHXX)%8(O+GgE`Q|C>B(4vzoFCR8z}o+d0P552MboV;ylQ zmrRI9Q6tiu1Y0bYF%L5Glvl{AbXoSZG{`j-@v3eVmu%@CmoT-niJYtRG?8h)5tcDB zM0VZRg*u8MH}$gNnLqZGkP9}?&qWVllrfVnux#k8@YU_MOnfxR#%!j zSEyhNv_NKN!_%J)3J+#EeBHI#LJVWuWw6^~M3d&3cnN2dR5!$!xBB`zSvTvG)iaBM zh54#17p{(I`Pt9Zw|}6HEwN^L?7E#RNC{N{ThxO7b0bn2{ZSc>h;}be=<%S0CbwA> zrAZ;Cj&FS%#vqh`%JIgQN|5fJLEXIN=xh0K@1_%rsv3)#$-PP8v%czm0@ZHu~5nd{EWYDt0Ye>wISD_H{grVX}|JlRp642ehx96E=`1*Y9q}&pY(V z5-grzZW#q`#usEMFi5Xsc>>~lHxM3?@UVj&K8OES6FhFfaJ4F&u|#8g71Fa5MC&h5yn7vC zFzD@j-9i=o*>l-$$!i&L`XJM}B*-1YkR~QAz7w`pLsDb+a3U#w;F8-5%rX49AUu~K zZz+S$bX^BAQ|L}U`OmIzq1u!rHE=;V_;(TFZ*P)cZz6>E-y%dFZ6F{*V9`McBUnO) zR>UidLvmjKNo+64zG;dd+XO-S$oC~maJWfD0;)T)wc5^fZMbFOM|<1ryK@xQj~doo z4dI?}b@pq$pxE%3pXos`_mg0#cWyuWbXpN6`D>~be}X&A*uzzQgi$5tLW6-m`4r}F z>_nm*g&!C|Q%n3K*t-m*n*Sp$FN`7hT%2NoX#bY_xJxT;p}+_{-<3kdktTIa8yt~E za(xC*F|)CpTk^QWE+dCg*?j>bsUmU=5|;wg>5nIPOsFl2*@(<@em1$lALd_wB)z7u zv(B~fogCWxil;lV&SkB#!Zw|eXJM+E$tz@5a^9;=qfm$D-scYhp3iiwvphFF^ZJz$ zOef6>SzZ%R-|WNVE}BiTgs0+tFnU-bBFDS-lMP-zbw9%1oDqFo673N|U1D?5=INvp zvPc|d*?VomWXtn1n~c56+8xR#*UBqqLGXB`m9cBoPDu=sSIn`;G@8dDZzA4Ih8LC+ z$!2)vtv4j3^OU-W{ov!NqOhmi8vCJ%n*SGWqV~ zYQ{h^{=k&U>DSBY&kn)P`%q=OnHWlD1y)aaLbZVQAbG7Pg*jIlW0*I>O&Oc}RT?ev z(y;veAHE|RZRLkF|8J=M)izW7Pm+gFySYv-fl3TW7Ld1a7|y6^)Ug;O1I1`@0&3Ug z0_3t+j|^M?`mqxZ7=(d7EA(>omdhi?8jQl`*bw!R(|W@I zJc=%}DQf|_s~lYWw%8`yYoWZc%v1Z6iR8#Yj)A7ei!w56BUB#O2MM8S z8j2UssqymU1w=a4uN)IcO)~TF{hTI%8fuZx%oYEYK|-1(b8NM|#ms=Wvkm+UDiFG8Na@4%rgr7^VgYKGhP0V{mN)mec=FUXR9goo zpE+3|@@^7ZP-cn)#T4NdV>c>*?c;R2KMNCPJg#y@`macT@_7@A(aDtEXFc|=-uN?Z z&Cj~CUN_gqLrw{goyYCj2ee6pf7Gt+azmK3?Ed3B4@vVC<&BE$hG`zU{)DG8mJRGC zj$BNxq-IaKn4DspADkW%NYSD-N73ndGlkaZb5H|TC-?SH%M8i4amlpi@(+;JK$n7nl81V6H`+4n;J0E-(I%=<1F`>{+$;Ez}o7+ zmNmA&I$Vk}cK^S!rbHpb{{<*(sTrS$AZi1Y)g`HO;&D8dxTFm2XD2*#2H!*aAfZQd zoP*vgglN=LN%+lIx!#T(r=_qzUCrI$zr)MkB?w=FJ6$jHcV4b`0BT#%^3*5rN6aq6 zMj&MIT(jS#e0G*9y0vM~T`U34e>Ll0J!~PEstuxZCuNjT}-u7uuB79VJDt| zkwOMGzRu%3Q(JyZ=vI9GZ4M}1X;@YnkA~(I^P(xYClm^8^U&YytFte({-?5eXFe$; z89V}&jr8xzhRw#N!_$o>WD)zH%BI@)cV!a;D%&)&#~rGSU7I#$(tj(PItE!&=7+hM z#S@o=5F@$6eNG@(;)4Tqdb*9e(~l8m5&DC7NI@>a#(U<@#n`z!St`Rmpii-)I|o0q zDUA6#37aJW(5M?yFB=+yFpU+9Kh37NqB+L*bvh3t2M?U5s%{P0`ShT8r|@6HmG*me zI_+MGhdqk`(yBTG02(N}42wp1gNQ{^e_g*?*6ZiNhOaXy%r<4Y;)zg98tQ}L>7D=Z zQEm-XI^YC2gZ$U^6x&~2sQ+4f1}u(@DFW(Sf@y zy)A=+p(w|DP-q5o)wqc5Gn~$rB$HnTBF82*W9iJlrq3|=lBu6uO=Q+j=r#7leqO%RQ-(eZ5?4(!ehbALz^P z-K6dE#+s_*ZFUD}^?i3t$oc#K-4Yj+|wKC^Xg9|X;K0jkqhN;{Wf z@`%lzRhN^6a%?MMuBX++TG;TyP}-6BxH(g6q46^+iv4Y+cLB?hdvtiP9Kkvv^ zn#a6;=umj^IGrzIa^B;`U_jj#LXB1OVSt^?QR^4eTeb_^4+jvJ;qzyPWCIz=QgC@cwQdSpk|ph7a6{Dg`Py2P)<|m@|4#NPRb^(0AfU7#2(o zm^HYa3YB^n3pw%^SmfXMKIcgMf3Bx@PT{~Q;<#Jhv8#-|)|WpGPyjrlP;4>)yzW2X z8N+VFY-7gC>5L_eVeD4V)VBOz{YIrs% zm`FOSVf%pAl#Gja?lJ(h?*;#~?{D77JG{@NVFhdqy=cCRBR!LNx_m*`zT9M}?Mf$8 zQH;|LGn7F!$AtxQWWN)Oga#dM`sxZ5O_zLW$ES%!DpxSrF%ok^LBpZ|v?eh>;yUe_ zh7^HUR#wACLKjg9f{zKj%Kpq(A7Pu-cK;HYMPOekT`3if0Jf{Cmp5AHx8+lR_ z2$#+TO=b9jW1J^(LO&BnY?!6fOVnrXuwX=auGPd~92ZWsos;Pz2dmg~2Uc~j=ZKsi zFL$|+YVHp)vy(i)f*`q05R2@o_4;Y@Zk#r`=ac4#a_nMTetLJ4EV%CvMy(dA_jR%; zjvHD9mEduQbB5(?5l!Y~615QiNTx3F>6(W(*US7N%y0mIf=pIB_6NO$Kj^jm2fgT0 zv_?5Z}8bt)avg^~32-Me~ zD5w<5np!NB8-Dc$m(ORyXnUowWwG&-_>??>M+?E@HN))O@Z$7I!eUVFoh zK2KM7z~y!Ky!#WgDC{?b7RY)A1A<5iP^E^_3JXx?hU^>-=V1|h_OG5W8*jO>ph2hV zx14Kx7euZuTkZ!7qqaopvUU;HO#2e9VhlB=Hew}el1rUS>q{bni`8X4;c3g&2|h4~ zRr*gbri%^%Ni?6=&~xC7b?n$}7lK<*_Lwq?I1+1g#6<8?(`6X~Zrv*d=QL7(Q8-2= z#R9ppDE-J)o18DZmEPfmnl9ccG!RyHH=lsq*mJMrS4h={?NAS;NG-5K68c7z&=$*a zFV(G)aZlN|+*ouu_ZbnuN$y$12Ds{G^o~f)B6a2%(cW$&W0~!W(Q((+vJh&sR7~(A>zSMA3q31xySd1Sre#zn zjSHTQeiK>c8i*-F;*{#os&}Yx1 zhK!X*8C0sTdC+7_(;7=Y zQ9cU%{yML~@H-+rPM*KX18YB**8h>f2Gqdgy`xh}dArgXj5pX5j$7>{CKkpRD7T#+y|)qw(I4SR^C-9&b4U&ZZv7cNJVGMmi9L#Rx zA-?ky#WF1utE5}B4KoWOD*Wg7FBDsif$QK6tgYO4JbOHQwe7FpUqH^0dPtCxWi|=% z3b!1P&^}A1D)qJawfK?4T(CzT_1D87tL`B4ZwI2eVh0vo1HQ;KNkh$zT>iS3w_siN zJq|sx^Q*`{5ofjH%$gV0t3{4$3U`jVbZN33`iVMf_qk~BAOdp-VS9j|j^pqW7#(z4 zrj=BmBkI-hrD6=BOTQLjV>OddRhRcFoqHyAXg?rGto-zeJY)Li)bc4YwUck21IdL# zqSa6be;qfeWggnbu~{NPnKUU&T?H%^zqtgH>{64oB&U8tRTt4ib5>Z}Iq1HJV zheu9=furr~-UzOtrA1n57cKb+$oQMHX*chHu|(JqrmgY`8H#U6hSw3B}T^!D^@2}AQ-lL#ezd@iLYxDZu?y{E)RcXG_+OIZYRkC!4s;{@G zLL_4XYAy|-F(G}zP}$=|2WS#z`xuiUF+7nl4h0Y&os`tce80Z;VD-3pUWYVv|NY^@ z_ZJb=3AXgfN5w_CZ&vv_e$428%ncEc2^bQt3=*r7gN9+TDP$Vt)#4PUpN()JGE&`PqKfgy{6Z!}|DK5>KJWM2N5`;WUP-o2wU zz|4Divk(p!64wYLNlLNLTUtDRGNw027=HVb;cFp89%=wO%@R^L)Z->L{XC(RO0te5 zYM|9Zt~4Fcx<95EuNvi38*ejM@R}+=b#P5PQ4ybP+(<5|>e7}vl?zOz(8>TIHd|UA zQBB@+ScJ3Tu%$H7>?=t%B&t1K%B#T9^1w?^8Kr>0tqPOdm6Y|=1>6X!pcH?_Fdsi%7ACbub}Xab*Y2{FQTYJWMM&VylH^<;#;cGI@C7^Q&^tP?|$yXBGHT$U;4n&E=axt z4L^0p=$;}!))H!r2J25wpI)S>F>E&vnF%jTGOlJLTC*l4wT->!cIwm(oL;133okx6 z7sMsgCgE#Fo~0|Oy=F{-Ae&5SX4!Ebbc)<_GFx!HUPZEI~{` zV;F6L`mboZ?&HSS`^~CocIANGATL5j3B-AXWHO%{tz$L)1TRRFPX|K51p~^nqrCbd z?9AzjrOD*R1%Eu^EtZH9OcT%9D-L?H_B)AwoSP52bx^_00GM}%sU<1V?VJ-lP^eCK|Bwe^1~ z3rkC^LC28r$XuhTfU?l%B_D-U7)pW06`FgPu@wvxXprO73MdN^Z_2{5+~BM1`By7l z>|j7yASbmMsV%6D_;fcS&eApcfr?5w1=zL~QK?vM_~N4s2w7wX5lVV65poL@Q*F4U zmGrkDQw@z1_q5Ewwk>h~ZzwWGdiy?3!wlX8@jeodI@|h71q}s;UA=kX!9YZw%pvp0 z15mUNFJ={KKfi&jwB*4L?U=1+U);O6d0!r=2Bh4pa2 z->isB-a?(ucg{z`vPBqu2=_o#sFw!}et@2{tr$Bk4`*55^LNWK_^)m2mCrnjy>*0^ zvVoaqedpb1D|%g|*VJw~24KP*ixV;nB}$m(>?kFcZr@nJ^6!PVr_?NXEFHa1_`h3r^26~Kxx_KH6A&{$j+@3I$4%JR zJTcD)|FrzlMywn-E(a6K8(vY;g&+!o1FHad;`-N=Mb8ZD_Fyy(v;{eIM#I=aitHK= zG#WX13+=015_k3yMMHH==#SAY-6hBDR8>s!A<6 zY7n4ZA>o#8)Vk(HCg`>24jDjPh9trY0!2%W3(&&9|HoMb_YEUm6@Yj7f4|xIy8$(1 zi==?@0#`p_P*a133QcRNVw>2cKrCt%EsC_rlKCsAdFtT3XfP&|V;*p-yD$_Qzr)O7 zLsqT#73PDVrJ2KSyU&{M)9urc+B>;(aWR}UuG5XS$d44}6qbOpm7x_$s>fgqRqFa2 zuV!<4U1W2hAzMCc%@!4g8>GI%iwGk{JB0Z3YhB$f_<5deNOFj^ab+VT_d1l3>?v_f zp9NRU%{lFIezJVZZ(-d-!>lp^h!s} zhiynqx5%}W`>2B0OmZw@18~LZr%o@qg+PbGx!zv})Eb9n4eETMJViqnf>Gy9{$%q2SSL~2n6kyGZ z1`&Er=5)ub4W`dY%`H=Pwh$Y0ZV+_d^r zCatz9(Y>OgFWshle|!dAM3@yO^_@j93HiQV{NRE%S7M|u_>-{5hol1;d6cZR%s6eL z4`D(`YJxbRYL&v~l9Dkjw{%8d&X|oJzW9j26B=wz3g@&fPNA~a^YqcI#;gUs+ckhDO-o3;BzgfZ38Ce@RI@W4J zdZEtD@b8V1FsJb7n-Uxk#j!$)&ntY@XCsInX{mrIxFB_h^dq4sqo=V2On~efRF>^+ zdYnZ;nN+lkRBf!Zv`Rg7ik#~gEwz>|)43h>&>&KL*3Rw*#vaxjPhT@UPhUq{?M?&H zU#njs-zAT=Nq}PNO`N;ldxE)A-WLV++++DZ`^~kC;@h3~r9EGu`L>j9aNpZ<(RJ}t z-Dg04@txEs@G%Aqd#28jH>b9gA_{(7Ozx2}JjW)rF$r|iv-bD4pD)@;dpxt^z6Py1 zTzSI2M!v4+`cmBsLFIm2g2UtK$9f*`>y2g%l)y205#NYX zPN0NRqQsG!A{b_g$gPg<1TSb1^k1+Bt|+jI$z_IxqF8QqnG8xjOt7E5Ju5nc3e?Mf z8Lo}W&yvrwF$7(ii{Z9-zv+?*gnVHXV2W#&?PqV@&*9uJ5C;E(ue?687@^ z-q}f2HmzT4ho4(}Qu~2VDWt|vCZe`zOH>Ns|gIdfw zzq{m!$|l#N#4>-nbcO2jEldH{<^Hx3O6#X#1bEWDOIasck$Ukj)Hd($ooI#X`M+e> zbW19>RK8i*x7zJd(&+Vgz`8uR_;o_5SdXu(uF)yZBcaY&*$3OvtIRW@VzV~%)rX_j zY4^Zl9bcTFLQyL?WP`&$x)gQ}DqBx>T%3FkR4JG*`K7zYCt#33RcF5b=pt;OLerVt<}d0t zoze;=<>T>n*0o5g644$Rtd@hNTBu?rhidS%M;B>>8!Fq0b;WJ9g?4ksfB2``P*}C@YlA6D8EyyiEa>@Q5|6T@qPpjtaES;Gq35itk!hEtrfy7ENJD>8cjcX?2}h-_s$B6^ zEK0eYwut8Ja@*DOnk0WeAiwRXGV~TGK)yj!o3O)nX^9 zx-z`g&8pP2-=&O7srH1y#)8P6PDmqQ9(^Y6Dhisq(%t(Y$sr%psV8Gt}zxi(UO%_Wv=4zFxbCw z(yLm+Q<8eOZzh2BZ6lzFb;ez)D2%FE%Voyw{9Z*>B!)k?n}%-d#b8xRTeGdIxOW&6XS2G)ruKjcb%nmHW4r&Q3|9n-K=i zuBkAxW-CRW_iC!DXbPPV?xgM}U?MCZTW6%2KB{eN%y>O#VMECg207U2N(^kUX1mL% zC3h8xk$jAIbz8A#DwYZ#QdLEDr}Z3O`PmY-c^K9ro@1$D@-Ub?grLYKlE!5SfsyH2 z&$@_aLl@#&L(;DxedeM?Gchqc*(PZ}OC&xrp&F)CMc71{Iu&DXJxCc*w|l9wIzNs? z%61WXq|-dko|Z)m%NcWXQ73H6@3~17mMg8DpjK5C!i3dY_IffUmBF@DnWFKV*(8$Z ztT$0zL(Ea8q?Af?N*J#PT0u7|I&Oh^OjyfEO2}5VU0@{E&8EgerKU8d59$3*qPgR@ zIb5`Fmpb$FlgaABQ*&Z^Jv}{E)v5|#x*tWc6fN^A`b^R8e)N73C(>+=2|&Yyv9t+C zwF#YZAu$9yGOES4h-LmQ@BjPZIR&5CM$=WnHrxcF|%9${b(tKsv#$ju-s`Sb22ji$Y(D=%&R4okNtAJ6D;aj{i) zBtmD(Z{xO>* zs_JL+%m{gR_)p`l?*(+!mq=zq;|GEro!}J}q{OZ|zx);o@8({#G?K!4uw?L1mD}^% zr=<+9C6_ev!ZwHBhAJ$)`KVN0G2n zUiDG%Sl|}n?8YErVc8z#RvhANZ{_sYli;z!Y++Svi>Y1+Z*RnJ!riJLLFaI{4TBl$ ztRGQsjYD{Q@Zb!{`En}qg^n?As}cD8`HB6SdT4DWy0Hvx2zkDA`=XU+t@Y{}ozG#C zE+;e&Wm?7WB+ z=Fr72%e<>A^A>Ou@S4`#m`J$mvs4}~o0>)oi z00SPbf#{|UV*$qLz5NUH@W4#$8zWFpIw8L^S|xu}mLDARedXU5U-K@n zW-oZoi=?MGE=BN6Yt6236*`VO=H=?YZDRX`G5=Kk5F=Q>^B0Q$DTj(Y(%| z>K%<&DxXbMk(}0Yi<0asbwsk=&NN zsvNU;yqDaBg?&EfC%TG8cFZUkp_ktB>qA>hI5!@nfDxmb0PGy3s+T(B^F#);)x zQ$U-4Fb+-LzhN}H>*n)e>g>CuR-(lYyGf>T{uoyGB!rD#G^Fg9NIlTT`aRUXbv|2; zdlgwFBibIkM4UYXH3CbkmevXWE4Z`N=q`0inTU}H$g-WLXJQ9%GrBRZXE4k~&%~Y> zw)(mElXFp~ZG5Yu^h}+V-VkkNuRY7GA3g5M66R*nRtC>b_X+UB*{+(Sh{Y~6?}hQc zeQ^nbU-)sq#^x;yC9I{DRyX;cr<$Dq0nep0SzGos8O}AhhbDxbRl)Uw%?CC<#FP6l zb=b$~Mr%QAQAZ~lrYn+x;zRCiKH+fxF}*0hR&M9Z@EJXy()=FbE!Y!N0R2Q1#0!qz zr{wvOyn2>4MzXx_HHai(_u*2gr_{V`61=r}fv)zUepmL(>yiX?_cppeDw}o7krmq zP~_+J5XgS3Z3$p3@t2=dL|0{gFLJBMqk_KI!@ce^c0thik@rN>$2FZ0x zKdTR#AahXKzwJ;QZ1)*rGLvtgHIvL-OdXV}gvKxFvSjfQ+HJMRquX*`m8;bXT|27GDQ_8k5@mm;ivHWId$oXuh3Bq!CxLG zxv!$L@Y&RpLKSR&MIJZol`|KsM|>M{sM;5fT+6)AYMo8}T}U>PRcp|qGo`apea_@3D(RbNaA^1AU;&wkP$E|TET>RI@_pP-eWr0Y`4k4>M9w~DDx1HV&(M}8Tb{WkK(B(CwD5&K1T1-6!XLWi}xtSI%Oz-8p7+k(Ze+!Ti{FUw&KBpx= z-X_kQ{c2#}tK{h8l5RC}&*ocypxN*)&sfjxkTmZJomfUK?q&`7GSh~RmbfZs-4Xq1 z-YP@*$MN`OmV4tFQ)i-GCL`H#VV7I*hDmt3I`(PXwDFk5l)dvI<74?RJS~bgOw~tE0ywd1&T%&bg;(2KQ%J z@N{e5_>^m{w%_w)B`PhGxqI9zMN)uSG=L>js`FHaMoIIjmm!H|`~rrrdC{6`i}dDV zvS7GF*CZ*pzh~b>(0UQ>s){PwHK!{VeoKx8EEa$)C}AEt4tc z@kHB}yU?n;-QtO3irV(zbGL5Ip1NMLY#kS2B4#G+s&693cmKG)$+tF@ZCzd>cja_k zsAK03AK3X$H!+j-W`L0Qg><}9*iI)A*_f&E32cJX=$V>!0k`ziwYB;K()y*iM~fNN z7aY+$X1>{V?Z`MQvyvv#PdoO>KZ4R)^UQwSGDYlhSz}i4SpL|FO*!>xR6&?b<-Ywv zwWbfs+oDd z(V+8f{?+dN0`YvNqoYPrs&%aH`lV^-(oV=JR%Uw~&t`Mm8WzfP z>BN(4D|QiP6|;m&zx1Yn$?TJ+0G<5=1r`+)Plu{DjY}6lnmX>u@U~ZpXFMQ@n;6U zaL~;Jqm#itPRYBm>^XTqwuRzj>7*BgPGsUz?@poVyt(<>BJ^2#RlxQ?!MWYB_b=VG z)NaL_@lVOqeXSjKd~5mT6Ro@d%P|&>k;V;e5!N{Zib)$KV`sZ5!Von=+#@)>0v-pYP%q2+(-Tpu)SFH`4zxxTlUM-D%gJGUAnrt<>@#-p{R7+1~kOlGb#hRF-eCEB0Th zg`=9ysEr=Wk8plYi@gfJZgk3t_>C+-#mzb`4l4Y*)wxT=Z(_L#7jWUMEy3%X5p)w=D5B9qh{(Mcqi)_}%8jEul|e;=Ndo$XrX6 zE$mbT+b;V@$51)*>1#RX`0b6L$sfs`97WADKTnnyx%fkYkMF~%+&uHk+Y;LU30ApE`sArY+SohLMmV8$5GCC%!lSJ z3Ia0jsu|OxHuL7)+*=gJ##H(nCS~5H_{BZQ=-rj9+}+?H)3IZSZ#gt3&h)`umX1Jw zt?x~Sh;5&(vyLfxqpNV~Lp`KLgVLRph zj~EZCiS{H;sfvxMA}>*=8qI-2H+fNCWA@m@bZ5lrq>z%6P71o|5%nIQnoLv%vpok> z^@!TZO-ciM<+|by(i^_QsyBG@&g|fEZMskG>Ju7`%4Am3?TfQGS>WAgDK{7=M!%zP zZdZdoZ?~h zixSxUu~{#A`2ckpYr009>0}dsx@yV_cD^cRY;ueq#q>VKc&M=Qy&_6TDo^`pE~OIQ zQHA5fu!$#59mN>b_-KxDfZDI&%^a7;sdIWB_)a+vmus>u%4JMi55*(SFf6KP>f{ z(LS?Z#73}Je&TXEEr6C^0fX~wN@e~TsLDAfyHoZRyF(dYpAPSHSlm^+@sNAS=s|DU z$VQikvwg444j)o<{OWyUR~nb8L#dtPlRKspAKkCVX$l3GDHk!msxNY`N)bq5n%wD? z_Bq_E;`5nZ`Q#SLD!4Epi%P4o(VAGv)GhHQXxbXnX)W9g{`@Ydqdk5U*H&`L7za_p^6 zC4B$Z@k#AzT=D`<$L0)S><#=*N-=sqF>D$hO-aYdCnD#ZyWML3{Nt%HR?2$r#hdtl zsUvsHx#9%qBkYH-QXb-1Jd01Kc_1Sqenq$YQQ~Pq)m=8}
X>^KgTXZw%fM_k!{zgOjw}_A;m8jW|=9&GeiLtcO>0 z+qF-B_sA*xx35@^a!4$2!wJbKomzbS;DYPy_t$X0+t3%w$h}pGmV-T?G4V&9MQcXa1#3=$HZH;qyLg4cgIf#e&-et) zWVNUcWqE094GnI{L^n6`^*Xdl@k(9YamUP?vEAA8kWnx$XyUyKzJ`fEP3J+;iD1q5 zcWgt&Uh_|LI^u-yhi^IJB6iiV=lKboi6p)A>*ul+;90l6$Fl>9Lb~_ooeLUPj!XLB zA1GsWql$kbwMuic_Nx!)ks-}@cirv3{OC%gTb8F!i+VhxhMsGuJDs}4h-E2F(ma^n zrr0c#q0%=-_x(STPpe;Ng`MZRheUxZomtVb^Rxl{Pui$>G7;*XS4??>h)$%XeiWXo-_U6pfa zR)C_T%tP>u^d)xF%b0`BNlDJQ@w*ilhuBZsC(lq}s^ zpXRhC^+^dAqpMf6t+Mgx*Zx14_up^bw`VCaN%mbfab2AULWb33HPp>^m~9D7lavag@7ZkTv{6ybV)#n$ z?a`Qv+*u>13XVQ2Z@l3C%B_XbW@VXz@<+Xfy=xoOVnaJ_Bl~BM#V)~6rb&sL7S}i4 zIMYu(ns|kW$vQ?tJoo%=gE##>`oUQeR9%~L4LY73EU{#}D#nv$9DC}pekOZO8%mhZ zLd^N$+0M$Yhpkd*%=v@^-%kg}sJw}GDG{*DecDvP*&16GbKQrwyMTKvsa*a8Bl_MV z(UvFqSL%NS!AfqApBrXB#={5W8$?oatES6y}xlq2pRgZkJQ%gD?dbB4j6%3r(3 zcpKGCuLN+Le7pY$)uPqoa8p-KQ}5KkS+maXzQxOn5#e;$I8DYd?+Wj78;3jQA2a&6 zo*eSDG?K=7FTc0L*LPQM6M$=a28F$=?W+BUOE0NWIjQ$5`_xZyE@=AM7s9M-R_&Z;Wf?`iXk`-{---8u=GB>c%l{U68l=VR$!}!Xs zP7x;k@)puB`vrK&xO#=5eGroIAtlqb`J0lSg7?B1tCc-h`91?#g@MUN!v7;B*3U;K z)WJW5;{HoyDmy7vmd*07U^^Ti}szBPxH|G|A?_w6l z@YH)e%C5mL^FijUN=6{1 zHZTeFRp*|O^jA{(QFfiOeGYTiZF>Dh@zoA@tAv(HX+o?^czfSCm#Fj=vRLfdrW?hc zY*hM;VZ6TKK&C)EXTg1*-am=V)ZDifS88ma_+YGNar^W^KK^-yXK%-f@8q!$ zIlkrE&LX~5tWVQo%828hIEKaDG2Qvu$Aeo#1Luc_$_%Db^-2=WBstt{nho1^SO)a? z49+PXbGFcaDeN(gTXwCTSJ`>%(h$QTd+C^AZwxA?eSngMM$K8;xAA4`_{y?(l+%-U zL!P|qdt;Rx(F|$1>{j{vCRMSeMnPVa8_O+?(i3m%IL};js7QS9p>tQJLrs(b`&XkJ zPIsCk8ejYOJZWG-EqS=yS1U;idTFUy^sjS`;Pao_9g{V<7jt<_Lp~ZSL^F2k^$L^F zKrATmlKB4+4K~0J^a+BzFoFAzSw1zDHjy$0PmjD|GiNSk`gF{iWv96dVrhTr79`poX zhy*WA!Z-gMg*Za1`8S!lu7$Co!G0SVbHgy3%z{tRe@KAqefRKog zV(d?fRqMmkF%y#fcM#Gs0%H??>32GG(Es^K`v2;tOc z{nNSozq=q4H8pGb`3-mJ5~Mi-Fyo-FA-WF!6Z~aeTn3>S*I+VTMq(tIQE~nbK)nU8 z^~mes9Ir##g!tK^y@N4({Cu!z&>!^+_}vR7Mpj9L9lHn;H-S?wzAnmqP$jNI>RK6# z=-Su>pnU_;u7qf9!mt?f)Ss+lKJW z$C%!D6P_>^IL1jkn9Q}Ft0x?MMh$00Vlvpn<1qGp(v3eBZtR%>TJUvEbjdD4O$gai>c%HnEOC)t>OC_+dObz9fhEMNg?HxT9e z+)*n-NDz6qc5jam0B4-A&~iQLmMVk<(f4<&Y%44v5oEcZM5hTMK?JV%$oWh-AgPn6 zpj-z+f+)Or*12srAl)R1$z2~pf=FCO{+F^dAZ;Ou>4g!51kt!d*Xur7K#C%X$=j4X zNdcnqB4uam7XTp3h`Sn%?9|iV%(8 zHD}c!Pde%cAwe`=nZJ)_fox2d10W=b#;v9wTab_GaWI4g(Rj2JofCP|=THao0R5eI^SAPmZf@u5{OYApHVyt#|ONI0y-%aay`DLBdnb?vTXPeVROJ7ewPu z!o$~;0jZaSq>=z3K{W1nZZ?+zkO=MY`rKVlgpeQ_FE#!ZPz5Ml;1Xj!>0>g41kt#3 zW=4-QP=FmhF{wWlLV{>~sGxi*1(0e%CzC#KgVr4MDhWe;t4AU3Z7Eq2 z`K!b3@1qBw0IR$Qf>KyzfcHWi-6JB5Ioj3J%NGNN_k%DZc9?)baOsM1+vDdO7=-o> z3M9{8W_KId0@#^AW}@r(0?kN#LvOSP1gXiEu5Syjrb={vuc=w37UIHN5Rl~KyQ%o< z&Up}@IS|VFqGf(h1het-!GNs#V9ArNWPFRK1f*J!Q^Hc8)uRyC_koDCI{KlH4nmWM zDX5B$7XiWwcx_)Lf%ifj_cReqH^|J-!vhmw2nG}U0>a4il+=XL!5YN`u9OC+1RY95NMEU&dxI2`W&F~ zlazufEdmYFoXhU3L$?815}*mKQwSFW0u46)AQSGhWe(U~7PK(yaRN+8I7q|3@1n0M zVa2u)Npu!$NH~Zc7{&~}=0PS8Z}@$ab~V3+I86=&9ATu>%Nr9w-v5`~9sN5%e6G(jX zn>f(k4P5R1fSChYp7r9H-cH0_Z7+~UXQLTpLUsV$SD?rBxGYg597H^~6!&zCk;PGp zBjF&G>77f7FUaFOC6I8C+SFh;WsrPkJEV|skm|Iba%hHpbc(V_I7odOX=KPk*pG>{ zC@#w*;UE?2_-5k;^3iSBg@l9DsN-IW1LV)02e@0a?$U3a?jTj_m-Ff(1OpSA$?bp5 zLGW9<3K9-hr>6ZH%OZ?Vkn$)s5P6VFmAN$jxG3OF0AH++PgNU{2QfxZlpM!2}FCq_Ou>GUI z4ilbELweS#Ob~exhjCspYt#q4&EU+fF9j}hL>|Oq){l1mV?dU7*%FZl@tBceLJ0Zt z;I~HPK}=SH?u{atjP(4)*dX#CF4I+II7^;4X@|svjVgQ9+*}kP8=s{EA`jxSr}j7F z^~jb-}K;%LEQD$*-|5A`jvZHvBdHom*3 z5qS`QeC^S`N$@Xec^D=j@nECbpQ9YVH3IK0gZp;d|Mr0hy}r&wL>|N+_!7K`AJB>p z=vA%fJqL4F;QrVJ@y95$91CGPR0%M$y`C3+36Tf!$LD>Y6E1`JqDkV*yMoAr_@iBd z^~`e+A2UgO$8!*Q5PzidU!%_i@!?3~ODI6(LHwa2uQ9X}@Cr!c^Y{mm2l0pbxp>wO zfHy}H-?LIg9>gC9Ke}Y&fSwZobA9*;y@$wy_=9*S*EQaaIz%4CAH@4=uJOF<5qS`Q5buGw#$#(lgES+lsC6oSPAO5PuNwp0vh$+=|G9_=9+hoHd^OQ$!xbAH>_Btnsos zk$9_{q7eIoc)N==9!(b_58@BvT@2QEkv)h!h(CxQO~1zb-iyeC_=EVF-D^C#encL` zAH+{qUgPx-Ao3voAbuL{8t>~6A`jvZ;)jo}@$5$tc@TdPKiqMRXEcGxgZP8^xqNH9 zfp-Wz^5fXVtJ&6I3lx;=J1_rZZ5y#oh}T)Ikt@Ni#&z8y_^K;nDDe`YHK-3Y5fr{4 zXbnm_Po1zl<@dox@$1(PTFSMV?yHMc;8V}n?qw4a?6=l3Lb;-wC2!0+@Vr)MpPAT$DSZQ+BJDGlgxQ&8lA Pe;HtAYCd5i2*v*cKQ-p4 literal 0 HcmV?d00001 diff --git a/lib/org/ciyam/AT/1.3.4/AT-1.3.4.pom b/lib/org/ciyam/AT/1.3.4/AT-1.3.4.pom new file mode 100644 index 00000000..39af6ac7 --- /dev/null +++ b/lib/org/ciyam/AT/1.3.4/AT-1.3.4.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + org.ciyam + AT + 1.3.4 + POM was created from install:install-file + diff --git a/lib/org/ciyam/AT/maven-metadata-local.xml b/lib/org/ciyam/AT/maven-metadata-local.xml new file mode 100644 index 00000000..2cf6d13a --- /dev/null +++ b/lib/org/ciyam/AT/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + org.ciyam + AT + + 1.3.4 + + 1.3.4 + + 20200414162728 + + From 8baf42765ed8502046b684c387a2a508a5c30ad2 Mon Sep 17 00:00:00 2001 From: catbref Date: Fri, 17 Apr 2020 16:56:29 +0100 Subject: [PATCH 13/15] Improved cross-chain AT and more API support for same. Reworked the cross-chain trading AT so it is now 2-stage: stage 1: 'offer' mode waiting for message from creator containing trade partner's address stage 2: 'trade' mode waiting for message from trade partner containing secret Adjusted unit tests to cover above. Changed QortalATAPI.putCreatorAddressIntoB from storing creator's public key to actually storing creator's address. Refactored BTCACCT.AtConstants to CrossChainTradeData. Now we also store hash of AT's code bytes in DB so we can look up ATs by what they do. Affects ATData class, ATRepository, etc. Added "Automated Transactions" and "Cross-Chain" API sections. New API call GET /at/byfunction/{codehash} for looking up ATs by what they do, based on hash of their code bytes. New API call GET /at/{ataddress} for fetching info for specific AT. New API call GET /at/{ataddress}/data for fetch an AT's data segment. Mostly for diagnosis of AT's current state. New API call POST /at for building a raw, unsigned DEPLOY_AT transaction. New API call GET /crosschain/tradeoffers for finding open BTC-QORT trading ATs. --- .../qortal/api/resource/ApiDefinition.java | 2 + .../org/qortal/api/resource/AtResource.java | 206 ++++++++++ .../api/resource/CrossChainResource.java | 105 +++++ src/main/java/org/qortal/at/AT.java | 9 +- src/main/java/org/qortal/at/QortalATAPI.java | 9 +- .../java/org/qortal/crosschain/BTCACCT.java | 358 ++++++++++++------ src/main/java/org/qortal/data/at/ATData.java | 27 +- .../data/crosschain/CrossChainTradeData.java | 61 +++ .../org/qortal/repository/ATRepository.java | 3 + .../repository/hsqldb/HSQLDBATRepository.java | 95 ++++- .../hsqldb/HSQLDBDatabaseUpdates.java | 5 + .../java/org/qortal/test/btcacct/AtTests.java | 239 +++++++++--- .../org/qortal/test/btcacct/DeployAT.java | 42 +- 13 files changed, 942 insertions(+), 219 deletions(-) create mode 100644 src/main/java/org/qortal/api/resource/AtResource.java create mode 100644 src/main/java/org/qortal/api/resource/CrossChainResource.java create mode 100644 src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java diff --git a/src/main/java/org/qortal/api/resource/ApiDefinition.java b/src/main/java/org/qortal/api/resource/ApiDefinition.java index 52b6ade0..c887d4bc 100644 --- a/src/main/java/org/qortal/api/resource/ApiDefinition.java +++ b/src/main/java/org/qortal/api/resource/ApiDefinition.java @@ -13,7 +13,9 @@ import io.swagger.v3.oas.annotations.tags.Tag; @Tag(name = "Admin"), @Tag(name = "Arbitrary"), @Tag(name = "Assets"), + @Tag(name = "Automated Transactions"), @Tag(name = "Blocks"), + @Tag(name = "Cross-Chain"), @Tag(name = "Groups"), @Tag(name = "Names"), @Tag(name = "Payments"), diff --git a/src/main/java/org/qortal/api/resource/AtResource.java b/src/main/java/org/qortal/api/resource/AtResource.java new file mode 100644 index 00000000..86b79da9 --- /dev/null +++ b/src/main/java/org/qortal/api/resource/AtResource.java @@ -0,0 +1,206 @@ +package org.qortal.api.resource; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; + +import org.ciyam.at.MachineState; +import org.qortal.api.ApiError; +import org.qortal.api.ApiErrors; +import org.qortal.api.ApiException; +import org.qortal.api.ApiExceptionFactory; +import org.qortal.at.QortalAtLoggerFactory; +import org.qortal.data.at.ATData; +import org.qortal.data.at.ATStateData; +import org.qortal.data.transaction.DeployAtTransactionData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.transaction.Transaction; +import org.qortal.transaction.Transaction.ValidationResult; +import org.qortal.transform.TransformationException; +import org.qortal.transform.transaction.DeployAtTransactionTransformer; +import org.qortal.utils.Base58; + +@Path("/at") +@Tag(name = "Automated Transactions") +public class AtResource { + + @Context + HttpServletRequest request; + + @GET + @Path("/byfunction/{codehash}") + @Operation( + summary = "Find automated transactions with matching functionality (code hash)", + responses = { + @ApiResponse( + description = "automated transactions", + content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = ATData.class + ) + ) + ) + ) + } + ) + @ApiErrors({ + ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE + }) + public List getByFunctionality( + @PathParam("codehash") + String codeHash58, + @Parameter(description = "whether to include ATs that can run, or not, or both (if omitted)") + @QueryParam("isExecutable") + Boolean isExecutable, + @Parameter( ref = "limit") @QueryParam("limit") Integer limit, + @Parameter( ref = "offset" ) @QueryParam("offset") Integer offset, + @Parameter( ref = "reverse" ) @QueryParam("reverse") Boolean reverse) { + // Decode codeHash + byte[] codeHash; + try { + codeHash = Base58.decode(codeHash58); + } catch (NumberFormatException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA, e); + } + + // codeHash must be present and have correct length + if (codeHash == null || codeHash.length != 32) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + // Impose a limit on 'limit' + if (limit != null && limit > 100) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + try (final Repository repository = RepositoryManager.getRepository()) { + return repository.getATRepository().getATsByFunctionality(codeHash, isExecutable, limit, offset, reverse); + } catch (ApiException e) { + throw e; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @GET + @Path("/{ataddress}") + @Operation( + summary = "Fetch info associated with AT address", + responses = { + @ApiResponse( + description = "automated transaction", + content = @Content( + schema = @Schema(implementation = ATData.class) + ) + ) + } + ) + @ApiErrors({ + ApiError.REPOSITORY_ISSUE + }) + public ATData getByAddress(@PathParam("ataddress") String atAddress) { + try (final Repository repository = RepositoryManager.getRepository()) { + return repository.getATRepository().fromATAddress(atAddress); + } catch (ApiException e) { + throw e; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @GET + @Path("/{ataddress}/data") + @Operation( + summary = "Fetch data segment associated with AT address", + responses = { + @ApiResponse( + description = "automated transaction", + content = @Content( + schema = @Schema(implementation = byte[].class) + ) + ) + } + ) + @ApiErrors({ + ApiError.REPOSITORY_ISSUE + }) + public byte[] getDataByAddress(@PathParam("ataddress") String atAddress) { + try (final Repository repository = RepositoryManager.getRepository()) { + ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); + byte[] stateData = atStateData.getStateData(); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData); + + return dataBytes; + } catch (ApiException e) { + throw e; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST + @Operation( + summary = "Build raw, unsigned, DEPLOY_AT transaction", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = DeployAtTransactionData.class + ) + ) + ), + responses = { + @ApiResponse( + description = "raw, unsigned, DEPLOY_AT transaction encoded in Base58", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) + public String createDeployAt(DeployAtTransactionData transactionData) { + if (Settings.getInstance().isApiRestricted()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); + + try (final Repository repository = RepositoryManager.getRepository()) { + Transaction transaction = Transaction.fromData(repository, transactionData); + + ValidationResult result = transaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + byte[] bytes = DeployAtTransactionTransformer.toBytes(transactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java new file mode 100644 index 00000000..83dbb0f8 --- /dev/null +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -0,0 +1,105 @@ +package org.qortal.api.resource; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; + +import org.ciyam.at.MachineState; +import org.qortal.api.ApiError; +import org.qortal.api.ApiErrors; +import org.qortal.api.ApiException; +import org.qortal.api.ApiExceptionFactory; +import org.qortal.asset.Asset; +import org.qortal.at.QortalAtLoggerFactory; +import org.qortal.crosschain.BTCACCT; +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.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; + +@Path("/crosschain") +@Tag(name = "Cross-Chain") +public class CrossChainResource { + + @Context + HttpServletRequest request; + + @GET + @Path("/tradeoffers") + @Operation( + summary = "Find cross-chain trade offers", + responses = { + @ApiResponse( + description = "automated transactions", + content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = CrossChainTradeData.class + ) + ) + ) + ) + } + ) + @ApiErrors({ + ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE + }) + public List getTradeOffers( + @Parameter( ref = "limit") @QueryParam("limit") Integer limit, + @Parameter( ref = "offset" ) @QueryParam("offset") Integer offset, + @Parameter( ref = "reverse" ) @QueryParam("reverse") Boolean reverse) { + // Impose a limit on 'limit' + if (limit != null && limit > 100) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + byte[] codeHash = BTCACCT.CODE_BYTES_HASH; + boolean isExecutable = true; + + try (final Repository repository = RepositoryManager.getRepository()) { + List atsData = repository.getATRepository().getATsByFunctionality(codeHash, isExecutable, limit, offset, reverse); + + List crossChainTradesData = new ArrayList<>(); + for (ATData atData : atsData) { + String atAddress = atData.getATAddress(); + + ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); + + CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); + crossChainTradeData.qortalAddress = atAddress; + crossChainTradeData.qortalCreator = Crypto.toAddress(atData.getCreatorPublicKey()); + crossChainTradeData.creationTimestamp = atData.getCreation(); + crossChainTradeData.qortBalance = repository.getAccountRepository().getBalance(atAddress, Asset.QORT).getBalance(); + + BTCACCT.populateTradeData(crossChainTradeData, dataBytes); + + crossChainTradesData.add(crossChainTradeData); + } + + return crossChainTradesData; + } catch (ApiException e) { + throw e; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/qortal/at/AT.java b/src/main/java/org/qortal/at/AT.java index 1ba5d8e2..d9c0adcd 100644 --- a/src/main/java/org/qortal/at/AT.java +++ b/src/main/java/org/qortal/at/AT.java @@ -58,7 +58,9 @@ public class AT { MachineState machineState = new MachineState(api, loggerFactory, deployATTransactionData.getCreationBytes()); - this.atData = new ATData(atAddress, creatorPublicKey, creation, machineState.version, assetId, machineState.getCodeBytes(), + byte[] codeHash = Crypto.digest(machineState.getCodeBytes()); + + this.atData = new ATData(atAddress, creatorPublicKey, creation, machineState.version, assetId, machineState.getCodeBytes(), codeHash, machineState.isSleeping(), machineState.getSleepUntilHeight(), machineState.isFinished(), machineState.hadFatalError(), machineState.isFrozen(), machineState.getFrozenBalance()); @@ -104,9 +106,10 @@ public class AT { boolean hadFatalError = false; boolean isFrozen = false; Long frozenBalance = null; + byte[] codeHash = Crypto.digest(codeBytes); - this.atData = new ATData(atAddress, creatorPublicKey, creation, version, Asset.QORT, codeBytes, isSleeping, sleepUntilHeight, isFinished, - hadFatalError, isFrozen, frozenBalance); + this.atData = new ATData(atAddress, creatorPublicKey, creation, version, Asset.QORT, codeBytes, codeHash, + isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance); this.atStateData = new ATStateData(atAddress, height, creation, null, null, BigDecimal.ZERO.setScale(8), true); } diff --git a/src/main/java/org/qortal/at/QortalATAPI.java b/src/main/java/org/qortal/at/QortalATAPI.java index 9455e59e..ea3f67dd 100644 --- a/src/main/java/org/qortal/at/QortalATAPI.java +++ b/src/main/java/org/qortal/at/QortalATAPI.java @@ -348,10 +348,15 @@ public class QortalATAPI extends API { @Override public void putCreatorAddressIntoB(MachineState state) { - // Simply use raw public key byte[] publicKey = atData.getCreatorPublicKey(); + String address = Crypto.toAddress(publicKey); - this.setB(state, publicKey); + // Convert to byte form as this only takes 25 bytes, + // compared to string-form's 34 bytes, + // and we only have 32 bytes available. + byte[] addressBytes = Bytes.ensureCapacity(Base58.decode(address), 32, 0); // pad to 32 bytes + + this.setB(state, addressBytes); } @Override diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index 2789fb3d..c89eb456 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -24,7 +24,9 @@ import org.ciyam.at.CompilationException; import org.ciyam.at.FunctionCode; import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; +import org.ciyam.at.Timestamp; import org.qortal.account.Account; +import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.utils.Base58; import org.qortal.utils.BitTwiddling; @@ -34,23 +36,7 @@ import com.google.common.primitives.Bytes; public class BTCACCT { public static final Coin DEFAULT_BTC_FEE = Coin.valueOf(1000L); // 0.00001000 BTC - public static final byte[] CODE_BYTES_HASH = HashCode.fromString("750012c7ae79d85a97e64e94c467c7791dd76cf3050b864f3166635a21d767c6").asBytes(); // SHA256 of AT code bytes - - public static class AtConstants { - public final byte[] secretHash; - public final BigDecimal initialPayout; - public final BigDecimal redeemPayout; - public final String recipient; - public final int refundMinutes; - - public AtConstants(byte[] secretHash, BigDecimal initialPayout, BigDecimal redeemPayout, String recipient, int refundMinutes) { - this.secretHash = secretHash; - this.initialPayout = initialPayout; - this.redeemPayout = redeemPayout; - this.recipient = recipient; - this.refundMinutes = refundMinutes; - } - } + public static final byte[] CODE_BYTES_HASH = HashCode.fromString("da7271e9aa697112ece632cf2b462fded74843944a704b9d5fd4ae5971f6686f").asBytes(); // SHA256 of AT code bytes /* * OP_TUCK (to copy public key to before signature) @@ -194,59 +180,98 @@ public class BTCACCT { return buildP2shTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, null, redeemSigScriptBuilder); } - @SuppressWarnings("unused") - public static byte[] buildQortalAT(byte[] secretHash, String recipientQortalAddress, int refundMinutes, BigDecimal initialPayout, BigDecimal redeemPayout) { + /* + * Bob generates Bitcoin private key + * private key required to sign P2SH redeem tx + * private key can be used to create 'secret' (e.g. double-SHA256) + * encrypted private key could be stored in Qortal AT for access by Bob from any node + * Bob creates Qortal AT + * Alice finds Qortal AT and wants to trade + * Alice generates Bitcoin private key + * Alice will need to send Bob her Qortal address and Bitcoin refund address + * Bob sends Alice's Qortal address to Qortal AT + * Qortal AT sends initial QORT payment to Alice (so she has QORT to send message to AT and claim funds) + * Alice receives funds and checks Qortal AT to confirm it's locked to her + * Alice creates/funds Bitcoin P2SH + * Alice requires: Bob's redeem Bitcoin address, Alice's refund Bitcoin address, derived locktime + * Bob checks P2SH is funded + * Bob requires: Bob's redeem Bitcoin address, Alice's refund Bitcoin address, derived locktime + * Bob uses secret to redeem P2SH + * Qortal core/UI will need to create, and sign, this transaction + * Alice scans P2SH redeem tx and uses secret to redeem Qortal AT + */ + public static byte[] buildQortalAT(String qortalCreator, byte[] secretHash, int offerTimeout, int tradeTimeout, BigDecimal initialPayout, BigDecimal redeemPayout, BigDecimal bitcoinAmount) { // Labels for data segment addresses int addrCounter = 0; + // Constants (with corresponding dataByteBuffer.put*() calls below) - final int addrHashPart1 = addrCounter++; - final int addrHashPart2 = addrCounter++; - final int addrHashPart3 = addrCounter++; - final int addrHashPart4 = addrCounter++; - final int addrAddressPart1 = addrCounter++; - final int addrAddressPart2 = addrCounter++; - final int addrAddressPart3 = addrCounter++; - final int addrAddressPart4 = addrCounter++; - final int addrRefundMinutes = addrCounter++; + + final int addrQortalCreator1 = addrCounter++; + final int addrQortalCreator2 = addrCounter++; + final int addrQortalCreator3 = addrCounter++; + final int addrQortalCreator4 = addrCounter++; + + final int addrSecretHash = addrCounter; + addrCounter += 4; + + final int addrOfferTimeout = addrCounter++; + final int addrTradeTimeout = addrCounter++; final int addrInitialPayoutAmount = addrCounter++; final int addrRedeemPayoutAmount = addrCounter++; - final int addrExpectedTxType = addrCounter++; - final int addrHashIndex = addrCounter++; - final int addrAddressIndex = addrCounter++; - final int addrAddressTempIndex = addrCounter++; - final int addrHashTempIndex = addrCounter++; - final int addrHashTempLength = addrCounter++; + final int addrBitcoinAmount = addrCounter++; + + final int addrMessageTxType = addrCounter++; + + final int addrSecretHashPointer = addrCounter++; + final int addrQortalRecipientPointer = addrCounter++; + final int addrMessageSenderPointer = addrCounter++; + + final int addrMessageDataPointer = addrCounter++; + final int addrMessageDataLength = addrCounter++; + final int addrEndOfConstants = addrCounter; + // Variables - final int addrRefundTimestamp = addrCounter++; - final int addrLastTimestamp = addrCounter++; + + final int addrQortalRecipient1 = addrCounter++; + final int addrQortalRecipient2 = addrCounter++; + final int addrQortalRecipient3 = addrCounter++; + final int addrQortalRecipient4 = addrCounter++; + + final int addrOfferRefundTimestamp = addrCounter++; + final int addrTradeRefundTimestamp = addrCounter++; + final int addrLastTxTimestamp = addrCounter++; final int addrBlockTimestamp = addrCounter++; final int addrTxType = addrCounter++; - final int addrComparator = addrCounter++; - final int addrAddressTemp1 = addrCounter++; - final int addrAddressTemp2 = addrCounter++; - final int addrAddressTemp3 = addrCounter++; - final int addrAddressTemp4 = addrCounter++; - final int addrHashTemp1 = addrCounter++; - final int addrHashTemp2 = addrCounter++; - final int addrHashTemp3 = addrCounter++; - final int addrHashTemp4 = addrCounter++; + final int addrResult = addrCounter++; + + final int addrMessageSender1 = addrCounter++; + final int addrMessageSender2 = addrCounter++; + final int addrMessageSender3 = addrCounter++; + final int addrMessageSender4 = addrCounter++; + + final int addrMessageData = addrCounter; + addrCounter += 4; // Data segment ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE); - // Hash of secret into HashPart1-4 - assert dataByteBuffer.position() == addrHashPart1 * MachineState.VALUE_SIZE : "addrHashPart1 incorrect"; + // AT creator's Qortal address, decoded from Base58 + assert dataByteBuffer.position() == addrQortalCreator1 * MachineState.VALUE_SIZE : "addrQortalCreator1 incorrect"; + byte[] qortalCreatorBytes = Base58.decode(qortalCreator); + dataByteBuffer.put(Bytes.ensureCapacity(qortalCreatorBytes, 32, 0)); + + // Hash of secret + assert dataByteBuffer.position() == addrSecretHash * MachineState.VALUE_SIZE : "addrSecretHash incorrect"; dataByteBuffer.put(Bytes.ensureCapacity(secretHash, 32, 0)); - // Recipient Qortal address, decoded from Base58 - assert dataByteBuffer.position() == addrAddressPart1 * MachineState.VALUE_SIZE : "addrAddressPart1 incorrect"; - byte[] recipientAddressBytes = Base58.decode(recipientQortalAddress); - dataByteBuffer.put(Bytes.ensureCapacity(recipientAddressBytes, 32, 0)); + // Open offer timeout in minutes + assert dataByteBuffer.position() == addrOfferTimeout * MachineState.VALUE_SIZE : "addrOfferTimeout incorrect"; + dataByteBuffer.putLong(offerTimeout); - // Expiry in minutes - assert dataByteBuffer.position() == addrRefundMinutes * MachineState.VALUE_SIZE : "addrRefundMinutes incorrect"; - dataByteBuffer.putLong(refundMinutes); + // Trade timeout in minutes + assert dataByteBuffer.position() == addrTradeTimeout * MachineState.VALUE_SIZE : "addrTradeTimeout incorrect"; + dataByteBuffer.putLong(tradeTimeout); // Initial payout amount assert dataByteBuffer.position() == addrInitialPayoutAmount * MachineState.VALUE_SIZE : "addrInitialPayoutAmount incorrect"; @@ -256,34 +281,42 @@ public class BTCACCT { assert dataByteBuffer.position() == addrRedeemPayoutAmount * MachineState.VALUE_SIZE : "addrRedeemPayoutAmount incorrect"; dataByteBuffer.putLong(redeemPayout.unscaledValue().longValue()); + // Expected Bitcoin amount + assert dataByteBuffer.position() == addrBitcoinAmount * MachineState.VALUE_SIZE : "addrBitcoinAmount incorrect"; + dataByteBuffer.putLong(bitcoinAmount.unscaledValue().longValue()); + // We're only interested in MESSAGE transactions - assert dataByteBuffer.position() == addrExpectedTxType * MachineState.VALUE_SIZE : "addrExpectedTxType incorrect"; + assert dataByteBuffer.position() == addrMessageTxType * MachineState.VALUE_SIZE : "addrMessageTxType incorrect"; dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value); // Index into data segment of hash, used by GET_B_IND - assert dataByteBuffer.position() == addrHashIndex * MachineState.VALUE_SIZE : "addrHashIndex incorrect"; - dataByteBuffer.putLong(addrHashPart1); + assert dataByteBuffer.position() == addrSecretHashPointer * MachineState.VALUE_SIZE : "addrSecretHashPointer incorrect"; + dataByteBuffer.putLong(addrSecretHash); // Index into data segment of recipient address, used by SET_B_IND - assert dataByteBuffer.position() == addrAddressIndex * MachineState.VALUE_SIZE : "addrAddressIndex incorrect"; - dataByteBuffer.putLong(addrAddressPart1); + assert dataByteBuffer.position() == addrQortalRecipientPointer * MachineState.VALUE_SIZE : "addrQortalRecipientPointer incorrect"; + dataByteBuffer.putLong(addrQortalRecipient1); // Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND - assert dataByteBuffer.position() == addrAddressTempIndex * MachineState.VALUE_SIZE : "addrAddressTempIndex incorrect"; - dataByteBuffer.putLong(addrAddressTemp1); + assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect"; + dataByteBuffer.putLong(addrMessageSender1); // Source location and length for hashing any passed secret - assert dataByteBuffer.position() == addrHashTempIndex * MachineState.VALUE_SIZE : "addrHashTempIndex incorrect"; - dataByteBuffer.putLong(addrHashTemp1); - assert dataByteBuffer.position() == addrHashTempLength * MachineState.VALUE_SIZE : "addrHashTempLength incorrect"; + assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect"; + dataByteBuffer.putLong(addrMessageData); + assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect"; dataByteBuffer.putLong(32L); assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants"; // Code labels - Integer labelTxLoop = null; Integer labelRefund = null; - Integer labelCheckTx = null; + + Integer labelOfferTxLoop = null; + Integer labelCheckOfferTx = null; + + Integer labelTradeTxLoop = null; + Integer labelCheckTradeTx = null; ByteBuffer codeByteBuffer = ByteBuffer.allocate(512); @@ -295,83 +328,136 @@ public class BTCACCT { /* Initialization */ // Use AT creation 'timestamp' as starting point for finding transactions sent to AT - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTimestamp)); - // Calculate refund 'timestamp' by adding minutes to above 'timestamp', then save into addrRefundTimestamp - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTimestamp, addrRefundMinutes)); + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp)); - // Load recipient's address into B register - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAddressIndex)); - // Send initial payment to recipient so they have enough funds to message AT if all goes well - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrInitialPayoutAmount)); + // Calculate offer timeout refund 'timestamp' by adding addrOfferTimeout minutes to above 'timestamp', then save into addrOfferRefundTimestamp + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrOfferRefundTimestamp, addrLastTxTimestamp, addrOfferTimeout)); // Set restart position to after this opcode codeByteBuffer.put(OpCode.SET_PCS.compile()); - /* Main loop */ + /* Loop, waiting for offer timeout or message from AT owner containing trade partner details */ // Fetch current block 'timestamp' codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp)); - // If we're not past refund 'timestamp' then look for next transaction - codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelTxLoop))); - // We're past refund 'timestamp' so go refund everything back to AT creator + // If we're not past offer timeout refund 'timestamp' then look for next transaction + codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrOfferRefundTimestamp, calcOffset(codeByteBuffer, labelOfferTxLoop))); + // We've past offer timeout refund 'timestamp' so go refund everything back to AT creator codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); /* Transaction processing loop */ - labelTxLoop = codeByteBuffer.position(); + labelOfferTxLoop = codeByteBuffer.position(); // Find next transaction to this AT since the last one (if any) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTimestamp)); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp)); // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrComparator)); - // If addrComparator is zero (i.e. A is non-zero, transaction was found) then go check transaction - codeByteBuffer.put(OpCode.BZR_DAT.compile(addrComparator, calcOffset(codeByteBuffer, labelCheckTx))); + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult)); + // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction + codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckOfferTx))); // Stop and wait for next block codeByteBuffer.put(OpCode.STP_IMD.compile()); /* Check transaction */ - labelCheckTx = codeByteBuffer.position(); + labelCheckOfferTx = codeByteBuffer.position(); // Update our 'last found transaction's timestamp' using 'timestamp' from transaction - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTimestamp)); + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp)); // Extract transaction type (message/payment) from transaction and save type in addrTxType codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType)); // If transaction type is not MESSAGE type then go look for another transaction - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrExpectedTxType, calcOffset(codeByteBuffer, labelTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrMessageTxType, calcOffset(codeByteBuffer, labelOfferTxLoop))); /* Check transaction's sender */ // Extract sender address from transaction into B register codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B)); - // Save B register into data segment starting at addrAddressTemp1 (as pointed to by addrAddressTempIndex) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrAddressTempIndex)); + // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer)); // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp1, addrAddressPart1, calcOffset(codeByteBuffer, labelTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp2, addrAddressPart2, calcOffset(codeByteBuffer, labelTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp3, addrAddressPart3, calcOffset(codeByteBuffer, labelTxLoop))); - codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp4, addrAddressPart4, calcOffset(codeByteBuffer, labelTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalCreator1, calcOffset(codeByteBuffer, labelOfferTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalCreator2, calcOffset(codeByteBuffer, labelOfferTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalCreator3, calcOffset(codeByteBuffer, labelOfferTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalCreator4, calcOffset(codeByteBuffer, labelOfferTxLoop))); + + /* Extract trade partner info from message */ + + // Extract message from transaction into B register + codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); + // Save B register into data segment starting at addrQortalRecipient1 (as pointed to by addrQortalRecipientPointer) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalRecipientPointer)); + // Send initial payment to recipient so they have enough funds to message AT if all goes well + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrInitialPayoutAmount)); + + // Calculate trade timeout refund 'timestamp' by adding addrTradeTimeout minutes to above message's 'timestamp', then save into addrTradeRefundTimestamp + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrTradeRefundTimestamp, addrLastTxTimestamp, addrTradeTimeout)); + + // Set restart position to after this opcode + codeByteBuffer.put(OpCode.SET_PCS.compile()); + + /* Loop, waiting for trade timeout or message from Qortal trade recipient containing secret */ + + // Fetch current block 'timestamp' + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp)); + // If we're not past refund 'timestamp' then look for next transaction + codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrTradeRefundTimestamp, calcOffset(codeByteBuffer, labelTradeTxLoop))); + // We're past refund 'timestamp' so go refund everything back to AT creator + codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); + + /* Transaction processing loop */ + labelTradeTxLoop = codeByteBuffer.position(); + + // Find next transaction to this AT since the last one (if any) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxTimestamp)); + // If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0. + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult)); + // If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction + codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTx))); + // Stop and wait for next block + codeByteBuffer.put(OpCode.STP_IMD.compile()); + + /* Check transaction */ + labelCheckTradeTx = codeByteBuffer.position(); + + // Update our 'last found transaction's timestamp' using 'timestamp' from transaction + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxTimestamp)); + // Extract transaction type (message/payment) from transaction and save type in addrTxType + codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType)); + // If transaction type is not MESSAGE type then go look for another transaction + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrMessageTxType, calcOffset(codeByteBuffer, labelTradeTxLoop))); + + /* Check transaction's sender */ + + // Extract sender address from transaction into B register + codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B)); + // Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer)); + // Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction. + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalRecipient1, calcOffset(codeByteBuffer, labelTradeTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalRecipient2, calcOffset(codeByteBuffer, labelTradeTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalRecipient3, calcOffset(codeByteBuffer, labelTradeTxLoop))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalRecipient4, calcOffset(codeByteBuffer, labelTradeTxLoop))); /* Check 'secret' in transaction's message */ // Extract message from transaction into B register codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); - // Save B register into data segment starting at addrHashTemp1 (as pointed to by addrHashTempIndex) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrHashTempIndex)); - // Load B register with expected hash result (as pointed to by addrHashIndex) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrHashIndex)); - // Perform HASH160 using source data at addrHashTemp1 through addrHashTemp4. (Location and length specified via addrHashTempIndex and addrHashTemplength). - // Save the equality result (1 if they match, 0 otherwise) into addrComparator. - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrComparator, addrHashTempIndex, addrHashTempLength)); - // If hashes don't match, addrComparator will be zero so go find another transaction - codeByteBuffer.put(OpCode.BZR_DAT.compile(addrComparator, calcOffset(codeByteBuffer, labelTxLoop))); + // Save B register into data segment starting at addrMessageData (as pointed to by addrMessageDataPointer) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageDataPointer)); + // Load B register with expected hash result (as pointed to by addrSecretHashPointer) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrSecretHashPointer)); + // Perform HASH160 using source data at addrMessageData. (Location and length specified via addrMessageDataPointer and addrMessageDataLength). + // Save the equality result (1 if they match, 0 otherwise) into addrResult. + codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength)); + // If hashes don't match, addrResult will be zero so go find another transaction + codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelTradeTxLoop))); /* Success! Pay arranged amount to intended recipient */ - // Load B register with intended recipient address (as pointed to by addrAddressIndex) - codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAddressIndex)); + // Load B register with intended recipient address (as pointed to by addrQortalRecipientPointer) + codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrQortalRecipientPointer)); // Pay AT's balance to recipient codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrRedeemPayoutAmount)); - // We're finished forever - codeByteBuffer.put(OpCode.FIN_IMD.compile()); + // Fall-through to refunding any remaining balance back to AT creator /* Refund balance back to AT creator */ labelRefund = codeByteBuffer.position(); @@ -400,23 +486,63 @@ public class BTCACCT { return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount); } - public static AtConstants extractAtConstants(byte[] dataBytes) { + public static void populateTradeData(CrossChainTradeData tradeData, byte[] dataBytes) { ByteBuffer dataByteBuffer = ByteBuffer.wrap(dataBytes); - - byte[] secretHash = new byte[32]; - dataByteBuffer.get(secretHash); - byte[] addressBytes = new byte[32]; + + // Skip AT creator address + dataByteBuffer.position(dataByteBuffer.position() + 32); + + // Hash of secret + tradeData.secretHash = new byte[32]; + dataByteBuffer.get(tradeData.secretHash); + + // Offer timeout + tradeData.offerRefundTimeout = dataByteBuffer.getLong(); + + // Trade timeout + tradeData.tradeRefundTimeout = dataByteBuffer.getLong(); + + // Initial payout + tradeData.initialPayout = BigDecimal.valueOf(dataByteBuffer.getLong(), 8); + + // Redeem payout + tradeData.redeemPayout = BigDecimal.valueOf(dataByteBuffer.getLong(), 8); + + // Expected BTC amount + tradeData.expectedBitcoin = BigDecimal.valueOf(dataByteBuffer.getLong(), 8); + + // Skip MESSAGE transaction type + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip pointer to secretHash + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip pointer to Qortal recipient + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip pointer to message sender + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip pointer to message data + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Skip message data length + dataByteBuffer.position(dataByteBuffer.position() + 8); + + // Qortal recipient (if any) dataByteBuffer.get(addressBytes); - String recipient = Base58.encode(Arrays.copyOf(addressBytes, Account.ADDRESS_LENGTH)); + if (addressBytes[0] != 0) + tradeData.qortalRecipient = Base58.encode(Arrays.copyOf(addressBytes, Account.ADDRESS_LENGTH)); - int refundMinutes = (int) dataByteBuffer.getLong(); + // Open offer timeout (AT 'timestamp' converted to Qortal block height) + long offerRefundTimestamp = dataByteBuffer.getLong(); + tradeData.offerRefundHeight = new Timestamp(offerRefundTimestamp).blockHeight; - BigDecimal initialPayout = BigDecimal.valueOf(dataByteBuffer.getLong(), 8); - - BigDecimal redeemPayout = BigDecimal.valueOf(dataByteBuffer.getLong(), 8); - - return new AtConstants(secretHash, initialPayout, redeemPayout, recipient, refundMinutes); + // Trade offer timeout (AT 'timestamp' converted to Qortal block height) + long tradeRefundTimestamp = dataByteBuffer.getLong(); + if (tradeRefundTimestamp != 0) + tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight; } } diff --git a/src/main/java/org/qortal/data/at/ATData.java b/src/main/java/org/qortal/data/at/ATData.java index b12439e0..2e2a0c49 100644 --- a/src/main/java/org/qortal/data/at/ATData.java +++ b/src/main/java/org/qortal/data/at/ATData.java @@ -2,6 +2,11 @@ package org.qortal.data.at; import java.math.BigDecimal; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) public class ATData { // Properties @@ -11,6 +16,7 @@ public class ATData { private int version; private long assetId; private byte[] codeBytes; + private byte[] codeHash; private boolean isSleeping; private Integer sleepUntilHeight; private boolean isFinished; @@ -20,14 +26,19 @@ public class ATData { // Constructors - public ATData(String ATAddress, byte[] creatorPublicKey, long creation, int version, long assetId, byte[] codeBytes, boolean isSleeping, - Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, BigDecimal frozenBalance) { + // necessary for JAX-RS serialization + protected ATData() { + } + + public ATData(String ATAddress, byte[] creatorPublicKey, long creation, int version, long assetId, byte[] codeBytes, byte[] codeHash, + boolean isSleeping, Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, BigDecimal frozenBalance) { this.ATAddress = ATAddress; this.creatorPublicKey = creatorPublicKey; this.creation = creation; this.version = version; this.assetId = assetId; this.codeBytes = codeBytes; + this.codeHash = codeHash; this.isSleeping = isSleeping; this.sleepUntilHeight = sleepUntilHeight; this.isFinished = isFinished; @@ -36,10 +47,10 @@ public class ATData { this.frozenBalance = frozenBalance; } - public ATData(String ATAddress, byte[] creatorPublicKey, long creation, int version, long assetId, byte[] codeBytes, boolean isSleeping, - Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, Long frozenBalance) { - this(ATAddress, creatorPublicKey, creation, version, assetId, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, - (BigDecimal) null); + public ATData(String ATAddress, byte[] creatorPublicKey, long creation, int version, long assetId, byte[] codeBytes, byte[] codeHash, + boolean isSleeping, Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, Long frozenBalance) { + this(ATAddress, creatorPublicKey, creation, version, assetId, codeBytes, codeHash, + isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, (BigDecimal) null); // Convert Long frozenBalance to BigDecimal if (frozenBalance != null) @@ -80,6 +91,10 @@ public class ATData { return this.codeBytes; } + public byte[] getCodeHash() { + return this.codeHash; + } + public boolean getIsSleeping() { return this.isSleeping; } diff --git a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java new file mode 100644 index 00000000..d055c830 --- /dev/null +++ b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java @@ -0,0 +1,61 @@ +package org.qortal.data.crosschain; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +// All properties to be converted to JSON via JAXB +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainTradeData { + + // Properties + + @Schema(description = "AT's Qortal address") + public String qortalAddress; + + @Schema(description = "AT creator's Qortal address") + public String qortalCreator; + + @Schema(description = "Timestamp when AT was created (milliseconds since epoch)") + public long creationTimestamp; + + @Schema(description = "AT's current QORT balance") + public BigDecimal qortBalance; + + @Schema(description = "HASH160 of 32-byte secret") + public byte[] secretHash; + + @Schema(description = "Initial QORT payment that will be sent to Qortal trade partner") + public BigDecimal initialPayout; + + @Schema(description = "Final QORT payment that will be sent to Qortal trade partner") + public BigDecimal redeemPayout; + + @Schema(description = "Trade partner's Qortal address (trade begins when this is set)") + public String qortalRecipient; + + @Schema(description = "How long from AT creation until AT triggers automatic refund to AT creator (minutes)") + public long offerRefundTimeout; + + @Schema(description = "Actual Qortal block height when AT will automatically refund to AT creator (before trade begins)") + public int offerRefundHeight; + + @Schema(description = "How long from beginning trade until AT triggers automatic refund to AT creator (minutes)") + public long tradeRefundTimeout; + + @Schema(description = "Actual Qortal block height when AT will automatically refund to AT creator (after trade begins)") + public Integer tradeRefundHeight; + + @Schema(description = "Amount, in BTC, that AT creator expects Bitcoin P2SH to pay out (excluding miner fees)") + public BigDecimal expectedBitcoin; + + // Constructors + + // Necessary for JAXB + public CrossChainTradeData() { + } + +} diff --git a/src/main/java/org/qortal/repository/ATRepository.java b/src/main/java/org/qortal/repository/ATRepository.java index 9653fc6c..affbaf18 100644 --- a/src/main/java/org/qortal/repository/ATRepository.java +++ b/src/main/java/org/qortal/repository/ATRepository.java @@ -18,6 +18,9 @@ public interface ATRepository { /** Returns list of executable ATs, empty if none found */ public List getAllExecutableATs() throws DataException; + /** Returns list of ATs with matching code hash, optionally executable only. */ + public List getATsByFunctionality(byte[] codeHash, Boolean isExecutable, Integer limit, Integer offset, Boolean reverse) throws DataException; + /** Returns creation block height given AT's address or null if not found */ public Integer getATCreationBlockHeight(String atAddress) throws DataException; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java index dd85a241..e161437a 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java @@ -26,7 +26,7 @@ public class HSQLDBATRepository implements ATRepository { @Override public ATData fromATAddress(String atAddress) throws DataException { - String sql = "SELECT creator, creation, version, asset_id, code_bytes, " + String sql = "SELECT creator, creation, version, asset_id, code_bytes, code_hash, " + "is_sleeping, sleep_until_height, is_finished, had_fatal_error, " + "is_frozen, frozen_balance " + "FROM ATs " @@ -41,20 +41,21 @@ public class HSQLDBATRepository implements ATRepository { int version = resultSet.getInt(3); long assetId = resultSet.getLong(4); byte[] codeBytes = resultSet.getBytes(5); // Actually BLOB - boolean isSleeping = resultSet.getBoolean(6); + byte[] codeHash = resultSet.getBytes(6); + boolean isSleeping = resultSet.getBoolean(7); - Integer sleepUntilHeight = resultSet.getInt(7); + Integer sleepUntilHeight = resultSet.getInt(8); if (sleepUntilHeight == 0 && resultSet.wasNull()) sleepUntilHeight = null; - boolean isFinished = resultSet.getBoolean(8); - boolean hadFatalError = resultSet.getBoolean(9); - boolean isFrozen = resultSet.getBoolean(10); + boolean isFinished = resultSet.getBoolean(9); + boolean hadFatalError = resultSet.getBoolean(10); + boolean isFrozen = resultSet.getBoolean(11); - BigDecimal frozenBalance = resultSet.getBigDecimal(11); + BigDecimal frozenBalance = resultSet.getBigDecimal(12); - return new ATData(atAddress, creatorPublicKey, creation, version, assetId, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, - isFrozen, frozenBalance); + return new ATData(atAddress, creatorPublicKey, creation, version, assetId, codeBytes, codeHash, + isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance); } catch (SQLException e) { throw new DataException("Unable to fetch AT from repository", e); } @@ -71,7 +72,7 @@ public class HSQLDBATRepository implements ATRepository { @Override public List getAllExecutableATs() throws DataException { - String sql = "SELECT AT_address, creator, creation, version, asset_id, code_bytes, " + String sql = "SELECT AT_address, creator, creation, version, asset_id, code_bytes, code_hash, " + "is_sleeping, sleep_until_height, had_fatal_error, " + "is_frozen, frozen_balance " + "FROM ATs " @@ -93,19 +94,20 @@ public class HSQLDBATRepository implements ATRepository { int version = resultSet.getInt(4); long assetId = resultSet.getLong(5); byte[] codeBytes = resultSet.getBytes(6); // Actually BLOB - boolean isSleeping = resultSet.getBoolean(7); + byte[] codeHash = resultSet.getBytes(7); + boolean isSleeping = resultSet.getBoolean(8); - Integer sleepUntilHeight = resultSet.getInt(8); + Integer sleepUntilHeight = resultSet.getInt(9); if (sleepUntilHeight == 0 && resultSet.wasNull()) sleepUntilHeight = null; - boolean hadFatalError = resultSet.getBoolean(9); - boolean isFrozen = resultSet.getBoolean(10); + boolean hadFatalError = resultSet.getBoolean(10); + boolean isFrozen = resultSet.getBoolean(11); - BigDecimal frozenBalance = resultSet.getBigDecimal(11); + BigDecimal frozenBalance = resultSet.getBigDecimal(12); - ATData atData = new ATData(atAddress, creatorPublicKey, creation, version, assetId, codeBytes, isSleeping, sleepUntilHeight, isFinished, - hadFatalError, isFrozen, frozenBalance); + ATData atData = new ATData(atAddress, creatorPublicKey, creation, version, assetId, codeBytes, codeHash, + isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance); executableATs.add(atData); } while (resultSet.next()); @@ -116,6 +118,62 @@ public class HSQLDBATRepository implements ATRepository { } } + @Override + public List getATsByFunctionality(byte[] codeHash, Boolean isExecutable, Integer limit, Integer offset, Boolean reverse) throws DataException { + StringBuilder sql = new StringBuilder(512); + sql.append("SELECT AT_address, creator, creation, version, asset_id, code_bytes, ") + .append("is_sleeping, sleep_until_height, is_finished, had_fatal_error, ") + .append("is_frozen, frozen_balance ") + .append("FROM ATs ") + .append("WHERE code_hash = ? "); + + if (isExecutable != null) + sql.append("AND is_finished = ").append(isExecutable ? "false" : "true"); + + sql.append(" ORDER BY creation "); + if (reverse != null && reverse) + sql.append("DESC"); + + HSQLDBRepository.limitOffsetSql(sql, limit, offset); + + List matchingATs = new ArrayList<>(); + + try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), codeHash)) { + if (resultSet == null) + return matchingATs; + + do { + String atAddress = resultSet.getString(1); + byte[] creatorPublicKey = resultSet.getBytes(2); + long creation = getZonedTimestampMilli(resultSet, 3); + int version = resultSet.getInt(4); + long assetId = resultSet.getLong(5); + byte[] codeBytes = resultSet.getBytes(6); // Actually BLOB + boolean isSleeping = resultSet.getBoolean(7); + + Integer sleepUntilHeight = resultSet.getInt(8); + if (sleepUntilHeight == 0 && resultSet.wasNull()) + sleepUntilHeight = null; + + boolean isFinished = resultSet.getBoolean(9); + + boolean hadFatalError = resultSet.getBoolean(10); + boolean isFrozen = resultSet.getBoolean(11); + + BigDecimal frozenBalance = resultSet.getBigDecimal(12); + + ATData atData = new ATData(atAddress, creatorPublicKey, creation, version, assetId, codeBytes, codeHash, + isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance); + + matchingATs.add(atData); + } while (resultSet.next()); + + return matchingATs; + } catch (SQLException e) { + throw new DataException("Unable to fetch matching ATs from repository", e); + } + } + @Override public Integer getATCreationBlockHeight(String atAddress) throws DataException { String sql = "SELECT height " @@ -140,7 +198,8 @@ public class HSQLDBATRepository implements ATRepository { HSQLDBSaver saveHelper = new HSQLDBSaver("ATs"); saveHelper.bind("AT_address", atData.getATAddress()).bind("creator", atData.getCreatorPublicKey()).bind("creation", toOffsetDateTime(atData.getCreation())) - .bind("version", atData.getVersion()).bind("asset_id", atData.getAssetId()).bind("code_bytes", atData.getCodeBytes()) + .bind("version", atData.getVersion()).bind("asset_id", atData.getAssetId()) + .bind("code_bytes", atData.getCodeBytes()).bind("code_hash", atData.getCodeHash()) .bind("is_sleeping", atData.getIsSleeping()).bind("sleep_until_height", atData.getSleepUntilHeight()) .bind("is_finished", atData.getIsFinished()).bind("had_fatal_error", atData.getHadFatalError()).bind("is_frozen", atData.getIsFrozen()) .bind("frozen_balance", atData.getFrozenBalance()); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 562f5030..c279c22a 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -980,6 +980,11 @@ public class HSQLDBDatabaseUpdates { stmt.execute("ALTER TABLE ATStates ADD COLUMN is_initial BOOLEAN NOT NULL DEFAULT TRUE"); break; + case 72: + // For ATs, add hash of code bytes to allow searching for specific function ATs, e.g. cross-chain trading + stmt.execute("ALTER TABLE ATs ADD COLUMN code_hash VARBINARY(32) NOT NULL BEFORE is_sleeping"); // Assuming something like SHA256 + break; + default: // nothing to do return false; diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java index 67968af6..21f4166c 100644 --- a/src/test/java/org/qortal/test/btcacct/AtTests.java +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -10,7 +10,9 @@ import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.Arrays; import java.util.List; +import java.util.function.Function; +import org.bitcoinj.core.Base58; import org.ciyam.at.MachineState; import org.junit.Before; import org.junit.Test; @@ -22,6 +24,7 @@ import org.qortal.crosschain.BTCACCT; 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; @@ -37,6 +40,7 @@ import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import com.google.common.hash.HashCode; +import com.google.common.primitives.Bytes; public class AtTests extends Common { @@ -46,6 +50,7 @@ public class AtTests extends Common { public static final BigDecimal initialPayout = new BigDecimal("0.001").setScale(8); public static final BigDecimal redeemAmount = new BigDecimal("80.4020").setScale(8); public static final BigDecimal fundingAmount = new BigDecimal("123.456").setScale(8); + public static final BigDecimal bitcoinAmount = new BigDecimal("0.00864200").setScale(8); @Before public void beforeTest() throws DataException { @@ -54,9 +59,9 @@ public class AtTests extends Common { @Test public void testCompile() { - String redeemAddress = Common.getTestAccount(null, "chloe").getAddress(); + Account deployer = Common.getTestAccount(null, "chloe"); - byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, redeemAddress, refundTimeout, initialPayout, redeemAmount); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); } @@ -69,7 +74,7 @@ public class AtTests extends Common { BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); BigDecimal expectedBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtTransaction.getTransactionData().getFee()); BigDecimal actualBalance = deployer.getBalance(Asset.QORT); @@ -106,6 +111,37 @@ public class AtTests extends Common { } } + @SuppressWarnings("unused") + @Test + public void testAutomaticOfferRefund() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); + BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee); + + checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + + describeAt(repository, atAddress); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + BigDecimal expectedBalance = deployersPostDeploymentBalance; + BigDecimal actualBalance = deployer.getBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); + } + } + @SuppressWarnings("unused") @Test public void testInitialPayment() throws DataException { @@ -116,9 +152,15 @@ public class AtTests extends Common { BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); - // Initial payment should happen 1st block after deployment + // Send recipient's address to AT + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(recipient.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress); + + // Initial payment should happen 1st block after receiving recipient address BlockUtils.mintBlock(repository); BigDecimal expectedBalance = recipientsInitialBalance.add(initialPayout); @@ -126,6 +168,8 @@ public class AtTests extends Common { Common.assertEqualBigDecimals("Recipient's post-initial-payout balance incorrect", expectedBalance, actualBalance); + describeAt(repository, atAddress); + // Test orphaning BlockUtils.orphanLastBlock(repository); @@ -136,9 +180,41 @@ public class AtTests extends Common { } } + // TEST SENDING RECIPIENT ADDRESS BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) @SuppressWarnings("unused") @Test - public void testAutomaticRefund() throws DataException { + public void testIncorrectTradeSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); + PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); + + BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); + BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + // Send recipient's address to AT BUT NOT FROM AT CREATOR + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(recipient.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, bystander, recipientAddressBytes, atAddress); + + // Initial payment should NOT happen + BlockUtils.mintBlock(repository); + + BigDecimal expectedBalance = recipientsInitialBalance; + BigDecimal actualBalance = recipient.getConfirmedBalance(Asset.QORT); + + Common.assertEqualBigDecimals("Recipient's post-initial-payout balance incorrect", expectedBalance, actualBalance); + + describeAt(repository, atAddress); + } + } + + @SuppressWarnings("unused") + @Test + public void testAutomaticTradeRefund() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); @@ -146,15 +222,28 @@ public class AtTests extends Common { BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + // Send recipient's address to AT + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(recipient.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress); + + // Initial payment should happen 1st block after receiving recipient address + BlockUtils.mintBlock(repository); BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); - BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee); + BigDecimal messageFee = messageTransaction.getTransactionData().getFee(); + BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee).subtract(messageFee); checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + describeAt(repository, atAddress); + // Test orphaning BlockUtils.orphanLastBlock(repository); + BlockUtils.orphanLastBlock(repository); BigDecimal expectedBalance = deployersPostDeploymentBalance; BigDecimal actualBalance = deployer.getBalance(Asset.QORT); @@ -173,12 +262,19 @@ public class AtTests extends Common { BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); + // Send recipient's address to AT + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(recipient.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress); + + // Initial payment should happen 1st block after receiving recipient address + BlockUtils.mintBlock(repository); + // Send correct secret to AT - MessageTransaction messageTransaction = sendSecret(repository, recipient, secret, atAddress); + messageTransaction = sendMessage(repository, recipient, secret, atAddress); // AT should send funds in the next block ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); @@ -189,6 +285,8 @@ public class AtTests extends Common { Common.assertEqualBigDecimals("Recipent's post-redeem balance incorrect", expectedBalance, actualBalance); + describeAt(repository, atAddress); + // Orphan redeem BlockUtils.orphanLastBlock(repository); @@ -215,14 +313,21 @@ public class AtTests extends Common { BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); + // Send recipient's address to AT + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(recipient.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress); + + // Initial payment should happen 1st block after receiving recipient address + BlockUtils.mintBlock(repository); + // Send correct secret to AT, but from wrong account - MessageTransaction messageTransaction = sendSecret(repository, bystander, secret, atAddress); + messageTransaction = sendMessage(repository, bystander, secret, atAddress); // AT should NOT send funds in the next block ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); @@ -233,6 +338,8 @@ public class AtTests extends Common { Common.assertEqualBigDecimals("Recipent's balance incorrect", expectedBalance, actualBalance); + describeAt(repository, atAddress); + checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); } } @@ -247,15 +354,22 @@ public class AtTests extends Common { BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); Account at = deployAtTransaction.getATAccount(); String atAddress = at.getAddress(); + // Send recipient's address to AT + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(recipient.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress); + + // Initial payment should happen 1st block after receiving recipient address + BlockUtils.mintBlock(repository); + // Send correct secret to AT, but from wrong account byte[] wrongSecret = Crypto.digest(secret); - MessageTransaction messageTransaction = sendSecret(repository, recipient, wrongSecret, atAddress); + messageTransaction = sendMessage(repository, recipient, wrongSecret, atAddress); // AT should NOT send funds in the next block ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); @@ -266,6 +380,8 @@ public class AtTests extends Common { Common.assertEqualBigDecimals("Recipent's balance incorrect", expectedBalance, actualBalance); + describeAt(repository, atAddress); + checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); } } @@ -280,10 +396,10 @@ public class AtTests extends Common { BigDecimal deployersInitialBalance = deployer.getBalance(Asset.QORT); BigDecimal recipientsInitialBalance = recipient.getBalance(Asset.QORT); - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, recipient); + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer); List executableAts = repository.getATRepository().getAllExecutableATs(); - + for (ATData atData : executableAts) { String atAddress = atData.getATAddress(); byte[] codeBytes = atData.getCodeBytes(); @@ -299,37 +415,13 @@ public class AtTests extends Common { if (!Arrays.equals(codeHash, BTCACCT.CODE_BYTES_HASH)) continue; - ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); - byte[] stateData = atStateData.getStateData(); - - QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); - byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData); - - BTCACCT.AtConstants atConstants = BTCACCT.extractAtConstants(dataBytes); - - long autoRefundTimestamp = atData.getCreation() + atConstants.refundMinutes * 60 * 1000L; - - String autoRefundString = LocalDateTime.ofInstant(Instant.ofEpochMilli(autoRefundTimestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - System.out.println(String.format("%s:\n" - + "\tcreator: %s,\n" - + "\tHASH160 of secret: %s,\n" - + "\trecipient: %s,\n" - + "\tinitial payout: %s QORT,\n" - + "\tredeem payout: %s QORT,\n" - + "\tauto-refund at: %s (local time)", - atAddress, - Crypto.toAddress(atData.getCreatorPublicKey()), - HashCode.fromBytes(atConstants.secretHash).toString().substring(0, 40), - atConstants.recipient, - atConstants.initialPayout.toPlainString(), - atConstants.redeemPayout.toPlainString(), - autoRefundString)); + describeAt(repository, atAddress); } } } - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, Account recipient) throws DataException { - byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, recipient.getAddress(), refundTimeout, initialPayout, redeemAmount); + private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer) throws DataException { + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); long txTimestamp = System.currentTimeMillis(); byte[] lastReference = deployer.getLastReference(); @@ -341,7 +433,7 @@ public class AtTests extends Common { BigDecimal fee = BigDecimal.ZERO; String name = "QORT-BTC cross-chain trade"; - String description = String.format("Qortal-Bitcoin cross-chain trade between %s and %s", deployer.getAddress(), recipient.getAddress()); + String description = String.format("Qortal-Bitcoin cross-chain trade"); String atType = "ACCT"; String tags = "QORT-BTC ACCT"; @@ -358,7 +450,7 @@ public class AtTests extends Common { return deployAtTransaction; } - private MessageTransaction sendSecret(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { + private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { long txTimestamp = System.currentTimeMillis(); byte[] lastReference = sender.getLastReference(); @@ -400,4 +492,61 @@ public class AtTests extends Common { assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance.toPlainString(), expectedMaximumBalance.toPlainString()), actualBalance.compareTo(expectedMaximumBalance) < 0); } + private void describeAt(Repository repository, String atAddress) throws DataException { + ATData atData = repository.getATRepository().fromATAddress(atAddress); + + ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); + byte[] stateData = atStateData.getStateData(); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData); + + CrossChainTradeData tradeData = new CrossChainTradeData(); + tradeData.qortalAddress = atAddress; + tradeData.qortalCreator = Crypto.toAddress(atData.getCreatorPublicKey()); + tradeData.creationTimestamp = atData.getCreation(); + tradeData.qortBalance = repository.getAccountRepository().getBalance(atAddress, Asset.QORT).getBalance(); + + BTCACCT.populateTradeData(tradeData, dataBytes); + + Function 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" + + "\tcreator: %s,\n" + + "\tcreation timestamp: %s,\n" + + "\tcurrent balance: %s QORT,\n" + + "\tHASH160 of secret: %s,\n" + + "\tinitial payout: %s QORT,\n" + + "\tredeem payout: %s QORT,\n" + + "\texpected bitcoin: %s BTC,\n" + + "\toffer timeout: %d minutes (from creation),\n" + + "\ttrade timeout: %d minutes (from trade start),\n" + + "\tcurrent block height: %d,\n", + tradeData.qortalAddress, + tradeData.qortalCreator, + epochMilliFormatter.apply(tradeData.creationTimestamp), + tradeData.qortBalance.toPlainString(), + HashCode.fromBytes(tradeData.secretHash).toString().substring(0, 40), + tradeData.initialPayout.toPlainString(), + tradeData.redeemPayout.toPlainString(), + tradeData.expectedBitcoin.toPlainString(), + tradeData.offerRefundTimeout, + tradeData.tradeRefundTimeout, + currentBlockHeight)); + + // Are we in 'offer' or 'trade' stage? + if (tradeData.tradeRefundHeight == null) { + // Offer + System.out.println(String.format("\toffer timeout: block %d", + tradeData.offerRefundHeight)); + } else { + // Trade + System.out.println(String.format("\ttrade timeout: block %d,\n" + + "\ttrade recipient: %s", + tradeData.tradeRefundHeight, + tradeData.qortalRecipient)); + } + } + } diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index 488de43f..87a64f7c 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -2,16 +2,12 @@ package org.qortal.test.btcacct; import java.math.BigDecimal; import java.security.Security; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; import org.qortal.controller.Controller; import org.qortal.crosschain.BTCACCT; -import org.qortal.crypto.Crypto; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; import org.qortal.data.transaction.TransactionData; @@ -38,14 +34,14 @@ public class DeployAT { if (error != null) System.err.println(error); - System.err.println(String.format("usage: DeployAT [ []]")); + System.err.println(String.format("usage: DeployAT [ []]")); System.err.println(String.format("example: DeployAT " + "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n" - + "\t3.1415 \\\n" - + "\tQgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v \\\n" + + "\t80.4020 \\\n" + + "\t0.00864200 \\\n" + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" - + "\t1585920000 \\\n" - + "\t0.0001")); + + "\t0.0001 \\\n" + + "\t123.456")); System.exit(1); } @@ -58,9 +54,8 @@ public class DeployAT { byte[] refundPrivateKey = null; BigDecimal redeemAmount = null; - String redeemAddress = null; + BigDecimal expectedBitcoin = null; byte[] secretHash = null; - int lockTime = 0; BigDecimal initialPayout = BigDecimal.ZERO.setScale(8); BigDecimal fundingAmount = null; @@ -74,16 +69,14 @@ public class DeployAT { if (redeemAmount.signum() <= 0) usage("QORT amount must be positive"); - redeemAddress = args[argIndex++]; - if (!Crypto.isValidAddress(redeemAddress)) - usage("Redeem address invalid"); + expectedBitcoin = new BigDecimal(args[argIndex++]).setScale(8); + if (expectedBitcoin.signum() <= 0) + usage("Expected BTC amount must be positive"); secretHash = HashCode.fromString(args[argIndex++]).asBytes(); if (secretHash.length != 20) usage("Hash of secret must be 20 bytes"); - lockTime = Integer.parseInt(args[argIndex++]); - if (args.length > argIndex) initialPayout = new BigDecimal(args[argIndex++]).setScale(8); @@ -118,20 +111,11 @@ public class DeployAT { System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); - System.out.println(String.format("Redeem Qortal address: %s", redeemAddress)); - - // New/derived info - - System.out.println("\nCHECKING info from other party:"); - - System.out.println(String.format("Redeem script lockTime: %s (%d)", LocalDateTime.ofInstant(Instant.ofEpochSecond(lockTime), ZoneId.systemDefault()), lockTime)); - System.out.println("Make sure this is BEFORE P2SH lockTime to allow you to refund AT before P2SH refunded"); - // Deploy AT - final int BLOCK_TIME = 60; // seconds - final int refundTimeout = (lockTime - (int) (System.currentTimeMillis() / 1000L)) / BLOCK_TIME; + final int offerTimeout = 2 * 60; // minutes + final int tradeTimeout = 60; // minutes - byte[] creationBytes = BTCACCT.buildQortalAT(secretHash, redeemAddress, refundTimeout, initialPayout, fundingAmount); + byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), secretHash, offerTimeout, tradeTimeout, initialPayout, fundingAmount, expectedBitcoin); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); long txTimestamp = System.currentTimeMillis(); @@ -144,7 +128,7 @@ public class DeployAT { BigDecimal fee = BigDecimal.ZERO; String name = "QORT-BTC cross-chain trade"; - String description = String.format("Qortal-Bitcoin cross-chain trade between %s and %s", refundAccount.getAddress(), redeemAddress); + String description = String.format("Qortal-Bitcoin cross-chain trade"); String atType = "ACCT"; String tags = "QORT-BTC ACCT"; From 94d18538d820adcfcd3b256cd6215baba7d35343 Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 21 Apr 2020 09:31:09 +0100 Subject: [PATCH 14/15] More work on cross-chain trading, including API calls. Added API calls to aid Qortal-side of cross-chain trading. POST /crosschain/build - for building Qortal AT POST /crosschain/tradeoffer/recipient - for sending trade partner/recipient to AT POST /crosschain/tradeoffer/secret - for sending secret to AT DELETE /crosschain/tradeoffer - for cancelling AT More fixes regarding Blocks processing/orphaning ATs. More fixes regarding sending/receiving blocks containing AT data. AT-related fix to genesis block. Improved cross-chain trading AT code, removing offer-mode timeout and replacing that with allowing AT creator to cancel offer/end AT by sending AT the creator's own address as trade partner/recipient. After all, they're not going to trade with themselves. Added assertion to check BTCACCT.CODE_BYTES_HASH matches compiled code hash. Added cross-chain AT's 'mode' for easier diagnosis, either OFFER or TRADE. We can't use AT's signature to generate AT address because address is needed before DEPLOY_AT transaction is signed. So we use a hash of signature-less transaction bytes. Corresponding changes to tests. --- .../api/model/CrossChainBuildRequest.java | 37 ++ .../api/model/CrossChainCancelRequest.java | 19 + .../api/model/CrossChainSecretRequest.java | 22 + .../api/model/CrossChainTradeRequest.java | 21 + .../api/resource/CrossChainResource.java | 375 ++++++++++++++++++ src/main/java/org/qortal/block/Block.java | 8 +- .../java/org/qortal/block/GenesisBlock.java | 4 + .../java/org/qortal/crosschain/BTCACCT.java | 173 ++++---- .../data/crosschain/CrossChainTradeData.java | 10 +- .../org/qortal/transaction/AtTransaction.java | 2 +- .../transaction/DeployAtTransaction.java | 14 +- .../transform/block/BlockTransformer.java | 11 + .../java/org/qortal/test/btcacct/AtTests.java | 42 +- .../org/qortal/test/btcacct/BuildP2SH.java | 6 +- .../org/qortal/test/btcacct/CheckP2SH.java | 2 +- .../java/org/qortal/test/btcacct/Common.java | 9 + .../org/qortal/test/btcacct/DeployAT.java | 31 +- .../java/org/qortal/test/btcacct/Redeem.java | 2 +- .../java/org/qortal/test/btcacct/Refund.java | 2 +- 19 files changed, 663 insertions(+), 127 deletions(-) create mode 100644 src/main/java/org/qortal/api/model/CrossChainBuildRequest.java create mode 100644 src/main/java/org/qortal/api/model/CrossChainCancelRequest.java create mode 100644 src/main/java/org/qortal/api/model/CrossChainSecretRequest.java create mode 100644 src/main/java/org/qortal/api/model/CrossChainTradeRequest.java create mode 100644 src/test/java/org/qortal/test/btcacct/Common.java diff --git a/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java new file mode 100644 index 00000000..094b4aed --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainBuildRequest.java @@ -0,0 +1,37 @@ +package org.qortal.api.model; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainBuildRequest { + + @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") + public byte[] creatorPublicKey; + + @Schema(description = "Initial QORT amount paid when trade agreed", example = "0.00100000") + public BigDecimal initialQortAmount; + + @Schema(description = "Final QORT amount paid out on successful trade", example = "80.40200000") + public BigDecimal finalQortAmount; + + @Schema(description = "QORT amount funding AT, including covering AT execution fees", example = "123.45670000") + public BigDecimal fundingQortAmount; + + @Schema(description = "HASH160 of secret", example = "43vnftqkjxrhb5kJdkU1ZFQLEnWV") + public byte[] secretHash; + + @Schema(description = "Bitcoin P2SH BTC balance for release of secret", example = "0.00864200") + public BigDecimal bitcoinAmount; + + @Schema(description = "Trade time window (minutes) from trade agreement to automatic refund", example = "10080") + public Integer tradeTimeout; + + public CrossChainBuildRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java new file mode 100644 index 00000000..8eab7f91 --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java @@ -0,0 +1,19 @@ +package org.qortal.api.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainCancelRequest { + + @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") + public byte[] creatorPublicKey; + + public String atAddress; + + public CrossChainCancelRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java new file mode 100644 index 00000000..64c7bc89 --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java @@ -0,0 +1,22 @@ +package org.qortal.api.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainSecretRequest { + + @Schema(description = "AT's 'recipient' public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") + public byte[] recipientPublicKey; + + public String atAddress; + + @Schema(description = "32-byte secret", example = "6gVbAXCVzJXAWwtAVGAfgAkkXpeXvPUwSciPmCfSfXJG") + public byte[] secret; + + public CrossChainSecretRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java new file mode 100644 index 00000000..ab53b587 --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java @@ -0,0 +1,21 @@ +package org.qortal.api.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainTradeRequest { + + @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") + public byte[] creatorPublicKey; + + public String atAddress; + + public String recipient; + + public CrossChainTradeRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index 83dbb0f8..7e53c53e 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -5,23 +5,34 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import org.ciyam.at.MachineState; +import org.qortal.account.PublicKeyAccount; import org.qortal.api.ApiError; import org.qortal.api.ApiErrors; import org.qortal.api.ApiException; import org.qortal.api.ApiExceptionFactory; +import org.qortal.api.model.CrossChainCancelRequest; +import org.qortal.api.model.CrossChainSecretRequest; +import org.qortal.api.model.CrossChainTradeRequest; +import org.qortal.api.model.CrossChainBuildRequest; import org.qortal.asset.Asset; import org.qortal.at.QortalAtLoggerFactory; import org.qortal.crosschain.BTCACCT; @@ -29,9 +40,26 @@ 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.transaction.DeployAtTransaction; +import org.qortal.transaction.MessageTransaction; +import org.qortal.transaction.Transaction; +import org.qortal.transaction.Transaction.ValidationResult; +import org.qortal.transform.TransformationException; +import org.qortal.transform.Transformer; +import org.qortal.transform.transaction.DeployAtTransactionTransformer; +import org.qortal.transform.transaction.MessageTransactionTransformer; +import org.qortal.utils.Base58; +import org.qortal.utils.NTP; + +import com.google.common.primitives.Bytes; @Path("/crosschain") @Tag(name = "Cross-Chain") @@ -102,4 +130,351 @@ public class CrossChainResource { } } + @POST + @Path("/build") + @Operation( + summary = "Build cross-chain trading AT", + description = "Returns raw, unsigned DEPLOY_AT transaction", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainBuildRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")) + ) + } + ) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.REPOSITORY_ISSUE}) + public String buildTrade(CrossChainBuildRequest tradeRequest) { + byte[] creatorPublicKey = tradeRequest.creatorPublicKey; + + if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + if (tradeRequest.secretHash == null || tradeRequest.secretHash.length != 20) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + if (tradeRequest.tradeTimeout == null) + tradeRequest.tradeTimeout = 7 * 24 * 60; // 7 days + else + if (tradeRequest.tradeTimeout < 10 || tradeRequest.tradeTimeout > 50000) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + if (tradeRequest.initialQortAmount == null || tradeRequest.initialQortAmount.signum() < 0) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + if (tradeRequest.finalQortAmount == null || tradeRequest.finalQortAmount.signum() <= 0) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + if (tradeRequest.fundingQortAmount == null || tradeRequest.fundingQortAmount.signum() <= 0) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + // funding amount must exceed initial + final + if (tradeRequest.fundingQortAmount.compareTo(tradeRequest.initialQortAmount.add(tradeRequest.finalQortAmount)) <= 0) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + if (tradeRequest.bitcoinAmount == null || tradeRequest.bitcoinAmount.signum() <= 0) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + try (final Repository repository = RepositoryManager.getRepository()) { + PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey); + + byte[] creationBytes = BTCACCT.buildQortalAT(creatorAccount.getAddress(), tradeRequest.secretHash, tradeRequest.tradeTimeout, tradeRequest.initialQortAmount, tradeRequest.finalQortAmount, tradeRequest.bitcoinAmount); + + long txTimestamp = NTP.getTime(); + byte[] lastReference = creatorAccount.getLastReference(); + if (lastReference == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_REFERENCE); + + BigDecimal fee = BigDecimal.ZERO; + 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, creatorAccount.getPublicKey(), fee, null); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, tradeRequest.fundingQortAmount, Asset.QORT); + + Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); + + fee = deployAtTransaction.calcRecommendedFee(); + deployAtTransactionData.setFee(fee); + + ValidationResult result = deployAtTransaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + byte[] bytes = DeployAtTransactionTransformer.toBytes(deployAtTransactionData); + return Base58.encode(bytes); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST + @Path("/tradeoffer/recipient") + @Operation( + summary = "Builds raw, unsigned MESSAGE transaction that sends cross-chain trade recipient address, triggering 'trade' mode", + description = "Specify address of cross-chain AT that needs to be messaged, and address of Qortal recipient.
" + + "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
" + + "You need to sign output with same account as the AT creator otherwise the MESSAGE transaction will be invalid.", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainTradeRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ + ApiError.REPOSITORY_ISSUE + }) + public String sendTradeRecipient(CrossChainTradeRequest tradeRequest) { + byte[] creatorPublicKey = tradeRequest.creatorPublicKey; + + if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + if (tradeRequest.atAddress == null || !Crypto.isValidAtAddress(tradeRequest.atAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + if (tradeRequest.recipient == null || !Crypto.isValidAddress(tradeRequest.recipient)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + try (final Repository repository = RepositoryManager.getRepository()) { + ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, tradeRequest.atAddress); + + // Determine state of AT + ATStateData atStateData = repository.getATRepository().getLatestATState(tradeRequest.atAddress); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); + + CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); + BTCACCT.populateTradeData(crossChainTradeData, dataBytes); + + if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + // Good to make MESSAGE + + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(tradeRequest.recipient), 32, 0); + byte[] messageTransactionBytes = buildAtMessage(repository, creatorPublicKey, tradeRequest.atAddress, recipientAddressBytes); + + return Base58.encode(messageTransactionBytes); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST + @Path("/tradeoffer/secret") + @Operation( + summary = "Builds raw, unsigned MESSAGE transaction that sends secret to AT, releasing funds to recipient", + description = "Specify address of cross-chain AT that needs to be messaged, and 32-byte secret.
" + + "AT needs to be in 'trade' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
" + + "You need to sign output with account the AT considers the 'recipient' otherwise the MESSAGE transaction will be invalid.", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainSecretRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ + ApiError.REPOSITORY_ISSUE + }) + public String sendSecret(CrossChainSecretRequest secretRequest) { + byte[] recipientPublicKey = secretRequest.recipientPublicKey; + + if (recipientPublicKey == null || recipientPublicKey.length != Transformer.PUBLIC_KEY_LENGTH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + if (secretRequest.secret == null || secretRequest.secret.length != 32) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + try (final Repository repository = RepositoryManager.getRepository()) { + ATData atData = fetchAtDataWithChecking(repository, null, secretRequest.atAddress); // null to skip creator check + + // Determine state of AT + ATStateData atStateData = repository.getATRepository().getLatestATState(secretRequest.atAddress); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); + + CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); + BTCACCT.populateTradeData(crossChainTradeData, dataBytes); + + if (crossChainTradeData.mode == CrossChainTradeData.Mode.OFFER) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + PublicKeyAccount recipientAccount = new PublicKeyAccount(repository, recipientPublicKey); + String recipientAddress = recipientAccount.getAddress(); + + // MESSAGE must come from address that AT considers trade partner / 'recipient' + if (!crossChainTradeData.qortalRecipient.equals(recipientAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + // Good to make MESSAGE + + byte[] messageTransactionBytes = buildAtMessage(repository, recipientPublicKey, secretRequest.atAddress, secretRequest.secret); + + return Base58.encode(messageTransactionBytes); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @DELETE + @Path("/tradeoffer") + @Operation( + summary = "Builds raw, unsigned MESSAGE transaction that cancels cross-chain trade offer", + description = "Specify address of cross-chain AT that needs to be cancelled.
" + + "AT needs to be in 'offer' mode. Messages sent to an AT in 'trade' mode will be ignored, but still cost fees to send!
" + + "You need to sign output with same account as the AT creator otherwise the MESSAGE transaction will be invalid.", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainCancelRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + @ApiErrors({ + ApiError.REPOSITORY_ISSUE + }) + public String cancelTradeOffer(CrossChainCancelRequest cancelRequest) { + byte[] creatorPublicKey = cancelRequest.creatorPublicKey; + + if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + if (cancelRequest.atAddress == null || !Crypto.isValidAtAddress(cancelRequest.atAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + try (final Repository repository = RepositoryManager.getRepository()) { + ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, cancelRequest.atAddress); + + // Determine state of AT + ATStateData atStateData = repository.getATRepository().getLatestATState(cancelRequest.atAddress); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); + + CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); + BTCACCT.populateTradeData(crossChainTradeData, dataBytes); + + if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + // Good to make MESSAGE + + PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, creatorPublicKey); + String creatorAddress = creatorAccount.getAddress(); + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(creatorAddress), 32, 0); + + byte[] messageTransactionBytes = buildAtMessage(repository, creatorPublicKey, cancelRequest.atAddress, recipientAddressBytes); + + return Base58.encode(messageTransactionBytes); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + private ATData fetchAtDataWithChecking(Repository repository, byte[] creatorPublicKey, String atAddress) throws DataException { + ATData atData = repository.getATRepository().fromATAddress(atAddress); + if (atData == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN); + + // Does supplied public key match that of AT? + if (creatorPublicKey != null && !Arrays.equals(creatorPublicKey, atData.getCreatorPublicKey())) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + // Must be correct AT - check functionality using code hash + if (!Arrays.equals(atData.getCodeHash(), BTCACCT.CODE_BYTES_HASH)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + // No point sending message to AT that's finished + if (atData.getIsFinished()) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + + return atData; + } + + private byte[] buildAtMessage(Repository repository, byte[] senderPublicKey, String atAddress, byte[] messageData) throws DataException { + PublicKeyAccount creatorAccount = new PublicKeyAccount(repository, senderPublicKey); + + long txTimestamp = NTP.getTime(); + byte[] lastReference = creatorAccount.getLastReference(); + + if (lastReference == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_REFERENCE); + + BigDecimal fee = BigDecimal.ZERO; + BigDecimal amount = BigDecimal.ZERO; + + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, senderPublicKey, fee, null); + TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, 4, atAddress, Asset.QORT, amount, messageData, false, false); + + MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); + + fee = messageTransaction.calcRecommendedFee(); + messageTransactionData.setFee(fee); + + ValidationResult result = messageTransaction.isValidUnconfirmed(); + if (result != ValidationResult.OK) + throw TransactionsResource.createTransactionInvalidException(request, result); + + try { + return MessageTransactionTransformer.toBytes(messageTransactionData); + } catch (TransformationException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); + } + } + } \ No newline at end of file diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 37ae1bf1..7478cfe6 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1108,9 +1108,6 @@ public class Block { * @throws DataException */ private ValidationResult areAtsValid() throws DataException { - if (this.blockData.getATCount() == 0) - return ValidationResult.OK; - // Locally generated AT states should be valid so no need to re-execute them if (this.ourAtStates == this.getATStates()) // Note object reference compare return ValidationResult.OK; @@ -1207,8 +1204,7 @@ public class Block { // AT Transactions do not affect block's transaction count - // We've added transactions, so recalculate transactions signature - calcTransactionsSignature(); + // AT Transactions do not affect block's transaction signature } /** Returns whether block's minter is actually allowed to mint this block. */ @@ -1414,7 +1410,7 @@ public class Block { protected void processAtFeesAndStates() throws DataException { ATRepository atRepository = this.repository.getATRepository(); - for (ATStateData atStateData : this.getATStates()) { + for (ATStateData atStateData : this.ourAtStates) { Account atAccount = new Account(this.repository, atStateData.getATAddress()); // Subtract AT-generated fees from AT accounts diff --git a/src/main/java/org/qortal/block/GenesisBlock.java b/src/main/java/org/qortal/block/GenesisBlock.java index 94cb64e7..c5fae118 100644 --- a/src/main/java/org/qortal/block/GenesisBlock.java +++ b/src/main/java/org/qortal/block/GenesisBlock.java @@ -342,6 +342,10 @@ public class GenesisBlock extends Block { for (Transaction transaction : this.getTransactions()) this.repository.getTransactionRepository().save(transaction.getTransactionData()); + // No ATs in genesis block + this.ourAtStates = Collections.emptyList(); + this.ourAtFees = BigDecimal.ZERO.setScale(8); + super.process(); } diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index c89eb456..81a8c66d 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -26,6 +26,7 @@ import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; import org.ciyam.at.Timestamp; import org.qortal.account.Account; +import org.qortal.crypto.Crypto; import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.utils.Base58; import org.qortal.utils.BitTwiddling; @@ -33,10 +34,30 @@ import org.qortal.utils.BitTwiddling; import com.google.common.hash.HashCode; import com.google.common.primitives.Bytes; +/* + * Bob generates Bitcoin private key + * private key required to sign P2SH redeem tx + * private key can be used to create 'secret' (e.g. double-SHA256) + * encrypted private key could be stored in Qortal AT for access by Bob from any node + * Bob creates Qortal AT + * Alice finds Qortal AT and wants to trade + * Alice generates Bitcoin private key + * Alice will need to send Bob her Qortal address and Bitcoin refund address + * Bob sends Alice's Qortal address to Qortal AT + * Qortal AT sends initial QORT payment to Alice (so she has QORT to send message to AT and claim funds) + * Alice receives funds and checks Qortal AT to confirm it's locked to her + * Alice creates/funds Bitcoin P2SH + * Alice requires: Bob's redeem Bitcoin address, Alice's refund Bitcoin address, derived locktime + * Bob checks P2SH is funded + * Bob requires: Bob's redeem Bitcoin address, Alice's refund Bitcoin address, derived locktime + * Bob uses secret to redeem P2SH + * Qortal core/UI will need to create, and sign, this transaction + * Alice scans P2SH redeem tx and uses secret to redeem Qortal AT + */ + public class BTCACCT { - public static final Coin DEFAULT_BTC_FEE = Coin.valueOf(1000L); // 0.00001000 BTC - public static final byte[] CODE_BYTES_HASH = HashCode.fromString("da7271e9aa697112ece632cf2b462fded74843944a704b9d5fd4ae5971f6686f").asBytes(); // SHA256 of AT code bytes + public static final byte[] CODE_BYTES_HASH = HashCode.fromString("edcdb1feb36e079c5f956faff2f24219b12e5fbaaa05654335e615e33218282f").asBytes(); // SHA256 of AT code bytes /* * OP_TUCK (to copy public key to before signature) @@ -62,23 +83,14 @@ public class BTCACCT { private static final byte[] redeemScript5 = HashCode.fromString("8768").asBytes(); // OP_EQUAL OP_ENDIF /** - * Returns Bitcoin redeem script. + * Returns Bitcoin redeemScript used for cross-chain trading. *

- *

-	 * OP_TUCK OP_CHECKSIGVERIFY
-	 * OP_HASH160 OP_DUP push(0x14) <refunder pubkeyhash> OP_EQUAL
-	 * OP_IF
-	 * 	OP_DROP push(0x04 bytes) <refund locktime> OP_CHECKLOCKTIMEVERIFY
-	 * OP_ELSE
-	 * 	push(0x14) <redeemer pubkeyhash> OP_EQUALVERIFY
-	 * 	OP_HASH160 push(0x14 bytes) <hash of secret> OP_EQUAL
-	 * OP_ENDIF
-	 * 
+ * See comments in {@link BTCACCT} for more details. * - * @param refunderPubKeyHash - * @param senderPubKey - * @param recipientPubKey - * @param lockTime + * @param refunderPubKeyHash 20-byte HASH160 of P2SH funder's public key, for refunding purposes + * @param lockTime seconds-since-epoch threshold, after which P2SH funder can claim refund + * @param redeemerPubKeyHash 20-byte HASH160 of P2SH redeemer's public key + * @param secretHash 20-byte HASH160 of secret, used by P2SH redeemer to claim funds * @return */ public static byte[] buildScript(byte[] refunderPubKeyHash, int lockTime, byte[] redeemerPubKeyHash, byte[] secretHash) { @@ -89,14 +101,13 @@ public class BTCACCT { /** * Builds a custom transaction to spend P2SH. * - * @param amount - * @param spendKey - * @param recipientPubKeyHash - * @param fundingOutput - * @param redeemScriptBytes - * @param lockTime - * @param scriptSigBuilder - * @return + * @param amount output amount, should be total of input amounts, less miner fees + * @param spendKey key for signing transaction, and also where funds are 'sent' (output) + * @param fundingOutput output from transaction that funded P2SH address + * @param redeemScriptBytes the redeemScript itself, in byte[] form + * @param lockTime (optional) transaction nLockTime, used in refund scenario + * @param scriptSigBuilder function for building scriptSig using transaction input signature + * @return Signed Bitcoin transaction for spending P2SH */ public static Transaction buildP2shTransaction(Coin amount, ECKey spendKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, Long lockTime, Function scriptSigBuilder) { NetworkParameters params = BTC.getInstance().getNetworkParameters(); @@ -135,6 +146,16 @@ public class BTCACCT { return transaction; } + /** + * Returns signed Bitcoin transaction claiming refund from P2SH address. + * + * @param refundAmount refund amount, should be total of input amounts, less miner fees + * @param refundKey key for signing transaction, and also where refund is 'sent' (output) + * @param fundingOutput output from transaction that funded P2SH address + * @param redeemScriptBytes the redeemScript itself, in byte[] form + * @param lockTime transaction nLockTime - must be at least locktime used in redeemScript + * @return Signed Bitcoin transaction for refunding P2SH + */ public static Transaction buildRefundTransaction(Coin refundAmount, ECKey refundKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, long lockTime) { Function refundSigScriptBuilder = (txSigBytes) -> { // Build scriptSig with... @@ -156,6 +177,16 @@ public class BTCACCT { return buildP2shTransaction(refundAmount, refundKey, fundingOutput, redeemScriptBytes, lockTime, refundSigScriptBuilder); } + /** + * Returns signed Bitcoin transaction redeeming funds from P2SH address. + * + * @param redeemAmount redeem amount, should be total of input amounts, less miner fees + * @param redeemKey key for signing transaction, and also where funds are 'sent' (output) + * @param fundingOutput output from transaction that funded P2SH address + * @param redeemScriptBytes the redeemScript itself, in byte[] form + * @param secret actual 32-byte secret used when building redeemScript + * @return Signed Bitcoin transaction for redeeming P2SH + */ public static Transaction buildRedeemTransaction(Coin redeemAmount, ECKey redeemKey, TransactionOutput fundingOutput, byte[] redeemScriptBytes, byte[] secret) { Function redeemSigScriptBuilder = (txSigBytes) -> { // Build scriptSig with... @@ -180,27 +211,21 @@ public class BTCACCT { return buildP2shTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, null, redeemSigScriptBuilder); } - /* - * Bob generates Bitcoin private key - * private key required to sign P2SH redeem tx - * private key can be used to create 'secret' (e.g. double-SHA256) - * encrypted private key could be stored in Qortal AT for access by Bob from any node - * Bob creates Qortal AT - * Alice finds Qortal AT and wants to trade - * Alice generates Bitcoin private key - * Alice will need to send Bob her Qortal address and Bitcoin refund address - * Bob sends Alice's Qortal address to Qortal AT - * Qortal AT sends initial QORT payment to Alice (so she has QORT to send message to AT and claim funds) - * Alice receives funds and checks Qortal AT to confirm it's locked to her - * Alice creates/funds Bitcoin P2SH - * Alice requires: Bob's redeem Bitcoin address, Alice's refund Bitcoin address, derived locktime - * Bob checks P2SH is funded - * Bob requires: Bob's redeem Bitcoin address, Alice's refund Bitcoin address, derived locktime - * Bob uses secret to redeem P2SH - * Qortal core/UI will need to create, and sign, this transaction - * Alice scans P2SH redeem tx and uses secret to redeem Qortal AT + /** + * Returns Qortal AT creation bytes for cross-chain trading AT. + *

+ * tradeTimeout (minutes) is the time window for the recipient to send the + * 32-byte secret to the AT, before the AT automatically refunds the AT's creator. + * + * @param qortalCreator Qortal address for AT creator, also used for refunds + * @param secretHash 20-byte HASH160 of 32-byte secret + * @param tradeTimeout how many minutes, from start of 'trade mode' until AT auto-refunds AT creator + * @param initialPayout how much QORT to pay trade partner upon switch to 'trade mode' + * @param redeemPayout how much QORT to pay trade partner if they send correct 32-byte secret to AT + * @param bitcoinAmount how much BTC the AT creator is expecting to trade + * @return */ - public static byte[] buildQortalAT(String qortalCreator, byte[] secretHash, int offerTimeout, int tradeTimeout, BigDecimal initialPayout, BigDecimal redeemPayout, BigDecimal bitcoinAmount) { + public static byte[] buildQortalAT(String qortalCreator, byte[] secretHash, int tradeTimeout, BigDecimal initialPayout, BigDecimal redeemPayout, BigDecimal bitcoinAmount) { // Labels for data segment addresses int addrCounter = 0; @@ -214,7 +239,6 @@ public class BTCACCT { final int addrSecretHash = addrCounter; addrCounter += 4; - final int addrOfferTimeout = addrCounter++; final int addrTradeTimeout = addrCounter++; final int addrInitialPayoutAmount = addrCounter++; final int addrRedeemPayoutAmount = addrCounter++; @@ -238,7 +262,6 @@ public class BTCACCT { final int addrQortalRecipient3 = addrCounter++; final int addrQortalRecipient4 = addrCounter++; - final int addrOfferRefundTimestamp = addrCounter++; final int addrTradeRefundTimestamp = addrCounter++; final int addrLastTxTimestamp = addrCounter++; final int addrBlockTimestamp = addrCounter++; @@ -265,10 +288,6 @@ public class BTCACCT { assert dataByteBuffer.position() == addrSecretHash * MachineState.VALUE_SIZE : "addrSecretHash incorrect"; dataByteBuffer.put(Bytes.ensureCapacity(secretHash, 32, 0)); - // Open offer timeout in minutes - assert dataByteBuffer.position() == addrOfferTimeout * MachineState.VALUE_SIZE : "addrOfferTimeout incorrect"; - dataByteBuffer.putLong(offerTimeout); - // Trade timeout in minutes assert dataByteBuffer.position() == addrTradeTimeout * MachineState.VALUE_SIZE : "addrTradeTimeout incorrect"; dataByteBuffer.putLong(tradeTimeout); @@ -315,6 +334,7 @@ public class BTCACCT { Integer labelOfferTxLoop = null; Integer labelCheckOfferTx = null; + Integer labelTradeMode = null; Integer labelTradeTxLoop = null; Integer labelCheckTradeTx = null; @@ -330,20 +350,10 @@ public class BTCACCT { // Use AT creation 'timestamp' as starting point for finding transactions sent to AT codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp)); - // Calculate offer timeout refund 'timestamp' by adding addrOfferTimeout minutes to above 'timestamp', then save into addrOfferRefundTimestamp - codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrOfferRefundTimestamp, addrLastTxTimestamp, addrOfferTimeout)); - // Set restart position to after this opcode codeByteBuffer.put(OpCode.SET_PCS.compile()); - /* Loop, waiting for offer timeout or message from AT owner containing trade partner details */ - - // Fetch current block 'timestamp' - codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp)); - // If we're not past offer timeout refund 'timestamp' then look for next transaction - codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrOfferRefundTimestamp, calcOffset(codeByteBuffer, labelOfferTxLoop))); - // We've past offer timeout refund 'timestamp' so go refund everything back to AT creator - codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); + /* Loop, waiting for message from AT owner containing trade partner details, or AT owner's address to cancel offer */ /* Transaction processing loop */ labelOfferTxLoop = codeByteBuffer.position(); @@ -385,6 +395,17 @@ public class BTCACCT { codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B)); // Save B register into data segment starting at addrQortalRecipient1 (as pointed to by addrQortalRecipientPointer) codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalRecipientPointer)); + // Compare each of recipient address with creator's address (for offer-cancel scenario). If they don't match, assume recipient is trade partner. + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient1, addrQortalCreator1, calcOffset(codeByteBuffer, labelTradeMode))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient2, addrQortalCreator2, calcOffset(codeByteBuffer, labelTradeMode))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient3, addrQortalCreator3, calcOffset(codeByteBuffer, labelTradeMode))); + codeByteBuffer.put(OpCode.BNE_DAT.compile(addrQortalRecipient4, addrQortalCreator4, calcOffset(codeByteBuffer, labelTradeMode))); + // Recipient address is AT creator's address, so cancel offer and finish. + codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund)); + + /* Switch to 'trade mode' */ + labelTradeMode = codeByteBuffer.position(); + // Send initial payment to recipient so they have enough funds to message AT if all goes well codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrInitialPayoutAmount)); @@ -478,6 +499,9 @@ public class BTCACCT { byte[] codeBytes = new byte[codeByteBuffer.limit()]; codeByteBuffer.get(codeBytes); + assert Arrays.equals(Crypto.digest(codeBytes), BTCACCT.CODE_BYTES_HASH) + : String.format("BTCACCT.CODE_BYTES_HASH mismatch: expected %s, actual %s", HashCode.fromBytes(CODE_BYTES_HASH), HashCode.fromBytes(Crypto.digest(codeBytes))); + final short ciyamAtVersion = 2; final short numCallStackPages = 0; final short numUserStackPages = 0; @@ -486,6 +510,12 @@ public class BTCACCT { return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount); } + /** + * Populates passed CrossChainTradeData with useful info extracted from AT data segment. + * + * @param tradeData + * @param dataBytes + */ public static void populateTradeData(CrossChainTradeData tradeData, byte[] dataBytes) { ByteBuffer dataByteBuffer = ByteBuffer.wrap(dataBytes); byte[] addressBytes = new byte[32]; @@ -497,9 +527,6 @@ public class BTCACCT { tradeData.secretHash = new byte[32]; dataByteBuffer.get(tradeData.secretHash); - // Offer timeout - tradeData.offerRefundTimeout = dataByteBuffer.getLong(); - // Trade timeout tradeData.tradeRefundTimeout = dataByteBuffer.getLong(); @@ -532,17 +559,19 @@ public class BTCACCT { // Qortal recipient (if any) dataByteBuffer.get(addressBytes); - if (addressBytes[0] != 0) - tradeData.qortalRecipient = Base58.encode(Arrays.copyOf(addressBytes, Account.ADDRESS_LENGTH)); - - // Open offer timeout (AT 'timestamp' converted to Qortal block height) - long offerRefundTimestamp = dataByteBuffer.getLong(); - tradeData.offerRefundHeight = new Timestamp(offerRefundTimestamp).blockHeight; // Trade offer timeout (AT 'timestamp' converted to Qortal block height) long tradeRefundTimestamp = dataByteBuffer.getLong(); - if (tradeRefundTimestamp != 0) + + if (tradeRefundTimestamp != 0) { + tradeData.mode = CrossChainTradeData.Mode.TRADE; tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight; + + if (addressBytes[0] != 0) + tradeData.qortalRecipient = Base58.encode(Arrays.copyOf(addressBytes, Account.ADDRESS_LENGTH)); + } else { + tradeData.mode = CrossChainTradeData.Mode.OFFER; + } } } diff --git a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java index d055c830..961b9519 100644 --- a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java +++ b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java @@ -11,6 +11,8 @@ import io.swagger.v3.oas.annotations.media.Schema; @XmlAccessorType(XmlAccessType.FIELD) public class CrossChainTradeData { + public static enum Mode { OFFER, TRADE }; + // Properties @Schema(description = "AT's Qortal address") @@ -37,12 +39,6 @@ public class CrossChainTradeData { @Schema(description = "Trade partner's Qortal address (trade begins when this is set)") public String qortalRecipient; - @Schema(description = "How long from AT creation until AT triggers automatic refund to AT creator (minutes)") - public long offerRefundTimeout; - - @Schema(description = "Actual Qortal block height when AT will automatically refund to AT creator (before trade begins)") - public int offerRefundHeight; - @Schema(description = "How long from beginning trade until AT triggers automatic refund to AT creator (minutes)") public long tradeRefundTimeout; @@ -52,6 +48,8 @@ public class CrossChainTradeData { @Schema(description = "Amount, in BTC, that AT creator expects Bitcoin P2SH to pay out (excluding miner fees)") public BigDecimal expectedBitcoin; + public Mode mode; + // Constructors // Necessary for JAXB diff --git a/src/main/java/org/qortal/transaction/AtTransaction.java b/src/main/java/org/qortal/transaction/AtTransaction.java index f55dfaf8..3871c6e2 100644 --- a/src/main/java/org/qortal/transaction/AtTransaction.java +++ b/src/main/java/org/qortal/transaction/AtTransaction.java @@ -187,7 +187,7 @@ public class AtTransaction extends Transaction { // For QORT amounts only: if recipient has no reference yet, then this is their starting reference if (assetId == Asset.QORT && recipient.getLastReference() == null) // In Qora1 last reference was set to 64-bytes of zero - // In Qortal we use AT-Transction's signature, which makes more sense + // In Qortal we use AT-Transaction's signature, which makes more sense recipient.setLastReference(this.atTransactionData.getSignature()); } } diff --git a/src/main/java/org/qortal/transaction/DeployAtTransaction.java b/src/main/java/org/qortal/transaction/DeployAtTransaction.java index 75d51dc0..d9c52ecc 100644 --- a/src/main/java/org/qortal/transaction/DeployAtTransaction.java +++ b/src/main/java/org/qortal/transaction/DeployAtTransaction.java @@ -22,7 +22,9 @@ import org.qortal.data.transaction.DeployAtTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.transform.TransformationException; import org.qortal.transform.Transformer; +import org.qortal.transform.transaction.DeployAtTransactionTransformer; import com.google.common.base.Utf8; @@ -92,11 +94,15 @@ public class DeployAtTransaction extends Transaction { if (this.deployATTransactionData.getAtAddress() != null) return; - // For new version, simply use transaction signature + // For new version, simply use transaction transformer if (this.getVersion() > 1) { - String atAddress = Crypto.toATAddress(this.deployATTransactionData.getSignature()); - this.deployATTransactionData.setAtAddress(atAddress); - return; + try { + String atAddress = Crypto.toATAddress(DeployAtTransactionTransformer.toBytesForSigningImpl(this.deployATTransactionData)); + this.deployATTransactionData.setAtAddress(atAddress); + return; + } catch (TransformationException e) { + throw new DataException("Unable to generate AT address"); + } } int blockHeight = this.getHeight(); diff --git a/src/main/java/org/qortal/transform/block/BlockTransformer.java b/src/main/java/org/qortal/transform/block/BlockTransformer.java index e588805d..0481dda3 100644 --- a/src/main/java/org/qortal/transform/block/BlockTransformer.java +++ b/src/main/java/org/qortal/transform/block/BlockTransformer.java @@ -162,6 +162,9 @@ public class BlockTransformer extends Transformer { } } + // Bump byteBuffer over AT states just read in slice + byteBuffer.position(byteBuffer.position() + atBytesLength); + // AT count to reflect the number of states we have atCount = atStates.size(); @@ -295,6 +298,10 @@ public class BlockTransformer extends Transformer { bytes.write(Ints.toByteArray(atBytesLength)); for (ATStateData atStateData : block.getATStates()) { + // Skip initial states generated by DEPLOY_AT transactions in the same block + if (atStateData.isInitial()) + continue; + bytes.write(Base58.decode(atStateData.getATAddress())); bytes.write(atStateData.getStateHash()); Serialization.serializeBigDecimal(bytes, atStateData.getFees()); @@ -319,6 +326,10 @@ public class BlockTransformer extends Transformer { bytes.write(Ints.toByteArray(blockData.getTransactionCount())); for (Transaction transaction : block.getTransactions()) { + // Don't serialize AT transactions! + if (transaction.getTransactionData().getType() == TransactionType.AT) + continue; + TransactionData transactionData = transaction.getTransactionData(); bytes.write(Ints.toByteArray(TransactionTransformer.getDataLength(transactionData))); bytes.write(TransactionTransformer.toBytes(transactionData)); diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java index 21f4166c..00ab6107 100644 --- a/src/test/java/org/qortal/test/btcacct/AtTests.java +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -61,7 +61,7 @@ public class AtTests extends Common { public void testCompile() { Account deployer = Common.getTestAccount(null, "chloe"); - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); } @@ -113,7 +113,7 @@ public class AtTests extends Common { @SuppressWarnings("unused") @Test - public void testAutomaticOfferRefund() throws DataException { + public void testOfferCancel() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); PrivateKeyAccount recipient = Common.getTestAccount(repository, "dilbert"); @@ -128,15 +128,29 @@ public class AtTests extends Common { BigDecimal deployAtFee = deployAtTransaction.getTransactionData().getFee(); BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee); - checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + // Send creator's address to AT + byte[] recipientAddressBytes = Bytes.ensureCapacity(Base58.decode(deployer.getAddress()), 32, 0); + MessageTransaction messageTransaction = sendMessage(repository, deployer, recipientAddressBytes, atAddress); + BigDecimal messageFee = messageTransaction.getTransactionData().getFee(); + + // Refund should happen 1st block after receiving recipient address + BlockUtils.mintBlock(repository); + + BigDecimal expectedMinimumBalance = deployersPostDeploymentBalance; + BigDecimal expectedMaximumBalance = deployersInitialBalance.subtract(deployAtFee).subtract(messageFee); + + BigDecimal actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance.toPlainString(), expectedMinimumBalance.toPlainString()), actualBalance.compareTo(expectedMinimumBalance) > 0); + assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance.toPlainString(), expectedMaximumBalance.toPlainString()), actualBalance.compareTo(expectedMaximumBalance) < 0); describeAt(repository, atAddress); // Test orphaning BlockUtils.orphanLastBlock(repository); - BigDecimal expectedBalance = deployersPostDeploymentBalance; - BigDecimal actualBalance = deployer.getBalance(Asset.QORT); + BigDecimal expectedBalance = deployersPostDeploymentBalance.subtract(messageFee); + actualBalance = deployer.getBalance(Asset.QORT); Common.assertEqualBigDecimals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); } @@ -237,7 +251,7 @@ public class AtTests extends Common { BigDecimal messageFee = messageTransaction.getTransactionData().getFee(); BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee).subtract(messageFee); - checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); describeAt(repository, atAddress); @@ -340,7 +354,7 @@ public class AtTests extends Common { describeAt(repository, atAddress); - checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); } } @@ -382,7 +396,7 @@ public class AtTests extends Common { describeAt(repository, atAddress); - checkAtRefund(repository, deployer, deployersInitialBalance, deployAtFee); + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); } } @@ -421,7 +435,7 @@ public class AtTests extends Common { } private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer) throws DataException { - byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); + byte[] creationBytes = BTCACCT.buildQortalAT(deployer.getAddress(), secretHash, refundTimeout, initialPayout, redeemAmount, bitcoinAmount); long txTimestamp = System.currentTimeMillis(); byte[] lastReference = deployer.getLastReference(); @@ -475,7 +489,7 @@ public class AtTests extends Common { return messageTransaction; } - private void checkAtRefund(Repository repository, Account deployer, BigDecimal deployersInitialBalance, BigDecimal deployAtFee) throws DataException { + private void checkTradeRefund(Repository repository, Account deployer, BigDecimal deployersInitialBalance, BigDecimal deployAtFee) throws DataException { BigDecimal deployersPostDeploymentBalance = deployersInitialBalance.subtract(fundingAmount).subtract(deployAtFee); // AT should automatically refund deployer after 'refundTimeout' blocks @@ -520,7 +534,6 @@ public class AtTests extends Common { + "\tinitial payout: %s QORT,\n" + "\tredeem payout: %s QORT,\n" + "\texpected bitcoin: %s BTC,\n" - + "\toffer timeout: %d minutes (from creation),\n" + "\ttrade timeout: %d minutes (from trade start),\n" + "\tcurrent block height: %d,\n", tradeData.qortalAddress, @@ -531,18 +544,17 @@ public class AtTests extends Common { tradeData.initialPayout.toPlainString(), tradeData.redeemPayout.toPlainString(), tradeData.expectedBitcoin.toPlainString(), - tradeData.offerRefundTimeout, tradeData.tradeRefundTimeout, currentBlockHeight)); // Are we in 'offer' or 'trade' stage? if (tradeData.tradeRefundHeight == null) { // Offer - System.out.println(String.format("\toffer timeout: block %d", - tradeData.offerRefundHeight)); + System.out.println(String.format("\tstatus: 'offer mode'")); } else { // Trade - System.out.println(String.format("\ttrade timeout: block %d,\n" + System.out.println(String.format("\tstatus: 'trade mode',\n" + + "\ttrade timeout: block %d,\n" + "\ttrade recipient: %s", tradeData.tradeRefundHeight, tradeData.qortalRecipient)); diff --git a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java index f03fb8b5..33f86526 100644 --- a/src/test/java/org/qortal/test/btcacct/BuildP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/BuildP2SH.java @@ -54,7 +54,7 @@ public class BuildP2SH { Address redeemBitcoinAddress = null; byte[] secretHash = null; int lockTime = 0; - Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + Coin bitcoinFee = Common.DEFAULT_BTC_FEE; int argIndex = 0; try { @@ -74,8 +74,8 @@ public class BuildP2SH { lockTime = Integer.parseInt(args[argIndex++]); int refundTimeoutDelay = lockTime - (int) (System.currentTimeMillis() / 1000L); - if (refundTimeoutDelay < 600 || refundTimeoutDelay > 7 * 24 * 60 * 60) - usage("Locktime (seconds) should be at between 10 minutes and 1 week from now"); + if (refundTimeoutDelay < 600 || refundTimeoutDelay > 30 * 24 * 60 * 60) + usage("Locktime (seconds) should be at between 10 minutes and 1 month from now"); if (args.length > argIndex) bitcoinFee = Coin.parseCoin(args[argIndex++]); diff --git a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java index e61a031e..7803e0e6 100644 --- a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java @@ -58,7 +58,7 @@ public class CheckP2SH { Address redeemBitcoinAddress = null; byte[] secretHash = null; int lockTime = 0; - Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + Coin bitcoinFee = Common.DEFAULT_BTC_FEE; int argIndex = 0; try { diff --git a/src/test/java/org/qortal/test/btcacct/Common.java b/src/test/java/org/qortal/test/btcacct/Common.java new file mode 100644 index 00000000..320d1c1c --- /dev/null +++ b/src/test/java/org/qortal/test/btcacct/Common.java @@ -0,0 +1,9 @@ +package org.qortal.test.btcacct; + +import org.bitcoinj.core.Coin; + +public abstract class Common { + + public static final Coin DEFAULT_BTC_FEE = Coin.parseCoin("0.00001000"); + +} diff --git a/src/test/java/org/qortal/test/btcacct/DeployAT.java b/src/test/java/org/qortal/test/btcacct/DeployAT.java index 87a64f7c..01061132 100644 --- a/src/test/java/org/qortal/test/btcacct/DeployAT.java +++ b/src/test/java/org/qortal/test/btcacct/DeployAT.java @@ -34,19 +34,20 @@ public class DeployAT { if (error != null) System.err.println(error); - System.err.println(String.format("usage: DeployAT [ []]")); + System.err.println(String.format("usage: DeployAT ")); System.err.println(String.format("example: DeployAT " + "AdTd9SUEYSdTW8mgK3Gu72K97bCHGdUwi2VvLNjUohot \\\n" + "\t80.4020 \\\n" + "\t0.00864200 \\\n" + "\tdaf59884b4d1aec8c1b17102530909ee43c0151a \\\n" + "\t0.0001 \\\n" - + "\t123.456")); + + "\t123.456 \\\n" + + "\t10")); System.exit(1); } public static void main(String[] args) { - if (args.length < 5 || args.length > 7) + if (args.length != 8) usage(null); Security.insertProviderAt(new BouncyCastleProvider(), 0); @@ -58,6 +59,7 @@ public class DeployAT { byte[] secretHash = null; BigDecimal initialPayout = BigDecimal.ZERO.setScale(8); BigDecimal fundingAmount = null; + int tradeTimeout = 0; int argIndex = 0; try { @@ -77,15 +79,15 @@ public class DeployAT { if (secretHash.length != 20) usage("Hash of secret must be 20 bytes"); - if (args.length > argIndex) - initialPayout = new BigDecimal(args[argIndex++]).setScale(8); + initialPayout = new BigDecimal(args[argIndex++]).setScale(8); - if (args.length > argIndex) { - fundingAmount = new BigDecimal(args[argIndex++]).setScale(8); + fundingAmount = new BigDecimal(args[argIndex++]).setScale(8); + if (fundingAmount.compareTo(redeemAmount) <= 0) + usage("AT funding amount must be greater than QORT redeem amount"); - if (fundingAmount.compareTo(redeemAmount) <= 0) - usage("AT funding amount must be greater than QORT redeem amount"); - } + tradeTimeout = Integer.parseInt(args[argIndex++]); + if (tradeTimeout < 10 || tradeTimeout > 50000) + usage("AT trade timeout should be between 10 and 50,000 minutes"); } catch (IllegalArgumentException e) { usage(String.format("Invalid argument %d: %s", argIndex, e.getMessage())); } @@ -105,17 +107,12 @@ public class DeployAT { System.out.println(String.format("QORT redeem amount: %s", redeemAmount.toPlainString())); - if (fundingAmount == null) - fundingAmount = redeemAmount.add(atFundingExtra); System.out.println(String.format("AT funding amount: %s", fundingAmount.toPlainString())); System.out.println(String.format("HASH160 of secret: %s", HashCode.fromBytes(secretHash))); // Deploy AT - final int offerTimeout = 2 * 60; // minutes - final int tradeTimeout = 60; // minutes - - byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), secretHash, offerTimeout, tradeTimeout, initialPayout, fundingAmount, expectedBitcoin); + byte[] creationBytes = BTCACCT.buildQortalAT(refundAccount.getAddress(), secretHash, tradeTimeout, initialPayout, redeemAmount, expectedBitcoin); System.out.println("CIYAM AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); long txTimestamp = System.currentTimeMillis(); @@ -133,7 +130,7 @@ public class DeployAT { 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, redeemAmount, Asset.QORT); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); Transaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); diff --git a/src/test/java/org/qortal/test/btcacct/Redeem.java b/src/test/java/org/qortal/test/btcacct/Redeem.java index ad403cd2..3249e070 100644 --- a/src/test/java/org/qortal/test/btcacct/Redeem.java +++ b/src/test/java/org/qortal/test/btcacct/Redeem.java @@ -64,7 +64,7 @@ public class Redeem { byte[] redeemPrivateKey = null; byte[] secret = null; int lockTime = 0; - Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + Coin bitcoinFee = Common.DEFAULT_BTC_FEE; int argIndex = 0; try { diff --git a/src/test/java/org/qortal/test/btcacct/Refund.java b/src/test/java/org/qortal/test/btcacct/Refund.java index 14752d8d..ffeb0080 100644 --- a/src/test/java/org/qortal/test/btcacct/Refund.java +++ b/src/test/java/org/qortal/test/btcacct/Refund.java @@ -64,7 +64,7 @@ public class Refund { Address redeemBitcoinAddress = null; byte[] secretHash = null; int lockTime = 0; - Coin bitcoinFee = BTCACCT.DEFAULT_BTC_FEE; + Coin bitcoinFee = Common.DEFAULT_BTC_FEE; int argIndex = 0; try { From 833a785996a80deb7c27bcdf4371ef754dd77fe6 Mon Sep 17 00:00:00 2001 From: catbref Date: Wed, 22 Apr 2020 16:18:21 +0100 Subject: [PATCH 15/15] More work on Bitcoin-side of cross-chain trading. Tidied up duplicated cross-chain API code that fetched Qortal AT info. Added Bitcoin-related cross-chain API calls for building, checking, refunding and redeeming P2SH. Added new Bitcoin-related API error codes. Controller now starts up, and shuts down, bitcoinj. Speed-up in BTC class so bitcoinj doesn't have to throw away all peers and rediscover & reconnect to them with every chain-related call. --- src/main/java/org/qortal/api/ApiError.java | 7 +- .../model/CrossChainBitcoinP2SHStatus.java | 31 ++ .../model/CrossChainBitcoinRedeemRequest.java | 31 ++ .../model/CrossChainBitcoinRefundRequest.java | 28 ++ .../CrossChainBitcoinTemplateRequest.java | 23 + .../api/model/CrossChainCancelRequest.java | 1 + .../api/model/CrossChainSecretRequest.java | 3 +- .../api/model/CrossChainTradeRequest.java | 2 + .../api/resource/CrossChainResource.java | 464 +++++++++++++++--- .../org/qortal/controller/Controller.java | 7 + src/main/java/org/qortal/crosschain/BTC.java | 109 +++- .../java/org/qortal/crosschain/BTCACCT.java | 97 +++- .../data/crosschain/CrossChainTradeData.java | 10 +- .../java/org/qortal/test/btcacct/AtTests.java | 21 +- .../org/qortal/test/btcacct/BtcTests.java | 65 +++ .../org/qortal/test/btcacct/CheckP2SH.java | 2 +- .../java/org/qortal/test/btcacct/Redeem.java | 2 +- .../java/org/qortal/test/btcacct/Refund.java | 2 +- src/test/resources/test-settings-v2.json | 1 + 19 files changed, 792 insertions(+), 114 deletions(-) create mode 100644 src/main/java/org/qortal/api/model/CrossChainBitcoinP2SHStatus.java create mode 100644 src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java create mode 100644 src/main/java/org/qortal/api/model/CrossChainBitcoinRefundRequest.java create mode 100644 src/main/java/org/qortal/api/model/CrossChainBitcoinTemplateRequest.java create mode 100644 src/test/java/org/qortal/test/btcacct/BtcTests.java diff --git a/src/main/java/org/qortal/api/ApiError.java b/src/main/java/org/qortal/api/ApiError.java index f4cfdfb5..50fa6294 100644 --- a/src/main/java/org/qortal/api/ApiError.java +++ b/src/main/java/org/qortal/api/ApiError.java @@ -117,7 +117,12 @@ public enum ApiError { // MESSAGESIZE_EXCEEDED(1004, 400), // Groups - GROUP_UNKNOWN(1101, 404); + GROUP_UNKNOWN(1101, 404), + + // Bitcoin + BTC_NETWORK_ISSUE(1201, 500), + BTC_BALANCE_ISSUE(1202, 422), + BTC_TOO_SOON(1203, 422); private static final Map map = stream(ApiError.values()).collect(toMap(apiError -> apiError.code, apiError -> apiError)); diff --git a/src/main/java/org/qortal/api/model/CrossChainBitcoinP2SHStatus.java b/src/main/java/org/qortal/api/model/CrossChainBitcoinP2SHStatus.java new file mode 100644 index 00000000..ff986e86 --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainBitcoinP2SHStatus.java @@ -0,0 +1,31 @@ +package org.qortal.api.model; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainBitcoinP2SHStatus { + + @Schema(description = "Bitcoin P2SH address", example = "3CdH27kTpV8dcFHVRYjQ8EEV5FJg9X8pSJ (mainnet), 2fMiRRXVsxhZeyfum9ifybZvaMHbQTmwdZw (testnet)") + public String bitcoinP2shAddress; + + @Schema(description = "Bitcoin P2SH balance") + public BigDecimal bitcoinP2shBalance; + + @Schema(description = "Can P2SH redeem yet?") + public boolean canRedeem; + + @Schema(description = "Can P2SH refund yet?") + public boolean canRefund; + + @Schema(description = "Secret extracted by P2SH redeemer") + public byte[] secret; + + public CrossChainBitcoinP2SHStatus() { + } + +} diff --git a/src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java b/src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java new file mode 100644 index 00000000..fcbf2ec4 --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainBitcoinRedeemRequest.java @@ -0,0 +1,31 @@ +package org.qortal.api.model; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainBitcoinRedeemRequest { + + @Schema(description = "Bitcoin P2PKH address for refund", example = "1BwG6aG2GapFX5b4JT4ohbsYvj1xZ8d2EJ (mainnet), mrTDPdM15cFWJC4g223BXX5snicfVJBx6M (testnet)") + public String refundAddress; + + @Schema(description = "Bitcoin PRIVATE KEY for redeem", example = "cSP3zTb6bfm8GATtAcEJ8LqYtNQmzZ9jE2wQUVnZGiBzojDdrwKV") + public byte[] redeemPrivateKey; + + @Schema(description = "Qortal AT address") + public String atAddress; + + @Schema(description = "Bitcoin miner fee", example = "0.00001000") + public BigDecimal bitcoinMinerFee; + + @Schema(description = "32-byte secret", example = "6gVbAXCVzJXAWwtAVGAfgAkkXpeXvPUwSciPmCfSfXJG") + public byte[] secret; + + public CrossChainBitcoinRedeemRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/model/CrossChainBitcoinRefundRequest.java b/src/main/java/org/qortal/api/model/CrossChainBitcoinRefundRequest.java new file mode 100644 index 00000000..490ee935 --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainBitcoinRefundRequest.java @@ -0,0 +1,28 @@ +package org.qortal.api.model; + +import java.math.BigDecimal; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainBitcoinRefundRequest { + + @Schema(description = "Bitcoin PRIVATE KEY for refund", example = "cSP3zTb6bfm8GATtAcEJ8LqYtNQmzZ9jE2wQUVnZGiBzojDdrwKV") + public byte[] refundPrivateKey; + + @Schema(description = "Bitcoin P2PKH address for redeem", example = "1BwG6aG2GapFX5b4JT4ohbsYvj1xZ8d2EJ (mainnet), mrTDPdM15cFWJC4g223BXX5snicfVJBx6M (testnet)") + public String redeemAddress; + + @Schema(description = "Qortal AT address") + public String atAddress; + + @Schema(description = "Bitcoin miner fee", example = "0.00001000") + public BigDecimal bitcoinMinerFee; + + public CrossChainBitcoinRefundRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/model/CrossChainBitcoinTemplateRequest.java b/src/main/java/org/qortal/api/model/CrossChainBitcoinTemplateRequest.java new file mode 100644 index 00000000..c23815bb --- /dev/null +++ b/src/main/java/org/qortal/api/model/CrossChainBitcoinTemplateRequest.java @@ -0,0 +1,23 @@ +package org.qortal.api.model; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +import io.swagger.v3.oas.annotations.media.Schema; + +@XmlAccessorType(XmlAccessType.FIELD) +public class CrossChainBitcoinTemplateRequest { + + @Schema(description = "Bitcoin P2PKH address for refund", example = "1BwG6aG2GapFX5b4JT4ohbsYvj1xZ8d2EJ (mainnet), mrTDPdM15cFWJC4g223BXX5snicfVJBx6M (testnet)") + public String refundAddress; + + @Schema(description = "Bitcoin P2PKH address for redeem", example = "1BwG6aG2GapFX5b4JT4ohbsYvj1xZ8d2EJ (mainnet), mrTDPdM15cFWJC4g223BXX5snicfVJBx6M (testnet)") + public String redeemAddress; + + @Schema(description = "Qortal AT address") + public String atAddress; + + public CrossChainBitcoinTemplateRequest() { + } + +} diff --git a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java index 8eab7f91..e1f57a7e 100644 --- a/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainCancelRequest.java @@ -11,6 +11,7 @@ public class CrossChainCancelRequest { @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") public byte[] creatorPublicKey; + @Schema(description = "Qortal AT address") public String atAddress; public CrossChainCancelRequest() { diff --git a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java index 64c7bc89..99820022 100644 --- a/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainSecretRequest.java @@ -8,9 +8,10 @@ import io.swagger.v3.oas.annotations.media.Schema; @XmlAccessorType(XmlAccessType.FIELD) public class CrossChainSecretRequest { - @Schema(description = "AT's 'recipient' public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") + @Schema(description = "Public key to match AT's 'recipient'", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") public byte[] recipientPublicKey; + @Schema(description = "Qortal AT address") public String atAddress; @Schema(description = "32-byte secret", example = "6gVbAXCVzJXAWwtAVGAfgAkkXpeXvPUwSciPmCfSfXJG") diff --git a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java index ab53b587..32737dd5 100644 --- a/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java +++ b/src/main/java/org/qortal/api/model/CrossChainTradeRequest.java @@ -11,8 +11,10 @@ public class CrossChainTradeRequest { @Schema(description = "AT creator's public key", example = "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry") public byte[] creatorPublicKey; + @Schema(description = "Qortal AT address") public String atAddress; + @Schema(description = "Qortal address for trade partner/recipient") public String recipient; public CrossChainTradeRequest() { diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index 7e53c53e..684e09ef 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -23,7 +23,14 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; -import org.ciyam.at.MachineState; +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.TransactionOutput; +import org.bitcoinj.script.Script.ScriptType; +import org.bitcoinj.wallet.WalletTransaction; import org.qortal.account.PublicKeyAccount; import org.qortal.api.ApiError; import org.qortal.api.ApiErrors; @@ -32,13 +39,16 @@ import org.qortal.api.ApiExceptionFactory; import org.qortal.api.model.CrossChainCancelRequest; import org.qortal.api.model.CrossChainSecretRequest; import org.qortal.api.model.CrossChainTradeRequest; +import org.qortal.api.model.CrossChainBitcoinP2SHStatus; +import org.qortal.api.model.CrossChainBitcoinRedeemRequest; +import org.qortal.api.model.CrossChainBitcoinRefundRequest; +import org.qortal.api.model.CrossChainBitcoinTemplateRequest; import org.qortal.api.model.CrossChainBuildRequest; import org.qortal.asset.Asset; -import org.qortal.at.QortalAtLoggerFactory; +import org.qortal.crosschain.BTC; import org.qortal.crosschain.BTCACCT; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.DeployAtTransactionData; @@ -85,9 +95,7 @@ public class CrossChainResource { ) } ) - @ApiErrors({ - ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE - }) + @ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE}) public List getTradeOffers( @Parameter( ref = "limit") @QueryParam("limit") Integer limit, @Parameter( ref = "offset" ) @QueryParam("offset") Integer offset, @@ -104,21 +112,7 @@ public class CrossChainResource { List crossChainTradesData = new ArrayList<>(); for (ATData atData : atsData) { - String atAddress = atData.getATAddress(); - - ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); - - QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); - byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); - - CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); - crossChainTradeData.qortalAddress = atAddress; - crossChainTradeData.qortalCreator = Crypto.toAddress(atData.getCreatorPublicKey()); - crossChainTradeData.creationTimestamp = atData.getCreation(); - crossChainTradeData.qortBalance = repository.getAccountRepository().getBalance(atAddress, Asset.QORT).getBalance(); - - BTCACCT.populateTradeData(crossChainTradeData, dataBytes); - + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); crossChainTradesData.add(crossChainTradeData); } @@ -150,14 +144,14 @@ public class CrossChainResource { ) } ) - @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.REPOSITORY_ISSUE}) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_DATA, ApiError.INVALID_REFERENCE, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) public String buildTrade(CrossChainBuildRequest tradeRequest) { byte[] creatorPublicKey = tradeRequest.creatorPublicKey; if (creatorPublicKey == null || creatorPublicKey.length != Transformer.PUBLIC_KEY_LENGTH) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); - if (tradeRequest.secretHash == null || tradeRequest.secretHash.length != 20) + if (tradeRequest.secretHash == null || tradeRequest.secretHash.length != BTC.HASH160_LENGTH) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); if (tradeRequest.tradeTimeout == null) @@ -194,7 +188,7 @@ public class CrossChainResource { BigDecimal fee = BigDecimal.ZERO; String name = "QORT-BTC cross-chain trade"; - String description = String.format("Qortal-Bitcoin cross-chain trade"); + String description = "Qortal-Bitcoin cross-chain trade"; String atType = "ACCT"; String tags = "QORT-BTC ACCT"; @@ -245,9 +239,7 @@ public class CrossChainResource { ) } ) - @ApiErrors({ - ApiError.REPOSITORY_ISSUE - }) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE}) public String sendTradeRecipient(CrossChainTradeRequest tradeRequest) { byte[] creatorPublicKey = tradeRequest.creatorPublicKey; @@ -262,15 +254,7 @@ public class CrossChainResource { try (final Repository repository = RepositoryManager.getRepository()) { ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, tradeRequest.atAddress); - - // Determine state of AT - ATStateData atStateData = repository.getATRepository().getLatestATState(tradeRequest.atAddress); - - QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); - byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); - - CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); - BTCACCT.populateTradeData(crossChainTradeData, dataBytes); + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); @@ -312,9 +296,7 @@ public class CrossChainResource { ) } ) - @ApiErrors({ - ApiError.REPOSITORY_ISSUE - }) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_DATA, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE}) public String sendSecret(CrossChainSecretRequest secretRequest) { byte[] recipientPublicKey = secretRequest.recipientPublicKey; @@ -324,20 +306,12 @@ public class CrossChainResource { if (secretRequest.atAddress == null || !Crypto.isValidAtAddress(secretRequest.atAddress)) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); - if (secretRequest.secret == null || secretRequest.secret.length != 32) + if (secretRequest.secret == null || secretRequest.secret.length != BTCACCT.SECRET_LENGTH) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); try (final Repository repository = RepositoryManager.getRepository()) { ATData atData = fetchAtDataWithChecking(repository, null, secretRequest.atAddress); // null to skip creator check - - // Determine state of AT - ATStateData atStateData = repository.getATRepository().getLatestATState(secretRequest.atAddress); - - QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); - byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); - - CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); - BTCACCT.populateTradeData(crossChainTradeData, dataBytes); + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); if (crossChainTradeData.mode == CrossChainTradeData.Mode.OFFER) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); @@ -385,9 +359,7 @@ public class CrossChainResource { ) } ) - @ApiErrors({ - ApiError.REPOSITORY_ISSUE - }) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE}) public String cancelTradeOffer(CrossChainCancelRequest cancelRequest) { byte[] creatorPublicKey = cancelRequest.creatorPublicKey; @@ -399,15 +371,7 @@ public class CrossChainResource { try (final Repository repository = RepositoryManager.getRepository()) { ATData atData = fetchAtDataWithChecking(repository, creatorPublicKey, cancelRequest.atAddress); - - // Determine state of AT - ATStateData atStateData = repository.getATRepository().getLatestATState(cancelRequest.atAddress); - - QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); - byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, atStateData.getStateData()); - - CrossChainTradeData crossChainTradeData = new CrossChainTradeData(); - BTCACCT.populateTradeData(crossChainTradeData, dataBytes); + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); if (crossChainTradeData.mode == CrossChainTradeData.Mode.TRADE) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); @@ -426,6 +390,384 @@ public class CrossChainResource { } } + @POST + @Path("/p2sh") + @Operation( + summary = "Returns Bitcoin P2SH address based on trade info", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainBitcoinTemplateRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")) + ) + } + ) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) + public String deriveP2sh(CrossChainBitcoinTemplateRequest templateRequest) { + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + + Address refundBitcoinAddress = null; + Address redeemBitcoinAddress = null; + + try { + if (templateRequest.refundAddress == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + refundBitcoinAddress = Address.fromString(params, templateRequest.refundAddress); + if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + try { + if (templateRequest.redeemAddress == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + redeemBitcoinAddress = Address.fromString(params, templateRequest.redeemAddress); + if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + if (templateRequest.atAddress == null || !Crypto.isValidAtAddress(templateRequest.atAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + // Extract data from cross-chain trading AT + try (final Repository repository = RepositoryManager.getRepository()) { + ATData atData = fetchAtDataWithChecking(repository, null, templateRequest.atAddress); // null to skip creator check + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); + + byte[] redeemScriptBytes = BTCACCT.buildScript(refundBitcoinAddress.getHash(), crossChainTradeData.lockTime, redeemBitcoinAddress.getHash(), crossChainTradeData.secretHash); + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + return p2shAddress.toString(); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST + @Path("/p2sh/check") + @Operation( + summary = "Checks Bitcoin P2SH address based on trade info", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainBitcoinTemplateRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = CrossChainBitcoinP2SHStatus.class)) + ) + } + ) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE}) + public CrossChainBitcoinP2SHStatus checkP2sh(CrossChainBitcoinTemplateRequest templateRequest) { + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + + Address refundBitcoinAddress = null; + Address redeemBitcoinAddress = null; + + try { + if (templateRequest.refundAddress == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + refundBitcoinAddress = Address.fromString(params, templateRequest.refundAddress); + if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + try { + if (templateRequest.redeemAddress == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + redeemBitcoinAddress = Address.fromString(params, templateRequest.redeemAddress); + if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + if (templateRequest.atAddress == null || !Crypto.isValidAtAddress(templateRequest.atAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + // Extract data from cross-chain trading AT + try (final Repository repository = RepositoryManager.getRepository()) { + ATData atData = fetchAtDataWithChecking(repository, null, templateRequest.atAddress); // null to skip creator check + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); + + byte[] redeemScriptBytes = BTCACCT.buildScript(refundBitcoinAddress.getHash(), crossChainTradeData.lockTime, redeemBitcoinAddress.getHash(), crossChainTradeData.secretHash); + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + + long medianBlockTime = BTC.getInstance().getMedianBlockTime(); + long now = NTP.getTime(); + + // Check P2SH is funded + final int startTime = (int) (crossChainTradeData.tradeModeTimestamp / 1000L); + List fundingOutputs = new ArrayList<>(); + List walletTransactions = new ArrayList<>(); + + Coin p2shBalance = BTC.getInstance().getBalanceAndOtherInfo(p2shAddress.toString(), startTime, fundingOutputs, walletTransactions); + if (p2shBalance == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN); + + CrossChainBitcoinP2SHStatus p2shStatus = new CrossChainBitcoinP2SHStatus(); + p2shStatus.bitcoinP2shAddress = p2shAddress.toString(); + p2shStatus.bitcoinP2shBalance = BigDecimal.valueOf(p2shBalance.value, 8); + + long unscaledExpectedBitcoin = crossChainTradeData.expectedBitcoin.unscaledValue().longValue(); + if (p2shBalance.value >= unscaledExpectedBitcoin && fundingOutputs.size() == 1) { + p2shStatus.canRedeem = now >= medianBlockTime * 1000L; + p2shStatus.canRefund = now >= crossChainTradeData.lockTime * 1000L; + } + + if (now >= medianBlockTime * 1000L) { + // See if we can extract secret + p2shStatus.secret = BTCACCT.findP2shSecret(p2shStatus.bitcoinP2shAddress, walletTransactions); + } + + return p2shStatus; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST + @Path("/p2sh/refund") + @Operation( + summary = "Returns serialized Bitcoin transaction attempting refund from P2SH address", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainBitcoinRefundRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")) + ) + } + ) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, + ApiError.BTC_TOO_SOON, ApiError.BTC_BALANCE_ISSUE, ApiError.BTC_NETWORK_ISSUE, ApiError.REPOSITORY_ISSUE}) + public String refundP2sh(CrossChainBitcoinRefundRequest refundRequest) { + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + + byte[] refundPrivateKey = refundRequest.refundPrivateKey; + if (refundPrivateKey == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + ECKey refundKey = null; + Address redeemBitcoinAddress = null; + + try { + // Auto-trim + if (refundPrivateKey.length >= 37 && refundPrivateKey.length <= 38) + refundPrivateKey = Arrays.copyOfRange(refundPrivateKey, 1, 33); + if (refundPrivateKey.length != 32) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + refundKey = ECKey.fromPrivate(refundPrivateKey); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + try { + if (refundRequest.redeemAddress == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + redeemBitcoinAddress = Address.fromString(params, refundRequest.redeemAddress); + if (redeemBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + if (refundRequest.atAddress == null || !Crypto.isValidAtAddress(refundRequest.atAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + Address refundAddress = Address.fromKey(params, refundKey, ScriptType.P2PKH); + + // Extract data from cross-chain trading AT + try (final Repository repository = RepositoryManager.getRepository()) { + ATData atData = fetchAtDataWithChecking(repository, null, refundRequest.atAddress); // null to skip creator check + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); + + byte[] redeemScriptBytes = BTCACCT.buildScript(refundAddress.getHash(), crossChainTradeData.lockTime, redeemBitcoinAddress.getHash(), crossChainTradeData.secretHash); + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + + long now = NTP.getTime(); + + // Check P2SH is funded + final int startTime = (int) (crossChainTradeData.tradeModeTimestamp / 1000L); + List fundingOutputs = new ArrayList<>(); + + Coin p2shBalance = BTC.getInstance().getBalanceAndOtherInfo(p2shAddress.toString(), startTime, fundingOutputs, null); + if (p2shBalance == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN); + + if (fundingOutputs.size() != 1) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + TransactionOutput fundingOutput = fundingOutputs.get(0); + boolean canRefund = now >= crossChainTradeData.lockTime * 1000L; + if (!canRefund) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON); + + long unscaledExpectedBitcoin = crossChainTradeData.expectedBitcoin.unscaledValue().longValue(); + if (p2shBalance.value < unscaledExpectedBitcoin) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_BALANCE_ISSUE); + + Coin refundAmount = p2shBalance.subtract(Coin.valueOf(refundRequest.bitcoinMinerFee.unscaledValue().longValue())); + + org.bitcoinj.core.Transaction refundTransaction = BTCACCT.buildRefundTransaction(refundAmount, refundKey, fundingOutput, redeemScriptBytes, crossChainTradeData.lockTime); + boolean wasBroadcast = BTC.getInstance().broadcastTransaction(refundTransaction); + + if (!wasBroadcast) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_NETWORK_ISSUE); + + return refundTransaction.getTxId().toString(); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @POST + @Path("/p2sh/redeem") + @Operation( + summary = "Returns serialized Bitcoin transaction attempting redeem from P2SH address", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = CrossChainBitcoinRedeemRequest.class + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")) + ) + } + ) + @ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, + ApiError.BTC_TOO_SOON, ApiError.BTC_BALANCE_ISSUE, ApiError.BTC_NETWORK_ISSUE, ApiError.REPOSITORY_ISSUE}) + public String redeemP2sh(CrossChainBitcoinRedeemRequest redeemRequest) { + BTC btc = BTC.getInstance(); + NetworkParameters params = btc.getNetworkParameters(); + + byte[] redeemPrivateKey = redeemRequest.redeemPrivateKey; + if (redeemPrivateKey == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + ECKey redeemKey = null; + Address refundBitcoinAddress = null; + + try { + // Auto-trim + if (redeemPrivateKey.length >= 37 && redeemPrivateKey.length <= 38) + redeemPrivateKey = Arrays.copyOfRange(redeemPrivateKey, 1, 33); + if (redeemPrivateKey.length != 32) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + redeemKey = ECKey.fromPrivate(redeemPrivateKey); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + try { + if (redeemRequest.refundAddress == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY); + + refundBitcoinAddress = Address.fromString(params, redeemRequest.refundAddress); + if (refundBitcoinAddress.getOutputScriptType() != ScriptType.P2PKH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } catch (IllegalArgumentException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + } + + if (redeemRequest.atAddress == null || !Crypto.isValidAtAddress(redeemRequest.atAddress)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + if (redeemRequest.secret == null || redeemRequest.secret.length != BTCACCT.SECRET_LENGTH) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA); + + Address redeemAddress = Address.fromKey(params, redeemKey, ScriptType.P2PKH); + + // Extract data from cross-chain trading AT + try (final Repository repository = RepositoryManager.getRepository()) { + ATData atData = fetchAtDataWithChecking(repository, null, redeemRequest.atAddress); // null to skip creator check + CrossChainTradeData crossChainTradeData = BTCACCT.populateTradeData(repository, atData); + + byte[] redeemScriptBytes = BTCACCT.buildScript(refundBitcoinAddress.getHash(), crossChainTradeData.lockTime, redeemAddress.getHash(), crossChainTradeData.secretHash); + byte[] redeemScriptHash = BTC.hash160(redeemScriptBytes); + + Address p2shAddress = LegacyAddress.fromScriptHash(params, redeemScriptHash); + + long medianBlockTime = BTC.getInstance().getMedianBlockTime(); + long now = NTP.getTime(); + + // Check P2SH is funded + final int startTime = (int) (crossChainTradeData.tradeModeTimestamp / 1000L); + List fundingOutputs = new ArrayList<>(); + + Coin p2shBalance = BTC.getInstance().getBalanceAndOtherInfo(p2shAddress.toString(), startTime, fundingOutputs, null); + if (p2shBalance == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN); + + if (fundingOutputs.size() != 1) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + TransactionOutput fundingOutput = fundingOutputs.get(0); + boolean canRedeem = now >= medianBlockTime * 1000L; + if (!canRedeem) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_TOO_SOON); + + long unscaledExpectedBitcoin = crossChainTradeData.expectedBitcoin.unscaledValue().longValue(); + if (p2shBalance.value < unscaledExpectedBitcoin) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_BALANCE_ISSUE); + + Coin redeemAmount = p2shBalance.subtract(Coin.valueOf(redeemRequest.bitcoinMinerFee.unscaledValue().longValue())); + + org.bitcoinj.core.Transaction redeemTransaction = BTCACCT.buildRedeemTransaction(redeemAmount, redeemKey, fundingOutput, redeemScriptBytes, redeemRequest.secret); + boolean wasBroadcast = BTC.getInstance().broadcastTransaction(redeemTransaction); + + if (!wasBroadcast) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BTC_NETWORK_ISSUE); + + return redeemTransaction.getTxId().toString(); + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + private ATData fetchAtDataWithChecking(Repository repository, byte[] creatorPublicKey, String atAddress) throws DataException { ATData atData = repository.getATRepository().fromATAddress(atAddress); if (atData == null) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 08fdc953..4106e5b0 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -35,6 +35,7 @@ import org.qortal.block.BlockChain; import org.qortal.block.BlockMinter; import org.qortal.block.BlockChain.BlockTimingByHeight; import org.qortal.controller.Synchronizer.SynchronizationResult; +import org.qortal.crosschain.BTC; import org.qortal.crypto.Crypto; import org.qortal.data.account.MintingAccountData; import org.qortal.data.account.RewardShareData; @@ -377,6 +378,9 @@ public class Controller extends Thread { return; // Not System.exit() so that GUI can display error } + LOGGER.info(String.format("Starting Bitcoin support using %s", Settings.getInstance().getBitcoinNet().name())); + BTC.getInstance(); + // If GUI is enabled, we're no longer starting up but actually running now Gui.getInstance().notifyRunning(); } @@ -687,6 +691,9 @@ public class Controller extends Thread { if (!isStopping) { isStopping = true; + LOGGER.info("Shutting down Bitcoin support"); + BTC.getInstance().shutdown(); + LOGGER.info("Shutting down API"); ApiService.getInstance().stop(); diff --git a/src/main/java/org/qortal/crosschain/BTC.java b/src/main/java/org/qortal/crosschain/BTC.java index 2ad069f7..caa10c36 100644 --- a/src/main/java/org/qortal/crosschain/BTC.java +++ b/src/main/java/org/qortal/crosschain/BTC.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.TreeMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.LogManager; @@ -35,11 +36,13 @@ import org.bitcoinj.core.CheckpointManager; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Peer; import org.bitcoinj.core.PeerAddress; import org.bitcoinj.core.PeerGroup; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.StoredBlock; import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionBroadcast; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.core.listeners.BlocksDownloadedEventListener; import org.bitcoinj.core.listeners.NewBestBlockListener; @@ -54,6 +57,7 @@ import org.bitcoinj.store.MemoryBlockStore; import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.utils.Threading; import org.bitcoinj.wallet.Wallet; +import org.bitcoinj.wallet.WalletTransaction; import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener; import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener; import org.qortal.settings.Settings; @@ -63,6 +67,7 @@ public class BTC { public static final MonetaryFormat FORMAT = new MonetaryFormat().minDecimals(8).postfixCode(); public static final long NO_LOCKTIME_NO_RBF_SEQUENCE = 0xFFFFFFFFL; public static final long LOCKTIME_NO_RBF_SEQUENCE = NO_LOCKTIME_NO_RBF_SEQUENCE - 1; + public static final int HASH160_LENGTH = 20; private static final MessageDigest RIPE_MD160_DIGESTER; private static final MessageDigest SHA256_DIGESTER; @@ -100,16 +105,6 @@ public class BTC { public abstract NetworkParameters getParams(); } - private static BTC instance; - - private final NetworkParameters params; - private final String checkpointsFileName; - private final File directory; - - private PeerGroup peerGroup; - private BlockStore blockStore; - private BlockChain chain; - private static class UpdateableCheckpointManager extends CheckpointManager implements NewBestBlockListener { private static final long CHECKPOINT_THRESHOLD = 7 * 24 * 60 * 60; // seconds @@ -235,6 +230,27 @@ public class BTC { } } } + + private static class ResettableBlockChain extends BlockChain { + public ResettableBlockChain(NetworkParameters params, BlockStore blockStore) throws BlockStoreException { + super(params, blockStore); + } + + public void setChainHead(StoredBlock chainHead) throws BlockStoreException { + super.setChainHead(chainHead); + } + } + + private static BTC instance; + + private final NetworkParameters params; + private final String checkpointsFileName; + private final File directory; + + private PeerGroup peerGroup; + private BlockStore blockStore; + private ResettableBlockChain chain; + private UpdateableCheckpointManager manager; // Constructors and instance @@ -278,6 +294,13 @@ public class BTC { } catch (IOException e) { throw new RuntimeException("Failed to load BTC checkpoints", e); } + + try { + this.start(System.currentTimeMillis() / 1000L); + // this.peerGroup.waitForPeers(this.peerGroup.getMaxConnections()).get(); + } catch (BlockStoreException e) { + throw new RuntimeException("Failed to start BTC instance", e); + } } public static synchronized BTC getInstance() { @@ -315,10 +338,12 @@ public class BTC { this.blockStore.put(checkpoint); this.blockStore.setChainHead(checkpoint); - this.chain = new BlockChain(this.params, this.blockStore); + this.chain = new ResettableBlockChain(this.params, this.blockStore); this.peerGroup = new PeerGroup(this.params, this.chain); this.peerGroup.setUserAgent("qortal", "1.0"); + this.peerGroup.setPingIntervalMsec(1000L); + this.peerGroup.setMaxConnections(20); if (this.params != RegTestParams.get()) { this.peerGroup.addPeerDiscovery(new DnsDiscovery(this.params)); @@ -329,7 +354,7 @@ public class BTC { this.peerGroup.start(); } - private void stop() { + public void shutdown() { this.peerGroup.stop(); } @@ -357,8 +382,11 @@ public class BTC { } } - private void replayChain(long startTime, Wallet wallet, ReplayHooks replayHooks) throws BlockStoreException { - this.start(startTime); + private void replayChain(int startTime, Wallet wallet, ReplayHooks replayHooks) throws BlockStoreException { + StoredBlock checkpoint = this.manager.getCheckpointBefore(startTime - 1); + this.blockStore.put(checkpoint); + this.blockStore.setChainHead(checkpoint); + this.chain.setChainHead(checkpoint); final WalletCoinsReceivedEventListener coinsReceivedListener = (someWallet, tx, prevBalance, newBalance) -> { LOGGER.debug(String.format("Wallet-related transaction %s", tx.getTxId())); @@ -398,24 +426,23 @@ public class BTC { this.chain.removeWallet(wallet); } - this.stop(); + // For safety, disconnect download peer just in case + Peer downloadPeer = this.peerGroup.getDownloadPeer(); + if (downloadPeer != null) + downloadPeer.close(); } } - private void replayChain(long startTime) throws BlockStoreException { - this.replayChain(startTime, null, null); - } - // Actual useful methods for use by other classes /** Returns median timestamp from latest 11 blocks, in seconds. */ public Long getMedianBlockTime() { // 11 blocks, at roughly 10 minutes per block, means we should go back at least 110 minutes // but some blocks have been way longer than 10 minutes, so be massively pessimistic - long startTime = (System.currentTimeMillis() / 1000L) - 11 * 60 * 60; // 11 hours before now, in seconds + int startTime = (int) (System.currentTimeMillis() / 1000L) - 110 * 60; // 110 minutes before now, in seconds try { - replayChain(startTime); + this.replayChain(startTime, null, null); List latestBlocks = new ArrayList<>(11); StoredBlock block = this.blockStore.getChainHead(); @@ -434,7 +461,7 @@ public class BTC { } } - public Coin getBalance(String base58Address, long startTime) { + public Coin getBalance(String base58Address, int startTime) { // Create new wallet containing only the address we're interested in, ignoring anything prior to startTime Wallet wallet = createEmptyWallet(); Address address = Address.fromString(this.params, base58Address); @@ -451,7 +478,7 @@ public class BTC { } } - public List getOutputs(String base58Address, long startTime) { + public List getOutputs(String base58Address, int startTime) { Wallet wallet = createEmptyWallet(); Address address = Address.fromString(this.params, base58Address); wallet.addWatchedAddress(address, startTime); @@ -467,7 +494,30 @@ public class BTC { } } - public List getOutputs(byte[] txId, long startTime) { + public Coin getBalanceAndOtherInfo(String base58Address, int startTime, List unspentOutputs, List walletTransactions) { + // Create new wallet containing only the address we're interested in, ignoring anything prior to startTime + Wallet wallet = createEmptyWallet(); + Address address = Address.fromString(this.params, base58Address); + wallet.addWatchedAddress(address, startTime); + + try { + replayChain(startTime, wallet, null); + + if (unspentOutputs != null) + unspentOutputs.addAll(wallet.getWatchedOutputs(true)); + + if (walletTransactions != null) + for (WalletTransaction walletTransaction : wallet.getWalletTransactions()) + walletTransactions.add(walletTransaction); + + return wallet.getBalance(); + } catch (BlockStoreException e) { + LOGGER.error(String.format("BTC blockstore issue: %s", e.getMessage())); + return null; + } + } + + public List getOutputs(byte[] txId, int startTime) { Wallet wallet = createEmptyWallet(); // Add random address to wallet @@ -505,4 +555,15 @@ public class BTC { } } + public boolean broadcastTransaction(Transaction transaction) { + TransactionBroadcast transactionBroadcast = this.peerGroup.broadcastTransaction(transaction); + + try { + transactionBroadcast.future().get(); + return true; + } catch (InterruptedException | ExecutionException e) { + return false; + } + } + } diff --git a/src/main/java/org/qortal/crosschain/BTCACCT.java b/src/main/java/org/qortal/crosschain/BTCACCT.java index 81a8c66d..943ad519 100644 --- a/src/main/java/org/qortal/crosschain/BTCACCT.java +++ b/src/main/java/org/qortal/crosschain/BTCACCT.java @@ -5,8 +5,10 @@ import static org.ciyam.at.OpCode.calcOffset; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.List; import java.util.function.Function; +import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.NetworkParameters; @@ -19,6 +21,8 @@ import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptChunk; import org.bitcoinj.script.ScriptOpCodes; +import org.bitcoinj.script.Script.ScriptType; +import org.bitcoinj.wallet.WalletTransaction; import org.ciyam.at.API; import org.ciyam.at.CompilationException; import org.ciyam.at.FunctionCode; @@ -26,8 +30,17 @@ import org.ciyam.at.MachineState; import org.ciyam.at.OpCode; import org.ciyam.at.Timestamp; import org.qortal.account.Account; +import org.qortal.asset.Asset; +import org.qortal.at.QortalAtLoggerFactory; +import org.qortal.block.BlockChain; +import org.qortal.block.BlockChain.CiyamAtSettings; import org.qortal.crypto.Crypto; +import org.qortal.data.at.ATData; +import org.qortal.data.at.ATStateData; +import org.qortal.data.block.BlockData; import org.qortal.data.crosschain.CrossChainTradeData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; import org.qortal.utils.Base58; import org.qortal.utils.BitTwiddling; @@ -57,6 +70,8 @@ import com.google.common.primitives.Bytes; public class BTCACCT { + public static final int SECRET_LENGTH = 32; + public static final int MIN_LOCKTIME = 1500000000; public static final byte[] CODE_BYTES_HASH = HashCode.fromString("edcdb1feb36e079c5f956faff2f24219b12e5fbaaa05654335e615e33218282f").asBytes(); // SHA256 of AT code bytes /* @@ -511,12 +526,27 @@ public class BTCACCT { } /** - * Populates passed CrossChainTradeData with useful info extracted from AT data segment. + * Returns CrossChainTradeData with useful info extracted from AT. * - * @param tradeData - * @param dataBytes + * @param repository + * @param atAddress + * @throws DataException */ - public static void populateTradeData(CrossChainTradeData tradeData, byte[] dataBytes) { + public static CrossChainTradeData populateTradeData(Repository repository, ATData atData) throws DataException { + String atAddress = atData.getATAddress(); + + ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); + byte[] stateData = atStateData.getStateData(); + + QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); + byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData); + + CrossChainTradeData tradeData = new CrossChainTradeData(); + tradeData.qortalAtAddress = atAddress; + tradeData.qortalCreator = Crypto.toAddress(atData.getCreatorPublicKey()); + tradeData.creationTimestamp = atData.getCreation(); + tradeData.qortBalance = repository.getAccountRepository().getBalance(atAddress, Asset.QORT).getBalance(); + ByteBuffer dataByteBuffer = ByteBuffer.wrap(dataBytes); byte[] addressBytes = new byte[32]; @@ -524,8 +554,9 @@ public class BTCACCT { dataByteBuffer.position(dataByteBuffer.position() + 32); // Hash of secret - tradeData.secretHash = new byte[32]; + tradeData.secretHash = new byte[20]; dataByteBuffer.get(tradeData.secretHash); + dataByteBuffer.position(dataByteBuffer.position() + 32 - 20); // skip to 32 bytes // Trade timeout tradeData.tradeRefundTimeout = dataByteBuffer.getLong(); @@ -569,9 +600,65 @@ public class BTCACCT { if (addressBytes[0] != 0) tradeData.qortalRecipient = Base58.encode(Arrays.copyOf(addressBytes, Account.ADDRESS_LENGTH)); + + // We'll suggest half of trade timeout + CiyamAtSettings ciyamAtSettings = BlockChain.getInstance().getCiyamAtSettings(); + + int tradeModeSwitchHeight = (int) (tradeData.tradeRefundHeight - tradeData.tradeRefundTimeout / ciyamAtSettings.minutesPerBlock); + + BlockData blockData = repository.getBlockRepository().fromHeight(tradeModeSwitchHeight); + if (blockData != null) { + tradeData.tradeModeTimestamp = blockData.getTimestamp(); // NOTE: milliseconds from epoch + tradeData.lockTime = (int) (tradeData.tradeModeTimestamp / 1000L + tradeData.tradeRefundTimeout / 2 * 60); + } } else { tradeData.mode = CrossChainTradeData.Mode.OFFER; } + + return tradeData; + } + + public static byte[] findP2shSecret(String p2shAddress, List walletTransactions) { + NetworkParameters params = BTC.getInstance().getNetworkParameters(); + + for (WalletTransaction walletTransaction : walletTransactions) { + Transaction transaction = walletTransaction.getTransaction(); + + // Cycle through inputs, looking for one that spends our P2SH + for (TransactionInput input : transaction.getInputs()) { + TransactionOutput connectedOutput = input.getConnectedOutput(); + if (connectedOutput == null) + // We don't know about this transaction that this input is spending, so won't be our P2SH + continue; + + Script scriptPubKey = connectedOutput.getScriptPubKey(); + ScriptType scriptType = scriptPubKey.getScriptType(); + if (scriptType != ScriptType.P2SH) + // Input isn't spending our P2SH + continue; + + Address inputAddress = scriptPubKey.getToAddress(params); + if (!inputAddress.toString().equals(p2shAddress)) + // Input isn't spending our P2SH + continue; + + Script scriptSig = input.getScriptSig(); + List scriptChunks = scriptSig.getChunks(); + + // Expected number of script chunks + int expectedChunkCount = 1 /* secret */ + 1 /* sig */ + 1 /* pubkey */ + 1 /* redeemScript */; + if (scriptChunks.size() != expectedChunkCount) + continue; + + byte[] secret = scriptChunks.get(0).data; + if (secret.length != BTCACCT.SECRET_LENGTH) + continue; + + return secret; + } + } + + return null; } } diff --git a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java index 961b9519..60d9f28d 100644 --- a/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java +++ b/src/main/java/org/qortal/data/crosschain/CrossChainTradeData.java @@ -11,12 +11,12 @@ import io.swagger.v3.oas.annotations.media.Schema; @XmlAccessorType(XmlAccessType.FIELD) public class CrossChainTradeData { - public static enum Mode { OFFER, TRADE }; + public enum Mode { OFFER, TRADE }; // Properties @Schema(description = "AT's Qortal address") - public String qortalAddress; + public String qortalAtAddress; @Schema(description = "AT creator's Qortal address") public String qortalCreator; @@ -39,6 +39,9 @@ public class CrossChainTradeData { @Schema(description = "Trade partner's Qortal address (trade begins when this is set)") public String qortalRecipient; + @Schema(description = "Timestamp when AT switched to trade mode") + public Long tradeModeTimestamp; + @Schema(description = "How long from beginning trade until AT triggers automatic refund to AT creator (minutes)") public long tradeRefundTimeout; @@ -50,6 +53,9 @@ public class CrossChainTradeData { public Mode mode; + @Schema(description = "Suggested Bitcoin P2SH nLockTime based on trade timeout") + public Integer lockTime; + // Constructors // Necessary for JAXB diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java index 00ab6107..20b2d8b7 100644 --- a/src/test/java/org/qortal/test/btcacct/AtTests.java +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -13,13 +13,11 @@ import java.util.List; import java.util.function.Function; import org.bitcoinj.core.Base58; -import org.ciyam.at.MachineState; 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.at.QortalAtLoggerFactory; import org.qortal.crosschain.BTCACCT; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; @@ -508,20 +506,7 @@ public class AtTests extends Common { private void describeAt(Repository repository, String atAddress) throws DataException { ATData atData = repository.getATRepository().fromATAddress(atAddress); - - ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress); - byte[] stateData = atStateData.getStateData(); - - QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance(); - byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData); - - CrossChainTradeData tradeData = new CrossChainTradeData(); - tradeData.qortalAddress = atAddress; - tradeData.qortalCreator = Crypto.toAddress(atData.getCreatorPublicKey()); - tradeData.creationTimestamp = atData.getCreation(); - tradeData.qortBalance = repository.getAccountRepository().getBalance(atAddress, Asset.QORT).getBalance(); - - BTCACCT.populateTradeData(tradeData, dataBytes); + CrossChainTradeData tradeData = BTCACCT.populateTradeData(repository, atData); Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); @@ -536,7 +521,7 @@ public class AtTests extends Common { + "\texpected bitcoin: %s BTC,\n" + "\ttrade timeout: %d minutes (from trade start),\n" + "\tcurrent block height: %d,\n", - tradeData.qortalAddress, + tradeData.qortalAtAddress, tradeData.qortalCreator, epochMilliFormatter.apply(tradeData.creationTimestamp), tradeData.qortBalance.toPlainString(), @@ -555,8 +540,10 @@ public class AtTests extends Common { // Trade System.out.println(String.format("\tstatus: 'trade mode',\n" + "\ttrade timeout: block %d,\n" + + "\tBitcoin P2SH nLockTime: %d (%s),\n" + "\ttrade recipient: %s", tradeData.tradeRefundHeight, + tradeData.lockTime, epochMilliFormatter.apply(tradeData.lockTime * 1000L), tradeData.qortalRecipient)); } } diff --git a/src/test/java/org/qortal/test/btcacct/BtcTests.java b/src/test/java/org/qortal/test/btcacct/BtcTests.java new file mode 100644 index 00000000..8a25b937 --- /dev/null +++ b/src/test/java/org/qortal/test/btcacct/BtcTests.java @@ -0,0 +1,65 @@ +package org.qortal.test.btcacct; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.bitcoinj.store.BlockStoreException; +import org.bitcoinj.wallet.WalletTransaction; +import org.junit.Before; +import org.junit.Test; +import org.qortal.crosschain.BTC; +import org.qortal.crosschain.BTCACCT; +import org.qortal.repository.DataException; +import org.qortal.test.common.Common; + +public class BtcTests extends Common { + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @Test + public void testGetMedianBlockTime() throws BlockStoreException { + System.out.println(String.format("Starting BTC instance...")); + BTC btc = BTC.getInstance(); + System.out.println(String.format("BTC instance started")); + + long before = System.currentTimeMillis(); + System.out.println(String.format("Bitcoin median blocktime: %d", btc.getMedianBlockTime())); + long afterFirst = System.currentTimeMillis(); + + System.out.println(String.format("Bitcoin median blocktime: %d", btc.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 testFindP2shSecret() { + // This actually exists on TEST3 + String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; + int startTime = 1587510000; + + List walletTransactions = new ArrayList<>(); + + BTC.getInstance().getBalanceAndOtherInfo(p2shAddress, startTime, null, walletTransactions); + + byte[] expectedSecret = AtTests.secret; + byte[] secret = BTCACCT.findP2shSecret(p2shAddress, walletTransactions); + + assertNotNull(secret); + assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); + } + +} diff --git a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java index 7803e0e6..ec2ccb86 100644 --- a/src/test/java/org/qortal/test/btcacct/CheckP2SH.java +++ b/src/test/java/org/qortal/test/btcacct/CheckP2SH.java @@ -134,7 +134,7 @@ public class CheckP2SH { System.out.println(String.format("Too soon (%s) to redeem based on median block time %s", LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneOffset.UTC), LocalDateTime.ofInstant(Instant.ofEpochSecond(medianBlockTime), ZoneOffset.UTC))); // Check P2SH is funded - final long startTime = lockTime - 86400; + final int startTime = lockTime - 86400; Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), startTime); if (p2shBalance == null) { diff --git a/src/test/java/org/qortal/test/btcacct/Redeem.java b/src/test/java/org/qortal/test/btcacct/Redeem.java index 3249e070..4c5b9fb7 100644 --- a/src/test/java/org/qortal/test/btcacct/Redeem.java +++ b/src/test/java/org/qortal/test/btcacct/Redeem.java @@ -146,7 +146,7 @@ public class Redeem { } // Check P2SH is funded - final long startTime = ((int) (System.currentTimeMillis() / 1000L)) - 86400; + final int startTime = ((int) (System.currentTimeMillis() / 1000L)) - 86400; Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), startTime); if (p2shBalance == null) { diff --git a/src/test/java/org/qortal/test/btcacct/Refund.java b/src/test/java/org/qortal/test/btcacct/Refund.java index ffeb0080..3393f8bb 100644 --- a/src/test/java/org/qortal/test/btcacct/Refund.java +++ b/src/test/java/org/qortal/test/btcacct/Refund.java @@ -150,7 +150,7 @@ public class Refund { } // Check P2SH is funded - final long startTime = ((int) (System.currentTimeMillis() / 1000L)) - 86400; + final int startTime = ((int) (System.currentTimeMillis() / 1000L)) - 86400; Coin p2shBalance = BTC.getInstance().getBalance(p2shAddress.toString(), startTime); if (p2shBalance == null) { diff --git a/src/test/resources/test-settings-v2.json b/src/test/resources/test-settings-v2.json index 57eb22a5..1cefddee 100644 --- a/src/test/resources/test-settings-v2.json +++ b/src/test/resources/test-settings-v2.json @@ -1,4 +1,5 @@ { + "bitcoinNet": "TEST3", "restrictedApi": false, "blockchainConfig": "src/test/resources/test-chain-v2.json", "wipeUnconfirmedOnStart": false,