forked from Qortal/qortal
Split AT state storage into two HSQLDB table for better management
This involves a database reshape, but before this happens the node-local data is exported to local files, giving the user the option to use a bootstrap file instead of waiting.
This commit is contained in:
parent
b3f859f290
commit
20777363cf
@ -250,7 +250,8 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
public ATStateData getATStateAtHeight(String atAddress, int height) throws DataException {
|
||||
String sql = "SELECT state_data, state_hash, fees, is_initial "
|
||||
+ "FROM ATStates "
|
||||
+ "WHERE AT_address = ? AND height = ? "
|
||||
+ "LEFT OUTER JOIN ATStatesData USING (AT_address, height) "
|
||||
+ "WHERE ATStates.AT_address = ? AND ATStates.height = ? "
|
||||
+ "LIMIT 1";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, atAddress, height)) {
|
||||
@ -272,10 +273,11 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
public ATStateData getLatestATState(String atAddress) throws DataException {
|
||||
String sql = "SELECT height, state_data, state_hash, fees, is_initial "
|
||||
+ "FROM ATStates "
|
||||
+ "WHERE AT_address = ? "
|
||||
// AT_address then height so the compound primary key is used as an index
|
||||
// Both must be the same direction also
|
||||
+ "ORDER BY AT_address DESC, height DESC "
|
||||
+ "JOIN ATStatesData USING (AT_address, height) "
|
||||
+ "WHERE ATStates.AT_address = ? "
|
||||
// Order by AT_address and height to use compound primary key as index
|
||||
// Both must be the same direction (DESC) also
|
||||
+ "ORDER BY ATStates.AT_address DESC, ATStates.height DESC "
|
||||
+ "LIMIT 1 ";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, atAddress)) {
|
||||
@ -306,16 +308,17 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
+ "CROSS JOIN LATERAL("
|
||||
+ "SELECT height, state_data, state_hash, fees, is_initial "
|
||||
+ "FROM ATStates "
|
||||
+ "JOIN ATStatesData USING (AT_address, height) "
|
||||
+ "WHERE ATStates.AT_address = ATs.AT_address ");
|
||||
|
||||
if (minimumFinalHeight != null) {
|
||||
sql.append("AND height >= ? ");
|
||||
sql.append("AND ATStates.height >= ? ");
|
||||
bindParams.add(minimumFinalHeight);
|
||||
}
|
||||
|
||||
// AT_address then height so the compound primary key is used as an index
|
||||
// Both must be the same direction also
|
||||
sql.append("ORDER BY AT_address DESC, height DESC "
|
||||
// Order by AT_address and height to use compound primary key as index
|
||||
// Both must be the same direction (DESC) also
|
||||
sql.append("ORDER BY ATStates.AT_address DESC, ATStates.height DESC "
|
||||
+ "LIMIT 1 "
|
||||
+ ") AS FinalATStates "
|
||||
+ "WHERE code_hash = ? ");
|
||||
@ -337,7 +340,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
bindParams.add(rawExpectedValue);
|
||||
}
|
||||
|
||||
sql.append(" ORDER BY height ");
|
||||
sql.append(" ORDER BY FinalATStates.height ");
|
||||
if (reverse != null && reverse)
|
||||
sql.append("DESC");
|
||||
|
||||
@ -431,7 +434,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
@Override
|
||||
public void prepareForAtStateTrimming() throws DataException {
|
||||
// Rebuild cache of latest, non-finished AT states that we can't trim
|
||||
// Rebuild cache of latest AT states that we can't trim
|
||||
String deleteSql = "DELETE FROM LatestATStates";
|
||||
try {
|
||||
this.repository.executeCheckedUpdate(deleteSql);
|
||||
@ -463,13 +466,12 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
// We're often called so no need to trim all states in one go.
|
||||
// Limit updates to reduce CPU and memory load.
|
||||
String sql = "UPDATE ATStates SET state_data = NULL "
|
||||
+ "WHERE state_data IS NOT NULL "
|
||||
+ "AND height BETWEEN ? AND ? "
|
||||
String sql = "DELETE FROM ATStatesData "
|
||||
+ "WHERE height BETWEEN ? AND ? "
|
||||
+ "AND NOT EXISTS("
|
||||
+ "SELECT TRUE FROM LatestATStates "
|
||||
+ "WHERE LatestATStates.AT_address = ATStates.AT_address "
|
||||
+ "AND LatestATStates.height = ATStates.height"
|
||||
+ "WHERE LatestATStates.AT_address = ATStatesData.AT_address "
|
||||
+ "AND LatestATStates.height = ATStatesData.height"
|
||||
+ ") "
|
||||
+ "LIMIT ?";
|
||||
|
||||
@ -487,23 +489,44 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
if (atStateData.getStateHash() == null || atStateData.getHeight() == null)
|
||||
throw new IllegalArgumentException("Refusing to save partial AT state into repository!");
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("ATStates");
|
||||
HSQLDBSaver atStatesSaver = new HSQLDBSaver("ATStates");
|
||||
|
||||
saveHelper.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight())
|
||||
.bind("state_data", atStateData.getStateData()).bind("state_hash", atStateData.getStateHash())
|
||||
atStatesSaver.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight())
|
||||
.bind("state_hash", atStateData.getStateHash())
|
||||
.bind("fees", atStateData.getFees()).bind("is_initial", atStateData.isInitial());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
atStatesSaver.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save AT state into repository", e);
|
||||
}
|
||||
|
||||
if (atStateData.getStateData() != null) {
|
||||
HSQLDBSaver atStatesDataSaver = new HSQLDBSaver("ATStatesData");
|
||||
|
||||
atStatesDataSaver.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight())
|
||||
.bind("state_data", atStateData.getStateData());
|
||||
|
||||
try {
|
||||
atStatesDataSaver.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save AT state data into repository", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
this.repository.delete("ATStatesData", "AT_address = ? AND height = ?",
|
||||
atStateData.getATAddress(), atStateData.getHeight());
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete AT state data from repository", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String atAddress, int height) throws DataException {
|
||||
try {
|
||||
this.repository.delete("ATStates", "AT_address = ? AND height = ?", atAddress, height);
|
||||
this.repository.delete("ATStatesData", "AT_address = ? AND height = ?", atAddress, height);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete AT state from repository", e);
|
||||
}
|
||||
@ -513,6 +536,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
public void deleteATStates(int height) throws DataException {
|
||||
try {
|
||||
this.repository.delete("ATStates", "height = ?", height);
|
||||
this.repository.delete("ATStatesData", "height = ?", height);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete AT states from repository", e);
|
||||
}
|
||||
|
@ -660,9 +660,10 @@ public class HSQLDBDatabaseUpdates {
|
||||
break;
|
||||
|
||||
case 25:
|
||||
// DISABLED: improved version in case 30!
|
||||
// Remove excess created_when from ATStates
|
||||
stmt.execute("ALTER TABLE ATStates DROP created_when");
|
||||
stmt.execute("CREATE INDEX ATStateHeightIndex on ATStates (height)");
|
||||
// stmt.execute("ALTER TABLE ATStates DROP created_when");
|
||||
// stmt.execute("CREATE INDEX ATStateHeightIndex on ATStates (height)");
|
||||
break;
|
||||
|
||||
case 26:
|
||||
@ -691,6 +692,69 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CHECKPOINT");
|
||||
break;
|
||||
|
||||
case 30:
|
||||
// Split AT state data off to new table for better performance/management.
|
||||
|
||||
if (!"mem".equals(HSQLDBRepository.getDbPathname(connection.getMetaData().getURL()))) {
|
||||
// First, backup node-local data in case user wants to avoid long reshape and use bootstrap instead
|
||||
stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE MintingAccounts DATA TO 'MintingAccounts.script'");
|
||||
stmt.execute("PERFORM EXPORT SCRIPT FOR TABLE TradeBotStates DATA TO 'TradeBotStates.script'");
|
||||
LOGGER.info("Exported sensitive/node-local data: minting keys and trade bot states");
|
||||
LOGGER.info("If following reshape takes too long, use bootstrap and import node-local data using API's POST /admin/repository");
|
||||
}
|
||||
|
||||
// Create new AT-states table without full state data
|
||||
stmt.execute("CREATE TABLE ATStatesNew ("
|
||||
+ "AT_address QortalAddress, height INTEGER NOT NULL, state_hash ATStateHash NOT NULL, "
|
||||
+ "fees QortalAmount NOT NULL, is_initial BOOLEAN NOT NULL, "
|
||||
+ "PRIMARY KEY (AT_address, height), "
|
||||
+ "FOREIGN KEY (AT_address) REFERENCES ATs (AT_address) ON DELETE CASCADE)");
|
||||
stmt.execute("SET TABLE ATStatesNew NEW SPACE");
|
||||
stmt.execute("CHECKPOINT");
|
||||
|
||||
ResultSet resultSet = stmt.executeQuery("SELECT height FROM Blocks ORDER BY height DESC LIMIT 1");
|
||||
final int blockchainHeight = resultSet.next() ? resultSet.getInt(1) : 0;
|
||||
final int heightStep = 100;
|
||||
|
||||
LOGGER.info("Rebuilding AT state summaries in repository - this might take a while... (approx. 2 mins on high-spec)");
|
||||
for (int minHeight = 1; minHeight < blockchainHeight; minHeight += heightStep) {
|
||||
stmt.execute("INSERT INTO ATStatesNew ("
|
||||
+ "SELECT AT_address, height, state_hash, fees, is_initial "
|
||||
+ "FROM ATStates "
|
||||
+ "WHERE height BETWEEN " + minHeight + " AND " + (minHeight + heightStep - 1)
|
||||
+ ")");
|
||||
stmt.execute("COMMIT");
|
||||
}
|
||||
stmt.execute("CHECKPOINT");
|
||||
|
||||
LOGGER.info("Rebuilding AT states height index in repository - this might take about 3x longer...");
|
||||
stmt.execute("CREATE INDEX ATStatesHeightIndex ON ATStatesNew (height)");
|
||||
stmt.execute("CHECKPOINT");
|
||||
|
||||
stmt.execute("CREATE TABLE ATStatesData ("
|
||||
+ "AT_address QortalAddress, height INTEGER NOT NULL, state_data ATState NOT NULL, "
|
||||
+ "PRIMARY KEY (height, AT_address), "
|
||||
+ "FOREIGN KEY (AT_address) REFERENCES ATs (AT_address) ON DELETE CASCADE)");
|
||||
stmt.execute("SET TABLE ATStatesData NEW SPACE");
|
||||
stmt.execute("CHECKPOINT");
|
||||
|
||||
LOGGER.info("Rebuilding AT state data in repository - this might take a while... (approx. 2 mins on high-spec)");
|
||||
for (int minHeight = 1; minHeight < blockchainHeight; minHeight += heightStep) {
|
||||
stmt.execute("INSERT INTO ATStatesData ("
|
||||
+ "SELECT AT_address, height, state_data "
|
||||
+ "FROM ATstates "
|
||||
+ "WHERE state_data IS NOT NULL "
|
||||
+ "AND height BETWEEN " + minHeight + " AND " + (minHeight + heightStep - 1)
|
||||
+ ")");
|
||||
stmt.execute("COMMIT");
|
||||
}
|
||||
stmt.execute("CHECKPOINT");
|
||||
|
||||
stmt.execute("DROP TABLE ATStates");
|
||||
stmt.execute("ALTER TABLE ATStatesNew RENAME TO ATStates");
|
||||
stmt.execute("CHECKPOINT");
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to do
|
||||
return false;
|
||||
|
@ -426,7 +426,7 @@ public class HSQLDBRepository implements Repository {
|
||||
}
|
||||
|
||||
/** Returns DB pathname from passed connection URL. If memory DB, returns "mem". */
|
||||
private static String getDbPathname(String connectionUrl) {
|
||||
/*package*/ static String getDbPathname(String connectionUrl) {
|
||||
Pattern pattern = Pattern.compile("hsqldb:(mem|file):(.*?)(;|$)");
|
||||
Matcher matcher = pattern.matcher(connectionUrl);
|
||||
|
||||
|
426
src/test/java/org/qortal/test/at/AtRepositoryTests.java
Normal file
426
src/test/java/org/qortal/test/at/AtRepositoryTests.java
Normal file
@ -0,0 +1,426 @@
|
||||
package org.qortal.test.at;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.ciyam.at.CompilationException;
|
||||
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.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.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;
|
||||
|
||||
public class AtRepositoryTests extends Common {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetATStateAtHeightWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer testHeight = 8;
|
||||
ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNotNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetATStateAtHeightWithoutData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
int maxHeight = 8;
|
||||
Integer testHeight = maxHeight - 2;
|
||||
|
||||
// Trim AT state data
|
||||
repository.getATRepository().prepareForAtStateTrimming();
|
||||
repository.getATRepository().trimAtStates(2, maxHeight, 1000);
|
||||
|
||||
ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLatestATStateWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
Integer testHeight = blockchainHeight;
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNotNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLatestATStatePostTrimming() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
int maxHeight = blockchainHeight + 100; // more than latest block height
|
||||
Integer testHeight = blockchainHeight;
|
||||
|
||||
// Trim AT state data
|
||||
repository.getATRepository().prepareForAtStateTrimming();
|
||||
repository.getATRepository().trimAtStates(2, maxHeight, 1000);
|
||||
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
// We should always have the latest AT state data available
|
||||
assertNotNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMatchingFinalATStatesWithoutDataValue() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
Integer testHeight = blockchainHeight;
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
|
||||
byte[] codeHash = atData.getCodeHash();
|
||||
Boolean isFinished = Boolean.FALSE;
|
||||
Integer dataByteOffset = null;
|
||||
Long expectedValue = null;
|
||||
Integer minimumFinalHeight = null;
|
||||
Integer limit = null;
|
||||
Integer offset = null;
|
||||
Boolean reverse = null;
|
||||
|
||||
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(
|
||||
codeHash,
|
||||
isFinished,
|
||||
dataByteOffset,
|
||||
expectedValue,
|
||||
minimumFinalHeight,
|
||||
limit, offset, reverse);
|
||||
|
||||
assertEquals(false, atStates.isEmpty());
|
||||
assertEquals(1, atStates.size());
|
||||
|
||||
ATStateData atStateData = atStates.get(0);
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNotNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMatchingFinalATStatesWithDataValue() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
Integer testHeight = blockchainHeight;
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
|
||||
byte[] codeHash = atData.getCodeHash();
|
||||
Boolean isFinished = Boolean.FALSE;
|
||||
Integer dataByteOffset = MachineState.HEADER_LENGTH + 0;
|
||||
Long expectedValue = 0L;
|
||||
Integer minimumFinalHeight = null;
|
||||
Integer limit = null;
|
||||
Integer offset = null;
|
||||
Boolean reverse = null;
|
||||
|
||||
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(
|
||||
codeHash,
|
||||
isFinished,
|
||||
dataByteOffset,
|
||||
expectedValue,
|
||||
minimumFinalHeight,
|
||||
limit, offset, reverse);
|
||||
|
||||
assertEquals(false, atStates.isEmpty());
|
||||
assertEquals(1, atStates.size());
|
||||
|
||||
ATStateData atStateData = atStates.get(0);
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNotNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBlockATStatesAtHeightWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer testHeight = 8;
|
||||
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(testHeight);
|
||||
|
||||
assertEquals(false, atStates.isEmpty());
|
||||
assertEquals(1, atStates.size());
|
||||
|
||||
ATStateData atStateData = atStates.get(0);
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
// getBlockATStatesAtHeight never returns actual AT state data anyway
|
||||
assertNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBlockATStatesAtHeightWithoutData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
int maxHeight = 8;
|
||||
Integer testHeight = maxHeight - 2;
|
||||
|
||||
// Trim AT state data
|
||||
repository.getATRepository().prepareForAtStateTrimming();
|
||||
repository.getATRepository().trimAtStates(2, maxHeight, 1000);
|
||||
|
||||
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(testHeight);
|
||||
|
||||
assertEquals(false, atStates.isEmpty());
|
||||
assertEquals(1, atStates.size());
|
||||
|
||||
ATStateData atStateData = atStates.get(0);
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
// getBlockATStatesAtHeight never returns actual AT state data anyway
|
||||
assertNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveATStateWithData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
Integer testHeight = blockchainHeight - 2;
|
||||
ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNotNull(atStateData.getStateData());
|
||||
|
||||
repository.getATRepository().save(atStateData);
|
||||
|
||||
atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNotNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveATStateWithoutData() throws DataException {
|
||||
byte[] creationBytes = buildSimpleAT();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, creationBytes, fundingAmount);
|
||||
String atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a few blocks
|
||||
for (int i = 0; i < 10; ++i)
|
||||
BlockUtils.mintBlock(repository);
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
Integer testHeight = blockchainHeight - 2;
|
||||
ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNotNull(atStateData.getStateData());
|
||||
|
||||
// Clear data
|
||||
ATStateData newAtStateData = new ATStateData(atStateData.getATAddress(),
|
||||
atStateData.getHeight(),
|
||||
/*StateData*/ null,
|
||||
atStateData.getStateHash(),
|
||||
atStateData.getFees(),
|
||||
atStateData.isInitial());
|
||||
repository.getATRepository().save(newAtStateData);
|
||||
|
||||
atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
||||
assertEquals(testHeight, atStateData.getHeight());
|
||||
assertNull(atStateData.getStateData());
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] buildSimpleAT() {
|
||||
// Pretend we use 4 values in data segment
|
||||
int addrCounter = 4;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
// Stop and wait for next block
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user