diff --git a/.github/workflows/pr-testing.yml b/.github/workflows/pr-testing.yml index f712a321..3d0925df 100644 --- a/.github/workflows/pr-testing.yml +++ b/.github/workflows/pr-testing.yml @@ -8,16 +8,16 @@ jobs: mavenTesting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Cache local Maven repository - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Set up the Java JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: '11' distribution: 'adopt' diff --git a/WindowsInstaller/Qortal.aip b/WindowsInstaller/Qortal.aip index 7af02485..51ba5f69 100755 --- a/WindowsInstaller/Qortal.aip +++ b/WindowsInstaller/Qortal.aip @@ -17,10 +17,10 @@ - + - + @@ -212,7 +212,7 @@ - + diff --git a/pom.xml b/pom.xml index 12f8472c..35c77bcc 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 3.8.4 + 3.8.9 jar true @@ -304,6 +304,7 @@ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> org.qortal.controller.Controller + true . .. diff --git a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java index dd967451..1e276e59 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java @@ -14,6 +14,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; +import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -35,6 +36,37 @@ public class CrossChainBitcoinResource { @Context HttpServletRequest request; + @GET + @Path("/height") + @Operation( + summary = "Returns current Bitcoin block height", + description = "Returns the height of the most recent block in the Bitcoin chain.", + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "number" + ) + ) + ) + } + ) + @ApiErrors({ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + public String getBitcoinHeight() { + Bitcoin bitcoin = Bitcoin.getInstance(); + + try { + Integer height = bitcoin.getBlockchainHeight(); + if (height == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + + return height.toString(); + + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/walletbalance") @Operation( @@ -118,6 +150,45 @@ public class CrossChainBitcoinResource { } } + @POST + @Path("/unusedaddress") + @Operation( + summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", + description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "BIP32 'm' private/public key in base58", + example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getUnusedBitcoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { + Security.checkApiCallAllowed(request); + + Bitcoin bitcoin = Bitcoin.getInstance(); + + if (!bitcoin.isValidDeterministicKey(key58)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + try { + return bitcoin.getUnusedReceiveAddress(key58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java index 31d51c73..781d78f6 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java @@ -14,6 +14,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; +import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -35,6 +36,37 @@ public class CrossChainDigibyteResource { @Context HttpServletRequest request; + @GET + @Path("/height") + @Operation( + summary = "Returns current Digibyte block height", + description = "Returns the height of the most recent block in the Digibyte chain.", + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "number" + ) + ) + ) + } + ) + @ApiErrors({ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + public String getDigibyteHeight() { + Digibyte digibyte = Digibyte.getInstance(); + + try { + Integer height = digibyte.getBlockchainHeight(); + if (height == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + + return height.toString(); + + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/walletbalance") @Operation( @@ -118,6 +150,45 @@ public class CrossChainDigibyteResource { } } + @POST + @Path("/unusedaddress") + @Operation( + summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", + description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "BIP32 'm' private/public key in base58", + example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getUnusedDigibyteReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { + Security.checkApiCallAllowed(request); + + Digibyte digibyte = Digibyte.getInstance(); + + if (!digibyte.isValidDeterministicKey(key58)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + try { + return digibyte.getUnusedReceiveAddress(key58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java index 28bebfb8..ff1d6d14 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java @@ -21,6 +21,7 @@ import org.qortal.crosschain.SimpleTransaction; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; +import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -33,6 +34,37 @@ public class CrossChainDogecoinResource { @Context HttpServletRequest request; + @GET + @Path("/height") + @Operation( + summary = "Returns current Dogecoin block height", + description = "Returns the height of the most recent block in the Dogecoin chain.", + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "number" + ) + ) + ) + } + ) + @ApiErrors({ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + public String getDogecoinHeight() { + Dogecoin dogecoin = Dogecoin.getInstance(); + + try { + Integer height = dogecoin.getBlockchainHeight(); + if (height == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + + return height.toString(); + + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/walletbalance") @Operation( @@ -116,6 +148,45 @@ public class CrossChainDogecoinResource { } } + @POST + @Path("/unusedaddress") + @Operation( + summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", + description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "BIP32 'm' private/public key in base58", + example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getUnusedDogecoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { + Security.checkApiCallAllowed(request); + + Dogecoin dogecoin = Dogecoin.getInstance(); + + if (!dogecoin.isValidDeterministicKey(key58)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + try { + return dogecoin.getUnusedReceiveAddress(key58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java index d12dd94c..3e2ff799 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java @@ -14,6 +14,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; +import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -35,6 +36,37 @@ public class CrossChainLitecoinResource { @Context HttpServletRequest request; + @GET + @Path("/height") + @Operation( + summary = "Returns current Litecoin block height", + description = "Returns the height of the most recent block in the Litecoin chain.", + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "number" + ) + ) + ) + } + ) + @ApiErrors({ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + public String getLitecoinHeight() { + Litecoin litecoin = Litecoin.getInstance(); + + try { + Integer height = litecoin.getBlockchainHeight(); + if (height == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + + return height.toString(); + + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/walletbalance") @Operation( @@ -118,6 +150,45 @@ public class CrossChainLitecoinResource { } } + @POST + @Path("/unusedaddress") + @Operation( + summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", + description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "BIP32 'm' private/public key in base58", + example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getUnusedLitecoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { + Security.checkApiCallAllowed(request); + + Litecoin litecoin = Litecoin.getInstance(); + + if (!litecoin.isValidDeterministicKey(key58)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + try { + return litecoin.getUnusedReceiveAddress(key58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java index bd7bf57d..6989e7c7 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java @@ -20,6 +20,7 @@ import org.qortal.crosschain.SimpleTransaction; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; +import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -32,6 +33,37 @@ public class CrossChainPirateChainResource { @Context HttpServletRequest request; + @GET + @Path("/height") + @Operation( + summary = "Returns current PirateChain block height", + description = "Returns the height of the most recent block in the PirateChain chain.", + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "number" + ) + ) + ) + } + ) + @ApiErrors({ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + public String getPirateChainHeight() { + PirateChain pirateChain = PirateChain.getInstance(); + + try { + Integer height = pirateChain.getBlockchainHeight(); + if (height == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + + return height.toString(); + + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/walletbalance") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java index 97550392..b1d6aed4 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java @@ -14,6 +14,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; +import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -35,6 +36,37 @@ public class CrossChainRavencoinResource { @Context HttpServletRequest request; + @GET + @Path("/height") + @Operation( + summary = "Returns current Ravencoin block height", + description = "Returns the height of the most recent block in the Ravencoin chain.", + responses = { + @ApiResponse( + content = @Content( + schema = @Schema( + type = "number" + ) + ) + ) + } + ) + @ApiErrors({ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + public String getRavencoinHeight() { + Ravencoin ravencoin = Ravencoin.getInstance(); + + try { + Integer height = ravencoin.getBlockchainHeight(); + if (height == null) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + + return height.toString(); + + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/walletbalance") @Operation( @@ -118,6 +150,45 @@ public class CrossChainRavencoinResource { } } + @POST + @Path("/unusedaddress") + @Operation( + summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", + description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string", + description = "BIP32 'm' private/public key in base58", + example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" + ) + ) + ), + responses = { + @ApiResponse( + content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) + ) + } + ) + @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) + @SecurityRequirement(name = "apiKey") + public String getUnusedRavencoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { + Security.checkApiCallAllowed(request); + + Ravencoin ravencoin = Ravencoin.getInstance(); + + if (!ravencoin.isValidDeterministicKey(key58)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); + + try { + return ravencoin.getUnusedReceiveAddress(key58); + } catch (ForeignBlockchainException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); + } + } + @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java index 06bafcc6..253939f7 100644 --- a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java @@ -222,6 +222,42 @@ public class AdminResource { } } + @GET + @Path("/summary/alltime") + @Operation( + summary = "Summary of activity since genesis", + responses = { + @ApiResponse( + content = @Content(schema = @Schema(implementation = ActivitySummary.class)) + ) + } + ) + @ApiErrors({ApiError.REPOSITORY_ISSUE}) + @SecurityRequirement(name = "apiKey") + public ActivitySummary allTimeSummary(@HeaderParam(Security.API_KEY_HEADER) String apiKey) { + Security.checkApiCallAllowed(request); + + ActivitySummary summary = new ActivitySummary(); + + try (final Repository repository = RepositoryManager.getRepository()) { + int startHeight = 1; + long start = repository.getBlockRepository().fromHeight(startHeight).getTimestamp(); + int endHeight = repository.getBlockRepository().getBlockchainHeight(); + + summary.setBlockCount(endHeight - startHeight); + + summary.setTransactionCountByType(repository.getTransactionRepository().getTransactionSummary(startHeight + 1, endHeight)); + + summary.setAssetsIssued(repository.getAssetRepository().getRecentAssetIds(start).size()); + + summary.setNamesRegistered (repository.getNameRepository().getRecentNames(start).size()); + + return summary; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @GET @Path("/enginestats") @Operation( diff --git a/src/main/java/org/qortal/api/restricted/resource/BootstrapResource.java b/src/main/java/org/qortal/api/restricted/resource/BootstrapResource.java index bbe03c61..47b7cf42 100644 --- a/src/main/java/org/qortal/api/restricted/resource/BootstrapResource.java +++ b/src/main/java/org/qortal/api/restricted/resource/BootstrapResource.java @@ -60,7 +60,7 @@ public class BootstrapResource { bootstrap.validateBlockchain(); return bootstrap.create(); - } catch (DataException | InterruptedException | IOException e) { + } catch (Exception e) { LOGGER.info("Unable to create bootstrap", e); throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage()); } diff --git a/src/main/java/org/qortal/arbitrary/misc/Service.java b/src/main/java/org/qortal/arbitrary/misc/Service.java index 01419d2f..5ea1b7aa 100644 --- a/src/main/java/org/qortal/arbitrary/misc/Service.java +++ b/src/main/java/org/qortal/arbitrary/misc/Service.java @@ -84,6 +84,8 @@ public enum Service { QCHAT_IMAGE(420, true, 500*1024L, null), VIDEO(500, false, null, null), AUDIO(600, false, null, null), + QCHAT_AUDIO(610, true, 10*1024*1024L, null), + QCHAT_VOICE(620, true, 10*1024*1024L, null), BLOG(700, false, null, null), BLOG_POST(777, false, null, null), BLOG_COMMENT(778, false, null, null), diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java index 39425b7e..34acf0cb 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java @@ -204,7 +204,7 @@ public class ArbitraryDataCleanupManager extends Thread { if (completeFileExists && !allChunksExist) { // We have the complete file but not the chunks, so let's convert it - LOGGER.info(String.format("Transaction %s has complete file but no chunks", + LOGGER.debug(String.format("Transaction %s has complete file but no chunks", Base58.encode(arbitraryTransactionData.getSignature()))); ArbitraryTransactionUtils.convertFileToChunks(arbitraryTransactionData, now, STALE_FILE_TIMEOUT); diff --git a/src/main/java/org/qortal/controller/repository/AtStatesPruner.java b/src/main/java/org/qortal/controller/repository/AtStatesPruner.java index 064fe0ea..f06efdb8 100644 --- a/src/main/java/org/qortal/controller/repository/AtStatesPruner.java +++ b/src/main/java/org/qortal/controller/repository/AtStatesPruner.java @@ -39,9 +39,10 @@ public class AtStatesPruner implements Runnable { try (final Repository repository = RepositoryManager.getRepository()) { int pruneStartHeight = repository.getATRepository().getAtPruneHeight(); + int maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository); repository.discardChanges(); - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight); repository.saveChanges(); while (!Controller.isStopping()) { @@ -92,7 +93,8 @@ public class AtStatesPruner implements Runnable { if (upperPrunableHeight > upperBatchHeight) { pruneStartHeight = upperBatchHeight; repository.getATRepository().setAtPruneHeight(pruneStartHeight); - repository.getATRepository().rebuildLatestAtStates(); + maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository); + repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight); repository.saveChanges(); final int finalPruneStartHeight = pruneStartHeight; diff --git a/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java b/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java index 6c026385..125628f1 100644 --- a/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java +++ b/src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java @@ -26,9 +26,10 @@ public class AtStatesTrimmer implements Runnable { try (final Repository repository = RepositoryManager.getRepository()) { int trimStartHeight = repository.getATRepository().getAtTrimHeight(); + int maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository); repository.discardChanges(); - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight); repository.saveChanges(); while (!Controller.isStopping()) { @@ -70,7 +71,8 @@ public class AtStatesTrimmer implements Runnable { if (upperTrimmableHeight > upperBatchHeight) { trimStartHeight = upperBatchHeight; repository.getATRepository().setAtTrimHeight(trimStartHeight); - repository.getATRepository().rebuildLatestAtStates(); + maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository); + repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight); repository.saveChanges(); final int finalTrimStartHeight = trimStartHeight; diff --git a/src/main/java/org/qortal/controller/repository/PruneManager.java b/src/main/java/org/qortal/controller/repository/PruneManager.java index ec27456f..dfb6290b 100644 --- a/src/main/java/org/qortal/controller/repository/PruneManager.java +++ b/src/main/java/org/qortal/controller/repository/PruneManager.java @@ -157,4 +157,18 @@ public class PruneManager { return (height < latestUnprunedHeight); } + /** + * When rebuilding the latest AT states, we need to specify a maxHeight, so that we aren't tracking + * very recent AT states that could potentially be orphaned. This method ensures that AT states + * are given a sufficient number of blocks to confirm before being tracked as a latest AT state. + */ + public static int getMaxHeightForLatestAtStates(Repository repository) throws DataException { + // Get current chain height, and subtract a certain number of "confirmation" blocks + // This is to ensure we are basing our latest AT states data on confirmed blocks - + // ones that won't be orphaned in any normal circumstances + final int confirmationBlocks = 250; + final int chainHeight = repository.getBlockRepository().getBlockchainHeight(); + return chainHeight - confirmationBlocks; + } + } diff --git a/src/main/java/org/qortal/crosschain/Bitcoin.java b/src/main/java/org/qortal/crosschain/Bitcoin.java index 7fec5a17..b65bac8e 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoin.java +++ b/src/main/java/org/qortal/crosschain/Bitcoin.java @@ -49,6 +49,7 @@ public class Bitcoin extends Bitcoiny { //CLOSED new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002), //CLOSED new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002), //CLOSED new Server("electrum.pabu.io", Server.ConnectionType.SSL, 50002), + //CLOSED new Server("electrumx.dev", Server.ConnectionType.SSL, 50002), //CLOSED new Server("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002), //CLOSED new Server("gd42.org", Server.ConnectionType.SSL, 50002), //CLOSED new Server("korea.electrum-server.com", Server.ConnectionType.SSL, 50002), @@ -56,28 +57,75 @@ public class Bitcoin extends Bitcoiny { //1.15.0 new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002), //1.15.0 new Server("electrum.acinq.co", Server.ConnectionType.SSL, 50002), //1.14.0 new Server("electrum.coinext.com.br", Server.ConnectionType.SSL, 50002), + //F1.7.0 new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002), new Server("104.248.139.211", Server.ConnectionType.SSL, 50002), + new Server("128.0.190.26", Server.ConnectionType.SSL, 50002), new Server("142.93.6.38", Server.ConnectionType.SSL, 50002), new Server("157.245.172.236", Server.ConnectionType.SSL, 50002), new Server("167.172.226.175", Server.ConnectionType.SSL, 50002), new Server("167.172.42.31", Server.ConnectionType.SSL, 50002), new Server("178.62.80.20", Server.ConnectionType.SSL, 50002), new Server("185.64.116.15", Server.ConnectionType.SSL, 50002), + new Server("188.165.206.215", Server.ConnectionType.SSL, 50002), + new Server("188.165.211.112", Server.ConnectionType.SSL, 50002), + new Server("2azzarita.hopto.org", Server.ConnectionType.SSL, 50002), + new Server("2electrumx.hopto.me", Server.ConnectionType.SSL, 56022), + new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002), + new Server("65.39.140.37", Server.ConnectionType.SSL, 50002), new Server("68.183.188.105", Server.ConnectionType.SSL, 50002), + new Server("71.73.14.254", Server.ConnectionType.SSL, 50002), + new Server("94.23.247.135", Server.ConnectionType.SSL, 50002), + new Server("assuredly.not.fyi", Server.ConnectionType.SSL, 50002), + new Server("ax101.blockeng.ch", Server.ConnectionType.SSL, 50002), + new Server("ax102.blockeng.ch", Server.ConnectionType.SSL, 50002), + new Server("b.1209k.com", Server.ConnectionType.SSL, 50002), + new Server("b6.1209k.com", Server.ConnectionType.SSL, 50002), + new Server("bitcoin.dermichi.com", Server.ConnectionType.SSL, 50002), + new Server("bitcoin.lu.ke", Server.ConnectionType.SSL, 50002), new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002), new Server("blkhub.net", Server.ConnectionType.SSL, 50002), - new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002), + new Server("btc.electroncash.dk", Server.ConnectionType.SSL, 60002), + new Server("btc.ocf.sh", Server.ConnectionType.SSL, 50002), new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002), new Server("caleb.vegas", Server.ConnectionType.SSL, 50002), new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002), + new Server("electrum.bhoovd.com", Server.ConnectionType.SSL, 50002), new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002), - new Server("electrumx.dev", Server.ConnectionType.SSL, 50002), + new Server("electrum.bitcoinlizard.net", Server.ConnectionType.SSL, 50002), + new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 50002), + new Server("electrum.emzy.de", Server.ConnectionType.SSL, 50002), + new Server("electrum.exan.tech", Server.ConnectionType.SSL, 50002), + new Server("electrum.kendigisland.xyz", Server.ConnectionType.SSL, 50002), + new Server("electrum.mmitech.info", Server.ConnectionType.SSL, 50002), + new Server("electrum.petrkr.net", Server.ConnectionType.SSL, 50002), + new Server("electrum.stippy.com", Server.ConnectionType.SSL, 50002), + new Server("electrum.thomasfischbach.de", Server.ConnectionType.SSL, 50002), + new Server("electrum0.snel.it", Server.ConnectionType.SSL, 50002), + new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 50002), + new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 50002), + new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 50002), + new Server("electrumx.alexridevski.net", Server.ConnectionType.SSL, 50002), + new Server("electrumx-core.1209k.com", Server.ConnectionType.SSL, 50002), new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002), + new Server("ex03.axalgo.com", Server.ConnectionType.SSL, 50002), + new Server("ex05.axalgo.com", Server.ConnectionType.SSL, 50002), + new Server("ex07.axalgo.com", Server.ConnectionType.SSL, 50002), new Server("fortress.qtornado.com", Server.ConnectionType.SSL, 50002), + new Server("fulcrum.grey.pw", Server.ConnectionType.SSL, 50002), + new Server("fulcrum.sethforprivacy.com", Server.ConnectionType.SSL, 51002), new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002), - new Server("kareoke.qoppa.org", Server.ConnectionType.SSL, 50002), new Server("hodlers.beer", Server.ConnectionType.SSL, 50002), + new Server("kareoke.qoppa.org", Server.ConnectionType.SSL, 50002), + new Server("kirsche.emzy.de", Server.ConnectionType.SSL, 50002), new Server("node1.btccuracao.com", Server.ConnectionType.SSL, 50002), + new Server("osr1ex1.compumundohipermegared.one", Server.ConnectionType.SSL, 50002), + new Server("smmalis37.ddns.net", Server.ConnectionType.SSL, 50002), + new Server("ulrichard.ch", Server.ConnectionType.SSL, 50002), + new Server("vmd104012.contaboserver.net", Server.ConnectionType.SSL, 50002), + new Server("vmd104014.contaboserver.net", Server.ConnectionType.SSL, 50002), + new Server("vmd63185.contaboserver.net", Server.ConnectionType.SSL, 50002), + new Server("vmd71287.contaboserver.net", Server.ConnectionType.SSL, 50002), + new Server("vmd84592.contaboserver.net", Server.ConnectionType.SSL, 50002), new Server("xtrum.com", Server.ConnectionType.SSL, 50002)); } diff --git a/src/main/java/org/qortal/crosschain/Bitcoiny.java b/src/main/java/org/qortal/crosschain/Bitcoiny.java index c08bd91e..d1523b50 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoiny.java +++ b/src/main/java/org/qortal/crosschain/Bitcoiny.java @@ -167,6 +167,16 @@ public abstract class Bitcoiny implements ForeignBlockchain { return blockTimestamps.get(5); } + /** + * Returns height from latest block. + *

+ * @throws ForeignBlockchainException if error occurs + */ + public int getBlockchainHeight() throws ForeignBlockchainException { + int height = this.blockchainProvider.getCurrentHeight(); + return height; + } + /** Returns fee per transaction KB. To be overridden for testnet/regtest. */ public Coin getFeePerKb() { return this.bitcoinjContext.getFeePerKb(); diff --git a/src/main/java/org/qortal/crosschain/Digibyte.java b/src/main/java/org/qortal/crosschain/Digibyte.java index 4358b3b3..c5d96383 100644 --- a/src/main/java/org/qortal/crosschain/Digibyte.java +++ b/src/main/java/org/qortal/crosschain/Digibyte.java @@ -45,6 +45,9 @@ public class Digibyte extends Bitcoiny { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! // Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 55002), + new Server("electrum-dgb.qortal.online", ConnectionType.SSL, 50002), + new Server("electrum1-dgb.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1.cipig.net", ConnectionType.SSL, 20059), new Server("electrum2.cipig.net", ConnectionType.SSL, 20059), new Server("electrum3.cipig.net", ConnectionType.SSL, 20059)); diff --git a/src/main/java/org/qortal/crosschain/Dogecoin.java b/src/main/java/org/qortal/crosschain/Dogecoin.java index 9af8d990..99f557a5 100644 --- a/src/main/java/org/qortal/crosschain/Dogecoin.java +++ b/src/main/java/org/qortal/crosschain/Dogecoin.java @@ -45,11 +45,13 @@ public class Dogecoin extends Bitcoiny { public Collection getServers() { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! + // Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=doge + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 54002), + new Server("electrum-doge.qortal.online", ConnectionType.SSL, 50002), + new Server("electrum1-doge.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1.cipig.net", ConnectionType.SSL, 20060), new Server("electrum2.cipig.net", ConnectionType.SSL, 20060), - new Server("electrum3.cipig.net", ConnectionType.SSL, 20060), - new Server("161.97.137.235", ConnectionType.SSL, 50002)); - // TODO: add more mainnet servers. It's too centralized. + new Server("electrum3.cipig.net", ConnectionType.SSL, 20060)); } @Override diff --git a/src/main/java/org/qortal/crosschain/Litecoin.java b/src/main/java/org/qortal/crosschain/Litecoin.java index 6fc6ba50..1dd9037a 100644 --- a/src/main/java/org/qortal/crosschain/Litecoin.java +++ b/src/main/java/org/qortal/crosschain/Litecoin.java @@ -45,17 +45,20 @@ public class Litecoin extends Bitcoiny { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! // Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc - //CLOSED new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002), //CLOSED new Server("electrum-ltc.someguy123.net", Server.ConnectionType.SSL, 50002), + //CLOSED new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022), + //BEHIND new Server("62.171.169.176", Server.ConnectionType.SSL, 50002), //PHISHY new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002), new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443), + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 50002), new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002), + new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002), + new Server("electrum-ltc.qortal.online", Server.ConnectionType.SSL, 50002), + new Server("electrum1-ltc.qortal.online", Server.ConnectionType.SSL, 50002), new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063), new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063), new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063), - new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022), - new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002), - new Server("62.171.169.176", Server.ConnectionType.SSL, 50002)); + new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002)); } @Override diff --git a/src/main/java/org/qortal/crosschain/PirateChain.java b/src/main/java/org/qortal/crosschain/PirateChain.java index 09b37481..a1d31a4e 100644 --- a/src/main/java/org/qortal/crosschain/PirateChain.java +++ b/src/main/java/org/qortal/crosschain/PirateChain.java @@ -57,9 +57,9 @@ public class PirateChain extends Bitcoiny { public Collection getServers() { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! - new Server("arrrlightd.qortal.online", ConnectionType.SSL, 443), - new Server("arrrlightd1.qortal.online", ConnectionType.SSL, 443), - new Server("arrrlightd2.qortal.online", ConnectionType.SSL, 443), + new Server("wallet-arrr1.qortal.online", ConnectionType.SSL, 443), + new Server("wallet-arrr2.qortal.online", ConnectionType.SSL, 443), + new Server("wallet-arrr3.qortal.online", ConnectionType.SSL, 443), new Server("lightd.pirate.black", ConnectionType.SSL, 443)); } diff --git a/src/main/java/org/qortal/crosschain/Ravencoin.java b/src/main/java/org/qortal/crosschain/Ravencoin.java index 7bf5b20f..6030fa50 100644 --- a/src/main/java/org/qortal/crosschain/Ravencoin.java +++ b/src/main/java/org/qortal/crosschain/Ravencoin.java @@ -45,13 +45,17 @@ public class Ravencoin extends Bitcoiny { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! // Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=rvn - new Server("aethyn.com", ConnectionType.SSL, 50002), - new Server("electrum2.rvn.rocks", ConnectionType.SSL, 50002), - new Server("rvn-dashboard.com", ConnectionType.SSL, 50002), - new Server("rvn4lyfe.com", ConnectionType.SSL, 50002), + //CLOSED new Server("aethyn.com", ConnectionType.SSL, 50002), + //CLOSED new Server("electrum2.rvn.rocks", ConnectionType.SSL, 50002), + //BEHIND new Server("electrum3.rvn.rocks", ConnectionType.SSL, 50002), + new Server("electrum.qortal.link", Server.ConnectionType.SSL, 56002), + new Server("electrum-rvn.qortal.online", ConnectionType.SSL, 50002), + new Server("electrum1-rvn.qortal.online", ConnectionType.SSL, 50002), new Server("electrum1.cipig.net", ConnectionType.SSL, 20051), new Server("electrum2.cipig.net", ConnectionType.SSL, 20051), - new Server("electrum3.cipig.net", ConnectionType.SSL, 20051)); + new Server("electrum3.cipig.net", ConnectionType.SSL, 20051), + new Server("rvn-dashboard.com", ConnectionType.SSL, 50002), + new Server("rvn4lyfe.com", ConnectionType.SSL, 50002)); } @Override diff --git a/src/main/java/org/qortal/data/chat/ActiveChats.java b/src/main/java/org/qortal/data/chat/ActiveChats.java index c546d637..d5ebcf3f 100644 --- a/src/main/java/org/qortal/data/chat/ActiveChats.java +++ b/src/main/java/org/qortal/data/chat/ActiveChats.java @@ -17,17 +17,21 @@ public class ActiveChats { private Long timestamp; private String sender; private String senderName; + private byte[] signature; + private byte[] data; protected GroupChat() { /* JAXB */ } - public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName) { + public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName, byte[] signature, byte[] data) { this.groupId = groupId; this.groupName = groupName; this.timestamp = timestamp; this.sender = sender; this.senderName = senderName; + this.signature = signature; + this.data = data; } public int getGroupId() { @@ -49,6 +53,14 @@ public class ActiveChats { public String getSenderName() { return this.senderName; } + + public byte[] getSignature() { + return this.signature; + } + + public byte[] getData() { + return this.data; + } } @XmlAccessorType(XmlAccessType.FIELD) @@ -118,4 +130,4 @@ public class ActiveChats { return this.direct; } -} +} \ No newline at end of file diff --git a/src/main/java/org/qortal/naming/Name.java b/src/main/java/org/qortal/naming/Name.java index ecf826a5..1751cca8 100644 --- a/src/main/java/org/qortal/naming/Name.java +++ b/src/main/java/org/qortal/naming/Name.java @@ -16,6 +16,8 @@ import org.qortal.repository.Repository; import org.qortal.transaction.Transaction.TransactionType; import org.qortal.utils.Unicode; +import java.util.Objects; + public class Name { // Properties @@ -116,7 +118,7 @@ public class Name { this.repository.getNameRepository().save(this.nameData); - if (!updateNameTransactionData.getNewName().isEmpty()) + if (!updateNameTransactionData.getNewName().isEmpty() && !Objects.equals(updateNameTransactionData.getName(), updateNameTransactionData.getNewName())) // Name has changed, delete old entry this.repository.getNameRepository().delete(updateNameTransactionData.getNewName()); diff --git a/src/main/java/org/qortal/repository/ATRepository.java b/src/main/java/org/qortal/repository/ATRepository.java index 0f537ae9..93da924c 100644 --- a/src/main/java/org/qortal/repository/ATRepository.java +++ b/src/main/java/org/qortal/repository/ATRepository.java @@ -119,7 +119,7 @@ public interface ATRepository { *

* NOTE: performs implicit repository.saveChanges(). */ - public void rebuildLatestAtStates() throws DataException; + public void rebuildLatestAtStates(int maxHeight) throws DataException; /** Returns height of first trimmable AT state. */ diff --git a/src/main/java/org/qortal/repository/Bootstrap.java b/src/main/java/org/qortal/repository/Bootstrap.java index 626433e8..2d2605cc 100644 --- a/src/main/java/org/qortal/repository/Bootstrap.java +++ b/src/main/java/org/qortal/repository/Bootstrap.java @@ -279,7 +279,9 @@ public class Bootstrap { LOGGER.info("Generating checksum file..."); String checksum = Crypto.digestHexString(compressedOutputPath.toFile(), 1024*1024); + LOGGER.info("checksum: {}", checksum); Path checksumPath = Paths.get(String.format("%s.sha256", compressedOutputPath.toString())); + LOGGER.info("Writing checksum to path: {}", checksumPath); Files.writeString(checksumPath, checksum, StandardOpenOption.CREATE); // Return the path to the compressed bootstrap file diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java index 04823925..dd0404a8 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java @@ -603,7 +603,7 @@ public class HSQLDBATRepository implements ATRepository { @Override - public void rebuildLatestAtStates() throws DataException { + public void rebuildLatestAtStates(int maxHeight) throws DataException { // latestATStatesLock is to prevent concurrent updates on LatestATStates // that could result in one process using a partial or empty dataset // because it was in the process of being rebuilt by another thread @@ -624,11 +624,12 @@ public class HSQLDBATRepository implements ATRepository { + "CROSS JOIN LATERAL(" + "SELECT height FROM ATStates " + "WHERE ATStates.AT_address = ATs.AT_address " + + "AND height <= ?" + "ORDER BY AT_address DESC, height DESC LIMIT 1" + ") " + ")"; try { - this.repository.executeCheckedUpdate(insertSql); + this.repository.executeCheckedUpdate(insertSql, maxHeight); } catch (SQLException e) { repository.examineException(e); throw new DataException("Unable to populate temporary latest AT states cache in repository", e); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 08226d53..a995a0b3 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -177,11 +177,11 @@ public class HSQLDBChatRepository implements ChatRepository { private List getActiveGroupChats(String address) throws DataException { // Find groups where address is a member and potential latest message details - String groupsSql = "SELECT group_id, group_name, latest_timestamp, sender, sender_name " + String groupsSql = "SELECT group_id, group_name, latest_timestamp, sender, sender_name, signature, data " + "FROM GroupMembers " + "JOIN Groups USING (group_id) " + "LEFT OUTER JOIN LATERAL(" - + "SELECT created_when AS latest_timestamp, sender, name AS sender_name " + + "SELECT created_when AS latest_timestamp, sender, name AS sender_name, signature, data " + "FROM ChatTransactions " + "JOIN Transactions USING (signature) " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " @@ -205,8 +205,10 @@ public class HSQLDBChatRepository implements ChatRepository { String sender = resultSet.getString(4); String senderName = resultSet.getString(5); + byte[] signature = resultSet.getBytes(6); + byte[] data = resultSet.getBytes(7); - GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName); + GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName, signature, data); groupChats.add(groupChat); } while (resultSet.next()); } @@ -215,7 +217,7 @@ public class HSQLDBChatRepository implements ChatRepository { } // We need different SQL to handle group-less chat - String grouplessSql = "SELECT created_when, sender, SenderNames.name " + String grouplessSql = "SELECT created_when, sender, SenderNames.name, signature, data " + "FROM ChatTransactions " + "JOIN Transactions USING (signature) " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " @@ -228,15 +230,19 @@ public class HSQLDBChatRepository implements ChatRepository { Long timestamp = null; String sender = null; String senderName = null; + byte[] signature = null; + byte[] data = null; if (resultSet != null) { // We found a recipient-less, group-less CHAT message, so report its details timestamp = resultSet.getLong(1); sender = resultSet.getString(2); senderName = resultSet.getString(3); + signature = resultSet.getBytes(4); + data = resultSet.getBytes(5); } - GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName); + GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName, signature, data); groupChats.add(groupChat); } catch (SQLException e) { throw new DataException("Unable to fetch active group chats from repository", e); @@ -291,4 +297,4 @@ public class HSQLDBChatRepository implements ChatRepository { return directChats; } -} +} \ No newline at end of file diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabasePruning.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabasePruning.java index 978ba25e..e2bfc9ef 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabasePruning.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabasePruning.java @@ -99,7 +99,7 @@ public class HSQLDBDatabasePruning { // It's essential that we rebuild the latest AT states here, as we are using this data in the next query. // Failing to do this will result in important AT states being deleted, rendering the database unusable. - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(endHeight); // Loop through all the LatestATStates and copy them to the new table diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index c39cb9f5..b5571c4c 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -216,7 +216,7 @@ public class Settings { public long recoveryModeTimeout = 10 * 60 * 1000L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "3.8.2"; + private String minPeerVersion = "3.8.7"; /** Whether to allow connections with peers below minPeerVersion * If true, we won't sync with them but they can still sync with us, and will show in the peers list * If false, sync will be blocked both ways, and they will not appear in the peers list */ diff --git a/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java b/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java index 788492a9..876f0aed 100644 --- a/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java +++ b/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java @@ -5,6 +5,7 @@ import java.util.List; import org.qortal.account.Account; import org.qortal.asset.Asset; +import org.qortal.controller.repository.NamesDatabaseIntegrityCheck; import org.qortal.data.naming.NameData; import org.qortal.data.transaction.CancelSellNameTransactionData; import org.qortal.data.transaction.TransactionData; @@ -81,7 +82,13 @@ public class CancelSellNameTransaction extends Transaction { @Override public void preProcess() throws DataException { - // Nothing to do + CancelSellNameTransactionData cancelSellNameTransactionData = (CancelSellNameTransactionData) transactionData; + + // Rebuild this name in the Names table from the transaction history + // This is necessary because in some rare cases names can be missing from the Names table after registration + // but we have been unable to reproduce the issue and track down the root cause + NamesDatabaseIntegrityCheck namesDatabaseIntegrityCheck = new NamesDatabaseIntegrityCheck(); + namesDatabaseIntegrityCheck.rebuildName(cancelSellNameTransactionData.getName(), this.repository); } @Override diff --git a/src/main/resources/i18n/ApiError_pl.properties b/src/main/resources/i18n/ApiError_pl.properties new file mode 100644 index 00000000..fcb6191c --- /dev/null +++ b/src/main/resources/i18n/ApiError_pl.properties @@ -0,0 +1,83 @@ +#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) +# Keys are from api.ApiError enum + +# "localeLang": "pl", + +### Common ### +JSON = nie udało się przetworzyć wiadomości JSON + +INSUFFICIENT_BALANCE = niedostateczne środki + +UNAUTHORIZED = nieautoryzowane połączenie API + +REPOSITORY_ISSUE = błąd repozytorium + +NON_PRODUCTION = to wywołanie API nie jest dozwolone dla systemów produkcyjnych + +BLOCKCHAIN_NEEDS_SYNC = blockchain musi się najpierw zsynchronizować + +NO_TIME_SYNC = zegar się jeszcze nie zsynchronizował + +### Validation ### +INVALID_SIGNATURE = nieprawidłowa sygnatura + +INVALID_ADDRESS = nieprawidłowy adres + +INVALID_PUBLIC_KEY = nieprawidłowy klucz publiczny + +INVALID_DATA = nieprawidłowe dane + +INVALID_NETWORK_ADDRESS = nieprawidłowy adres sieci + +ADDRESS_UNKNOWN = nieznany adres konta + +INVALID_CRITERIA = nieprawidłowe kryteria wyszukiwania + +INVALID_REFERENCE = nieprawidłowe skierowanie + +TRANSFORMATION_ERROR = nie udało się przekształcić JSON w transakcję + +INVALID_PRIVATE_KEY = klucz prywatny jest niepoprawny + +INVALID_HEIGHT = nieprawidłowa wysokość bloku + +CANNOT_MINT = konto nie możne bić monet + +### Blocks ### +BLOCK_UNKNOWN = blok nieznany + +### Transactions ### +TRANSACTION_UNKNOWN = nieznana transakcja + +PUBLIC_KEY_NOT_FOUND = nie znaleziono klucza publicznego + +# this one is special in that caller expected to pass two additional strings, hence the two %s +TRANSACTION_INVALID = transakcja nieważna: %s (%s) + +### Naming ### +NAME_UNKNOWN = nazwa nieznana + +### Asset ### +INVALID_ASSET_ID = nieprawidłowy identyfikator aktywy + +INVALID_ORDER_ID = nieprawidłowy identyfikator zlecenia aktywy + +ORDER_UNKNOWN = nieznany identyfikator zlecenia aktywy + +### Groups ### +GROUP_UNKNOWN = nieznana grupa + +### Foreign Blockchain ### +FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = obcy blockchain lub problem z siecią ElectrumX + +FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = niewystarczające środki na obcym blockchainie + +FOREIGN_BLOCKCHAIN_TOO_SOON = zbyt wczesne nadawanie transakcji na obcym blockchainie (okres karencji/średni czas bloku) + +### Trade Portal ### +ORDER_SIZE_TOO_SMALL = zbyt niska kwota zlecenia + +### Data ### +FILE_NOT_FOUND = plik nie został znaleziony + +NO_REPLY = peer nie odpowiedział w wyznaczonym czasie diff --git a/src/main/resources/i18n/ApiError_ru.properties b/src/main/resources/i18n/ApiError_ru.properties index 52580ac8..1367f29b 100644 --- a/src/main/resources/i18n/ApiError_ru.properties +++ b/src/main/resources/i18n/ApiError_ru.properties @@ -16,7 +16,7 @@ NON_PRODUCTION = этот вызов API не разрешен для произ BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться -NO_TIME_SYNC = пока нет синхронизации часов +NO_TIME_SYNC = время не синхронизировано ### Validation ### INVALID_SIGNATURE = недействительная подпись @@ -72,7 +72,7 @@ FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = проблема с внешним блокч FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = недостаточный баланс на внешнем блокчейне -FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внений блокчей (время блокировки/среднее время блока) +FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внешний блокчей (время блокировки/среднее время блока) ### Trade Portal ### ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера @@ -80,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера ### Data ### FILE_NOT_FOUND = файл не найден -NO_REPLY = узел не ответил данными +NO_REPLY = нет ответа diff --git a/src/main/resources/i18n/SysTray_de.properties b/src/main/resources/i18n/SysTray_de.properties index 4dc7edd2..7fa041b3 100644 --- a/src/main/resources/i18n/SysTray_de.properties +++ b/src/main/resources/i18n/SysTray_de.properties @@ -5,11 +5,11 @@ APPLYING_UPDATE_AND_RESTARTING = Automatisches Update anwenden und neu starten AUTO_UPDATE = Automatisches Update -BLOCK_HEIGHT = height +BLOCK_HEIGHT = Blockhöhe BLOCKS_REMAINING = blocks remaining -BUILD_VERSION = Build-Version +BUILD_VERSION = Entwicklungs-Version CHECK_TIME_ACCURACY = Prüfe Zeitgenauigkeit @@ -23,7 +23,7 @@ CREATING_BACKUP_OF_DB_FILES = Erstellen Backup von Datenbank Dateien … DB_BACKUP = Datenbank Backup -DB_CHECKPOINT = Datenbank Kontrollpunkt +DB_CHECKPOINT = Datenbank Check DB_MAINTENANCE = Datenbank Instandhaltung @@ -31,18 +31,18 @@ EXIT = Verlassen LITE_NODE = Lite node -MINTING_DISABLED = NOT minting +MINTING_DISABLED = Kein minting -MINTING_ENABLED = \u2714 Minting +MINTING_ENABLED = \u2714 Minting aktiviert OPEN_UI = Öffne UI -PERFORMING_DB_CHECKPOINT = Speichern nicht übergebener Datenbank Änderungen … +PERFORMING_DB_CHECKPOINT = Speichern von unbestätigten Datenbankänderungen... PERFORMING_DB_MAINTENANCE = Planmäßige Wartung durchführen... SYNCHRONIZE_CLOCK = Synchronisiere Uhr -SYNCHRONIZING_BLOCKCHAIN = Synchronisierung +SYNCHRONIZING_BLOCKCHAIN = Synchronisierung der Blockchain -SYNCHRONIZING_CLOCK = Synchronisierung Uhr +SYNCHRONIZING_CLOCK = Synchronisierung der Uhr diff --git a/src/main/resources/i18n/SysTray_pl.properties b/src/main/resources/i18n/SysTray_pl.properties new file mode 100644 index 00000000..84740da0 --- /dev/null +++ b/src/main/resources/i18n/SysTray_pl.properties @@ -0,0 +1,46 @@ +#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) +# SysTray pop-up menu + +APPLYING_UPDATE_AND_RESTARTING = Zastosowanie automatycznej aktualizacji i ponowne uruchomienie... + +AUTO_UPDATE = Automatyczna aktualizacja + +BLOCK_HEIGHT = wysokość + +BUILD_VERSION = Wersja kompilacji + +CHECK_TIME_ACCURACY = Sprawdz dokładność czasu + +CONNECTING = Łączenie + +CONNECTION = połączenie + +CONNECTIONS = połączenia + +CREATING_BACKUP_OF_DB_FILES = Tworzenie kopii zapasowej plików bazy danych... + +DB_BACKUP = Kopia zapasowa bazy danych + +DB_CHECKPOINT = Punkt kontrolny bazy danych... + +DB_MAINTENANCE = Konserwacja bazy danych + +EXIT = Zakończ + +LITE_NODE = Lite node + +MINTING_DISABLED = Mennica zamknięta + +MINTING_ENABLED = \u2714 Mennica aktywna + +OPEN_UI = Otwórz interfejs użytkownika + +PERFORMING_DB_CHECKPOINT = Zapisywanie niezaksięgowanych zmian w bazie danych... + +PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance... + +SYNCHRONIZE_CLOCK = Synchronizuj zegar + +SYNCHRONIZING_BLOCKCHAIN = Synchronizacja + +SYNCHRONIZING_CLOCK = Synchronizacja zegara diff --git a/src/main/resources/i18n/SysTray_ru.properties b/src/main/resources/i18n/SysTray_ru.properties index ff346304..c8615f73 100644 --- a/src/main/resources/i18n/SysTray_ru.properties +++ b/src/main/resources/i18n/SysTray_ru.properties @@ -1,7 +1,7 @@ #Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) # SysTray pop-up menu -APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуска... +APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуск... AUTO_UPDATE = Автоматическое обновление diff --git a/src/main/resources/i18n/TransactionValidity_de.properties b/src/main/resources/i18n/TransactionValidity_de.properties new file mode 100644 index 00000000..1827482b --- /dev/null +++ b/src/main/resources/i18n/TransactionValidity_de.properties @@ -0,0 +1,195 @@ +# + +ACCOUNT_ALREADY_EXISTS = Account existiert bereits + +ACCOUNT_CANNOT_REWARD_SHARE = Account kann keine Belohnung teilen + +ADDRESS_ABOVE_RATE_LIMIT = address hat das angegebene Geschwindigkeitlimit erreicht + +ADDRESS_BLOCKED = Addresse ist geblockt + +ALREADY_GROUP_ADMIN = bereits Gruppen Admin + +ALREADY_GROUP_MEMBER = bereits Gruppen Mitglied + +ALREADY_VOTED_FOR_THAT_OPTION = bereits für diese Option gestimmt + +ASSET_ALREADY_EXISTS = asset existiert bereits + +ASSET_DOES_NOT_EXIST = asset nicht gefunden + +ASSET_DOES_NOT_MATCH_AT = asset passt nicht mit AT's asset + +ASSET_NOT_SPENDABLE = asset ist nicht ausgabefähig + +AT_ALREADY_EXISTS = AT existiert bereits + +AT_IS_FINISHED = AT ist fertig + +AT_UNKNOWN = AT unbekannt + +BAN_EXISTS = ban besteht bereits + +BAN_UNKNOWN = ban unbekannt + +BANNED_FROM_GROUP = von der gruppe gebannt + +BUYER_ALREADY_OWNER = Käufer ist bereits Besitzer + +CLOCK_NOT_SYNCED = Uhr nicht synchronisiert + +DUPLICATE_MESSAGE = Adresse sendete doppelte Nachricht + +DUPLICATE_OPTION = Duplizierungsmöglichkeit + +GROUP_ALREADY_EXISTS = Gruppe besteht bereits + +GROUP_APPROVAL_DECIDED = Gruppenfreigabe bereits beschlossen + +GROUP_APPROVAL_NOT_REQUIRED = Gruppenfreigabe nicht erforderlich + +GROUP_DOES_NOT_EXIST = Gruppe nicht vorhanden + +GROUP_ID_MISMATCH = Gruppen-ID stimmt nicht überein + +GROUP_OWNER_CANNOT_LEAVE = Gruppenbesitzer kann Gruppe nicht verlassen + +HAVE_EQUALS_WANT = das bessesene-asset ist das selbe wie das gesuchte-asset + +INCORRECT_NONCE = falsche PoW-Nonce + +INSUFFICIENT_FEE = unzureichende Gebühr + +INVALID_ADDRESS = ungültige Adresse + +INVALID_AMOUNT = ungültiger Betrag + +INVALID_ASSET_OWNER = Ungültiger Eigentümer + +INVALID_AT_TRANSACTION = ungültige AT-Transaktion + +INVALID_AT_TYPE_LENGTH = ungültige AT 'Typ' Länge + +INVALID_BUT_OK = ungültig aber OK + +INVALID_CREATION_BYTES = ungültige Erstellungs der bytes + +INVALID_DATA_LENGTH = ungültige Datenlänge + +INVALID_DESCRIPTION_LENGTH = ungültige Länge der Beschreibung + +INVALID_GROUP_APPROVAL_THRESHOLD = ungültiger Schwellenwert für die Gruppenzulassung + +INVALID_GROUP_BLOCK_DELAY = Ungültige Blockverzögerung der Gruppenfreigabe + +INVALID_GROUP_ID = ungültige Gruppen-ID + +INVALID_GROUP_OWNER = ungültiger Gruppenbesitzer + +INVALID_LIFETIME = unzulässige Lebensdauer + +INVALID_NAME_LENGTH = ungültige Namenslänge + +INVALID_NAME_OWNER = ungültiger Besitzername + +INVALID_OPTION_LENGTH = ungültige Länge der Optionen + +INVALID_OPTIONS_COUNT = Anzahl ungültiger Optionen + +INVALID_ORDER_CREATOR = ungültiger Auftragsersteller + +INVALID_PAYMENTS_COUNT = Anzahl ungültiger Zahlungen + +INVALID_PUBLIC_KEY = ungültiger öffentlicher Schlüssel + +INVALID_QUANTITY = unzulässige Menge + +INVALID_REFERENCE = ungültige Referenz + +INVALID_RETURN = ungültige Rückgabe + +INVALID_REWARD_SHARE_PERCENT = ungültig Prozent der Belohnunganteile + +INVALID_SELLER = unzulässiger Verkäufer + +INVALID_TAGS_LENGTH = ungültige 'tags'-Länge + +INVALID_TIMESTAMP_SIGNATURE = Ungültige Zeitstempel-Signatur + +INVALID_TX_GROUP_ID = Ungültige Transaktionsgruppen-ID + +INVALID_VALUE_LENGTH = ungültige 'Wert'-Länge + +INVITE_UNKNOWN = Gruppeneinladung unbekannt + +JOIN_REQUEST_EXISTS = Gruppeneinladung existiert bereits + +MAXIMUM_REWARD_SHARES = die maximale Anzahl von Reward-Shares für dieses Konto erreicht + +MISSING_CREATOR = fehlender Ersteller + +MULTIPLE_NAMES_FORBIDDEN = mehrere registrierte Namen pro Konto sind untersagt + +NAME_ALREADY_FOR_SALE = Name bereits zum Verkauf + +NAME_ALREADY_REGISTERED = Name bereits registriert + +NAME_BLOCKED = Name geblockt + +NAME_DOES_NOT_EXIST = Name nicht vorhanden + +NAME_NOT_FOR_SALE = Name ist unverkäuflich + +NAME_NOT_NORMALIZED = Name nicht in Unicode-'normalisierter' Form + +NEGATIVE_AMOUNT = ungültiger/negativer Betrag + +NEGATIVE_FEE = ungültige/negative Gebühr + +NEGATIVE_PRICE = ungültiger/negativer Preis + +NO_BALANCE = unzureichendes Guthaben + +NO_BLOCKCHAIN_LOCK = die Blockchain des Knotens ist beschäftigt + +NO_FLAG_PERMISSION = Konto hat diese Berechtigung nicht + +NOT_GROUP_ADMIN = Account ist kein Gruppenadmin + +NOT_GROUP_MEMBER = Account kein Gruppenmitglied + +NOT_MINTING_ACCOUNT = Account kann nicht minten + +NOT_YET_RELEASED = Funktion noch nicht freigegeben + +OK = OK + +ORDER_ALREADY_CLOSED = Asset Trade Order ist bereits geschlossen + +ORDER_DOES_NOT_EXIST = asset trade order existiert nicht + +POLL_ALREADY_EXISTS = Umfrage bereits vorhanden + +POLL_DOES_NOT_EXIST = Umfrage nicht vorhanden + +POLL_OPTION_DOES_NOT_EXIST = Umfrageoption existiert nicht + +PUBLIC_KEY_UNKNOWN = öffentlicher Schlüssel unbekannt + +REWARD_SHARE_UNKNOWN = Geteilte Belohnungen unbekant + +SELF_SHARE_EXISTS = Selbstbeteiligung (Geteilte Belohnungen) sind breits vorhanden + +TIMESTAMP_TOO_NEW = Zeitstempel zu neu + +TIMESTAMP_TOO_OLD = Zeitstempel zu alt + +TOO_MANY_UNCONFIRMED = Account hat zu viele unbestätigte Transaktionen am laufen + +TRANSACTION_ALREADY_CONFIRMED = Transaktionen sind bereits bestätigt + +TRANSACTION_ALREADY_EXISTS = Transaktionen existiert bereits + +TRANSACTION_UNKNOWN = Unbekante Transaktion + +TX_GROUP_ID_MISMATCH = Transaktion Gruppen ID stimmt nicht überein diff --git a/src/main/resources/i18n/TransactionValidity_pl.properties b/src/main/resources/i18n/TransactionValidity_pl.properties new file mode 100644 index 00000000..bcdceb6e --- /dev/null +++ b/src/main/resources/i18n/TransactionValidity_pl.properties @@ -0,0 +1,196 @@ +# + +ACCOUNT_ALREADY_EXISTS = konto już istnieje + +ACCOUNT_CANNOT_REWARD_SHARE = konto nie może udostępniać nagród + +ADDRESS_ABOVE_RATE_LIMIT = adres osiągnął określony limit stawki + +ADDRESS_BLOCKED = ten adres jest zablokowany + +ALREADY_GROUP_ADMIN = już adminem grupy + +ALREADY_GROUP_MEMBER = już członkiem grupy + +ALREADY_VOTED_FOR_THAT_OPTION = już zagłosowano na ta opcje + +ASSET_ALREADY_EXISTS = aktywa już istnieje + +ASSET_DOES_NOT_EXIST = aktywa nie istnieje + +ASSET_DOES_NOT_MATCH_AT = aktywa nie pasuje do aktywy AT + +ASSET_NOT_SPENDABLE = aktywa nie jest rozporządzalna + +AT_ALREADY_EXISTS = AT już istnieje + +AT_IS_FINISHED = AT zakończył + +AT_UNKNOWN = AT nieznany + +BAN_EXISTS = ban już istnieje + +BAN_UNKNOWN = ban nieznany + +BANNED_FROM_GROUP = zbanowany z grupy + +BUYER_ALREADY_OWNER = kupca jest już właścicielem + +CLOCK_NOT_SYNCED = zegar nie zsynchronizowany + +DUPLICATE_MESSAGE = adres wysłał duplikat wiadomości + +DUPLICATE_OPTION = duplikat opcji + +GROUP_ALREADY_EXISTS = grupa już istnieje + +GROUP_APPROVAL_DECIDED = zatwierdzenie grupy już zdecydowano + +GROUP_APPROVAL_NOT_REQUIRED = zatwierdzenie grupy nie jest wymagane + +GROUP_DOES_NOT_EXIST = grupa nie istnieje + +GROUP_ID_MISMATCH = niedopasowanie identyfikatora grupy + +GROUP_OWNER_CANNOT_LEAVE = właściciel grupy nie może opuścić grupy + +HAVE_EQUALS_WANT = posiadana aktywa równa się chcianej aktywie + +INCORRECT_NONCE = nieprawidłowy nonce PoW + +INSUFFICIENT_FEE = niewystarczająca opłata + +INVALID_ADDRESS = nieprawidłowy adres + +INVALID_AMOUNT = nieprawidłowa kwota + +INVALID_ASSET_OWNER = nieprawidłowy właściciel aktywów + +INVALID_AT_TRANSACTION = nieważna transakcja AT + +INVALID_AT_TYPE_LENGTH = nieprawidłowa długość typu AT + +INVALID_BUT_OK = nieważne, ale OK + +INVALID_CREATION_BYTES = nieprawidłowe bajty tworzenia + +INVALID_DATA_LENGTH = nieprawidłowa długość danych + +INVALID_DESCRIPTION_LENGTH = nieprawidłowa długość opisu + +INVALID_GROUP_APPROVAL_THRESHOLD = nieprawidłowy próg zatwierdzenia grupy + +INVALID_GROUP_BLOCK_DELAY = nieprawidłowe opóźnienie bloku zatwierdzenia grupy + +INVALID_GROUP_ID = nieprawidłowy identyfikator grupy + +INVALID_GROUP_OWNER = nieprawidłowy właściciel grupy + +INVALID_LIFETIME = nieprawidłowy czas istnienia + +INVALID_NAME_LENGTH = nieprawidłowa długość nazwy + +INVALID_NAME_OWNER = nieprawidłowy właściciel nazwy + +INVALID_OPTION_LENGTH = nieprawidłowa długość opcji + +INVALID_OPTIONS_COUNT = nieprawidłowa liczba opcji + +INVALID_ORDER_CREATOR = nieprawidłowy twórca zlecenia + +INVALID_PAYMENTS_COUNT = nieprawidłowa liczba płatności + +INVALID_PUBLIC_KEY = nieprawidłowy klucz publiczny + +INVALID_QUANTITY = nieprawidłowa ilość + +INVALID_REFERENCE = nieprawidłowe skierowanie + +INVALID_RETURN = nieprawidłowy zwrot + +INVALID_REWARD_SHARE_PERCENT = nieprawidłowy procent udziału w nagrodzie + +INVALID_SELLER = nieprawidłowy sprzedawca + +INVALID_TAGS_LENGTH = nieprawidłowa długość tagów + +INVALID_TIMESTAMP_SIGNATURE = nieprawidłowa sygnatura znacznika czasu + +INVALID_TX_GROUP_ID = nieprawidłowy identyfikator grupy transakcji + +INVALID_VALUE_LENGTH = nieprawidłowa długość wartości + +INVITE_UNKNOWN = zaproszenie do grupy nieznane + +JOIN_REQUEST_EXISTS = wniosek o dołączenie do grupy już istnieje + +MAXIMUM_REWARD_SHARES = osiągnięto już maksymalną liczbę udziałów w nagrodzie dla tego konta + +MISSING_CREATOR = brak twórcy + +MULTIPLE_NAMES_FORBIDDEN = zabronione jest używanie wielu nazw na jednym koncie + +NAME_ALREADY_FOR_SALE = nazwa już wystawiona na sprzedaż + +NAME_ALREADY_REGISTERED = nazwa już zarejestrowana + +NAME_BLOCKED = ta nazwa jest zablokowana + +NAME_DOES_NOT_EXIST = nazwa nie istnieje + +NAME_NOT_FOR_SALE = nazwa nie jest przeznaczona do sprzedaży + +NAME_NOT_NORMALIZED = nazwa nie jest w formie 'znormalizowanej' Unicode + +NEGATIVE_AMOUNT = nieprawidłowa/ujemna kwota + +NEGATIVE_FEE = nieprawidłowa/ujemna opłata + +NEGATIVE_PRICE = nieprawidłowa/ujemna cena + +NO_BALANCE = niewystarczające środki + +NO_BLOCKCHAIN_LOCK = węzeł blockchain jest obecnie zajęty + +NO_FLAG_PERMISSION = konto nie ma tego uprawnienia + +NOT_GROUP_ADMIN = konto nie jest adminem grupy + +NOT_GROUP_MEMBER = konto nie jest członkiem grupy + +NOT_MINTING_ACCOUNT = konto nie może bić monet + +NOT_YET_RELEASED = funkcja nie została jeszcze udostępniona + +OK = OK + +ORDER_ALREADY_CLOSED = zlecenie handlu aktywami jest już zakończone + +ORDER_DOES_NOT_EXIST = zlecenie sprzedaży aktywów nie istnieje + +POLL_ALREADY_EXISTS = ankieta już istnieje + +POLL_DOES_NOT_EXIST = ankieta nie istnieje + +POLL_OPTION_DOES_NOT_EXIST = opcja ankiety nie istnieje + +PUBLIC_KEY_UNKNOWN = klucz publiczny nieznany + +REWARD_SHARE_UNKNOWN = nieznany udział w nagrodzie + +SELF_SHARE_EXISTS = samoudział (udział w nagrodzie) już istnieje + +TIMESTAMP_TOO_NEW = zbyt nowy znacznik czasu + +TIMESTAMP_TOO_OLD = zbyt stary znacznik czasu + +TOO_MANY_UNCONFIRMED = rachunek ma zbyt wiele niepotwierdzonych transakcji w toku + +TRANSACTION_ALREADY_CONFIRMED = transakcja została już potwierdzona + +TRANSACTION_ALREADY_EXISTS = transakcja już istnieje + +TRANSACTION_UNKNOWN = transakcja nieznana + +TX_GROUP_ID_MISMATCH = niezgodność ID grupy transakcji + diff --git a/src/test/java/org/qortal/test/BlockArchiveTests.java b/src/test/java/org/qortal/test/BlockArchiveTests.java index 3bfa4e84..8b3de67b 100644 --- a/src/test/java/org/qortal/test/BlockArchiveTests.java +++ b/src/test/java/org/qortal/test/BlockArchiveTests.java @@ -23,7 +23,6 @@ import org.qortal.transform.TransformationException; import org.qortal.transform.block.BlockTransformation; import org.qortal.utils.BlockArchiveUtils; import org.qortal.utils.NTP; -import org.qortal.utils.Triple; import java.io.File; import java.io.IOException; @@ -314,9 +313,10 @@ public class BlockArchiveTests extends Common { repository.getBlockRepository().setBlockPruneHeight(901); // Prune the AT states for the archived blocks - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(900); + repository.saveChanges(); int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 900); - assertEquals(900-1, numATStatesPruned); + assertEquals(900-2, numATStatesPruned); // Minus 1 for genesis block, and another for the latest AT state repository.getATRepository().setAtPruneHeight(901); // Now ensure the SQL repository is missing blocks 2 and 900... @@ -563,16 +563,23 @@ public class BlockArchiveTests extends Common { // Trim the first 500 blocks repository.getBlockRepository().trimOldOnlineAccountsSignatures(0, 500); repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(501); + repository.getATRepository().rebuildLatestAtStates(500); repository.getATRepository().trimAtStates(0, 500, 1000); repository.getATRepository().setAtTrimHeight(501); - // Now block 500 should only have the AT state data hash - block500AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(500); - atStatesData = repository.getATRepository().getATStateAtHeight(block500AtStatesData.get(0).getATAddress(), 500); + // Now block 499 should only have the AT state data hash + List block499AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(499); + atStatesData = repository.getATRepository().getATStateAtHeight(block499AtStatesData.get(0).getATAddress(), 499); assertNotNull(atStatesData.getStateHash()); assertNull(atStatesData.getStateData()); - // ... but block 501 should have the full data + // ... but block 500 should have the full data (due to being retained as the "latest" AT state in the trimmed range + block500AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(500); + atStatesData = repository.getATRepository().getATStateAtHeight(block500AtStatesData.get(0).getATAddress(), 500); + assertNotNull(atStatesData.getStateHash()); + assertNotNull(atStatesData.getStateData()); + + // ... and block 501 should also have the full data List block501AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(501); atStatesData = repository.getATRepository().getATStateAtHeight(block501AtStatesData.get(0).getATAddress(), 501); assertNotNull(atStatesData.getStateHash()); @@ -612,9 +619,10 @@ public class BlockArchiveTests extends Common { repository.getBlockRepository().setBlockPruneHeight(501); // Prune the AT states for the archived blocks - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(500); + repository.saveChanges(); int numATStatesPruned = repository.getATRepository().pruneAtStates(2, 500); - assertEquals(499, numATStatesPruned); + assertEquals(498, numATStatesPruned); // Minus 1 for genesis block, and another for the latest AT state repository.getATRepository().setAtPruneHeight(501); // Now ensure the SQL repository is missing blocks 2 and 500... diff --git a/src/test/java/org/qortal/test/BootstrapTests.java b/src/test/java/org/qortal/test/BootstrapTests.java index aa641e71..b60b412c 100644 --- a/src/test/java/org/qortal/test/BootstrapTests.java +++ b/src/test/java/org/qortal/test/BootstrapTests.java @@ -176,7 +176,8 @@ public class BootstrapTests extends Common { repository.getBlockRepository().setBlockPruneHeight(901); // Prune the AT states for the archived blocks - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(900); + repository.saveChanges(); repository.getATRepository().pruneAtStates(0, 900); repository.getATRepository().setAtPruneHeight(901); diff --git a/src/test/java/org/qortal/test/PruneTests.java b/src/test/java/org/qortal/test/PruneTests.java index 0914d794..5a31146e 100644 --- a/src/test/java/org/qortal/test/PruneTests.java +++ b/src/test/java/org/qortal/test/PruneTests.java @@ -1,16 +1,33 @@ package org.qortal.test; +import com.google.common.hash.HashCode; import org.junit.Before; import org.junit.Test; +import org.qortal.account.Account; import org.qortal.account.PrivateKeyAccount; +import org.qortal.asset.Asset; +import org.qortal.block.Block; import org.qortal.controller.BlockMinter; +import org.qortal.crosschain.AcctMode; +import org.qortal.crosschain.LitecoinACCTv3; +import org.qortal.data.at.ATData; import org.qortal.data.at.ATStateData; import org.qortal.data.block.BlockData; +import org.qortal.data.crosschain.CrossChainTradeData; +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.AtUtils; +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 java.util.ArrayList; import java.util.List; @@ -19,6 +36,13 @@ import static org.junit.Assert.*; public class PruneTests extends Common { + // Constants for test AT (an LTC ACCT) + public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); + public static final int tradeTimeout = 20; // blocks + public static final long redeemAmount = 80_40200000L; + public static final long fundingAmount = 123_45600000L; + public static final long litecoinAmount = 864200L; // 0.00864200 LTC + @Before public void beforeTest() throws DataException { Common.useDefaultSettings(); @@ -62,23 +86,32 @@ public class PruneTests extends Common { repository.getBlockRepository().setBlockPruneHeight(6); // Prune AT states for blocks 2-5 + repository.getATRepository().rebuildLatestAtStates(5); + repository.saveChanges(); int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 5); - assertEquals(4, numATStatesPruned); + assertEquals(3, numATStatesPruned); repository.getATRepository().setAtPruneHeight(6); - // Make sure that blocks 2-5 are now missing block data and AT states data - for (Integer i=2; i <= 5; i++) { + // Make sure that blocks 2-4 are now missing block data and AT states data + for (Integer i=2; i <= 4; i++) { BlockData blockData = repository.getBlockRepository().fromHeight(i); assertNull(blockData); List atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i); assertTrue(atStatesDataList.isEmpty()); } - // ... but blocks 6-10 have block data and full AT states data + // Block 5 should have full AT states data even though it was pruned. + // This is because we identified that as the "latest" AT state in that block range + BlockData blockData = repository.getBlockRepository().fromHeight(5); + assertNull(blockData); + List atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(5); + assertEquals(1, atStatesDataList.size()); + + // Blocks 6-10 have block data and full AT states data for (Integer i=6; i <= 10; i++) { - BlockData blockData = repository.getBlockRepository().fromHeight(i); + blockData = repository.getBlockRepository().fromHeight(i); assertNotNull(blockData.getSignature()); - List atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i); + atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i); assertNotNull(atStatesDataList); assertFalse(atStatesDataList.isEmpty()); ATStateData atStatesData = repository.getATRepository().getATStateAtHeight(atStatesDataList.get(0).getATAddress(), i); @@ -88,4 +121,102 @@ public class PruneTests extends Common { } } + @Test + public void testPruneSleepingAt() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = Common.getTestAccount(repository, "alice"); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + // Mint enough blocks to take the original DEPLOY_AT past the prune threshold (in this case 20) + Block block = BlockUtils.mintBlocks(repository, 25); + + // Send creator's address to AT, instead of typical partner's address + byte[] messageData = LitecoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); + long txTimestamp = block.getBlockData().getTimestamp(); + MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress, txTimestamp); + + // AT should process 'cancel' message in next block + BlockUtils.mintBlock(repository); + + // Prune AT states up to block 20 + repository.getATRepository().rebuildLatestAtStates(20); + repository.saveChanges(); + int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 20); + assertEquals(1, numATStatesPruned); // deleted state at heights 2, but state at height 3 remains + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in CANCELLED mode + CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.CANCELLED, tradeData.mode); + + // Test orphaning - should be possible because the previous AT state at height 3 is still available + BlockUtils.orphanLastBlock(repository); + } + } + + + // Helper methods for AT testing + private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { + byte[] creationBytes = LitecoinACCTv3.buildQortalAT(tradeAddress, litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout); + + 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 = "QORT-LTC cross-chain trade"; + String description = String.format("Qortal-Litecoin cross-chain trade"); + String atType = "ACCT"; + String tags = "QORT-LTC ACCT"; + + 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, long txTimestamp) throws DataException { + 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; + } } diff --git a/src/test/java/org/qortal/test/at/AtRepositoryTests.java b/src/test/java/org/qortal/test/at/AtRepositoryTests.java index 8ef4c774..8441731f 100644 --- a/src/test/java/org/qortal/test/at/AtRepositoryTests.java +++ b/src/test/java/org/qortal/test/at/AtRepositoryTests.java @@ -2,29 +2,20 @@ 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.AtUtils; 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 { @@ -76,7 +67,7 @@ public class AtRepositoryTests extends Common { Integer testHeight = maxHeight - 2; // Trim AT state data - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(maxHeight); repository.getATRepository().trimAtStates(2, maxHeight, 1000); ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight); @@ -130,7 +121,7 @@ public class AtRepositoryTests extends Common { Integer testHeight = blockchainHeight; // Trim AT state data - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(maxHeight); // COMMIT to check latest AT states persist / TEMPORARY table interaction repository.saveChanges(); @@ -163,8 +154,8 @@ public class AtRepositoryTests extends Common { int maxTrimHeight = blockchainHeight - 4; Integer testHeight = maxTrimHeight + 1; - // Trim AT state data - repository.getATRepository().rebuildLatestAtStates(); + // Trim AT state data (using a max height of maxTrimHeight + 1, so it is beyond the trimmed range) + repository.getATRepository().rebuildLatestAtStates(maxTrimHeight + 1); repository.saveChanges(); repository.getATRepository().trimAtStates(2, maxTrimHeight, 1000); @@ -333,7 +324,7 @@ public class AtRepositoryTests extends Common { Integer testHeight = maxHeight - 2; // Trim AT state data - repository.getATRepository().rebuildLatestAtStates(); + repository.getATRepository().rebuildLatestAtStates(maxHeight); repository.getATRepository().trimAtStates(2, maxHeight, 1000); List atStates = repository.getATRepository().getBlockATStatesAtHeight(testHeight); diff --git a/src/test/java/org/qortal/test/common/BlockUtils.java b/src/test/java/org/qortal/test/common/BlockUtils.java index 3077b65b..ab57dadf 100644 --- a/src/test/java/org/qortal/test/common/BlockUtils.java +++ b/src/test/java/org/qortal/test/common/BlockUtils.java @@ -20,6 +20,15 @@ public class BlockUtils { return BlockMinter.mintTestingBlock(repository, mintingAccount); } + /** Mints multiple blocks using "alice-reward-share" test account, and returns the final block. */ + public static Block mintBlocks(Repository repository, int count) throws DataException { + Block block = null; + for (int i=0; i