3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-11 17:55:50 +00:00

AT-related changes: new Qortal functions, tests, etc.

Added GET_MESSAGE_LENGTH_FROM_TX_IN_A
and PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.

Replaced AT-1.3.4 with version including bug-fix for off-by-one
data address bounds checking.

Moved long-from-bytes method to BitTwiddling class.

Renamed some methods to make it more obvious they work with
little/big endian data.
This commit is contained in:
catbref 2020-06-10 09:10:24 +01:00
parent cc13d1d0f1
commit 65ccb80aa4
9 changed files with 549 additions and 36 deletions

Binary file not shown.

View File

@ -7,6 +7,6 @@
<versions>
<version>1.3.4</version>
</versions>
<lastUpdated>20200414162728</lastUpdated>
<lastUpdated>20200609101009</lastUpdated>
</versioning>
</metadata>

View File

@ -37,6 +37,7 @@ import org.qortal.transaction.AtTransaction;
import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.TransactionType;
import org.qortal.utils.Base58;
import org.qortal.utils.BitTwiddling;
import com.google.common.primitives.Bytes;
@ -133,9 +134,9 @@ public class QortalATAPI extends API {
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));
this.setA2(state, BitTwiddling.longFromBEBytes(signature, 52));
this.setA3(state, BitTwiddling.longFromBEBytes(signature, 60));
this.setA4(state, BitTwiddling.longFromBEBytes(signature, 68));
} catch (DataException e) {
throw new RuntimeException("AT API unable to fetch previous block?", e);
}
@ -186,9 +187,9 @@ public class QortalATAPI extends API {
// 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));
this.setA2(state, BitTwiddling.longFromBEBytes(signature, 8));
this.setA3(state, BitTwiddling.longFromBEBytes(signature, 16));
this.setA4(state, BitTwiddling.longFromBEBytes(signature, 24));
return;
}
@ -282,7 +283,7 @@ public class QortalATAPI extends API {
byte[] hash = Crypto.digest(input);
return fromBytes(hash, 0);
return BitTwiddling.longFromBEBytes(hash, 0);
} catch (DataException e) {
throw new RuntimeException("AT API unable to fetch latest block from repository?", e);
}
@ -296,20 +297,7 @@ public class QortalATAPI extends API {
TransactionData transactionData = this.getTransactionFromA(state);
byte[] messageData = null;
switch (transactionData.getType()) {
case MESSAGE:
messageData = ((MessageTransactionData) transactionData).getData();
break;
case AT:
messageData = ((ATTransactionData) transactionData).getMessage();
break;
default:
return;
}
byte[] messageData = this.getMessageFromTransaction(transactionData);
// Check data length is appropriate, i.e. not larger than B
if (messageData.length > 4 * 8)
@ -457,12 +445,6 @@ public class QortalATAPI extends API {
// Utility methods
/** Convert part of little-endian byte[] to long */
/* package */ static long fromBytes(byte[] bytes, int start) {
return (bytes[start] & 0xffL) | (bytes[start + 1] & 0xffL) << 8 | (bytes[start + 2] & 0xffL) << 16 | (bytes[start + 3] & 0xffL) << 24
| (bytes[start + 4] & 0xffL) << 32 | (bytes[start + 5] & 0xffL) << 40 | (bytes[start + 6] & 0xffL) << 48 | (bytes[start + 7] & 0xffL) << 56;
}
/** 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);
@ -473,7 +455,7 @@ public class QortalATAPI extends API {
// Compare end of transaction's signature against A2 thru A4
byte[] sig = transactionData.getSignature();
if (this.getA2(state) != fromBytes(sig, 8) || this.getA3(state) != fromBytes(sig, 16) || this.getA4(state) != fromBytes(sig, 24))
if (this.getA2(state) != BitTwiddling.longFromBEBytes(sig, 8) || this.getA3(state) != BitTwiddling.longFromBEBytes(sig, 16) || this.getA4(state) != BitTwiddling.longFromBEBytes(sig, 24))
throw new IllegalStateException("Transaction signature in A no longer matches signature from repository");
}
@ -497,6 +479,20 @@ public class QortalATAPI extends API {
}
}
/** Returns message data from transaction. */
/*package*/ byte[] getMessageFromTransaction(TransactionData transactionData) {
switch (transactionData.getType()) {
case MESSAGE:
return ((MessageTransactionData) transactionData).getData();
case AT:
return ((ATTransactionData) transactionData).getMessage();
default:
return null;
}
}
/** Returns AT's account */
/* package */ Account getATAccount() {
return new Account(this.repository, this.atData.getATAddress());
@ -563,4 +559,8 @@ public class QortalATAPI extends API {
super.setB(state, bBytes);
}
protected void zeroB(MachineState state) {
super.zeroB(state);
}
}

View File

@ -12,6 +12,7 @@ import org.ciyam.at.IllegalFunctionCodeException;
import org.ciyam.at.MachineState;
import org.qortal.crosschain.BTC;
import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.TransactionData;
import org.qortal.settings.Settings;
/**
@ -22,8 +23,70 @@ import org.qortal.settings.Settings;
*/
public enum QortalFunctionCode {
/**
* <tt>0x0510</tt><br>
* Convert address in B to 20-byte value in LSB of B1, and all of B2 & B3.
* Returns length of message data from transaction in A.<br>
* <tt>0x0501</tt><br>
* If transaction has no 'message', returns -1.
*/
GET_MESSAGE_LENGTH_FROM_TX_IN_A(0x0501, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
QortalATAPI api = (QortalATAPI) state.getAPI();
TransactionData transactionData = api.getTransactionFromA(state);
byte[] messageData = api.getMessageFromTransaction(transactionData);
if (messageData == null)
functionData.returnValue = -1L;
else
functionData.returnValue = (long) messageData.length;
}
},
/**
* Put offset 'message' from transaction in A into B<br>
* <tt>0x0502 start-offset</tt><br>
* Copies up to 32 bytes of message data, starting at <tt>start-offset</tt> into B.<br>
* If transaction has no 'message', or <tt>start-offset</tt> out of bounds, then zero B<br>
* Example 'message' could be 256-bit shared secret
*/
PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B(0x0502, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
QortalATAPI api = (QortalATAPI) state.getAPI();
// In case something goes wrong, or we don't have enough message data.
api.zeroB(state);
if (functionData.value1 < 0 || functionData.value1 > Integer.MAX_VALUE)
return;
int startOffset = functionData.value1.intValue();
TransactionData transactionData = api.getTransactionFromA(state);
byte[] messageData = api.getMessageFromTransaction(transactionData);
if (messageData == null || startOffset > messageData.length)
return;
/*
* Copy up to 32 bytes of message data into B,
* retain order but pad with zeros in lower bytes.
*
* So a 4-byte message "a b c d" would copy thusly:
* a b c d 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
*/
int byteCount = Math.min(32, messageData.length - startOffset);
byte[] bBytes = new byte[32];
System.arraycopy(messageData, startOffset, bBytes, 0, byteCount);
api.setB(state, bBytes);
}
},
/**
* Convert address in B to 20-byte value in LSB of B1, and all of B2 & B3.<br>
* <tt>0x0510</tt>
*/
CONVERT_B_TO_PKH(0x0510, 0, false) {
@Override
@ -38,8 +101,8 @@ public enum QortalFunctionCode {
}
},
/**
* <tt>0x0511</tt><br>
* Convert 20-byte value in LSB of B1, and all of B2 & B3 to P2SH.<br>
* <tt>0x0511</tt><br>
* P2SH stored in lower 25 bytes of B.
*/
CONVERT_B_TO_P2SH(0x0511, 0, false) {
@ -51,8 +114,8 @@ public enum QortalFunctionCode {
}
},
/**
* <tt>0x0512</tt><br>
* Convert 20-byte value in LSB of B1, and all of B2 & B3 to Qortal address.<br>
* <tt>0x0512</tt><br>
* Qortal address stored in lower 25 bytes of B.
*/
CONVERT_B_TO_QORTAL(0x0512, 0, false) {

View File

@ -99,7 +99,7 @@ public class BTC {
if (blockHeaders == null || blockHeaders.size() < 11)
return null;
List<Integer> blockTimestamps = blockHeaders.stream().map(blockHeader -> BitTwiddling.fromLEBytes(blockHeader, TIMESTAMP_OFFSET)).collect(Collectors.toList());
List<Integer> blockTimestamps = blockHeaders.stream().map(blockHeader -> BitTwiddling.intFromLEBytes(blockHeader, TIMESTAMP_OFFSET)).collect(Collectors.toList());
// Descending, but order shouldn't matter as we're picking median...
blockTimestamps.sort((a, b) -> Integer.compare(b, a));

View File

@ -27,8 +27,14 @@ public class BitTwiddling {
}
/** Convert little-endian bytes to int */
public static int fromLEBytes(byte[] bytes, int offset) {
public static int intFromLEBytes(byte[] bytes, int offset) {
return (bytes[offset] & 0xff) | (bytes[offset + 1] & 0xff) << 8 | (bytes[offset + 2] & 0xff) << 16 | (bytes[offset + 3] & 0xff) << 24;
}
/** Convert big-endian bytes to long */
public static long longFromBEBytes(byte[] bytes, int start) {
return (bytes[start] & 0xffL) << 56 | (bytes[start + 1] & 0xffL) << 48 | (bytes[start + 2] & 0xffL) << 40 | (bytes[start + 3] & 0xffL) << 32
| (bytes[start + 4] & 0xffL) << 24 | (bytes[start + 5] & 0xffL) << 16 | (bytes[start + 6] & 0xffL) << 8 | (bytes[start + 7] & 0xffL);
}
}

View File

@ -0,0 +1,223 @@
package org.qortal.test.at;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.util.Random;
import org.ciyam.at.CompilationException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.Before;
import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.at.QortalAtLoggerFactory;
import org.qortal.at.QortalFunctionCode;
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.AccountUtils;
import org.qortal.test.common.BlockUtils;
import org.qortal.test.common.Common;
import org.qortal.test.common.TransactionUtils;
import org.qortal.transaction.DeployAtTransaction;
import org.qortal.transaction.MessageTransaction;
import org.qortal.utils.BitTwiddling;
public class GetMessageLengthTests extends Common {
private static final Random RANDOM = new Random();
@Before
public void before() throws DataException {
Common.useDefaultSettings();
}
@Test
public void testGetMessageLength() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
byte[] creationBytes = buildMessageLengthAT();
long fundingAmount = 1_00000000L;
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
String atAddress = deployAtTransaction.getATAccount().getAddress();
// Send messages with known length
checkMessageLength(repository, deployer, atAddress, 1);
checkMessageLength(repository, deployer, atAddress, 10);
checkMessageLength(repository, deployer, atAddress, 32);
checkMessageLength(repository, deployer, atAddress, 99);
// Finally, send a payment instead and check returned length is -1
AccountUtils.pay(repository, deployer, atAddress, 123L);
// Mint another block so AT can process payment
BlockUtils.mintBlock(repository);
// Check AT result
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
byte[] stateData = atStateData.getStateData();
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData);
long extractedLength = BitTwiddling.longFromBEBytes(dataBytes, 0);
assertEquals(-1L, extractedLength);
}
}
private void checkMessageLength(Repository repository, PrivateKeyAccount sender, String atAddress, int messageLength) throws DataException {
byte[] testMessage = new byte[messageLength];
RANDOM.nextBytes(testMessage);
sendMessage(repository, sender, testMessage, atAddress);
// Mint another block so AT can process message
BlockUtils.mintBlock(repository);
// Check AT result
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
byte[] stateData = atStateData.getStateData();
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData);
long extractedLength = BitTwiddling.longFromBEBytes(dataBytes, 0);
assertEquals(messageLength, extractedLength);
}
private byte[] buildMessageLengthAT() {
// Labels for data segment addresses
int addrCounter = 0;
// Make result first for easier extraction
final int addrResult = addrCounter++;
final int addrLastTxTimestamp = addrCounter++;
// Data segment
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
// Code labels
Integer labelCheckTx = null;
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
// Two-pass version
for (int pass = 0; pass < 2; ++pass) {
codeByteBuffer.clear();
try {
/* Initialization */
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp));
// Set restart position to after this opcode
codeByteBuffer.put(OpCode.SET_PCS.compile());
/* Loop, waiting for message to AT */
// 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, OpCode.calcOffset(codeByteBuffer, labelCheckTx)));
// Stop and wait for next block
codeByteBuffer.put(OpCode.STP_IMD.compile());
/* Check transaction */
labelCheckTx = 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));
// Save message length
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrResult));
// Stop and wait for next block (and hence more transactions)
codeByteBuffer.put(OpCode.STP_IMD.compile());
} catch (CompilationException e) {
throw new IllegalStateException("Unable to compile AT?", 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, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
}
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, byte[] creationBytes, long fundingAmount) throws DataException {
long txTimestamp = System.currentTimeMillis();
byte[] lastReference = deployer.getLastReference();
if (lastReference == null) {
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
System.exit(2);
}
Long fee = null;
String name = "Test AT";
String description = "Test AT";
String atType = "Test";
String tags = "TEST";
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
fee = deployAtTransaction.calcRecommendedFee();
deployAtTransactionData.setFee(fee);
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
return deployAtTransaction;
}
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
long txTimestamp = System.currentTimeMillis();
byte[] lastReference = sender.getLastReference();
if (lastReference == null) {
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
System.exit(2);
}
Long fee = null;
int version = 4;
int nonce = 0;
long amount = 0;
Long assetId = null; // because amount is zero
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
fee = messageTransaction.calcRecommendedFee();
messageTransactionData.setFee(fee);
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
return messageTransaction;
}
}

View File

@ -0,0 +1,221 @@
package org.qortal.test.at;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import org.ciyam.at.CompilationException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.Before;
import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.at.QortalAtLoggerFactory;
import org.qortal.at.QortalFunctionCode;
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;
public class GetPartialMessageTests extends Common {
@Before
public void before() throws DataException {
Common.useDefaultSettings();
}
@Test
public void testGetPartialMessage() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
byte[] messageData = "The quick brown fox jumped over the lazy dog.".getBytes();
int[] offsets = new int[] { 0, 7, 32, 44, messageData.length };
byte[] creationBytes = buildGetPartialMessageAT(offsets);
long fundingAmount = 1_00000000L;
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
String atAddress = deployAtTransaction.getATAccount().getAddress();
sendMessage(repository, deployer, messageData, atAddress);
for (int offset : offsets) {
// Mint another block so AT can process message
BlockUtils.mintBlock(repository);
byte[] expectedData = new byte[32];
int byteCount = Math.min(32, messageData.length - offset);
System.arraycopy(messageData, offset, expectedData, 0, byteCount);
// Check AT result
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
byte[] stateData = atStateData.getStateData();
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
byte[] dataBytes = MachineState.extractDataBytes(loggerFactory, stateData);
byte[] actualData = new byte[32];
System.arraycopy(dataBytes, MachineState.VALUE_SIZE, actualData, 0, 32);
assertArrayEquals(expectedData, actualData);
}
}
}
private byte[] buildGetPartialMessageAT(int... offsets) {
// Labels for data segment addresses
int addrCounter = 0;
final int addrCopyOfBIndex = addrCounter++;
// 2nd position for easy extraction
final int addrCopyOfB = addrCounter;
addrCounter += 4;
final int addrResult = addrCounter++;
final int addrLastTxTimestamp = addrCounter++;
final int addrOffset = addrCounter++;
// Data segment
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
dataByteBuffer.putLong(addrCopyOfB);
// Code labels
Integer labelCheckTx = null;
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
// Two-pass version
for (int pass = 0; pass < 2; ++pass) {
codeByteBuffer.clear();
try {
/* Initialization */
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxTimestamp));
// Set restart position to after this opcode
codeByteBuffer.put(OpCode.SET_PCS.compile());
/* Loop, waiting for message to AT */
// 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, OpCode.calcOffset(codeByteBuffer, labelCheckTx)));
// Stop and wait for next block
codeByteBuffer.put(OpCode.STP_IMD.compile());
/* Check transaction */
labelCheckTx = codeByteBuffer.position();
// Generate code per offset
for (int i = 0; i < offsets.length; ++i) {
if (i > 0)
// Wait for next block
codeByteBuffer.put(OpCode.SLP_IMD.compile());
// Set offset
codeByteBuffer.put(OpCode.SET_VAL.compile(addrOffset, offsets[i]));
// Extract partial message
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrOffset));
// Copy B to data segment
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrCopyOfBIndex));
}
// We're done
codeByteBuffer.put(OpCode.FIN_IMD.compile());
} catch (CompilationException e) {
throw new IllegalStateException("Unable to compile AT?", 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, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
}
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, byte[] creationBytes, long fundingAmount) throws DataException {
long txTimestamp = System.currentTimeMillis();
byte[] lastReference = deployer.getLastReference();
if (lastReference == null) {
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
System.exit(2);
}
Long fee = null;
String name = "Test AT";
String description = "Test AT";
String atType = "Test";
String tags = "TEST";
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
fee = deployAtTransaction.calcRecommendedFee();
deployAtTransactionData.setFee(fee);
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
return deployAtTransaction;
}
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
long txTimestamp = System.currentTimeMillis();
byte[] lastReference = sender.getLastReference();
if (lastReference == null) {
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
System.exit(2);
}
Long fee = null;
int version = 4;
int nonce = 0;
long amount = 0;
Long assetId = null; // because amount is zero
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
fee = messageTransaction.calcRecommendedFee();
messageTransactionData.setFee(fee);
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
return messageTransaction;
}
}

View File

@ -61,7 +61,7 @@ public class ElectrumXTests {
// Timestamp(int) is at 4 + 32 + 32 = 68 bytes offset
int offset = 4 + 32 + 32;
int timestamp = BitTwiddling.fromLEBytes(blockHeader, offset);
int timestamp = BitTwiddling.intFromLEBytes(blockHeader, offset);
System.out.println(String.format("Block %d timestamp: %d", height + i, timestamp));
}
}