Browse Source

Merge branch 'master' into q-apps

qdn-on-chain-data
CalDescent 2 years ago
parent
commit
64cd21b0dd
  1. 6
      .github/workflows/pr-testing.yml
  2. 6
      WindowsInstaller/Qortal.aip
  3. 3
      pom.xml
  4. 71
      src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java
  5. 71
      src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java
  6. 71
      src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java
  7. 71
      src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java
  8. 32
      src/main/java/org/qortal/api/resource/CrossChainPirateChainResource.java
  9. 71
      src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java
  10. 36
      src/main/java/org/qortal/api/restricted/resource/AdminResource.java
  11. 2
      src/main/java/org/qortal/api/restricted/resource/BootstrapResource.java
  12. 2
      src/main/java/org/qortal/arbitrary/misc/Service.java
  13. 2
      src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java
  14. 6
      src/main/java/org/qortal/controller/repository/AtStatesPruner.java
  15. 6
      src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java
  16. 14
      src/main/java/org/qortal/controller/repository/PruneManager.java
  17. 54
      src/main/java/org/qortal/crosschain/Bitcoin.java
  18. 10
      src/main/java/org/qortal/crosschain/Bitcoiny.java
  19. 3
      src/main/java/org/qortal/crosschain/Digibyte.java
  20. 8
      src/main/java/org/qortal/crosschain/Dogecoin.java
  21. 11
      src/main/java/org/qortal/crosschain/Litecoin.java
  22. 6
      src/main/java/org/qortal/crosschain/PirateChain.java
  23. 14
      src/main/java/org/qortal/crosschain/Ravencoin.java
  24. 16
      src/main/java/org/qortal/data/chat/ActiveChats.java
  25. 4
      src/main/java/org/qortal/naming/Name.java
  26. 2
      src/main/java/org/qortal/repository/ATRepository.java
  27. 2
      src/main/java/org/qortal/repository/Bootstrap.java
  28. 5
      src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java
  29. 18
      src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java
  30. 2
      src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabasePruning.java
  31. 2
      src/main/java/org/qortal/settings/Settings.java
  32. 9
      src/main/java/org/qortal/transaction/CancelSellNameTransaction.java
  33. 83
      src/main/resources/i18n/ApiError_pl.properties
  34. 6
      src/main/resources/i18n/ApiError_ru.properties
  35. 16
      src/main/resources/i18n/SysTray_de.properties
  36. 46
      src/main/resources/i18n/SysTray_pl.properties
  37. 2
      src/main/resources/i18n/SysTray_ru.properties
  38. 195
      src/main/resources/i18n/TransactionValidity_de.properties
  39. 196
      src/main/resources/i18n/TransactionValidity_pl.properties
  40. 24
      src/test/java/org/qortal/test/BlockArchiveTests.java
  41. 3
      src/test/java/org/qortal/test/BootstrapTests.java
  42. 143
      src/test/java/org/qortal/test/PruneTests.java
  43. 19
      src/test/java/org/qortal/test/at/AtRepositoryTests.java
  44. 9
      src/test/java/org/qortal/test/common/BlockUtils.java
  45. 59
      src/test/java/org/qortal/test/naming/UpdateTests.java

6
.github/workflows/pr-testing.yml

@ -8,16 +8,16 @@ jobs:
mavenTesting: mavenTesting:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Cache local Maven repository - name: Cache local Maven repository
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: ~/.m2/repository path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: | restore-keys: |
${{ runner.os }}-maven- ${{ runner.os }}-maven-
- name: Set up the Java JDK - name: Set up the Java JDK
uses: actions/setup-java@v2 uses: actions/setup-java@v3
with: with:
java-version: '11' java-version: '11'
distribution: 'adopt' distribution: 'adopt'

6
WindowsInstaller/Qortal.aip

@ -17,10 +17,10 @@
<ROW Property="Manufacturer" Value="Qortal"/> <ROW Property="Manufacturer" Value="Qortal"/>
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/> <ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
<ROW Property="NTP_GOOD" Value="false"/> <ROW Property="NTP_GOOD" Value="false"/>
<ROW Property="ProductCode" Value="1033:{6C93A96C-E3AF-42FD-BE11-7EC3734905C6} 1049:{754F5347-82E5-4251-AED0-F4141CDD11F5} 2052:{413BD7B3-A3F8-47D0-BCA4-5C7694A40936} 2057:{71450AC8-1E6F-4469-852D-0591FA693680} " Type="16"/> <ROW Property="ProductCode" Value="1033:{CB85115E-ECCE-4B3D-BB7F-6251A2764922} 1049:{09AC1C62-4E33-4312-826A-38F597ED1B17} 2052:{3CF701B3-E118-4A31-A4B7-156CEA19FBCC} 2057:{468F337D-0EF8-41D1-B5DE-4EEE66BA2AF6} " Type="16"/>
<ROW Property="ProductLanguage" Value="2057"/> <ROW Property="ProductLanguage" Value="2057"/>
<ROW Property="ProductName" Value="Qortal"/> <ROW Property="ProductName" Value="Qortal"/>
<ROW Property="ProductVersion" Value="3.8.3" Type="32"/> <ROW Property="ProductVersion" Value="3.8.5" Type="32"/>
<ROW Property="RECONFIG_NTP" Value="true"/> <ROW Property="RECONFIG_NTP" Value="true"/>
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/> <ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/> <ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
@ -212,7 +212,7 @@
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/> <ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/> <ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/> <ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
<ROW Component="AI_CustomARPName" ComponentId="{EC7B4AD9-F2D9-48C4-A586-C4697D9C380C}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/> <ROW Component="AI_CustomARPName" ComponentId="{094B5D07-2258-4A39-9917-2E2F7F6E210B}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/> <ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/> <ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
<ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/> <ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/>

3
pom.xml

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId> <groupId>org.qortal</groupId>
<artifactId>qortal</artifactId> <artifactId>qortal</artifactId>
<version>3.8.4</version> <version>3.8.9</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<skipTests>true</skipTests> <skipTests>true</skipTests>
@ -304,6 +304,7 @@
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.qortal.controller.Controller</mainClass> <mainClass>org.qortal.controller.Controller</mainClass>
<manifestEntries> <manifestEntries>
<Multi-Release>true</Multi-Release>
<Class-Path>. ..</Class-Path> <Class-Path>. ..</Class-Path>
</manifestEntries> </manifestEntries>
</transformer> </transformer>

71
src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java

@ -14,6 +14,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -35,6 +36,37 @@ public class CrossChainBitcoinResource {
@Context @Context
HttpServletRequest request; 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 @POST
@Path("/walletbalance") @Path("/walletbalance")
@Operation( @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 @POST
@Path("/send") @Path("/send")
@Operation( @Operation(

71
src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java

@ -14,6 +14,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -35,6 +36,37 @@ public class CrossChainDigibyteResource {
@Context @Context
HttpServletRequest request; 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 @POST
@Path("/walletbalance") @Path("/walletbalance")
@Operation( @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 @POST
@Path("/send") @Path("/send")
@Operation( @Operation(

71
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.servlet.http.HttpServletRequest;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -33,6 +34,37 @@ public class CrossChainDogecoinResource {
@Context @Context
HttpServletRequest request; 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 @POST
@Path("/walletbalance") @Path("/walletbalance")
@Operation( @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 @POST
@Path("/send") @Path("/send")
@Operation( @Operation(

71
src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java

@ -14,6 +14,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -35,6 +36,37 @@ public class CrossChainLitecoinResource {
@Context @Context
HttpServletRequest request; 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 @POST
@Path("/walletbalance") @Path("/walletbalance")
@Operation( @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 @POST
@Path("/send") @Path("/send")
@Operation( @Operation(

32
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.servlet.http.HttpServletRequest;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -32,6 +33,37 @@ public class CrossChainPirateChainResource {
@Context @Context
HttpServletRequest request; 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 @POST
@Path("/walletbalance") @Path("/walletbalance")
@Operation( @Operation(

71
src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java

@ -14,6 +14,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -35,6 +36,37 @@ public class CrossChainRavencoinResource {
@Context @Context
HttpServletRequest request; 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 @POST
@Path("/walletbalance") @Path("/walletbalance")
@Operation( @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 @POST
@Path("/send") @Path("/send")
@Operation( @Operation(

36
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 @GET
@Path("/enginestats") @Path("/enginestats")
@Operation( @Operation(

2
src/main/java/org/qortal/api/restricted/resource/BootstrapResource.java

@ -60,7 +60,7 @@ public class BootstrapResource {
bootstrap.validateBlockchain(); bootstrap.validateBlockchain();
return bootstrap.create(); return bootstrap.create();
} catch (DataException | InterruptedException | IOException e) { } catch (Exception e) {
LOGGER.info("Unable to create bootstrap", e); LOGGER.info("Unable to create bootstrap", e);
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage()); throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage());
} }

2
src/main/java/org/qortal/arbitrary/misc/Service.java

@ -84,6 +84,8 @@ public enum Service {
QCHAT_IMAGE(420, true, 500*1024L, null), QCHAT_IMAGE(420, true, 500*1024L, null),
VIDEO(500, false, null, null), VIDEO(500, false, null, null),
AUDIO(600, 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(700, false, null, null),
BLOG_POST(777, false, null, null), BLOG_POST(777, false, null, null),
BLOG_COMMENT(778, false, null, null), BLOG_COMMENT(778, false, null, null),

2
src/main/java/org/qortal/controller/arbitrary/ArbitraryDataCleanupManager.java

@ -204,7 +204,7 @@ public class ArbitraryDataCleanupManager extends Thread {
if (completeFileExists && !allChunksExist) { if (completeFileExists && !allChunksExist) {
// We have the complete file but not the chunks, so let's convert it // 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()))); Base58.encode(arbitraryTransactionData.getSignature())));
ArbitraryTransactionUtils.convertFileToChunks(arbitraryTransactionData, now, STALE_FILE_TIMEOUT); ArbitraryTransactionUtils.convertFileToChunks(arbitraryTransactionData, now, STALE_FILE_TIMEOUT);

6
src/main/java/org/qortal/controller/repository/AtStatesPruner.java

@ -39,9 +39,10 @@ public class AtStatesPruner implements Runnable {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
int pruneStartHeight = repository.getATRepository().getAtPruneHeight(); int pruneStartHeight = repository.getATRepository().getAtPruneHeight();
int maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository);
repository.discardChanges(); repository.discardChanges();
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight);
repository.saveChanges(); repository.saveChanges();
while (!Controller.isStopping()) { while (!Controller.isStopping()) {
@ -92,7 +93,8 @@ public class AtStatesPruner implements Runnable {
if (upperPrunableHeight > upperBatchHeight) { if (upperPrunableHeight > upperBatchHeight) {
pruneStartHeight = upperBatchHeight; pruneStartHeight = upperBatchHeight;
repository.getATRepository().setAtPruneHeight(pruneStartHeight); repository.getATRepository().setAtPruneHeight(pruneStartHeight);
repository.getATRepository().rebuildLatestAtStates(); maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository);
repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight);
repository.saveChanges(); repository.saveChanges();
final int finalPruneStartHeight = pruneStartHeight; final int finalPruneStartHeight = pruneStartHeight;

6
src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java

@ -26,9 +26,10 @@ public class AtStatesTrimmer implements Runnable {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
int trimStartHeight = repository.getATRepository().getAtTrimHeight(); int trimStartHeight = repository.getATRepository().getAtTrimHeight();
int maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository);
repository.discardChanges(); repository.discardChanges();
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight);
repository.saveChanges(); repository.saveChanges();
while (!Controller.isStopping()) { while (!Controller.isStopping()) {
@ -70,7 +71,8 @@ public class AtStatesTrimmer implements Runnable {
if (upperTrimmableHeight > upperBatchHeight) { if (upperTrimmableHeight > upperBatchHeight) {
trimStartHeight = upperBatchHeight; trimStartHeight = upperBatchHeight;
repository.getATRepository().setAtTrimHeight(trimStartHeight); repository.getATRepository().setAtTrimHeight(trimStartHeight);
repository.getATRepository().rebuildLatestAtStates(); maxLatestAtStatesHeight = PruneManager.getMaxHeightForLatestAtStates(repository);
repository.getATRepository().rebuildLatestAtStates(maxLatestAtStatesHeight);
repository.saveChanges(); repository.saveChanges();
final int finalTrimStartHeight = trimStartHeight; final int finalTrimStartHeight = trimStartHeight;

14
src/main/java/org/qortal/controller/repository/PruneManager.java

@ -157,4 +157,18 @@ public class PruneManager {
return (height < latestUnprunedHeight); 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;
}
} }

54
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("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
//CLOSED new Server("btc.litepay.ch", 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("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("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002),
//CLOSED new Server("gd42.org", Server.ConnectionType.SSL, 50002), //CLOSED new Server("gd42.org", Server.ConnectionType.SSL, 50002),
//CLOSED new Server("korea.electrum-server.com", 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("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
//1.15.0 new Server("electrum.acinq.co", 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), //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("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("142.93.6.38", Server.ConnectionType.SSL, 50002),
new Server("157.245.172.236", 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.226.175", Server.ConnectionType.SSL, 50002),
new Server("167.172.42.31", 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("178.62.80.20", Server.ConnectionType.SSL, 50002),
new Server("185.64.116.15", 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("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("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
new Server("blkhub.net", 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("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002), new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
new Server("eai.coincited.net", 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("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("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("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("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("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("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)); new Server("xtrum.com", Server.ConnectionType.SSL, 50002));
} }

10
src/main/java/org/qortal/crosschain/Bitcoiny.java

@ -167,6 +167,16 @@ public abstract class Bitcoiny implements ForeignBlockchain {
return blockTimestamps.get(5); return blockTimestamps.get(5);
} }
/**
* Returns height from latest block.
* <p>
* @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. */ /** Returns fee per transaction KB. To be overridden for testnet/regtest. */
public Coin getFeePerKb() { public Coin getFeePerKb() {
return this.bitcoinjContext.getFeePerKb(); return this.bitcoinjContext.getFeePerKb();

3
src/main/java/org/qortal/crosschain/Digibyte.java

@ -45,6 +45,9 @@ public class Digibyte extends Bitcoiny {
return Arrays.asList( return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources! // Servers chosen on NO BASIS WHATSOEVER from various sources!
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb // 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("electrum1.cipig.net", ConnectionType.SSL, 20059),
new Server("electrum2.cipig.net", ConnectionType.SSL, 20059), new Server("electrum2.cipig.net", ConnectionType.SSL, 20059),
new Server("electrum3.cipig.net", ConnectionType.SSL, 20059)); new Server("electrum3.cipig.net", ConnectionType.SSL, 20059));

8
src/main/java/org/qortal/crosschain/Dogecoin.java

@ -45,11 +45,13 @@ public class Dogecoin extends Bitcoiny {
public Collection<Server> getServers() { public Collection<Server> getServers() {
return Arrays.asList( return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources! // 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("electrum1.cipig.net", ConnectionType.SSL, 20060),
new Server("electrum2.cipig.net", ConnectionType.SSL, 20060), new Server("electrum2.cipig.net", ConnectionType.SSL, 20060),
new Server("electrum3.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.
} }
@Override @Override

11
src/main/java/org/qortal/crosschain/Litecoin.java

@ -45,17 +45,20 @@ public class Litecoin extends Bitcoiny {
return Arrays.asList( return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources! // Servers chosen on NO BASIS WHATSOEVER from various sources!
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc // 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("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), //PHISHY new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002),
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443), 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.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("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
new Server("electrum2.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("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("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002),
new Server("62.171.169.176", Server.ConnectionType.SSL, 50002));
} }
@Override @Override

6
src/main/java/org/qortal/crosschain/PirateChain.java

@ -57,9 +57,9 @@ public class PirateChain extends Bitcoiny {
public Collection<Server> getServers() { public Collection<Server> getServers() {
return Arrays.asList( return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources! // Servers chosen on NO BASIS WHATSOEVER from various sources!
new Server("arrrlightd.qortal.online", ConnectionType.SSL, 443), new Server("wallet-arrr1.qortal.online", ConnectionType.SSL, 443),
new Server("arrrlightd1.qortal.online", ConnectionType.SSL, 443), new Server("wallet-arrr2.qortal.online", ConnectionType.SSL, 443),
new Server("arrrlightd2.qortal.online", ConnectionType.SSL, 443), new Server("wallet-arrr3.qortal.online", ConnectionType.SSL, 443),
new Server("lightd.pirate.black", ConnectionType.SSL, 443)); new Server("lightd.pirate.black", ConnectionType.SSL, 443));
} }

14
src/main/java/org/qortal/crosschain/Ravencoin.java

@ -45,13 +45,17 @@ public class Ravencoin extends Bitcoiny {
return Arrays.asList( return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources! // Servers chosen on NO BASIS WHATSOEVER from various sources!
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=rvn // Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=rvn
new Server("aethyn.com", ConnectionType.SSL, 50002), //CLOSED new Server("aethyn.com", ConnectionType.SSL, 50002),
new Server("electrum2.rvn.rocks", ConnectionType.SSL, 50002), //CLOSED new Server("electrum2.rvn.rocks", ConnectionType.SSL, 50002),
new Server("rvn-dashboard.com", ConnectionType.SSL, 50002), //BEHIND new Server("electrum3.rvn.rocks", ConnectionType.SSL, 50002),
new Server("rvn4lyfe.com", 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("electrum1.cipig.net", ConnectionType.SSL, 20051),
new Server("electrum2.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 @Override

16
src/main/java/org/qortal/data/chat/ActiveChats.java

@ -17,17 +17,21 @@ public class ActiveChats {
private Long timestamp; private Long timestamp;
private String sender; private String sender;
private String senderName; private String senderName;
private byte[] signature;
private byte[] data;
protected GroupChat() { protected GroupChat() {
/* JAXB */ /* 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.groupId = groupId;
this.groupName = groupName; this.groupName = groupName;
this.timestamp = timestamp; this.timestamp = timestamp;
this.sender = sender; this.sender = sender;
this.senderName = senderName; this.senderName = senderName;
this.signature = signature;
this.data = data;
} }
public int getGroupId() { public int getGroupId() {
@ -49,6 +53,14 @@ public class ActiveChats {
public String getSenderName() { public String getSenderName() {
return this.senderName; return this.senderName;
} }
public byte[] getSignature() {
return this.signature;
}
public byte[] getData() {
return this.data;
}
} }
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
@ -118,4 +130,4 @@ public class ActiveChats {
return this.direct; return this.direct;
} }
} }

4
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.transaction.Transaction.TransactionType;
import org.qortal.utils.Unicode; import org.qortal.utils.Unicode;
import java.util.Objects;
public class Name { public class Name {
// Properties // Properties
@ -116,7 +118,7 @@ public class Name {
this.repository.getNameRepository().save(this.nameData); 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 // Name has changed, delete old entry
this.repository.getNameRepository().delete(updateNameTransactionData.getNewName()); this.repository.getNameRepository().delete(updateNameTransactionData.getNewName());

2
src/main/java/org/qortal/repository/ATRepository.java

@ -119,7 +119,7 @@ public interface ATRepository {
* <p> * <p>
* NOTE: performs implicit <tt>repository.saveChanges()</tt>. * NOTE: performs implicit <tt>repository.saveChanges()</tt>.
*/ */
public void rebuildLatestAtStates() throws DataException; public void rebuildLatestAtStates(int maxHeight) throws DataException;
/** Returns height of first trimmable AT state. */ /** Returns height of first trimmable AT state. */

2
src/main/java/org/qortal/repository/Bootstrap.java

@ -279,7 +279,9 @@ public class Bootstrap {
LOGGER.info("Generating checksum file..."); LOGGER.info("Generating checksum file...");
String checksum = Crypto.digestHexString(compressedOutputPath.toFile(), 1024*1024); String checksum = Crypto.digestHexString(compressedOutputPath.toFile(), 1024*1024);
LOGGER.info("checksum: {}", checksum);
Path checksumPath = Paths.get(String.format("%s.sha256", compressedOutputPath.toString())); Path checksumPath = Paths.get(String.format("%s.sha256", compressedOutputPath.toString()));
LOGGER.info("Writing checksum to path: {}", checksumPath);
Files.writeString(checksumPath, checksum, StandardOpenOption.CREATE); Files.writeString(checksumPath, checksum, StandardOpenOption.CREATE);
// Return the path to the compressed bootstrap file // Return the path to the compressed bootstrap file

5
src/main/java/org/qortal/repository/hsqldb/HSQLDBATRepository.java

@ -603,7 +603,7 @@ public class HSQLDBATRepository implements ATRepository {
@Override @Override
public void rebuildLatestAtStates() throws DataException { public void rebuildLatestAtStates(int maxHeight) throws DataException {
// latestATStatesLock is to prevent concurrent updates on LatestATStates // latestATStatesLock is to prevent concurrent updates on LatestATStates
// that could result in one process using a partial or empty dataset // that could result in one process using a partial or empty dataset
// because it was in the process of being rebuilt by another thread // because it was in the process of being rebuilt by another thread
@ -624,11 +624,12 @@ public class HSQLDBATRepository implements ATRepository {
+ "CROSS JOIN LATERAL(" + "CROSS JOIN LATERAL("
+ "SELECT height FROM ATStates " + "SELECT height FROM ATStates "
+ "WHERE ATStates.AT_address = ATs.AT_address " + "WHERE ATStates.AT_address = ATs.AT_address "
+ "AND height <= ?"
+ "ORDER BY AT_address DESC, height DESC LIMIT 1" + "ORDER BY AT_address DESC, height DESC LIMIT 1"
+ ") " + ") "
+ ")"; + ")";
try { try {
this.repository.executeCheckedUpdate(insertSql); this.repository.executeCheckedUpdate(insertSql, maxHeight);
} catch (SQLException e) { } catch (SQLException e) {
repository.examineException(e); repository.examineException(e);
throw new DataException("Unable to populate temporary latest AT states cache in repository", e); throw new DataException("Unable to populate temporary latest AT states cache in repository", e);

18
src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java

@ -177,11 +177,11 @@ public class HSQLDBChatRepository implements ChatRepository {
private List<GroupChat> getActiveGroupChats(String address) throws DataException { private List<GroupChat> getActiveGroupChats(String address) throws DataException {
// Find groups where address is a member and potential latest message details // 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 " + "FROM GroupMembers "
+ "JOIN Groups USING (group_id) " + "JOIN Groups USING (group_id) "
+ "LEFT OUTER JOIN LATERAL(" + "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 " + "FROM ChatTransactions "
+ "JOIN Transactions USING (signature) " + "JOIN Transactions USING (signature) "
+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " + "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 sender = resultSet.getString(4);
String senderName = resultSet.getString(5); 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); groupChats.add(groupChat);
} while (resultSet.next()); } while (resultSet.next());
} }
@ -215,7 +217,7 @@ public class HSQLDBChatRepository implements ChatRepository {
} }
// We need different SQL to handle group-less chat // 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 " + "FROM ChatTransactions "
+ "JOIN Transactions USING (signature) " + "JOIN Transactions USING (signature) "
+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
@ -228,15 +230,19 @@ public class HSQLDBChatRepository implements ChatRepository {
Long timestamp = null; Long timestamp = null;
String sender = null; String sender = null;
String senderName = null; String senderName = null;
byte[] signature = null;
byte[] data = null;
if (resultSet != null) { if (resultSet != null) {
// We found a recipient-less, group-less CHAT message, so report its details // We found a recipient-less, group-less CHAT message, so report its details
timestamp = resultSet.getLong(1); timestamp = resultSet.getLong(1);
sender = resultSet.getString(2); sender = resultSet.getString(2);
senderName = resultSet.getString(3); 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); groupChats.add(groupChat);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to fetch active group chats from repository", e); throw new DataException("Unable to fetch active group chats from repository", e);
@ -291,4 +297,4 @@ public class HSQLDBChatRepository implements ChatRepository {
return directChats; return directChats;
} }
} }

2
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. // 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. // 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 // Loop through all the LatestATStates and copy them to the new table

2
src/main/java/org/qortal/settings/Settings.java

@ -216,7 +216,7 @@ public class Settings {
public long recoveryModeTimeout = 10 * 60 * 1000L; public long recoveryModeTimeout = 10 * 60 * 1000L;
/** Minimum peer version number required in order to sync with them */ /** 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 /** 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 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 */ * If false, sync will be blocked both ways, and they will not appear in the peers list */

9
src/main/java/org/qortal/transaction/CancelSellNameTransaction.java

@ -5,6 +5,7 @@ import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.CancelSellNameTransactionData; import org.qortal.data.transaction.CancelSellNameTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -81,7 +82,13 @@ public class CancelSellNameTransaction extends Transaction {
@Override @Override
public void preProcess() throws DataException { 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 @Override

83
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

6
src/main/resources/i18n/ApiError_ru.properties

@ -16,7 +16,7 @@ NON_PRODUCTION = этот вызов API не разрешен для произ
BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться
NO_TIME_SYNC = пока нет синхронизации часов NO_TIME_SYNC = время не синхронизировано
### Validation ### ### Validation ###
INVALID_SIGNATURE = недействительная подпись INVALID_SIGNATURE = недействительная подпись
@ -72,7 +72,7 @@ FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = проблема с внешним блокч
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = недостаточный баланс на внешнем блокчейне FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = недостаточный баланс на внешнем блокчейне
FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внений блокчей (время блокировки/среднее время блока) FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внешний блокчей (время блокировки/среднее время блока)
### Trade Portal ### ### Trade Portal ###
ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера
@ -80,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера
### Data ### ### Data ###
FILE_NOT_FOUND = файл не найден FILE_NOT_FOUND = файл не найден
NO_REPLY = узел не ответил данными NO_REPLY = нет ответа

16
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 AUTO_UPDATE = Automatisches Update
BLOCK_HEIGHT = height BLOCK_HEIGHT = Blockhöhe
BLOCKS_REMAINING = blocks remaining BLOCKS_REMAINING = blocks remaining
BUILD_VERSION = Build-Version BUILD_VERSION = Entwicklungs-Version
CHECK_TIME_ACCURACY = Prüfe Zeitgenauigkeit 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_BACKUP = Datenbank Backup
DB_CHECKPOINT = Datenbank Kontrollpunkt DB_CHECKPOINT = Datenbank Check
DB_MAINTENANCE = Datenbank Instandhaltung DB_MAINTENANCE = Datenbank Instandhaltung
@ -31,18 +31,18 @@ EXIT = Verlassen
LITE_NODE = Lite node 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 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... PERFORMING_DB_MAINTENANCE = Planmäßige Wartung durchführen...
SYNCHRONIZE_CLOCK = Synchronisiere Uhr SYNCHRONIZE_CLOCK = Synchronisiere Uhr
SYNCHRONIZING_BLOCKCHAIN = Synchronisierung SYNCHRONIZING_BLOCKCHAIN = Synchronisierung der Blockchain
SYNCHRONIZING_CLOCK = Synchronisierung Uhr SYNCHRONIZING_CLOCK = Synchronisierung der Uhr

46
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

2
src/main/resources/i18n/SysTray_ru.properties

@ -1,7 +1,7 @@
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) #Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
# SysTray pop-up menu # SysTray pop-up menu
APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуска... APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуск...
AUTO_UPDATE = Автоматическое обновление AUTO_UPDATE = Автоматическое обновление

195
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

196
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

24
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.transform.block.BlockTransformation;
import org.qortal.utils.BlockArchiveUtils; import org.qortal.utils.BlockArchiveUtils;
import org.qortal.utils.NTP; import org.qortal.utils.NTP;
import org.qortal.utils.Triple;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -314,9 +313,10 @@ public class BlockArchiveTests extends Common {
repository.getBlockRepository().setBlockPruneHeight(901); repository.getBlockRepository().setBlockPruneHeight(901);
// Prune the AT states for the archived blocks // Prune the AT states for the archived blocks
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(900);
repository.saveChanges();
int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 900); 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); repository.getATRepository().setAtPruneHeight(901);
// Now ensure the SQL repository is missing blocks 2 and 900... // 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 // Trim the first 500 blocks
repository.getBlockRepository().trimOldOnlineAccountsSignatures(0, 500); repository.getBlockRepository().trimOldOnlineAccountsSignatures(0, 500);
repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(501); repository.getBlockRepository().setOnlineAccountsSignaturesTrimHeight(501);
repository.getATRepository().rebuildLatestAtStates(500);
repository.getATRepository().trimAtStates(0, 500, 1000); repository.getATRepository().trimAtStates(0, 500, 1000);
repository.getATRepository().setAtTrimHeight(501); repository.getATRepository().setAtTrimHeight(501);
// Now block 500 should only have the AT state data hash // Now block 499 should only have the AT state data hash
List<ATStateData> block499AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(499);
atStatesData = repository.getATRepository().getATStateAtHeight(block499AtStatesData.get(0).getATAddress(), 499);
assertNotNull(atStatesData.getStateHash());
assertNull(atStatesData.getStateData());
// ... 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); block500AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(500);
atStatesData = repository.getATRepository().getATStateAtHeight(block500AtStatesData.get(0).getATAddress(), 500); atStatesData = repository.getATRepository().getATStateAtHeight(block500AtStatesData.get(0).getATAddress(), 500);
assertNotNull(atStatesData.getStateHash()); assertNotNull(atStatesData.getStateHash());
assertNull(atStatesData.getStateData()); assertNotNull(atStatesData.getStateData());
// ... but block 501 should have the full data // ... and block 501 should also have the full data
List<ATStateData> block501AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(501); List<ATStateData> block501AtStatesData = repository.getATRepository().getBlockATStatesAtHeight(501);
atStatesData = repository.getATRepository().getATStateAtHeight(block501AtStatesData.get(0).getATAddress(), 501); atStatesData = repository.getATRepository().getATStateAtHeight(block501AtStatesData.get(0).getATAddress(), 501);
assertNotNull(atStatesData.getStateHash()); assertNotNull(atStatesData.getStateHash());
@ -612,9 +619,10 @@ public class BlockArchiveTests extends Common {
repository.getBlockRepository().setBlockPruneHeight(501); repository.getBlockRepository().setBlockPruneHeight(501);
// Prune the AT states for the archived blocks // Prune the AT states for the archived blocks
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(500);
repository.saveChanges();
int numATStatesPruned = repository.getATRepository().pruneAtStates(2, 500); 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); repository.getATRepository().setAtPruneHeight(501);
// Now ensure the SQL repository is missing blocks 2 and 500... // Now ensure the SQL repository is missing blocks 2 and 500...

3
src/test/java/org/qortal/test/BootstrapTests.java

@ -176,7 +176,8 @@ public class BootstrapTests extends Common {
repository.getBlockRepository().setBlockPruneHeight(901); repository.getBlockRepository().setBlockPruneHeight(901);
// Prune the AT states for the archived blocks // Prune the AT states for the archived blocks
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(900);
repository.saveChanges();
repository.getATRepository().pruneAtStates(0, 900); repository.getATRepository().pruneAtStates(0, 900);
repository.getATRepository().setAtPruneHeight(901); repository.getATRepository().setAtPruneHeight(901);

143
src/test/java/org/qortal/test/PruneTests.java

@ -1,16 +1,33 @@
package org.qortal.test; package org.qortal.test;
import com.google.common.hash.HashCode;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qortal.account.Account;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.block.Block;
import org.qortal.controller.BlockMinter; 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.at.ATStateData;
import org.qortal.data.block.BlockData; 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.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager; import org.qortal.repository.RepositoryManager;
import org.qortal.test.common.AtUtils; import org.qortal.test.common.AtUtils;
import org.qortal.test.common.BlockUtils;
import org.qortal.test.common.Common; 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.ArrayList;
import java.util.List; import java.util.List;
@ -19,6 +36,13 @@ import static org.junit.Assert.*;
public class PruneTests extends Common { 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 @Before
public void beforeTest() throws DataException { public void beforeTest() throws DataException {
Common.useDefaultSettings(); Common.useDefaultSettings();
@ -62,23 +86,32 @@ public class PruneTests extends Common {
repository.getBlockRepository().setBlockPruneHeight(6); repository.getBlockRepository().setBlockPruneHeight(6);
// Prune AT states for blocks 2-5 // Prune AT states for blocks 2-5
repository.getATRepository().rebuildLatestAtStates(5);
repository.saveChanges();
int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 5); int numATStatesPruned = repository.getATRepository().pruneAtStates(0, 5);
assertEquals(4, numATStatesPruned); assertEquals(3, numATStatesPruned);
repository.getATRepository().setAtPruneHeight(6); repository.getATRepository().setAtPruneHeight(6);
// Make sure that blocks 2-5 are now missing block data and AT states data // Make sure that blocks 2-4 are now missing block data and AT states data
for (Integer i=2; i <= 5; i++) { for (Integer i=2; i <= 4; i++) {
BlockData blockData = repository.getBlockRepository().fromHeight(i); BlockData blockData = repository.getBlockRepository().fromHeight(i);
assertNull(blockData); assertNull(blockData);
List<ATStateData> atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i); List<ATStateData> atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i);
assertTrue(atStatesDataList.isEmpty()); 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<ATStateData> 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++) { for (Integer i=6; i <= 10; i++) {
BlockData blockData = repository.getBlockRepository().fromHeight(i); blockData = repository.getBlockRepository().fromHeight(i);
assertNotNull(blockData.getSignature()); assertNotNull(blockData.getSignature());
List<ATStateData> atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i); atStatesDataList = repository.getATRepository().getBlockATStatesAtHeight(i);
assertNotNull(atStatesDataList); assertNotNull(atStatesDataList);
assertFalse(atStatesDataList.isEmpty()); assertFalse(atStatesDataList.isEmpty());
ATStateData atStatesData = repository.getATRepository().getATStateAtHeight(atStatesDataList.get(0).getATAddress(), i); 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;
}
} }

19
src/test/java/org/qortal/test/at/AtRepositoryTests.java

@ -2,29 +2,20 @@ package org.qortal.test.at;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
import org.ciyam.at.CompilationException;
import org.ciyam.at.MachineState; import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qortal.account.PrivateKeyAccount; import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.at.ATData; import org.qortal.data.at.ATData;
import org.qortal.data.at.ATStateData; 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.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager; import org.qortal.repository.RepositoryManager;
import org.qortal.test.common.AtUtils; import org.qortal.test.common.AtUtils;
import org.qortal.test.common.BlockUtils; import org.qortal.test.common.BlockUtils;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
import org.qortal.test.common.TransactionUtils;
import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.DeployAtTransaction;
public class AtRepositoryTests extends Common { public class AtRepositoryTests extends Common {
@ -76,7 +67,7 @@ public class AtRepositoryTests extends Common {
Integer testHeight = maxHeight - 2; Integer testHeight = maxHeight - 2;
// Trim AT state data // Trim AT state data
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(maxHeight);
repository.getATRepository().trimAtStates(2, maxHeight, 1000); repository.getATRepository().trimAtStates(2, maxHeight, 1000);
ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight); ATStateData atStateData = repository.getATRepository().getATStateAtHeight(atAddress, testHeight);
@ -130,7 +121,7 @@ public class AtRepositoryTests extends Common {
Integer testHeight = blockchainHeight; Integer testHeight = blockchainHeight;
// Trim AT state data // Trim AT state data
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(maxHeight);
// COMMIT to check latest AT states persist / TEMPORARY table interaction // COMMIT to check latest AT states persist / TEMPORARY table interaction
repository.saveChanges(); repository.saveChanges();
@ -163,8 +154,8 @@ public class AtRepositoryTests extends Common {
int maxTrimHeight = blockchainHeight - 4; int maxTrimHeight = blockchainHeight - 4;
Integer testHeight = maxTrimHeight + 1; Integer testHeight = maxTrimHeight + 1;
// Trim AT state data // Trim AT state data (using a max height of maxTrimHeight + 1, so it is beyond the trimmed range)
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(maxTrimHeight + 1);
repository.saveChanges(); repository.saveChanges();
repository.getATRepository().trimAtStates(2, maxTrimHeight, 1000); repository.getATRepository().trimAtStates(2, maxTrimHeight, 1000);
@ -333,7 +324,7 @@ public class AtRepositoryTests extends Common {
Integer testHeight = maxHeight - 2; Integer testHeight = maxHeight - 2;
// Trim AT state data // Trim AT state data
repository.getATRepository().rebuildLatestAtStates(); repository.getATRepository().rebuildLatestAtStates(maxHeight);
repository.getATRepository().trimAtStates(2, maxHeight, 1000); repository.getATRepository().trimAtStates(2, maxHeight, 1000);
List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(testHeight); List<ATStateData> atStates = repository.getATRepository().getBlockATStatesAtHeight(testHeight);

9
src/test/java/org/qortal/test/common/BlockUtils.java

@ -20,6 +20,15 @@ public class BlockUtils {
return BlockMinter.mintTestingBlock(repository, mintingAccount); 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<count; i++) {
block = BlockUtils.mintBlock(repository);
}
return block;
}
public static Long getNextBlockReward(Repository repository) throws DataException { public static Long getNextBlockReward(Repository repository) throws DataException {
int currentHeight = repository.getBlockRepository().getBlockchainHeight(); int currentHeight = repository.getBlockRepository().getBlockchainHeight();

59
src/test/java/org/qortal/test/naming/UpdateTests.java

@ -219,6 +219,65 @@ public class UpdateTests extends Common {
} }
} }
// Test that multiple UPDATE_NAME transactions work as expected, when using a matching name and newName string
@Test
public void testDoubleUpdateNameWithMatchingNewName() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Register-name
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String name = "name";
String reducedName = "name";
String data = "{\"age\":30}";
TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
initialTransactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(initialTransactionData.getTimestamp()));
TransactionUtils.signAndMint(repository, initialTransactionData, alice);
// Check name exists
assertTrue(repository.getNameRepository().nameExists(name));
assertNotNull(repository.getNameRepository().fromReducedName(reducedName));
// Update name
TransactionData middleTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, name, data);
TransactionUtils.signAndMint(repository, middleTransactionData, alice);
// Check name still exists
assertTrue(repository.getNameRepository().nameExists(name));
assertNotNull(repository.getNameRepository().fromReducedName(reducedName));
// Update name again
TransactionData newestTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, name, data);
TransactionUtils.signAndMint(repository, newestTransactionData, alice);
// Check name still exists
assertTrue(repository.getNameRepository().nameExists(name));
assertNotNull(repository.getNameRepository().fromReducedName(reducedName));
// Check updated timestamp is correct
assertEquals((Long) newestTransactionData.getTimestamp(), repository.getNameRepository().fromName(name).getUpdated());
// orphan and recheck
BlockUtils.orphanLastBlock(repository);
// Check name still exists
assertTrue(repository.getNameRepository().nameExists(name));
assertNotNull(repository.getNameRepository().fromReducedName(reducedName));
// Check updated timestamp is correct
assertEquals((Long) middleTransactionData.getTimestamp(), repository.getNameRepository().fromName(name).getUpdated());
// orphan and recheck
BlockUtils.orphanLastBlock(repository);
// Check name still exists
assertTrue(repository.getNameRepository().nameExists(name));
assertNotNull(repository.getNameRepository().fromReducedName(reducedName));
// Check updated timestamp is empty
assertNull(repository.getNameRepository().fromName(name).getUpdated());
}
}
// Test that reverting using previous UPDATE_NAME works as expected // Test that reverting using previous UPDATE_NAME works as expected
@Test @Test
public void testIntermediateUpdateName() throws DataException { public void testIntermediateUpdateName() throws DataException {

Loading…
Cancel
Save