forked from Qortal/qortal
WIP: initial implementation of AT sleep-until-message (untested)
This commit is contained in:
parent
3ef8b81e51
commit
3253d9d3fb
@ -1,5 +1,7 @@
|
||||
package org.qortal.at;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.ciyam.at.MachineState;
|
||||
@ -56,12 +58,12 @@ public class AT {
|
||||
|
||||
this.atData = new ATData(atAddress, creatorPublicKey, creation, machineState.version, assetId, codeBytes, codeHash,
|
||||
machineState.isSleeping(), machineState.getSleepUntilHeight(), machineState.isFinished(), machineState.hadFatalError(),
|
||||
machineState.isFrozen(), machineState.getFrozenBalance());
|
||||
machineState.isFrozen(), machineState.getFrozenBalance(), null);
|
||||
|
||||
byte[] stateData = machineState.toBytes();
|
||||
byte[] stateHash = Crypto.digest(stateData);
|
||||
|
||||
this.atStateData = new ATStateData(atAddress, height, stateData, stateHash, 0L, true);
|
||||
this.atStateData = new ATStateData(atAddress, height, stateData, stateHash, 0L, true, null);
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
@ -84,13 +86,27 @@ public class AT {
|
||||
this.repository.getATRepository().delete(this.atData.getATAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially execute AT.
|
||||
* <p>
|
||||
* Note that sleep-until-message support might set/reset
|
||||
* sleep-related flags/values.
|
||||
* <p>
|
||||
* {@link #getATStateData()} will return null if nothing happened.
|
||||
* <p>
|
||||
* @param blockHeight
|
||||
* @param blockTimestamp
|
||||
* @return AT-generated transactions, possibly empty
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<AtTransaction> run(int blockHeight, long blockTimestamp) throws DataException {
|
||||
String atAddress = this.atData.getATAddress();
|
||||
|
||||
QortalATAPI api = new QortalATAPI(repository, this.atData, blockTimestamp);
|
||||
QortalAtLoggerFactory loggerFactory = QortalAtLoggerFactory.getInstance();
|
||||
|
||||
byte[] codeBytes = this.atData.getCodeBytes();
|
||||
if (!api.willExecute(blockHeight))
|
||||
return Collections.emptyList();
|
||||
|
||||
// Fetch latest ATStateData for this AT
|
||||
ATStateData latestAtStateData = this.repository.getATRepository().getLatestATState(atAddress);
|
||||
@ -100,8 +116,10 @@ public class AT {
|
||||
throw new IllegalStateException("No previous AT state data found");
|
||||
|
||||
// [Re]create AT machine state using AT state data or from scratch as applicable
|
||||
byte[] codeBytes = this.atData.getCodeBytes();
|
||||
MachineState state = MachineState.fromBytes(api, loggerFactory, latestAtStateData.getStateData(), codeBytes);
|
||||
try {
|
||||
api.preExecute(state);
|
||||
state.execute();
|
||||
} catch (Exception e) {
|
||||
throw new DataException(String.format("Uncaught exception while running AT '%s'", atAddress), e);
|
||||
@ -109,9 +127,16 @@ public class AT {
|
||||
|
||||
byte[] stateData = state.toBytes();
|
||||
byte[] stateHash = Crypto.digest(stateData);
|
||||
long atFees = api.calcFinalFees(state);
|
||||
|
||||
this.atStateData = new ATStateData(atAddress, blockHeight, stateData, stateHash, atFees, false);
|
||||
// Nothing happened?
|
||||
if (state.getSteps() == 0 && Arrays.equals(stateHash, latestAtStateData.getStateHash()))
|
||||
// this.atStateData will be null
|
||||
return Collections.emptyList();
|
||||
|
||||
long atFees = api.calcFinalFees(state);
|
||||
Long sleepUntilMessageTimestamp = this.atData.getSleepUntilMessageTimestamp();
|
||||
|
||||
this.atStateData = new ATStateData(atAddress, blockHeight, stateData, stateHash, atFees, false, sleepUntilMessageTimestamp);
|
||||
|
||||
return api.getTransactions();
|
||||
}
|
||||
@ -130,6 +155,10 @@ public class AT {
|
||||
this.atData.setHadFatalError(state.hadFatalError());
|
||||
this.atData.setIsFrozen(state.isFrozen());
|
||||
this.atData.setFrozenBalance(state.getFrozenBalance());
|
||||
|
||||
// Special sleep-until-message support
|
||||
this.atData.setSleepUntilMessageTimestamp(this.atStateData.getSleepUntilMessageTimestamp());
|
||||
|
||||
this.repository.getATRepository().save(this.atData);
|
||||
}
|
||||
|
||||
@ -157,6 +186,10 @@ public class AT {
|
||||
this.atData.setHadFatalError(state.hadFatalError());
|
||||
this.atData.setIsFrozen(state.isFrozen());
|
||||
this.atData.setFrozenBalance(state.getFrozenBalance());
|
||||
|
||||
// Special sleep-until-message support
|
||||
this.atData.setSleepUntilMessageTimestamp(previousStateData.getSleepUntilMessageTimestamp());
|
||||
|
||||
this.repository.getATRepository().save(this.atData);
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ import org.qortal.group.Group;
|
||||
import org.qortal.repository.ATRepository;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.ATRepository.NextTransactionInfo;
|
||||
import org.qortal.transaction.AtTransaction;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
import org.qortal.utils.Base58;
|
||||
@ -74,8 +75,46 @@ public class QortalATAPI extends API {
|
||||
return this.transactions;
|
||||
}
|
||||
|
||||
public long calcFinalFees(MachineState state) {
|
||||
return state.getSteps() * this.ciyamAtSettings.feePerStep;
|
||||
public boolean willExecute(int blockHeight) throws DataException {
|
||||
// Sleep-until-message/height checking
|
||||
Long sleepUntilMessageTimestamp = this.atData.getSleepUntilMessageTimestamp();
|
||||
|
||||
if (sleepUntilMessageTimestamp != null) {
|
||||
// Quicker to check height, if sleep-until-height also active
|
||||
Integer sleepUntilHeight = this.atData.getSleepUntilHeight();
|
||||
|
||||
boolean wakeDueToHeight = sleepUntilHeight != null && blockHeight >= sleepUntilHeight;
|
||||
|
||||
boolean wakeDueToMessage = false;
|
||||
if (!wakeDueToHeight) {
|
||||
// No avoiding asking repository
|
||||
Timestamp previousTxTimestamp = new Timestamp(sleepUntilMessageTimestamp);
|
||||
NextTransactionInfo nextTransactionInfo = this.repository.getATRepository().findNextTransaction(this.atData.getATAddress(),
|
||||
previousTxTimestamp.blockHeight,
|
||||
previousTxTimestamp.transactionSequence);
|
||||
|
||||
wakeDueToMessage = nextTransactionInfo != null;
|
||||
}
|
||||
|
||||
// Can we skip?
|
||||
if (!wakeDueToHeight && !wakeDueToMessage)
|
||||
// this.atStateData will be null
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void preExecute(MachineState state) {
|
||||
// Sleep-until-message/height checking
|
||||
Long sleepUntilMessageTimestamp = this.atData.getSleepUntilMessageTimestamp();
|
||||
|
||||
if (sleepUntilMessageTimestamp != null) {
|
||||
// We've passed checks, so clear sleep-related flags/values
|
||||
this.setIsSleeping(state, false);
|
||||
this.setSleepUntilHeight(state, null);
|
||||
this.atData.setSleepUntilMessageTimestamp(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Inherited methods from CIYAM AT API
|
||||
@ -408,6 +447,10 @@ public class QortalATAPI extends API {
|
||||
|
||||
// Utility methods
|
||||
|
||||
public long calcFinalFees(MachineState state) {
|
||||
return state.getSteps() * this.ciyamAtSettings.feePerStep;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
@ -456,6 +499,15 @@ public class QortalATAPI extends API {
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ void sleepUntilMessageOrHeight(MachineState state, long txTimestamp, Long sleepUntilHeight) {
|
||||
this.setIsSleeping(state, true);
|
||||
|
||||
this.atData.setSleepUntilMessageTimestamp(txTimestamp);
|
||||
|
||||
if (sleepUntilHeight != null)
|
||||
this.setSleepUntilHeight(state, new Timestamp(sleepUntilHeight).blockHeight);
|
||||
}
|
||||
|
||||
/** Returns AT's account */
|
||||
/* package */ Account getATAccount() {
|
||||
return new Account(this.repository, this.atData.getATAddress());
|
||||
|
@ -84,6 +84,43 @@ public enum QortalFunctionCode {
|
||||
api.setB(state, bBytes);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Sleep AT until a new message arrives after 'tx-timestamp'.<br>
|
||||
* <tt>0x0503 tx-timestamp</tt>
|
||||
*/
|
||||
SLEEP_UNTIL_MESSAGE(0x0503, 1, false) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
if (functionData.value1 <= 0)
|
||||
return;
|
||||
|
||||
long txTimestamp = functionData.value1;
|
||||
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
api.sleepUntilMessageOrHeight(state, txTimestamp, null);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Sleep AT until a new message arrives, after 'tx-timestamp', or height reached.<br>
|
||||
* <tt>0x0504 tx-timestamp height</tt>
|
||||
*/
|
||||
SLEEP_UNTIL_MESSAGE_OR_HEIGHT(0x0504, 2, false) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
if (functionData.value1 <= 0)
|
||||
return;
|
||||
|
||||
long txTimestamp = functionData.value1;
|
||||
|
||||
if (functionData.value2 <= 0)
|
||||
return;
|
||||
|
||||
long sleepUntilHeight = functionData.value2;
|
||||
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
api.sleepUntilMessageOrHeight(state, txTimestamp, sleepUntilHeight);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Convert address in B to 20-byte value in LSB of B1, and all of B2 & B3.<br>
|
||||
* <tt>0x0510</tt>
|
||||
|
@ -1246,12 +1246,13 @@ public class Block {
|
||||
for (ATData atData : executableATs) {
|
||||
AT at = new AT(this.repository, atData);
|
||||
List<AtTransaction> atTransactions = at.run(this.blockData.getHeight(), this.blockData.getTimestamp());
|
||||
ATStateData atStateData = at.getATStateData();
|
||||
// Didn't execute? (e.g. sleeping)
|
||||
if (atStateData == null)
|
||||
continue;
|
||||
|
||||
allAtTransactions.addAll(atTransactions);
|
||||
|
||||
ATStateData atStateData = at.getATStateData();
|
||||
this.ourAtStates.add(atStateData);
|
||||
|
||||
this.ourAtFees += atStateData.getFees();
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ public class ATData {
|
||||
private boolean isFrozen;
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private Long frozenBalance;
|
||||
private Long sleepUntilMessageTimestamp;
|
||||
|
||||
// Constructors
|
||||
|
||||
@ -31,7 +32,8 @@ public class 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, Long frozenBalance) {
|
||||
boolean isSleeping, Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, Long frozenBalance,
|
||||
Long sleepUntilMessageTimestamp) {
|
||||
this.ATAddress = ATAddress;
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
this.creation = creation;
|
||||
@ -45,6 +47,7 @@ public class ATData {
|
||||
this.hadFatalError = hadFatalError;
|
||||
this.isFrozen = isFrozen;
|
||||
this.frozenBalance = frozenBalance;
|
||||
this.sleepUntilMessageTimestamp = sleepUntilMessageTimestamp;
|
||||
}
|
||||
|
||||
/** For constructing skeleton ATData with bare minimum info. */
|
||||
@ -133,4 +136,12 @@ public class ATData {
|
||||
this.frozenBalance = frozenBalance;
|
||||
}
|
||||
|
||||
public Long getSleepUntilMessageTimestamp() {
|
||||
return this.sleepUntilMessageTimestamp;
|
||||
}
|
||||
|
||||
public void setSleepUntilMessageTimestamp(Long sleepUntilMessageTimestamp) {
|
||||
this.sleepUntilMessageTimestamp = sleepUntilMessageTimestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,35 +10,32 @@ public class ATStateData {
|
||||
private Long fees;
|
||||
private boolean isInitial;
|
||||
|
||||
// Qortal-AT-specific
|
||||
private Long sleepUntilMessageTimestamp;
|
||||
|
||||
// Constructors
|
||||
|
||||
/** Create new ATStateData */
|
||||
public ATStateData(String ATAddress, Integer height, byte[] stateData, byte[] stateHash, Long fees, boolean isInitial) {
|
||||
public ATStateData(String ATAddress, Integer height, byte[] stateData, byte[] stateHash, Long fees,
|
||||
boolean isInitial, Long sleepUntilMessageTimestamp) {
|
||||
this.ATAddress = ATAddress;
|
||||
this.height = height;
|
||||
this.stateData = stateData;
|
||||
this.stateHash = stateHash;
|
||||
this.fees = fees;
|
||||
this.isInitial = isInitial;
|
||||
this.sleepUntilMessageTimestamp = sleepUntilMessageTimestamp;
|
||||
}
|
||||
|
||||
/** For recreating per-block ATStateData from repository where not all info is needed */
|
||||
public ATStateData(String ATAddress, int height, byte[] stateHash, Long fees, boolean isInitial) {
|
||||
this(ATAddress, height, 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 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, stateHash, null, false);
|
||||
this(ATAddress, height, null, stateHash, fees, isInitial, null);
|
||||
}
|
||||
|
||||
/** For creating ATStateData from serialized bytes when we don't have all the info */
|
||||
public ATStateData(String ATAddress, byte[] stateHash, Long 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, stateHash, fees, false);
|
||||
// This won't ever be initial AT state from deployment, as that's never serialized over the network.
|
||||
this(ATAddress, null, null, stateHash, fees, false, null);
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
@ -72,4 +69,12 @@ public class ATStateData {
|
||||
return this.isInitial;
|
||||
}
|
||||
|
||||
public Long getSleepUntilMessageTimestamp() {
|
||||
return this.sleepUntilMessageTimestamp;
|
||||
}
|
||||
|
||||
public void setSleepUntilMessageTimestamp(Long sleepUntilMessageTimestamp) {
|
||||
this.sleepUntilMessageTimestamp = sleepUntilMessageTimestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ public interface ATRepository {
|
||||
/**
|
||||
* Returns all ATStateData for a given block height.
|
||||
* <p>
|
||||
* Unlike <tt>getATState</tt>, only returns ATStateData saved at the given height.
|
||||
* Unlike <tt>getATState</tt>, only returns <i>partial</i> ATStateData saved at the given height.
|
||||
*
|
||||
* @param height
|
||||
* - block height
|
||||
|
@ -26,7 +26,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
public ATData fromATAddress(String atAddress) throws DataException {
|
||||
String sql = "SELECT creator, created_when, version, asset_id, code_bytes, code_hash, "
|
||||
+ "is_sleeping, sleep_until_height, is_finished, had_fatal_error, "
|
||||
+ "is_frozen, frozen_balance "
|
||||
+ "is_frozen, frozen_balance, sleep_until_message_timestamp "
|
||||
+ "FROM ATs "
|
||||
+ "WHERE AT_address = ? LIMIT 1";
|
||||
|
||||
@ -54,8 +54,13 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
if (frozenBalance == 0 && resultSet.wasNull())
|
||||
frozenBalance = null;
|
||||
|
||||
Long sleepUntilMessageTimestamp = resultSet.getLong(12);
|
||||
if (sleepUntilMessageTimestamp == 0 && resultSet.wasNull())
|
||||
sleepUntilMessageTimestamp = null;
|
||||
|
||||
return new ATData(atAddress, creatorPublicKey, created, version, assetId, codeBytes, codeHash,
|
||||
isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance);
|
||||
isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance,
|
||||
sleepUntilMessageTimestamp);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch AT from repository", e);
|
||||
}
|
||||
@ -88,7 +93,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
public List<ATData> getAllExecutableATs() throws DataException {
|
||||
String sql = "SELECT AT_address, creator, created_when, version, asset_id, code_bytes, code_hash, "
|
||||
+ "is_sleeping, sleep_until_height, had_fatal_error, "
|
||||
+ "is_frozen, frozen_balance "
|
||||
+ "is_frozen, frozen_balance, sleep_until_message_timestamp "
|
||||
+ "FROM ATs "
|
||||
+ "WHERE is_finished = false "
|
||||
+ "ORDER BY created_when ASC";
|
||||
@ -122,8 +127,13 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
if (frozenBalance == 0 && resultSet.wasNull())
|
||||
frozenBalance = null;
|
||||
|
||||
Long sleepUntilMessageTimestamp = resultSet.getLong(12);
|
||||
if (sleepUntilMessageTimestamp == 0 && resultSet.wasNull())
|
||||
sleepUntilMessageTimestamp = null;
|
||||
|
||||
ATData atData = new ATData(atAddress, creatorPublicKey, created, version, assetId, codeBytes, codeHash,
|
||||
isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance);
|
||||
isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance,
|
||||
sleepUntilMessageTimestamp);
|
||||
|
||||
executableATs.add(atData);
|
||||
} while (resultSet.next());
|
||||
@ -141,7 +151,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
sql.append("SELECT AT_address, creator, created_when, version, asset_id, code_bytes, ")
|
||||
.append("is_sleeping, sleep_until_height, is_finished, had_fatal_error, ")
|
||||
.append("is_frozen, frozen_balance ")
|
||||
.append("is_frozen, frozen_balance, sleep_until_message_timestamp ")
|
||||
.append("FROM ATs ")
|
||||
.append("WHERE code_hash = ? ");
|
||||
bindParams.add(codeHash);
|
||||
@ -185,8 +195,13 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
if (frozenBalance == 0 && resultSet.wasNull())
|
||||
frozenBalance = null;
|
||||
|
||||
Long sleepUntilMessageTimestamp = resultSet.getLong(13);
|
||||
if (sleepUntilMessageTimestamp == 0 && resultSet.wasNull())
|
||||
sleepUntilMessageTimestamp = null;
|
||||
|
||||
ATData atData = new ATData(atAddress, creatorPublicKey, created, version, assetId, codeBytes, codeHash,
|
||||
isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance);
|
||||
isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance,
|
||||
sleepUntilMessageTimestamp);
|
||||
|
||||
matchingATs.add(atData);
|
||||
} while (resultSet.next());
|
||||
@ -225,7 +240,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
.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());
|
||||
.bind("frozen_balance", atData.getFrozenBalance()).bind("sleep_until_message_timestamp", atData.getSleepUntilMessageTimestamp());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
@ -248,7 +263,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
@Override
|
||||
public ATStateData getATStateAtHeight(String atAddress, int height) throws DataException {
|
||||
String sql = "SELECT state_data, state_hash, fees, is_initial "
|
||||
String sql = "SELECT state_data, state_hash, fees, is_initial, sleep_until_message_timestamp "
|
||||
+ "FROM ATStates "
|
||||
+ "LEFT OUTER JOIN ATStatesData USING (AT_address, height) "
|
||||
+ "WHERE ATStates.AT_address = ? AND ATStates.height = ? "
|
||||
@ -263,7 +278,11 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
long fees = resultSet.getLong(3);
|
||||
boolean isInitial = resultSet.getBoolean(4);
|
||||
|
||||
return new ATStateData(atAddress, height, stateData, stateHash, fees, isInitial);
|
||||
Long sleepUntilMessageTimestamp = resultSet.getLong(5);
|
||||
if (sleepUntilMessageTimestamp == 0 && resultSet.wasNull())
|
||||
sleepUntilMessageTimestamp = null;
|
||||
|
||||
return new ATStateData(atAddress, height, stateData, stateHash, fees, isInitial, sleepUntilMessageTimestamp);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch AT state from repository", e);
|
||||
}
|
||||
@ -271,7 +290,7 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
@Override
|
||||
public ATStateData getLatestATState(String atAddress) throws DataException {
|
||||
String sql = "SELECT height, state_data, state_hash, fees, is_initial "
|
||||
String sql = "SELECT height, state_data, state_hash, fees, is_initial, sleep_until_message_timestamp "
|
||||
+ "FROM ATStates "
|
||||
+ "JOIN ATStatesData USING (AT_address, height) "
|
||||
+ "WHERE ATStates.AT_address = ? "
|
||||
@ -290,7 +309,11 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
long fees = resultSet.getLong(4);
|
||||
boolean isInitial = resultSet.getBoolean(5);
|
||||
|
||||
return new ATStateData(atAddress, height, stateData, stateHash, fees, isInitial);
|
||||
Long sleepUntilMessageTimestamp = resultSet.getLong(6);
|
||||
if (sleepUntilMessageTimestamp == 0 && resultSet.wasNull())
|
||||
sleepUntilMessageTimestamp = null;
|
||||
|
||||
return new ATStateData(atAddress, height, stateData, stateHash, fees, isInitial, sleepUntilMessageTimestamp);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch latest AT state from repository", e);
|
||||
}
|
||||
@ -303,10 +326,10 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
sql.append("SELECT AT_address, height, state_data, state_hash, fees, is_initial "
|
||||
sql.append("SELECT AT_address, height, state_data, state_hash, fees, is_initial, FinalATStates.sleep_until_message_timestamp "
|
||||
+ "FROM ATs "
|
||||
+ "CROSS JOIN LATERAL("
|
||||
+ "SELECT height, state_data, state_hash, fees, is_initial "
|
||||
+ "SELECT height, state_data, state_hash, fees, is_initial, sleep_until_message_timestamp "
|
||||
+ "FROM ATStates "
|
||||
+ "JOIN ATStatesData USING (AT_address, height) "
|
||||
+ "WHERE ATStates.AT_address = ATs.AT_address ");
|
||||
@ -360,7 +383,11 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
long fees = resultSet.getLong(5);
|
||||
boolean isInitial = resultSet.getBoolean(6);
|
||||
|
||||
ATStateData atStateData = new ATStateData(atAddress, height, stateData, stateHash, fees, isInitial);
|
||||
Long sleepUntilMessageTimestamp = resultSet.getLong(7);
|
||||
if (sleepUntilMessageTimestamp == 0 && resultSet.wasNull())
|
||||
sleepUntilMessageTimestamp = null;
|
||||
|
||||
ATStateData atStateData = new ATStateData(atAddress, height, stateData, stateHash, fees, isInitial, sleepUntilMessageTimestamp);
|
||||
|
||||
atStates.add(atStateData);
|
||||
} while (resultSet.next());
|
||||
@ -494,7 +521,8 @@ public class HSQLDBATRepository implements ATRepository {
|
||||
|
||||
atStatesSaver.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight())
|
||||
.bind("state_hash", atStateData.getStateHash())
|
||||
.bind("fees", atStateData.getFees()).bind("is_initial", atStateData.isInitial());
|
||||
.bind("fees", atStateData.getFees()).bind("is_initial", atStateData.isInitial())
|
||||
.bind("sleep_until_message_timestamp", atStateData.getSleepUntilMessageTimestamp());
|
||||
|
||||
try {
|
||||
atStatesSaver.execute(this.repository);
|
||||
|
@ -771,6 +771,12 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CHECKPOINT");
|
||||
break;
|
||||
|
||||
case 31:
|
||||
// AT sleep-until-message support
|
||||
stmt.execute("ALTER TABLE ATs ADD sleep_until_message_timestamp BIGINT");
|
||||
stmt.execute("ALTER TABLE ATStates ADD sleep_until_message_timestamp BIGINT");
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to do
|
||||
return false;
|
||||
|
@ -351,7 +351,8 @@ public class AtRepositoryTests extends Common {
|
||||
/*StateData*/ null,
|
||||
atStateData.getStateHash(),
|
||||
atStateData.getFees(),
|
||||
atStateData.isInitial());
|
||||
atStateData.isInitial(),
|
||||
atStateData.getSleepUntilMessageTimestamp());
|
||||
repository.getATRepository().save(newAtStateData);
|
||||
|
||||
atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
|
||||
|
Loading…
x
Reference in New Issue
Block a user