forked from Qortal/qortal
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.
This commit is contained in:
parent
7ded8954c6
commit
3eaeb927ec
2
pom.xml
2
pom.xml
@ -406,7 +406,7 @@
|
||||
<dependency>
|
||||
<groupId>org.ciyam</groupId>
|
||||
<artifactId>at</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<!-- Bitcoin support -->
|
||||
<dependency>
|
||||
|
@ -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(),
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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,<br>
|
||||
* and bytes 26 to 32 are zero, then use as an address, but only if valid.
|
||||
* <p>
|
||||
* 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);
|
||||
|
@ -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<TransactionOutput> 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<Transaction> foundTransaction = new AtomicReference<>();
|
||||
|
||||
final BlocksDownloadedEventListener listener = (peer, block, filteredBlock, blocksLeft) -> {
|
||||
List<Transaction> 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()));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -22,32 +22,32 @@ public class GetTransaction {
|
||||
if (error != null)
|
||||
System.err.println(error);
|
||||
|
||||
System.err.println(String.format("usage: GetTransaction <bitcoin-tx>"));
|
||||
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 <bitcoin-tx> <start-time>"));
|
||||
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<TransactionOutput> fundingOutputs = BTC.getInstance().getOutputs(transactionId, startTime);
|
||||
if (fundingOutputs == null) {
|
||||
|
Loading…
Reference in New Issue
Block a user