diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 1101e71d..dee27413 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -1173,7 +1173,11 @@ public class ArbitraryResource { throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, error); } - final Long minLatestBlockTimestamp = NTP.getTime() - (60 * 60 * 1000L); + final Long now = NTP.getTime(); + if (now == null) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NO_TIME_SYNC); + } + final Long minLatestBlockTimestamp = now - (60 * 60 * 1000L); if (!Controller.getInstance().isUpToDate(minLatestBlockTimestamp)) { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCKCHAIN_NEEDS_SYNC); } @@ -1231,7 +1235,7 @@ public class ArbitraryResource { // The actual data will be in a randomly-named subfolder of tempDirectory // Remove hidden folders, i.e. starting with "_", as some systems can add them, e.g. "__MACOSX" String[] files = tempDirectory.toFile().list((parent, child) -> !child.startsWith("_")); - if (files.length == 1) { // Single directory or file only + if (files != null && files.length == 1) { // Single directory or file only path = Paths.get(tempDirectory.toString(), files[0]).toString(); } } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java index b6b17ea5..fba6a32b 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataBuilder.java @@ -54,10 +54,6 @@ public class ArbitraryDataBuilder { /** * Process transactions, but do not build anything * This is useful for checking the status of a given resource - * - * @throws DataException - * @throws IOException - * @throws MissingDataException */ public void process() throws DataException, IOException, MissingDataException { this.fetchTransactions(); @@ -69,10 +65,6 @@ public class ArbitraryDataBuilder { /** * Build the latest state of a given resource - * - * @throws DataException - * @throws IOException - * @throws MissingDataException */ public void build() throws DataException, IOException, MissingDataException { this.process(); diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java index 779e4024..a7876236 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java @@ -9,7 +9,6 @@ import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.misc.Service; import org.qortal.controller.arbitrary.ArbitraryDataBuildManager; import org.qortal.controller.arbitrary.ArbitraryDataManager; -import org.qortal.controller.arbitrary.ArbitraryDataStorageManager; import org.qortal.crypto.AES; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData.*; @@ -154,9 +153,6 @@ public class ArbitraryDataReader { * If no exception is thrown, you can then use getFilePath() to access the data immediately after returning * * @param overwrite - set to true to force rebuild an existing cache - * @throws IOException - * @throws DataException - * @throws MissingDataException */ public void loadSynchronously(boolean overwrite) throws DataException, IOException, MissingDataException { try { @@ -223,7 +219,6 @@ public class ArbitraryDataReader { /** * Working directory should only be deleted on failure, since it is currently used to * serve a cached version of the resource for subsequent requests. - * @throws IOException */ private void deleteWorkingDirectory() { try { @@ -303,7 +298,7 @@ public class ArbitraryDataReader { break; default: - throw new DataException(String.format("Unknown resource ID type specified: %s", resourceIdType.toString())); + throw new DataException(String.format("Unknown resource ID type specified: %s", resourceIdType)); } } @@ -368,6 +363,9 @@ public class ArbitraryDataReader { // Load data file(s) ArbitraryDataFile arbitraryDataFile = ArbitraryDataFile.fromTransactionData(transactionData); ArbitraryTransactionUtils.checkAndRelocateMiscFiles(transactionData); + if (arbitraryDataFile == null) { + throw new DataException(String.format("arbitraryDataFile is null")); + } if (!arbitraryDataFile.allFilesExist()) { if (ListUtils.isNameBlocked(transactionData.getName())) { diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java index 79bb882b..a4650dfc 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataResource.java @@ -150,6 +150,9 @@ public class ArbitraryDataResource { for (ArbitraryTransactionData transactionData : transactionDataList) { ArbitraryDataFile arbitraryDataFile = ArbitraryDataFile.fromTransactionData(transactionData); + if (arbitraryDataFile == null) { + continue; + } // Delete any chunks or complete files from each transaction arbitraryDataFile.deleteAll(deleteMetadata); diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index fd2c38df..224228b8 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -504,110 +504,118 @@ public class OnlineAccountsManager { computeOurAccountsForTimestamp(onlineAccountsTimestamp); } - private boolean computeOurAccountsForTimestamp(long onlineAccountsTimestamp) { - List mintingAccounts; - try (final Repository repository = RepositoryManager.getRepository()) { - mintingAccounts = repository.getAccountRepository().getMintingAccounts(); + private boolean computeOurAccountsForTimestamp(Long onlineAccountsTimestamp) { + if (onlineAccountsTimestamp != null) { + List mintingAccounts; + try (final Repository repository = RepositoryManager.getRepository()) { + mintingAccounts = repository.getAccountRepository().getMintingAccounts(); - // We have no accounts to send - if (mintingAccounts.isEmpty()) + // We have no accounts to send + if (mintingAccounts.isEmpty()) + return false; + + // Only active reward-shares allowed + Iterator iterator = mintingAccounts.iterator(); + int i = 0; + while (iterator.hasNext()) { + MintingAccountData mintingAccountData = iterator.next(); + + RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey()); + if (rewardShareData == null) { + // Reward-share doesn't even exist - probably not a good sign + iterator.remove(); + continue; + } + + Account mintingAccount = new Account(repository, rewardShareData.getMinter()); + if (!mintingAccount.canMint()) { + // Minting-account component of reward-share can no longer mint - disregard + iterator.remove(); + continue; + } + + if (++i > 1 + 1) { + iterator.remove(); + continue; + } + } + } catch (DataException e) { + LOGGER.warn(String.format("Repository issue trying to fetch minting accounts: %s", e.getMessage())); return false; + } - // Only active reward-shares allowed - Iterator iterator = mintingAccounts.iterator(); - while (iterator.hasNext()) { - MintingAccountData mintingAccountData = iterator.next(); + byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); + List ourOnlineAccounts = new ArrayList<>(); - RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(mintingAccountData.getPublicKey()); - if (rewardShareData == null) { - // Reward-share doesn't even exist - probably not a good sign - iterator.remove(); + int remaining = mintingAccounts.size(); + for (MintingAccountData mintingAccountData : mintingAccounts) { + remaining--; + byte[] privateKey = mintingAccountData.getPrivateKey(); + byte[] publicKey = Crypto.toPublicKey(privateKey); + + // We don't want to compute the online account nonce and signature again if it already exists + Set onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountsTimestamp, k -> ConcurrentHashMap.newKeySet()); + boolean alreadyExists = onlineAccounts.stream().anyMatch(a -> Arrays.equals(a.getPublicKey(), publicKey)); + if (alreadyExists) { + this.hasOurOnlineAccounts = true; + + if (remaining > 0) { + // Move on to next account + continue; + } else { + // Everything exists, so return true + return true; + } + } + + // Generate bytes for mempow + byte[] mempowBytes; + try { + mempowBytes = this.getMemoryPoWBytes(publicKey, onlineAccountsTimestamp); + } catch (IOException e) { + LOGGER.info("Unable to create bytes for MemoryPoW. Moving on to next account..."); continue; } - Account mintingAccount = new Account(repository, rewardShareData.getMinter()); - if (!mintingAccount.canMint()) { - // Minting-account component of reward-share can no longer mint - disregard - iterator.remove(); - continue; - } - } - } catch (DataException e) { - LOGGER.warn(String.format("Repository issue trying to fetch minting accounts: %s", e.getMessage())); - return false; - } - - byte[] timestampBytes = Longs.toByteArray(onlineAccountsTimestamp); - List ourOnlineAccounts = new ArrayList<>(); - - int remaining = mintingAccounts.size(); - for (MintingAccountData mintingAccountData : mintingAccounts) { - remaining--; - byte[] privateKey = mintingAccountData.getPrivateKey(); - byte[] publicKey = Crypto.toPublicKey(privateKey); - - // We don't want to compute the online account nonce and signature again if it already exists - Set onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountsTimestamp, k -> ConcurrentHashMap.newKeySet()); - boolean alreadyExists = onlineAccounts.stream().anyMatch(a -> Arrays.equals(a.getPublicKey(), publicKey)); - if (alreadyExists) { - this.hasOurOnlineAccounts = true; - - if (remaining > 0) { - // Move on to next account - continue; - } - else { - // Everything exists, so return true - return true; - } - } - - // Generate bytes for mempow - byte[] mempowBytes; - try { - mempowBytes = this.getMemoryPoWBytes(publicKey, onlineAccountsTimestamp); - } - catch (IOException e) { - LOGGER.info("Unable to create bytes for MemoryPoW. Moving on to next account..."); - continue; - } - - // Compute nonce - Integer nonce; - try { - nonce = this.computeMemoryPoW(mempowBytes, publicKey, onlineAccountsTimestamp); - if (nonce == null) { - // A nonce is required + // Compute nonce + Integer nonce; + try { + nonce = this.computeMemoryPoW(mempowBytes, publicKey, onlineAccountsTimestamp); + if (nonce == null) { + // A nonce is required + return false; + } + } catch (TimeoutException e) { + LOGGER.info(String.format("Timed out computing nonce for account %.8s", Base58.encode(publicKey))); return false; } - } catch (TimeoutException e) { - LOGGER.info(String.format("Timed out computing nonce for account %.8s", Base58.encode(publicKey))); + + byte[] signature = Qortal25519Extras.signForAggregation(privateKey, timestampBytes); + + // Our account is online + OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); + + // Make sure to verify before adding + if (verifyMemoryPoW(ourOnlineAccountData, null)) { + ourOnlineAccounts.add(ourOnlineAccountData); + } + } + + this.hasOurOnlineAccounts = !ourOnlineAccounts.isEmpty(); + + boolean hasInfoChanged = addAccounts(ourOnlineAccounts); + + if (!hasInfoChanged) return false; - } - byte[] signature = Qortal25519Extras.signForAggregation(privateKey, timestampBytes); + Network.getInstance().broadcast(peer -> new OnlineAccountsV3Message(ourOnlineAccounts)); - // Our account is online - OnlineAccountData ourOnlineAccountData = new OnlineAccountData(onlineAccountsTimestamp, signature, publicKey, nonce); + LOGGER.debug("Broadcasted {} online account{} with timestamp {}", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp); - // Make sure to verify before adding - if (verifyMemoryPoW(ourOnlineAccountData, null)) { - ourOnlineAccounts.add(ourOnlineAccountData); - } + return true; } - this.hasOurOnlineAccounts = !ourOnlineAccounts.isEmpty(); - - boolean hasInfoChanged = addAccounts(ourOnlineAccounts); - - if (!hasInfoChanged) - return false; - - Network.getInstance().broadcast(peer -> new OnlineAccountsV3Message(ourOnlineAccounts)); - - LOGGER.debug("Broadcasted {} online account{} with timestamp {}", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp); - - return true; + return false; }