diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index e631f930..744d1307 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -491,6 +491,8 @@ public class BlockChain { rebuildBlockchain(); try (final Repository repository = RepositoryManager.getRepository()) { + repository.checkConsistency(); + int startHeight = Math.max(repository.getBlockRepository().getBlockchainHeight() - 1440, 1); BlockData detachedBlockData = repository.getBlockRepository().getDetachedBlockSignature(startHeight); diff --git a/src/main/java/org/qortal/repository/ATRepository.java b/src/main/java/org/qortal/repository/ATRepository.java index b21a4909..7970d548 100644 --- a/src/main/java/org/qortal/repository/ATRepository.java +++ b/src/main/java/org/qortal/repository/ATRepository.java @@ -148,4 +148,8 @@ public interface ATRepository { */ public NextTransactionInfo findNextTransaction(String recipient, int height, int sequence) throws DataException; + // Other + + public void checkConsistency() throws DataException; + } diff --git a/src/main/java/org/qortal/repository/Repository.java b/src/main/java/org/qortal/repository/Repository.java index 527b23f3..d4ef35ce 100644 --- a/src/main/java/org/qortal/repository/Repository.java +++ b/src/main/java/org/qortal/repository/Repository.java @@ -55,4 +55,6 @@ public interface Repository extends AutoCloseable { public void importDataFromFile(String filename) throws DataException; + public void checkConsistency() throws DataException; + } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java index 82af283b..10d7874a 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java @@ -5,6 +5,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.qortal.data.at.ATData; import org.qortal.data.at.ATStateData; import org.qortal.repository.ATRepository; @@ -14,6 +16,8 @@ import com.google.common.primitives.Longs; public class HSQLDBATRepository implements ATRepository { + private static final Logger LOGGER = LogManager.getLogger(HSQLDBATRepository.class); + protected HSQLDBRepository repository; public HSQLDBATRepository(HSQLDBRepository repository) { @@ -577,7 +581,35 @@ public class HSQLDBATRepository implements ATRepository { } catch (SQLException e) { throw new DataException("Unable to find next transaction to AT from repository", e); } + } + // Other + + public void checkConsistency() throws DataException { + String sql = "SELECT COUNT(*) FROM ATs " + + "CROSS JOIN LATERAL(" + + "SELECT height FROM ATStates " + + "WHERE ATStates.AT_address = ATs.AT_address " + + "ORDER BY AT_address DESC, height DESC " + + "LIMIT 1" + + ") AS LatestATState (height) " + + "LEFT OUTER JOIN ATStatesData " + + "ON ATStatesData.AT_address = ATs.AT_address AND ATStatesData.height = LatestATState.height " + + "WHERE ATStatesData.AT_address IS NULL"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql)) { + if (resultSet == null) + throw new DataException("Unable to check AT repository consistency"); + + int atCount = resultSet.getInt(1); + + if (atCount > 0) { + LOGGER.warn(() -> String.format("Missing %d latest AT state data row%s!", atCount, (atCount != 1 ? "s" : ""))); + LOGGER.warn("Export key data then resync using bootstrap as soon as possible"); + } + } catch (SQLException e) { + throw new DataException("Unable to check AT repository consistency", e); + } } } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java index a270f0e4..4fbe1649 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBRepository.java @@ -426,6 +426,11 @@ public class HSQLDBRepository implements Repository { } } + @Override + public void checkConsistency() throws DataException { + this.getATRepository().checkConsistency(); + } + /** Returns DB pathname from passed connection URL. If memory DB, returns "mem". */ /*package*/ static String getDbPathname(String connectionUrl) { Pattern pattern = Pattern.compile("hsqldb:(mem|file):(.*?)(;|$)");