mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-30 05:31:23 +00:00
Compare commits
77 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a95a37277c | ||
|
8aed84e6af | ||
|
b1f184c493 | ||
|
d66dd51bf6 | ||
|
0baed55a44 | ||
|
311f41c610 | ||
|
0a156c76a2 | ||
|
337b03aa68 | ||
|
3d99f86630 | ||
|
8d1a58ec06 | ||
|
2e5a7cb5a1 | ||
|
895f02f178 | ||
|
c59869982b | ||
|
3b3368f950 | ||
|
3f02c760c2 | ||
|
fee603e500 | ||
|
ad31d8014d | ||
|
58a0ac74d2 | ||
|
8388aa9c23 | ||
|
c1894d8c00 | ||
|
f7f9cdc518 | ||
|
850d7f8220 | ||
|
051043283c | ||
|
15bc69de01 | ||
|
ee3cfa4d6d | ||
|
df1f3079a5 | ||
|
d9ae8a5552 | ||
|
2326c31ee7 | ||
|
91cb0f30dd | ||
|
c0307c352c | ||
|
8fd7c1b313 | ||
|
b8147659b1 | ||
|
7a1bac682f | ||
|
9fdb7c977f | ||
|
4f3948323b | ||
|
70fcc1f712 | ||
|
f20fe9199f | ||
|
91dee4a3b8 | ||
|
0b89b8084e | ||
|
a5a80302b2 | ||
|
e61a24ee7b | ||
|
55ed342b59 | ||
|
3c6f79eec0 | ||
|
590800ac1d | ||
|
95c412b946 | ||
|
a232395750 | ||
|
6edbc8b6a5 | ||
|
f8ffeed302 | ||
|
e2ee68427c | ||
|
74ff23239d | ||
|
f1fa2ba2f6 | ||
|
e1522cec94 | ||
|
8841b3cbb1 | ||
|
94260bd93f | ||
|
15ff8af7ac | ||
|
d420033b36 | ||
|
bda63f0310 | ||
|
54add26ccb | ||
|
089b068362 | ||
|
fe474b4507 | ||
|
bbe15b563c | ||
|
59025b8f47 | ||
|
1b42c5edb1 | ||
|
362335913d | ||
|
4340dac595 | ||
|
f3e1fc884c | ||
|
39c06d8817 | ||
|
91cee36c21 | ||
|
6bef883942 | ||
|
25ba2406c0 | ||
|
e4dc8f85a7 | ||
|
12a4a260c8 | ||
|
268f02b5c3 | ||
|
13eff43b87 | ||
|
e604a19bce | ||
|
e63e39fe9a | ||
|
eb9b94b9c6 |
@@ -17,10 +17,10 @@
|
||||
<ROW Property="Manufacturer" Value="Qortal"/>
|
||||
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
||||
<ROW Property="NTP_GOOD" Value="false"/>
|
||||
<ROW Property="ProductCode" Value="1033:{81A2CCD1-29B5-4097-8F7E-D9C48DA95F8D} 1049:{BEE79661-F551-439B-8347-3BF3711D3A61} 2052:{E3B4D203-929E-401D-A727-47C0B2C096AB} 2057:{43AC68FB-C26A-4A87-B774-A87205BC41AD} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{DEA09B3D-AFFA-409F-B208-E148E9A9005D} 1049:{79180B3D-8A6B-4DED-BD60-1A58F941E1DE} 2052:{90F65B96-22CD-41FA-82B0-E65183EA1EF9} 2057:{AB4872AC-E794-42BD-9305-8DFD06243A88} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="3.2.0" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="3.2.3" Type="32"/>
|
||||
<ROW Property="RECONFIG_NTP" Value="true"/>
|
||||
<ROW Property="REMOVE_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_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="AI_CustomARPName" ComponentId="{581C0A48-702C-429E-A23B-A04F64220E51}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{63FD92A7-4AE2-46A0-9B83-EB27DA636C65}" 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="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"/>
|
||||
|
21
pom.xml
21
pom.xml
@@ -3,11 +3,11 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<version>3.2.3</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
<altcoinj.version>bf9fb80</altcoinj.version>
|
||||
<altcoinj.version>02e0d3d</altcoinj.version>
|
||||
<bitcoinj.version>0.15.10</bitcoinj.version>
|
||||
<bouncycastle.version>1.64</bouncycastle.version>
|
||||
<build.timestamp>${maven.build.timestamp}</build.timestamp>
|
||||
@@ -21,6 +21,8 @@
|
||||
<dagger.version>1.2.2</dagger.version>
|
||||
<guava.version>28.1-jre</guava.version>
|
||||
<hsqldb.version>2.5.1</hsqldb.version>
|
||||
<homoglyph.version>1.2.1</homoglyph.version>
|
||||
<icu4j.version>70.1</icu4j.version>
|
||||
<upnp.version>1.1</upnp.version>
|
||||
<jersey.version>2.29.1</jersey.version>
|
||||
<jetty.version>9.4.29.v20200521</jetty.version>
|
||||
@@ -442,7 +444,7 @@
|
||||
</dependency>
|
||||
<!-- For Litecoin, etc. support, requires bitcoinj -->
|
||||
<dependency>
|
||||
<groupId>com.github.jjos2372</groupId>
|
||||
<groupId>com.github.qortal</groupId>
|
||||
<artifactId>altcoinj</artifactId>
|
||||
<version>${altcoinj.version}</version>
|
||||
</dependency>
|
||||
@@ -568,7 +570,18 @@
|
||||
<dependency>
|
||||
<groupId>net.codebox</groupId>
|
||||
<artifactId>homoglyph</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<version>${homoglyph.version}</version>
|
||||
</dependency>
|
||||
<!-- Unicode support -->
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>${icu4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j-charset</artifactId>
|
||||
<version>${icu4j.version}</version>
|
||||
</dependency>
|
||||
<!-- Jetty -->
|
||||
<dependency>
|
||||
|
@@ -205,6 +205,12 @@ public class Account {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns account's blockMinted (0+) or null if account not found in repository. */
|
||||
public Integer getBlocksMinted() throws DataException {
|
||||
return this.repository.getAccountRepository().getMintedBlockCount(this.address);
|
||||
}
|
||||
|
||||
|
||||
/** Returns whether account can build reward-shares.
|
||||
* <p>
|
||||
* To be able to create reward-shares, the account needs to pass at least one of these tests:<br>
|
||||
|
@@ -0,0 +1,29 @@
|
||||
package org.qortal.api.model.crosschain;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class DigibyteSendRequest {
|
||||
|
||||
@Schema(description = "Digibyte BIP32 extended private key", example = "tprv___________________________________________________________________________________________________________")
|
||||
public String xprv58;
|
||||
|
||||
@Schema(description = "Recipient's Digibyte address ('legacy' P2PKH only)", example = "1DigByteEaterAddressDontSendf59kuE")
|
||||
public String receivingAddress;
|
||||
|
||||
@Schema(description = "Amount of DGB to send", type = "number")
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long digibyteAmount;
|
||||
|
||||
@Schema(description = "Transaction fee per byte (optional). Default is 0.00000100 DGB (100 sats) per byte", example = "0.00000100", type = "number")
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public Long feePerByte;
|
||||
|
||||
public DigibyteSendRequest() {
|
||||
}
|
||||
|
||||
}
|
@@ -588,10 +588,6 @@ public class AdminResource {
|
||||
public String importRepository(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String filename) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
// Hard-coded because it's too dangerous to allow user-supplied filenames in weaker security contexts
|
||||
if (Settings.getInstance().getApiKey() == null)
|
||||
filename = "qortal-backup/TradeBotStates.json";
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||
|
||||
|
@@ -1267,13 +1267,19 @@ public class ArbitraryResource {
|
||||
// Determine and add the status of each resource
|
||||
List<ArbitraryResourceInfo> updatedResources = new ArrayList<>();
|
||||
for (ArbitraryResourceInfo resourceInfo : resources) {
|
||||
ArbitraryDataResource resource = new ArbitraryDataResource(resourceInfo.name, ResourceIdType.NAME,
|
||||
resourceInfo.service, resourceInfo.identifier);
|
||||
ArbitraryResourceStatus status = resource.getStatus(true);
|
||||
if (status != null) {
|
||||
resourceInfo.status = status;
|
||||
try {
|
||||
ArbitraryDataResource resource = new ArbitraryDataResource(resourceInfo.name, ResourceIdType.NAME,
|
||||
resourceInfo.service, resourceInfo.identifier);
|
||||
ArbitraryResourceStatus status = resource.getStatus(true);
|
||||
if (status != null) {
|
||||
resourceInfo.status = status;
|
||||
}
|
||||
updatedResources.add(resourceInfo);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Catch and log all exceptions, since some systems are experiencing 500 errors when including statuses
|
||||
LOGGER.info("Caught exception when adding status to resource %s: %s", resourceInfo, e.toString());
|
||||
}
|
||||
updatedResources.add(resourceInfo);
|
||||
}
|
||||
return updatedResources;
|
||||
}
|
||||
|
@@ -0,0 +1,177 @@
|
||||
package org.qortal.api.resource;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.HeaderParam;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiErrors;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.api.Security;
|
||||
import org.qortal.api.model.crosschain.DigibyteSendRequest;
|
||||
import org.qortal.crosschain.Digibyte;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.SimpleTransaction;
|
||||
|
||||
@Path("/crosschain/dgb")
|
||||
@Tag(name = "Cross-Chain (Digibyte)")
|
||||
public class CrossChainDigibyteResource {
|
||||
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
|
||||
@POST
|
||||
@Path("/walletbalance")
|
||||
@Operation(
|
||||
summary = "Returns DGB balance 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(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", description = "balance (satoshis)"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
|
||||
@SecurityRequirement(name = "apiKey")
|
||||
public String getDigibyteWalletBalance(@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 {
|
||||
Long balance = digibyte.getWalletBalanceFromTransactions(key58);
|
||||
if (balance == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
|
||||
return balance.toString();
|
||||
|
||||
} catch (ForeignBlockchainException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/wallettransactions")
|
||||
@Operation(
|
||||
summary = "Returns transactions 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 List<SimpleTransaction> getDigibyteWalletTransactions(@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.getWalletTransactions(key58);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/send")
|
||||
@Operation(
|
||||
summary = "Sends DGB from hierarchical, deterministic BIP32 wallet to specific address",
|
||||
description = "Currently supports 'legacy' P2PKH Digibyte addresses and Native SegWit (P2WPKH) addresses. Supply BIP32 'm' private key in base58, starting with 'xprv' for mainnet, 'tprv' for testnet",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(
|
||||
implementation = DigibyteSendRequest.class
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", description = "transaction hash"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.FOREIGN_BLOCKCHAIN_BALANCE_ISSUE, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
|
||||
@SecurityRequirement(name = "apiKey")
|
||||
public String sendBitcoin(@HeaderParam(Security.API_KEY_HEADER) String apiKey, DigibyteSendRequest digibyteSendRequest) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
if (digibyteSendRequest.digibyteAmount <= 0)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
if (digibyteSendRequest.feePerByte != null && digibyteSendRequest.feePerByte <= 0)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
Digibyte digibyte = Digibyte.getInstance();
|
||||
|
||||
if (!digibyte.isValidAddress(digibyteSendRequest.receivingAddress))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
if (!digibyte.isValidDeterministicKey(digibyteSendRequest.xprv58))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||
|
||||
Transaction spendTransaction = digibyte.buildSpend(digibyteSendRequest.xprv58,
|
||||
digibyteSendRequest.receivingAddress,
|
||||
digibyteSendRequest.digibyteAmount,
|
||||
digibyteSendRequest.feePerByte);
|
||||
|
||||
if (spendTransaction == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_BALANCE_ISSUE);
|
||||
|
||||
try {
|
||||
digibyte.broadcastTransaction(spendTransaction);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
|
||||
}
|
||||
|
||||
return spendTransaction.getTxId().toString();
|
||||
}
|
||||
|
||||
}
|
@@ -98,7 +98,15 @@ public class GroupsResource {
|
||||
ref = "reverse"
|
||||
) @QueryParam("reverse") Boolean reverse) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getGroupRepository().getAllGroups(limit, offset, reverse);
|
||||
List<GroupData> allGroupData = repository.getGroupRepository().getAllGroups(limit, offset, reverse);
|
||||
allGroupData.forEach(groupData -> {
|
||||
try {
|
||||
groupData.memberCount = repository.getGroupRepository().countGroupMembers(groupData.getGroupId());
|
||||
} catch (DataException e) {
|
||||
// Exclude memberCount for this group
|
||||
}
|
||||
});
|
||||
return allGroupData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@@ -150,7 +158,15 @@ public class GroupsResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
return repository.getGroupRepository().getGroupsWithMember(member);
|
||||
List<GroupData> allGroupData = repository.getGroupRepository().getGroupsWithMember(member);
|
||||
allGroupData.forEach(groupData -> {
|
||||
try {
|
||||
groupData.memberCount = repository.getGroupRepository().countGroupMembers(groupData.getGroupId());
|
||||
} catch (DataException e) {
|
||||
// Exclude memberCount for this group
|
||||
}
|
||||
});
|
||||
return allGroupData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@@ -177,6 +193,7 @@ public class GroupsResource {
|
||||
if (groupData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.GROUP_UNKNOWN);
|
||||
|
||||
groupData.memberCount = repository.getGroupRepository().countGroupMembers(groupId);
|
||||
return groupData;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
@@ -922,4 +939,4 @@ public class GroupsResource {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -96,7 +96,7 @@ public class ArbitraryDataFile {
|
||||
this.filePath = outputFilePath;
|
||||
// Verify hash
|
||||
if (!this.hash58.equals(this.digest58())) {
|
||||
LOGGER.error("Hash {} does not match file digest {}", this.hash58, this.digest58());
|
||||
LOGGER.error("Hash {} does not match file digest {} for signature: {}", this.hash58, this.digest58(), Base58.encode(signature));
|
||||
this.delete();
|
||||
throw new DataException("Data file digest validation failed");
|
||||
}
|
||||
@@ -478,6 +478,14 @@ public class ArbitraryDataFile {
|
||||
|
||||
// Read the metadata
|
||||
List<byte[]> chunks = metadata.getChunks();
|
||||
|
||||
// If the chunks array is empty, then this resource has no chunks,
|
||||
// so we must return false to avoid confusing the caller.
|
||||
if (chunks.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, we need to check each chunk individually
|
||||
for (byte[] chunkHash : chunks) {
|
||||
ArbitraryDataFileChunk chunk = ArbitraryDataFileChunk.fromHash(chunkHash, this.signature);
|
||||
if (!chunk.exists()) {
|
||||
|
@@ -551,7 +551,7 @@ public class QortalATAPI extends API {
|
||||
* <p>
|
||||
* Otherwise, assume B is a public key.
|
||||
*/
|
||||
private Account getAccountFromB(MachineState state) {
|
||||
/*package*/ Account getAccountFromB(MachineState state) {
|
||||
byte[] bBytes = this.getB(state);
|
||||
|
||||
if ((bBytes[0] == Crypto.ADDRESS_VERSION || bBytes[0] == Crypto.AT_ADDRESS_VERSION)
|
||||
|
@@ -10,9 +10,11 @@ import org.ciyam.at.ExecutionException;
|
||||
import org.ciyam.at.FunctionData;
|
||||
import org.ciyam.at.IllegalFunctionCodeException;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
/**
|
||||
@@ -160,6 +162,68 @@ public enum QortalFunctionCode {
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
convertAddressInB(Crypto.ADDRESS_VERSION, state);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns account level of account in B.<br>
|
||||
* <tt>0x0520</tt><br>
|
||||
* B should contain either Qortal address or public key,<br>
|
||||
* e.g. as a result of calling function {@link org.ciyam.at.FunctionCode#PUT_ADDRESS_FROM_TX_IN_A_INTO_B}</code>.
|
||||
* <p></p>
|
||||
* Returns account level, or -1 if account unknown.
|
||||
* <p></p>
|
||||
* @see QortalATAPI#getAccountFromB(MachineState)
|
||||
*/
|
||||
GET_ACCOUNT_LEVEL_FROM_ACCOUNT_IN_B(0x0520, 0, true) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
Account account = api.getAccountFromB(state);
|
||||
|
||||
Integer accountLevel = null;
|
||||
|
||||
if (account != null) {
|
||||
try {
|
||||
accountLevel = account.getLevel();
|
||||
} catch (DataException e) {
|
||||
throw new RuntimeException("AT API unable to fetch account level?", e);
|
||||
}
|
||||
}
|
||||
|
||||
functionData.returnValue = accountLevel != null
|
||||
? accountLevel.longValue()
|
||||
: -1;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns account's minted block count of account in B.<br>
|
||||
* <tt>0x0521</tt><br>
|
||||
* B should contain either Qortal address or public key,<br>
|
||||
* e.g. as a result of calling function {@link org.ciyam.at.FunctionCode#PUT_ADDRESS_FROM_TX_IN_A_INTO_B}</code>.
|
||||
* <p></p>
|
||||
* Returns account level, or -1 if account unknown.
|
||||
* <p></p>
|
||||
* @see QortalATAPI#getAccountFromB(MachineState)
|
||||
*/
|
||||
GET_BLOCKS_MINTED_FROM_ACCOUNT_IN_B(0x0521, 0, true) {
|
||||
@Override
|
||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||
QortalATAPI api = (QortalATAPI) state.getAPI();
|
||||
Account account = api.getAccountFromB(state);
|
||||
|
||||
Integer blocksMinted = null;
|
||||
|
||||
if (account != null) {
|
||||
try {
|
||||
blocksMinted = account.getBlocksMinted();
|
||||
} catch (DataException e) {
|
||||
throw new RuntimeException("AT API unable to fetch account's minted block count?", e);
|
||||
}
|
||||
}
|
||||
|
||||
functionData.returnValue = blocksMinted != null
|
||||
? blocksMinted.longValue()
|
||||
: -1;
|
||||
}
|
||||
};
|
||||
|
||||
public final short value;
|
||||
|
@@ -73,9 +73,13 @@ public class BlockChain {
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private long nameRegistrationUnitFee;
|
||||
private long nameRegistrationUnitFeeTimestamp;
|
||||
/** Unit fees by transaction timestamp */
|
||||
public static class UnitFeesByTimestamp {
|
||||
public long timestamp;
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long fee;
|
||||
}
|
||||
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
||||
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
@XmlJavaTypeAdapter(StringLongMapXmlAdapter.class)
|
||||
@@ -306,16 +310,6 @@ public class BlockChain {
|
||||
return this.maxBlockSize;
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
public long getNameRegistrationUnitFee() {
|
||||
return this.nameRegistrationUnitFee;
|
||||
}
|
||||
|
||||
public long getNameRegistrationUnitFeeTimestamp() {
|
||||
// FUTURE: we could use a separate structure to indicate fee adjustments for different transaction types
|
||||
return this.nameRegistrationUnitFeeTimestamp;
|
||||
}
|
||||
|
||||
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||
public boolean getRequireGroupForApproval() {
|
||||
return this.requireGroupForApproval;
|
||||
@@ -430,6 +424,16 @@ public class BlockChain {
|
||||
throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight));
|
||||
}
|
||||
|
||||
public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) {
|
||||
// Scan through for reward at our height
|
||||
for (int i = 0; i < nameRegistrationUnitFees.size(); ++i)
|
||||
if (ourTimestamp >= nameRegistrationUnitFees.get(i).timestamp)
|
||||
return nameRegistrationUnitFees.get(i).fee;
|
||||
|
||||
// Default to system-wide unit fee
|
||||
return this.getUnitFee();
|
||||
}
|
||||
|
||||
/** Validate blockchain config read from JSON */
|
||||
private void validateConfig() {
|
||||
if (this.genesisInfo == null)
|
||||
|
@@ -655,6 +655,29 @@ public class Controller extends Thread {
|
||||
return lastMisbehaved != null && lastMisbehaved > NTP.getTime() - MISBEHAVIOUR_COOLOFF;
|
||||
};
|
||||
|
||||
/** True if peer has unknown height, lower height or same height and same block signature (unless we don't have their block signature). */
|
||||
public static Predicate<Peer> hasShorterBlockchain = peer -> {
|
||||
BlockData highestBlockData = getInstance().getChainTip();
|
||||
int ourHeight = highestBlockData.getHeight();
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
|
||||
// Ensure we have chain tip data for this peer
|
||||
if (peerChainTipData == null)
|
||||
return true;
|
||||
|
||||
// Remove if peer is at a lower height than us
|
||||
Integer peerHeight = peerChainTipData.getLastHeight();
|
||||
if (peerHeight == null || peerHeight < ourHeight)
|
||||
return true;
|
||||
|
||||
// Don't remove if peer is on a greater height chain than us, or if we don't have their block signature
|
||||
if (peerHeight > ourHeight || peerChainTipData.getLastBlockSignature() == null)
|
||||
return false;
|
||||
|
||||
// Remove if signatures match
|
||||
return Arrays.equals(peerChainTipData.getLastBlockSignature(), highestBlockData.getSignature());
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> hasNoRecentBlock = peer -> {
|
||||
final Long minLatestBlockTimestamp = getMinimumLatestBlockTimestamp();
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
@@ -675,7 +698,7 @@ public class Controller extends Thread {
|
||||
public static final Predicate<Peer> hasInferiorChainTip = peer -> {
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
final List<ByteArray> inferiorChainTips = Synchronizer.getInstance().inferiorChainSignatures;
|
||||
return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || inferiorChainTips.contains(new ByteArray(peerChainTipData.getLastBlockSignature()));
|
||||
return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || inferiorChainTips.contains(ByteArray.wrap(peerChainTipData.getLastBlockSignature()));
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> hasOldVersion = peer -> {
|
||||
@@ -1203,7 +1226,7 @@ public class Controller extends Thread {
|
||||
byte[] signature = getBlockMessage.getSignature();
|
||||
this.stats.getBlockMessageStats.requests.incrementAndGet();
|
||||
|
||||
ByteArray signatureAsByteArray = new ByteArray(signature);
|
||||
ByteArray signatureAsByteArray = ByteArray.wrap(signature);
|
||||
|
||||
CachedBlockMessage cachedBlockMessage = this.blockMessageCache.get(signatureAsByteArray);
|
||||
int blockCacheSize = Settings.getInstance().getBlockCacheSize();
|
||||
@@ -1283,7 +1306,7 @@ public class Controller extends Thread {
|
||||
if (getChainHeight() - blockData.getHeight() <= blockCacheSize) {
|
||||
this.stats.getBlockMessageStats.cacheFills.incrementAndGet();
|
||||
|
||||
this.blockMessageCache.put(new ByteArray(blockData.getSignature()), blockMessage);
|
||||
this.blockMessageCache.put(ByteArray.wrap(blockData.getSignature()), blockMessage);
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.error(String.format("Repository issue while send block %s to peer %s", Base58.encode(signature), peer), e);
|
||||
|
@@ -95,7 +95,7 @@ public class Synchronizer extends Thread {
|
||||
private static Synchronizer instance;
|
||||
|
||||
public enum SynchronizationResult {
|
||||
OK, NOTHING_TO_DO, GENESIS_ONLY, NO_COMMON_BLOCK, TOO_DIVERGENT, NO_REPLY, INFERIOR_CHAIN, INVALID_DATA, NO_BLOCKCHAIN_LOCK, REPOSITORY_ISSUE, SHUTTING_DOWN;
|
||||
OK, NOTHING_TO_DO, GENESIS_ONLY, NO_COMMON_BLOCK, TOO_DIVERGENT, NO_REPLY, INFERIOR_CHAIN, INVALID_DATA, NO_BLOCKCHAIN_LOCK, REPOSITORY_ISSUE, SHUTTING_DOWN, CHAIN_TIP_TOO_OLD;
|
||||
}
|
||||
|
||||
public static class NewChainTipEvent implements Event {
|
||||
@@ -235,6 +235,9 @@ public class Synchronizer extends Thread {
|
||||
// Disregard peers that are on the same block as last sync attempt and we didn't like their chain
|
||||
peers.removeIf(Controller.hasInferiorChainTip);
|
||||
|
||||
// Remove peers with unknown height, lower height or same height and same block signature (unless we don't have their block signature)
|
||||
peers.removeIf(Controller.hasShorterBlockchain);
|
||||
|
||||
final int peersBeforeComparison = peers.size();
|
||||
|
||||
// Request recent block summaries from the remaining peers, and locate our common block with each
|
||||
@@ -246,6 +249,12 @@ public class Synchronizer extends Thread {
|
||||
// We may have added more inferior chain tips when comparing peers, so remove any peers that are currently on those chains
|
||||
peers.removeIf(Controller.hasInferiorChainTip);
|
||||
|
||||
// Remove any peers that are no longer on a recent block since the last check
|
||||
// Except for times when we're in recovery mode, in which case we need to keep them
|
||||
if (!recoveryMode) {
|
||||
peers.removeIf(Controller.hasNoRecentBlock);
|
||||
}
|
||||
|
||||
final int peersRemoved = peersBeforeComparison - peers.size();
|
||||
if (peersRemoved > 0 && peers.size() > 0)
|
||||
LOGGER.debug(String.format("Ignoring %d peers on inferior chains. Peers remaining: %d", peersRemoved, peers.size()));
|
||||
@@ -308,7 +317,7 @@ public class Synchronizer extends Thread {
|
||||
|
||||
case INFERIOR_CHAIN: {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = new ByteArray(peer.getChainTipData().getLastBlockSignature());
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature());
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
|
||||
@@ -324,6 +333,7 @@ public class Synchronizer extends Thread {
|
||||
case NO_REPLY:
|
||||
case NO_BLOCKCHAIN_LOCK:
|
||||
case REPOSITORY_ISSUE:
|
||||
case CHAIN_TIP_TOO_OLD:
|
||||
// These are minor failure results so fine to try again
|
||||
LOGGER.debug(() -> String.format("Failed to synchronize with peer %s (%s)", peer, syncResult.name()));
|
||||
break;
|
||||
@@ -336,7 +346,7 @@ public class Synchronizer extends Thread {
|
||||
// fall-through...
|
||||
case NOTHING_TO_DO: {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = new ByteArray(peer.getChainTipData().getLastBlockSignature());
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature());
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
|
||||
@@ -412,7 +422,7 @@ public class Synchronizer extends Thread {
|
||||
|
||||
public void addInferiorChainSignature(byte[] inferiorSignature) {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = new ByteArray(inferiorSignature);
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(inferiorSignature);
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
}
|
||||
@@ -563,7 +573,7 @@ public class Synchronizer extends Thread {
|
||||
// If our latest block is very old, it's best that we don't try and determine the best peers to sync to.
|
||||
// This is because it can involve very large chain comparisons, which is too intensive.
|
||||
// In reality, most forking problems occur near the chain tips, so we will reserve this functionality for those situations.
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (minLatestBlockTimestamp == null)
|
||||
return peers;
|
||||
|
||||
@@ -719,6 +729,7 @@ public class Synchronizer extends Thread {
|
||||
LOGGER.debug(String.format("Listing peers with common block %.8s...", Base58.encode(commonBlockSummary.getSignature())));
|
||||
for (Peer peer : peersSharingCommonBlock) {
|
||||
final int peerHeight = peer.getChainTipData().getLastHeight();
|
||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getLastBlockTimestamp();
|
||||
final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight();
|
||||
final CommonBlockData peerCommonBlockData = peer.getCommonBlockData();
|
||||
|
||||
@@ -729,6 +740,14 @@ public class Synchronizer extends Thread {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If peer is our of date (since our last check), we should exclude it from this round
|
||||
minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
||||
LOGGER.debug(String.format("Peer %s is out of date - removing it from this round", peer));
|
||||
peers.remove(peer);
|
||||
continue;
|
||||
}
|
||||
|
||||
final List<BlockSummaryData> peerBlockSummariesAfterCommonBlock = peerCommonBlockData.getBlockSummariesAfterCommonBlock();
|
||||
populateBlockSummariesMinterLevels(repository, peerBlockSummariesAfterCommonBlock);
|
||||
|
||||
@@ -1291,6 +1310,16 @@ public class Synchronizer extends Thread {
|
||||
return SynchronizationResult.INVALID_DATA;
|
||||
}
|
||||
|
||||
// Final check to make sure the peer isn't out of date (except for when we're in recovery mode)
|
||||
if (!recoveryMode && peer.getChainTipData() != null) {
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getLastBlockTimestamp();
|
||||
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
||||
LOGGER.info(String.format("Peer %s is out of date, so abandoning sync attempt", peer));
|
||||
return SynchronizationResult.CHAIN_TIP_TOO_OLD;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] nextPeerSignature = peerBlockSignatures.get(0);
|
||||
int nextHeight = height + 1;
|
||||
|
||||
|
@@ -140,7 +140,7 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
Long startTime = NTP.getTime();
|
||||
ArbitraryDataFileMessage receivedArbitraryDataFileMessage = fetchArbitraryDataFile(peer, null, signature, hash, null);
|
||||
Long endTime = NTP.getTime();
|
||||
if (receivedArbitraryDataFileMessage != null) {
|
||||
if (receivedArbitraryDataFileMessage != null && receivedArbitraryDataFileMessage.getArbitraryDataFile() != null) {
|
||||
LOGGER.debug("Received data file {} from peer {}. Time taken: {} ms", receivedArbitraryDataFileMessage.getArbitraryDataFile().getHash58(), peer, (endTime-startTime));
|
||||
receivedAtLeastOneFile = true;
|
||||
|
||||
|
@@ -216,9 +216,19 @@ public class ArbitraryMetadataManager {
|
||||
|
||||
long timeSinceLastAttempt = NTP.getTime() - lastAttemptTimestamp;
|
||||
|
||||
// Allow a second attempt after 15 seconds, and another after 30 seconds
|
||||
if (timeSinceLastAttempt > 15 * 1000L) {
|
||||
// We haven't tried for at least 15 seconds
|
||||
// Allow a second attempt after 60 seconds
|
||||
if (timeSinceLastAttempt > 60 * 1000L) {
|
||||
// We haven't tried for at least 60 seconds
|
||||
|
||||
if (networkBroadcastCount < 2) {
|
||||
// We've made less than 2 total attempts
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Then allow another attempt after 60 minutes
|
||||
if (timeSinceLastAttempt > 60 * 60 * 1000L) {
|
||||
// We haven't tried for at least 60 minutes
|
||||
|
||||
if (networkBroadcastCount < 3) {
|
||||
// We've made less than 3 total attempts
|
||||
@@ -226,22 +236,6 @@ public class ArbitraryMetadataManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Then allow another 5 attempts, each 5 minutes apart
|
||||
if (timeSinceLastAttempt > 5 * 60 * 1000L) {
|
||||
// We haven't tried for at least 5 minutes
|
||||
|
||||
if (networkBroadcastCount < 5) {
|
||||
// We've made less than 5 total attempts
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// From then on, only try once every 24 hours, to reduce network spam
|
||||
if (timeSinceLastAttempt > 24 * 60 * 60 * 1000L) {
|
||||
// We haven't tried for at least 24 hours
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -29,6 +29,15 @@ public class NamesDatabaseIntegrityCheck {
|
||||
private List<TransactionData> nameTransactions = new ArrayList<>();
|
||||
|
||||
public int rebuildName(String name, Repository repository) {
|
||||
return this.rebuildName(name, repository, null);
|
||||
}
|
||||
|
||||
public int rebuildName(String name, Repository repository, List<String> referenceNames) {
|
||||
// "referenceNames" tracks the linked names that have already been rebuilt, to prevent circular dependencies
|
||||
if (referenceNames == null) {
|
||||
referenceNames = new ArrayList<>();
|
||||
}
|
||||
|
||||
int modificationCount = 0;
|
||||
try {
|
||||
List<TransactionData> transactions = this.fetchAllTransactionsInvolvingName(name, repository);
|
||||
@@ -56,7 +65,14 @@ public class NamesDatabaseIntegrityCheck {
|
||||
if (Objects.equals(updateNameTransactionData.getNewName(), name) &&
|
||||
!Objects.equals(updateNameTransactionData.getName(), updateNameTransactionData.getNewName())) {
|
||||
// This renames an existing name, so we need to process that instead
|
||||
this.rebuildName(updateNameTransactionData.getName(), repository);
|
||||
|
||||
if (!referenceNames.contains(name)) {
|
||||
referenceNames.add(name);
|
||||
this.rebuildName(updateNameTransactionData.getName(), repository, referenceNames);
|
||||
}
|
||||
else {
|
||||
// We've already processed this name so there's nothing more to do
|
||||
}
|
||||
}
|
||||
else {
|
||||
Name nameObj = new Name(repository, name);
|
||||
@@ -193,7 +209,12 @@ public class NamesDatabaseIntegrityCheck {
|
||||
newName = registeredName;
|
||||
}
|
||||
NameData newNameData = repository.getNameRepository().fromName(newName);
|
||||
if (!Objects.equals(creator.getAddress(), newNameData.getOwner())) {
|
||||
if (newNameData == null) {
|
||||
LOGGER.info("Error: registered name {} has no new name data. This is likely due to account {} " +
|
||||
"being renamed another time, which is a scenario that is not yet checked automatically.",
|
||||
updateNameTransactionData.getNewName(), creator.getAddress());
|
||||
}
|
||||
else if (!Objects.equals(creator.getAddress(), newNameData.getOwner())) {
|
||||
LOGGER.info("Error: registered name {} is owned by {}, but it should be {}",
|
||||
updateNameTransactionData.getNewName(), newNameData.getOwner(), creator.getAddress());
|
||||
integrityCheckFailed = true;
|
||||
@@ -313,6 +334,10 @@ public class NamesDatabaseIntegrityCheck {
|
||||
transactions.add(transactionData);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by lowest timestamp first
|
||||
transactions.sort(Comparator.comparingLong(TransactionData::getTimestamp));
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,885 @@
|
||||
package org.qortal.controller.tradebot;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.script.Script.ScriptType;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.api.model.crosschain.TradeBotCreateRequest;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.crosschain.*;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||
import org.qortal.data.crosschain.TradeBotData;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
/**
|
||||
* Performing cross-chain trading steps on behalf of user.
|
||||
* <p>
|
||||
* We deal with three different independent state-spaces here:
|
||||
* <ul>
|
||||
* <li>Qortal blockchain</li>
|
||||
* <li>Foreign blockchain</li>
|
||||
* <li>Trade-bot entries</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(DigibyteACCTv3TradeBot.class);
|
||||
|
||||
public enum State implements TradeBot.StateNameAndValueSupplier {
|
||||
BOB_WAITING_FOR_AT_CONFIRM(10, false, false),
|
||||
BOB_WAITING_FOR_MESSAGE(15, true, true),
|
||||
BOB_WAITING_FOR_AT_REDEEM(25, true, true),
|
||||
BOB_DONE(30, false, false),
|
||||
BOB_REFUNDED(35, false, false),
|
||||
|
||||
ALICE_WAITING_FOR_AT_LOCK(85, true, true),
|
||||
ALICE_DONE(95, false, false),
|
||||
ALICE_REFUNDING_A(105, true, true),
|
||||
ALICE_REFUNDED(110, false, false);
|
||||
|
||||
private static final Map<Integer, State> map = stream(State.values()).collect(toMap(state -> state.value, state -> state));
|
||||
|
||||
public final int value;
|
||||
public final boolean requiresAtData;
|
||||
public final boolean requiresTradeData;
|
||||
|
||||
State(int value, boolean requiresAtData, boolean requiresTradeData) {
|
||||
this.value = value;
|
||||
this.requiresAtData = requiresAtData;
|
||||
this.requiresTradeData = requiresTradeData;
|
||||
}
|
||||
|
||||
public static State valueOf(int value) {
|
||||
return map.get(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getState() {
|
||||
return this.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStateValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Maximum time Bob waits for his AT creation transaction to be confirmed into a block. (milliseconds) */
|
||||
private static final long MAX_AT_CONFIRMATION_PERIOD = 24 * 60 * 60 * 1000L; // ms
|
||||
|
||||
private static DigibyteACCTv3TradeBot instance;
|
||||
|
||||
private final List<String> endStates = Arrays.asList(State.BOB_DONE, State.BOB_REFUNDED, State.ALICE_DONE, State.ALICE_REFUNDING_A, State.ALICE_REFUNDED).stream()
|
||||
.map(State::name)
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
|
||||
private DigibyteACCTv3TradeBot() {
|
||||
}
|
||||
|
||||
public static synchronized DigibyteACCTv3TradeBot getInstance() {
|
||||
if (instance == null)
|
||||
instance = new DigibyteACCTv3TradeBot();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getEndStates() {
|
||||
return this.endStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new trade-bot entry from the "Bob" viewpoint, i.e. OFFERing QORT in exchange for DGB.
|
||||
* <p>
|
||||
* Generates:
|
||||
* <ul>
|
||||
* <li>new 'trade' private key</li>
|
||||
* </ul>
|
||||
* Derives:
|
||||
* <ul>
|
||||
* <li>'native' (as in Qortal) public key, public key hash, address (starting with Q)</li>
|
||||
* <li>'foreign' (as in Digibyte) public key, public key hash</li>
|
||||
* </ul>
|
||||
* A Qortal AT is then constructed including the following as constants in the 'data segment':
|
||||
* <ul>
|
||||
* <li>'native'/Qortal 'trade' address - used as a MESSAGE contact</li>
|
||||
* <li>'foreign'/Digibyte public key hash - used by Alice's P2SH scripts to allow redeem</li>
|
||||
* <li>QORT amount on offer by Bob</li>
|
||||
* <li>DGB amount expected in return by Bob (from Alice)</li>
|
||||
* <li>trading timeout, in case things go wrong and everyone needs to refund</li>
|
||||
* </ul>
|
||||
* Returns a DEPLOY_AT transaction that needs to be signed and broadcast to the Qortal network.
|
||||
* <p>
|
||||
* Trade-bot will wait for Bob's AT to be deployed before taking next step.
|
||||
* <p>
|
||||
* @param repository
|
||||
* @param tradeBotCreateRequest
|
||||
* @return raw, unsigned DEPLOY_AT transaction
|
||||
* @throws DataException
|
||||
*/
|
||||
public byte[] createTrade(Repository repository, TradeBotCreateRequest tradeBotCreateRequest) throws DataException {
|
||||
byte[] tradePrivateKey = TradeBot.generateTradePrivateKey();
|
||||
|
||||
byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey);
|
||||
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
|
||||
String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey);
|
||||
|
||||
byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey);
|
||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
||||
|
||||
// Convert Digibyte receiving address into public key hash (we only support P2PKH at this time)
|
||||
Address digibyteReceivingAddress;
|
||||
try {
|
||||
digibyteReceivingAddress = Address.fromString(Digibyte.getInstance().getNetworkParameters(), tradeBotCreateRequest.receivingAddress);
|
||||
} catch (AddressFormatException e) {
|
||||
throw new DataException("Unsupported Digibyte receiving address: " + tradeBotCreateRequest.receivingAddress);
|
||||
}
|
||||
if (digibyteReceivingAddress.getOutputScriptType() != ScriptType.P2PKH)
|
||||
throw new DataException("Unsupported Digibyte receiving address: " + tradeBotCreateRequest.receivingAddress);
|
||||
|
||||
byte[] digibyteReceivingAccountInfo = digibyteReceivingAddress.getHash();
|
||||
|
||||
PublicKeyAccount creator = new PublicKeyAccount(repository, tradeBotCreateRequest.creatorPublicKey);
|
||||
|
||||
// Deploy AT
|
||||
long timestamp = NTP.getTime();
|
||||
byte[] reference = creator.getLastReference();
|
||||
long fee = 0L;
|
||||
byte[] signature = null;
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, creator.getPublicKey(), fee, signature);
|
||||
|
||||
String name = "QORT/DGB ACCT";
|
||||
String description = "QORT/DGB cross-chain trade";
|
||||
String aTType = "ACCT";
|
||||
String tags = "ACCT QORT DGB";
|
||||
byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeNativeAddress, tradeForeignPublicKeyHash, tradeBotCreateRequest.qortAmount,
|
||||
tradeBotCreateRequest.foreignAmount, tradeBotCreateRequest.tradeTimeout);
|
||||
long amount = tradeBotCreateRequest.fundingQortAmount;
|
||||
|
||||
DeployAtTransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, aTType, tags, creationBytes, amount, Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||
fee = deployAtTransaction.calcRecommendedFee();
|
||||
deployAtTransactionData.setFee(fee);
|
||||
|
||||
DeployAtTransaction.ensureATAddress(deployAtTransactionData);
|
||||
String atAddress = deployAtTransactionData.getAtAddress();
|
||||
|
||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, DigibyteACCTv3.NAME,
|
||||
State.BOB_WAITING_FOR_AT_CONFIRM.name(), State.BOB_WAITING_FOR_AT_CONFIRM.value,
|
||||
creator.getAddress(), atAddress, timestamp, tradeBotCreateRequest.qortAmount,
|
||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||
null, null,
|
||||
SupportedBlockchain.DIGIBYTE.name(),
|
||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||
tradeBotCreateRequest.foreignAmount, null, null, null, digibyteReceivingAccountInfo);
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Built AT %s. Waiting for deployment", atAddress));
|
||||
|
||||
// Attempt to backup the trade bot data
|
||||
TradeBot.backupTradeBotData(repository, null);
|
||||
|
||||
// Return to user for signing and broadcast as we don't have their Qortal private key
|
||||
try {
|
||||
return DeployAtTransactionTransformer.toBytes(deployAtTransactionData);
|
||||
} catch (TransformationException e) {
|
||||
throw new DataException("Failed to transform DEPLOY_AT transaction?", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trade-bot entry from the 'Alice' viewpoint, i.e. matching DGB to an existing offer.
|
||||
* <p>
|
||||
* Requires a chosen trade offer from Bob, passed by <tt>crossChainTradeData</tt>
|
||||
* and access to a Digibyte wallet via <tt>xprv58</tt>.
|
||||
* <p>
|
||||
* The <tt>crossChainTradeData</tt> contains the current trade offer state
|
||||
* as extracted from the AT's data segment.
|
||||
* <p>
|
||||
* Access to a funded wallet is via a Digibyte BIP32 hierarchical deterministic key,
|
||||
* passed via <tt>xprv58</tt>.
|
||||
* <b>This key will be stored in your node's database</b>
|
||||
* to allow trade-bot to create/fund the necessary P2SH transactions!
|
||||
* However, due to the nature of BIP32 keys, it is possible to give the trade-bot
|
||||
* only a subset of wallet access (see BIP32 for more details).
|
||||
* <p>
|
||||
* As an example, the xprv58 can be extract from a <i>legacy, password-less</i>
|
||||
* Electrum wallet by going to the console tab and entering:<br>
|
||||
* <tt>wallet.keystore.xprv</tt><br>
|
||||
* which should result in a base58 string starting with either 'xprv' (for Digibyte main-net)
|
||||
* or 'tprv' for (Digibyte test-net).
|
||||
* <p>
|
||||
* It is envisaged that the value in <tt>xprv58</tt> will actually come from a Qortal-UI-managed wallet.
|
||||
* <p>
|
||||
* If sufficient funds are available, <b>this method will actually fund the P2SH-A</b>
|
||||
* with the Digibyte amount expected by 'Bob'.
|
||||
* <p>
|
||||
* If the Digibyte transaction is successfully broadcast to the network then
|
||||
* we also send a MESSAGE to Bob's trade-bot to let them know.
|
||||
* <p>
|
||||
* The trade-bot entry is saved to the repository and the cross-chain trading process commences.
|
||||
* <p>
|
||||
* @param repository
|
||||
* @param crossChainTradeData chosen trade OFFER that Alice wants to match
|
||||
* @param xprv58 funded wallet xprv in base58
|
||||
* @return true if P2SH-A funding transaction successfully broadcast to Digibyte network, false otherwise
|
||||
* @throws DataException
|
||||
*/
|
||||
public ResponseResult startResponse(Repository repository, ATData atData, ACCT acct, CrossChainTradeData crossChainTradeData, String xprv58, String receivingAddress) throws DataException {
|
||||
byte[] tradePrivateKey = TradeBot.generateTradePrivateKey();
|
||||
byte[] secretA = TradeBot.generateSecret();
|
||||
byte[] hashOfSecretA = Crypto.hash160(secretA);
|
||||
|
||||
byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey);
|
||||
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey);
|
||||
String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey);
|
||||
|
||||
byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey);
|
||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey);
|
||||
byte[] receivingPublicKeyHash = Base58.decode(receivingAddress); // Actually the whole address, not just PKH
|
||||
|
||||
// We need to generate lockTime-A: add tradeTimeout to now
|
||||
long now = NTP.getTime();
|
||||
int lockTimeA = crossChainTradeData.tradeTimeout * 60 + (int) (now / 1000L);
|
||||
|
||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, DigibyteACCTv3.NAME,
|
||||
State.ALICE_WAITING_FOR_AT_LOCK.name(), State.ALICE_WAITING_FOR_AT_LOCK.value,
|
||||
receivingAddress, crossChainTradeData.qortalAtAddress, now, crossChainTradeData.qortAmount,
|
||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress,
|
||||
secretA, hashOfSecretA,
|
||||
SupportedBlockchain.DIGIBYTE.name(),
|
||||
tradeForeignPublicKey, tradeForeignPublicKeyHash,
|
||||
crossChainTradeData.expectedForeignAmount, xprv58, null, lockTimeA, receivingPublicKeyHash);
|
||||
|
||||
// Attempt to backup the trade bot data
|
||||
// Include tradeBotData as an additional parameter, since it's not in the repository yet
|
||||
TradeBot.backupTradeBotData(repository, Arrays.asList(tradeBotData));
|
||||
|
||||
// Check we have enough funds via xprv58 to fund P2SH to cover expectedForeignAmount
|
||||
long p2shFee;
|
||||
try {
|
||||
p2shFee = Digibyte.getInstance().getP2shFee(now);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
LOGGER.debug("Couldn't estimate Digibyte fees?");
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
|
||||
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
||||
// Do not include fee for funding transaction as this is covered by buildSpend()
|
||||
long amountA = crossChainTradeData.expectedForeignAmount + p2shFee /*redeeming/refunding P2SH-A*/;
|
||||
|
||||
// P2SH-A to be funded
|
||||
byte[] redeemScriptBytes = BitcoinyHTLC.buildScript(tradeForeignPublicKeyHash, lockTimeA, crossChainTradeData.creatorForeignPKH, hashOfSecretA);
|
||||
String p2shAddress = Digibyte.getInstance().deriveP2shAddress(redeemScriptBytes);
|
||||
|
||||
// Build transaction for funding P2SH-A
|
||||
Transaction p2shFundingTransaction = Digibyte.getInstance().buildSpend(tradeBotData.getForeignKey(), p2shAddress, amountA);
|
||||
if (p2shFundingTransaction == null) {
|
||||
LOGGER.debug("Unable to build P2SH-A funding transaction - lack of funds?");
|
||||
return ResponseResult.BALANCE_ISSUE;
|
||||
}
|
||||
|
||||
try {
|
||||
Digibyte.getInstance().broadcastTransaction(p2shFundingTransaction);
|
||||
} catch (ForeignBlockchainException e) {
|
||||
// We couldn't fund P2SH-A at this time
|
||||
LOGGER.debug("Couldn't broadcast P2SH-A funding transaction?");
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
|
||||
// Attempt to send MESSAGE to Bob's Qortal trade address
|
||||
byte[] messageData = DigibyteACCTv3.buildOfferMessage(tradeBotData.getTradeForeignPublicKeyHash(), tradeBotData.getHashOfSecret(), tradeBotData.getLockTimeA());
|
||||
String messageRecipient = crossChainTradeData.qortalCreatorTradeAddress;
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
|
||||
return ResponseResult.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDelete(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||
State tradeBotState = State.valueOf(tradeBotData.getStateValue());
|
||||
if (tradeBotState == null)
|
||||
return true;
|
||||
|
||||
// If the AT doesn't exist then we might as well let the user tidy up
|
||||
if (!repository.getATRepository().exists(tradeBotData.getAtAddress()))
|
||||
return true;
|
||||
|
||||
switch (tradeBotState) {
|
||||
case BOB_WAITING_FOR_AT_CONFIRM:
|
||||
case ALICE_DONE:
|
||||
case BOB_DONE:
|
||||
case ALICE_REFUNDED:
|
||||
case BOB_REFUNDED:
|
||||
case ALICE_REFUNDING_A:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(Repository repository, TradeBotData tradeBotData) throws DataException, ForeignBlockchainException {
|
||||
State tradeBotState = State.valueOf(tradeBotData.getStateValue());
|
||||
if (tradeBotState == null) {
|
||||
LOGGER.info(() -> String.format("Trade-bot entry for AT %s has invalid state?", tradeBotData.getAtAddress()));
|
||||
return;
|
||||
}
|
||||
|
||||
ATData atData = null;
|
||||
CrossChainTradeData tradeData = null;
|
||||
|
||||
if (tradeBotState.requiresAtData) {
|
||||
// Attempt to fetch AT data
|
||||
atData = repository.getATRepository().fromATAddress(tradeBotData.getAtAddress());
|
||||
if (atData == null) {
|
||||
LOGGER.debug(() -> String.format("Unable to fetch trade AT %s from repository", tradeBotData.getAtAddress()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (tradeBotState.requiresTradeData) {
|
||||
tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
if (tradeData == null) {
|
||||
LOGGER.warn(() -> String.format("Unable to fetch ACCT trade data for AT %s from repository", tradeBotData.getAtAddress()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (tradeBotState) {
|
||||
case BOB_WAITING_FOR_AT_CONFIRM:
|
||||
handleBobWaitingForAtConfirm(repository, tradeBotData);
|
||||
break;
|
||||
|
||||
case BOB_WAITING_FOR_MESSAGE:
|
||||
TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData);
|
||||
handleBobWaitingForMessage(repository, tradeBotData, atData, tradeData);
|
||||
break;
|
||||
|
||||
case ALICE_WAITING_FOR_AT_LOCK:
|
||||
TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData);
|
||||
handleAliceWaitingForAtLock(repository, tradeBotData, atData, tradeData);
|
||||
break;
|
||||
|
||||
case BOB_WAITING_FOR_AT_REDEEM:
|
||||
TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData);
|
||||
handleBobWaitingForAtRedeem(repository, tradeBotData, atData, tradeData);
|
||||
break;
|
||||
|
||||
case ALICE_DONE:
|
||||
case BOB_DONE:
|
||||
break;
|
||||
|
||||
case ALICE_REFUNDING_A:
|
||||
TradeBot.getInstance().updatePresence(repository, tradeBotData, tradeData);
|
||||
handleAliceRefundingP2shA(repository, tradeBotData, atData, tradeData);
|
||||
break;
|
||||
|
||||
case ALICE_REFUNDED:
|
||||
case BOB_REFUNDED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trade-bot is waiting for Bob's AT to deploy.
|
||||
* <p>
|
||||
* If AT is deployed, then trade-bot's next step is to wait for MESSAGE from Alice.
|
||||
*/
|
||||
private void handleBobWaitingForAtConfirm(Repository repository, TradeBotData tradeBotData) throws DataException {
|
||||
if (!repository.getATRepository().exists(tradeBotData.getAtAddress())) {
|
||||
if (NTP.getTime() - tradeBotData.getTimestamp() <= MAX_AT_CONFIRMATION_PERIOD)
|
||||
return;
|
||||
|
||||
// We've waited ages for AT to be confirmed into a block but something has gone awry.
|
||||
// After this long we assume transaction loss so give up with trade-bot entry too.
|
||||
tradeBotData.setState(State.BOB_REFUNDED.name());
|
||||
tradeBotData.setStateValue(State.BOB_REFUNDED.value);
|
||||
tradeBotData.setTimestamp(NTP.getTime());
|
||||
// We delete trade-bot entry here instead of saving, hence not using updateTradeBotState()
|
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey());
|
||||
repository.saveChanges();
|
||||
|
||||
LOGGER.info(() -> String.format("AT %s never confirmed. Giving up on trade", tradeBotData.getAtAddress()));
|
||||
TradeBot.notifyStateChange(tradeBotData);
|
||||
return;
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_WAITING_FOR_MESSAGE,
|
||||
() -> String.format("AT %s confirmed ready. Waiting for trade message", tradeBotData.getAtAddress()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trade-bot is waiting for MESSAGE from Alice's trade-bot, containing Alice's trade info.
|
||||
* <p>
|
||||
* It's possible Bob has cancelling his trade offer, receiving an automatic QORT refund,
|
||||
* in which case trade-bot is done with this specific trade and finalizes on refunded state.
|
||||
* <p>
|
||||
* Assuming trade is still on offer, trade-bot checks the contents of MESSAGE from Alice's trade-bot.
|
||||
* <p>
|
||||
* Details from Alice are used to derive P2SH-A address and this is checked for funding balance.
|
||||
* <p>
|
||||
* Assuming P2SH-A has at least expected Digibyte balance,
|
||||
* Bob's trade-bot constructs a zero-fee, PoW MESSAGE to send to Bob's AT with more trade details.
|
||||
* <p>
|
||||
* On processing this MESSAGE, Bob's AT should switch into 'TRADE' mode and only trade with Alice.
|
||||
* <p>
|
||||
* Trade-bot's next step is to wait for Alice to redeem the AT, which will allow Bob to
|
||||
* extract secret-A needed to redeem Alice's P2SH.
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
private void handleBobWaitingForMessage(Repository repository, TradeBotData tradeBotData,
|
||||
ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
|
||||
// If AT has finished then Bob likely cancelled his trade offer
|
||||
if (atData.getIsFinished()) {
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED,
|
||||
() -> String.format("AT %s cancelled - trading aborted", tradeBotData.getAtAddress()));
|
||||
return;
|
||||
}
|
||||
|
||||
Digibyte digibyte = Digibyte.getInstance();
|
||||
|
||||
String address = tradeBotData.getTradeNativeAddress();
|
||||
List<MessageTransactionData> messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, address, null, null, null);
|
||||
|
||||
for (MessageTransactionData messageTransactionData : messageTransactionsData) {
|
||||
if (messageTransactionData.isText())
|
||||
continue;
|
||||
|
||||
// We're expecting: HASH160(secret-A), Alice's Digibyte pubkeyhash and lockTime-A
|
||||
byte[] messageData = messageTransactionData.getData();
|
||||
DigibyteACCTv3.OfferMessageData offerMessageData = DigibyteACCTv3.extractOfferMessageData(messageData);
|
||||
if (offerMessageData == null)
|
||||
continue;
|
||||
|
||||
byte[] aliceForeignPublicKeyHash = offerMessageData.partnerDigibytePKH;
|
||||
byte[] hashOfSecretA = offerMessageData.hashOfSecretA;
|
||||
int lockTimeA = (int) offerMessageData.lockTimeA;
|
||||
long messageTimestamp = messageTransactionData.getTimestamp();
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(messageTimestamp, lockTimeA);
|
||||
|
||||
// Determine P2SH-A address and confirm funded
|
||||
byte[] redeemScriptA = BitcoinyHTLC.buildScript(aliceForeignPublicKeyHash, lockTimeA, tradeBotData.getTradeForeignPublicKeyHash(), hashOfSecretA);
|
||||
String p2shAddressA = digibyte.deriveP2shAddress(redeemScriptA);
|
||||
|
||||
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = Digibyte.getInstance().getP2shFee(feeTimestamp);
|
||||
final long minimumAmountA = tradeBotData.getForeignAmount() + p2shFee;
|
||||
|
||||
BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(digibyte.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
case FUNDING_IN_PROGRESS:
|
||||
// There might be another MESSAGE from someone else with an actually funded P2SH-A...
|
||||
continue;
|
||||
|
||||
case REDEEM_IN_PROGRESS:
|
||||
case REDEEMED:
|
||||
// We've already redeemed this?
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_DONE,
|
||||
() -> String.format("P2SH-A %s already spent? Assuming trade complete", p2shAddressA));
|
||||
return;
|
||||
|
||||
case REFUND_IN_PROGRESS:
|
||||
case REFUNDED:
|
||||
// This P2SH-A is burnt, but there might be another MESSAGE from someone else with an actually funded P2SH-A...
|
||||
continue;
|
||||
|
||||
case FUNDED:
|
||||
// Fall-through out of switch...
|
||||
break;
|
||||
}
|
||||
|
||||
// Good to go - send MESSAGE to AT
|
||||
|
||||
String aliceNativeAddress = Crypto.toAddress(messageTransactionData.getCreatorPublicKey());
|
||||
|
||||
// Build outgoing message, padding each part to 32 bytes to make it easier for AT to consume
|
||||
byte[] outgoingMessageData = DigibyteACCTv3.buildTradeMessage(aliceNativeAddress, aliceForeignPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
String messageRecipient = tradeBotData.getAtAddress();
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, outgoingMessageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_WAITING_FOR_AT_REDEEM,
|
||||
() -> String.format("Locked AT %s to %s. Waiting for AT redeem", tradeBotData.getAtAddress(), aliceNativeAddress));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trade-bot is waiting for Bob's AT to switch to TRADE mode and lock trade to Alice only.
|
||||
* <p>
|
||||
* It's possible that Bob has cancelled his trade offer in the mean time, or that somehow
|
||||
* this process has taken so long that we've reached P2SH-A's locktime, or that someone else
|
||||
* has managed to trade with Bob. In any of these cases, trade-bot switches to begin the refunding process.
|
||||
* <p>
|
||||
* Assuming Bob's AT is locked to Alice, trade-bot checks AT's state data to make sure it is correct.
|
||||
* <p>
|
||||
* If all is well, trade-bot then redeems AT using Alice's secret-A, releasing Bob's QORT to Alice.
|
||||
* <p>
|
||||
* In revealing a valid secret-A, Bob can then redeem the DGB funds from P2SH-A.
|
||||
* <p>
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
private void handleAliceWaitingForAtLock(Repository repository, TradeBotData tradeBotData,
|
||||
ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
|
||||
if (aliceUnexpectedState(repository, tradeBotData, atData, crossChainTradeData))
|
||||
return;
|
||||
|
||||
Digibyte digibyte = Digibyte.getInstance();
|
||||
int lockTimeA = tradeBotData.getLockTimeA();
|
||||
|
||||
// Refund P2SH-A if we've passed lockTime-A
|
||||
if (NTP.getTime() >= lockTimeA * 1000L) {
|
||||
byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
String p2shAddressA = digibyte.deriveP2shAddress(redeemScriptA);
|
||||
|
||||
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = Digibyte.getInstance().getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
|
||||
BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(digibyte.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
case FUNDING_IN_PROGRESS:
|
||||
case FUNDED:
|
||||
break;
|
||||
|
||||
case REDEEM_IN_PROGRESS:
|
||||
case REDEEMED:
|
||||
// Already redeemed?
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
|
||||
() -> String.format("P2SH-A %s already spent? Assuming trade completed", p2shAddressA));
|
||||
return;
|
||||
|
||||
case REFUND_IN_PROGRESS:
|
||||
case REFUNDED:
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED,
|
||||
() -> String.format("P2SH-A %s already refunded. Trade aborted", p2shAddressA));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A,
|
||||
() -> atData.getIsFinished()
|
||||
? String.format("AT %s cancelled. Refunding P2SH-A %s - aborting trade", tradeBotData.getAtAddress(), p2shAddressA)
|
||||
: String.format("LockTime-A reached, refunding P2SH-A %s - aborting trade", p2shAddressA));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We're waiting for AT to be in TRADE mode
|
||||
if (crossChainTradeData.mode != AcctMode.TRADING)
|
||||
return;
|
||||
|
||||
// AT is in TRADE mode and locked to us as checked by aliceUnexpectedState() above
|
||||
|
||||
// Find our MESSAGE to AT from previous state
|
||||
List<MessageTransactionData> messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(tradeBotData.getTradeNativePublicKey(),
|
||||
crossChainTradeData.qortalCreatorTradeAddress, null, null, null);
|
||||
if (messageTransactionsData == null || messageTransactionsData.isEmpty()) {
|
||||
LOGGER.warn(() -> String.format("Unable to find our message to trade creator %s?", crossChainTradeData.qortalCreatorTradeAddress));
|
||||
return;
|
||||
}
|
||||
|
||||
long recipientMessageTimestamp = messageTransactionsData.get(0).getTimestamp();
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(recipientMessageTimestamp, lockTimeA);
|
||||
|
||||
// Our calculated refundTimeout should match AT's refundTimeout
|
||||
if (refundTimeout != crossChainTradeData.refundTimeout) {
|
||||
LOGGER.debug(() -> String.format("Trade AT refundTimeout '%d' doesn't match our refundTimeout '%d'", crossChainTradeData.refundTimeout, refundTimeout));
|
||||
// We'll eventually refund
|
||||
return;
|
||||
}
|
||||
|
||||
// We're good to redeem AT
|
||||
|
||||
// Send 'redeem' MESSAGE to AT using both secret
|
||||
byte[] secretA = tradeBotData.getSecret();
|
||||
String qortalReceivingAddress = Base58.encode(tradeBotData.getReceivingAccountInfo()); // Actually contains whole address, not just PKH
|
||||
byte[] messageData = DigibyteACCTv3.buildRedeemMessage(secretA, qortalReceivingAddress);
|
||||
String messageRecipient = tradeBotData.getAtAddress();
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
|
||||
() -> String.format("Redeeming AT %s. Funds should arrive at %s",
|
||||
tradeBotData.getAtAddress(), qortalReceivingAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trade-bot is waiting for Alice to redeem Bob's AT, thus revealing secret-A which is required to spend the DGB funds from P2SH-A.
|
||||
* <p>
|
||||
* It's possible that Bob's AT has reached its trading timeout and automatically refunded QORT back to Bob. In which case,
|
||||
* trade-bot is done with this specific trade and finalizes in refunded state.
|
||||
* <p>
|
||||
* Assuming trade-bot can extract a valid secret-A from Alice's MESSAGE then trade-bot uses that to redeem the DGB funds from P2SH-A
|
||||
* to Bob's 'foreign'/Digibyte trade legacy-format address, as derived from trade private key.
|
||||
* <p>
|
||||
* (This could potentially be 'improved' to send DGB to any address of Bob's choosing by changing the transaction output).
|
||||
* <p>
|
||||
* If trade-bot successfully broadcasts the transaction, then this specific trade is done.
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
private void handleBobWaitingForAtRedeem(Repository repository, TradeBotData tradeBotData,
|
||||
ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
|
||||
// AT should be 'finished' once Alice has redeemed QORT funds
|
||||
if (!atData.getIsFinished())
|
||||
// Not finished yet
|
||||
return;
|
||||
|
||||
// If AT is REFUNDED or CANCELLED then something has gone wrong
|
||||
if (crossChainTradeData.mode == AcctMode.REFUNDED || crossChainTradeData.mode == AcctMode.CANCELLED) {
|
||||
// Alice hasn't redeemed the QORT, so there is no point in trying to redeem the DGB
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_REFUNDED,
|
||||
() -> String.format("AT %s has auto-refunded - trade aborted", tradeBotData.getAtAddress()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] secretA = DigibyteACCTv3.getInstance().findSecretA(repository, crossChainTradeData);
|
||||
if (secretA == null) {
|
||||
LOGGER.debug(() -> String.format("Unable to find secret-A from redeem message to AT %s?", tradeBotData.getAtAddress()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Use secret-A to redeem P2SH-A
|
||||
|
||||
Digibyte digibyte = Digibyte.getInstance();
|
||||
|
||||
byte[] receivingAccountInfo = tradeBotData.getReceivingAccountInfo();
|
||||
int lockTimeA = crossChainTradeData.lockTimeA;
|
||||
byte[] redeemScriptA = BitcoinyHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTimeA, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA);
|
||||
String p2shAddressA = digibyte.deriveP2shAddress(redeemScriptA);
|
||||
|
||||
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
||||
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = Digibyte.getInstance().getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(digibyte.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
case FUNDING_IN_PROGRESS:
|
||||
// P2SH-A suddenly not funded? Our best bet at this point is to hope for AT auto-refund
|
||||
return;
|
||||
|
||||
case REDEEM_IN_PROGRESS:
|
||||
case REDEEMED:
|
||||
// Double-check that we have redeemed P2SH-A...
|
||||
break;
|
||||
|
||||
case REFUND_IN_PROGRESS:
|
||||
case REFUNDED:
|
||||
// Wait for AT to auto-refund
|
||||
return;
|
||||
|
||||
case FUNDED: {
|
||||
Coin redeemAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
|
||||
ECKey redeemKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = digibyte.getUnspentOutputs(p2shAddressA);
|
||||
|
||||
Transaction p2shRedeemTransaction = BitcoinyHTLC.buildRedeemTransaction(digibyte.getNetworkParameters(), redeemAmount, redeemKey,
|
||||
fundingOutputs, redeemScriptA, secretA, receivingAccountInfo);
|
||||
|
||||
digibyte.broadcastTransaction(p2shRedeemTransaction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
String receivingAddress = digibyte.pkhToAddress(receivingAccountInfo);
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.BOB_DONE,
|
||||
() -> String.format("P2SH-A %s redeemed. Funds should arrive at %s", tradeBotData.getAtAddress(), receivingAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trade-bot is attempting to refund P2SH-A.
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
private void handleAliceRefundingP2shA(Repository repository, TradeBotData tradeBotData,
|
||||
ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
|
||||
int lockTimeA = tradeBotData.getLockTimeA();
|
||||
|
||||
// We can't refund P2SH-A until lockTime-A has passed
|
||||
if (NTP.getTime() <= lockTimeA * 1000L)
|
||||
return;
|
||||
|
||||
Digibyte digibyte = Digibyte.getInstance();
|
||||
|
||||
// We can't refund P2SH-A until median block time has passed lockTime-A (see BIP113)
|
||||
int medianBlockTime = digibyte.getMedianBlockTime();
|
||||
if (medianBlockTime <= lockTimeA)
|
||||
return;
|
||||
|
||||
byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTimeA, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
String p2shAddressA = digibyte.deriveP2shAddress(redeemScriptA);
|
||||
|
||||
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
||||
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = Digibyte.getInstance().getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(digibyte.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
case FUNDING_IN_PROGRESS:
|
||||
// Still waiting for P2SH-A to be funded...
|
||||
return;
|
||||
|
||||
case REDEEM_IN_PROGRESS:
|
||||
case REDEEMED:
|
||||
// Too late!
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
|
||||
() -> String.format("P2SH-A %s already spent!", p2shAddressA));
|
||||
return;
|
||||
|
||||
case REFUND_IN_PROGRESS:
|
||||
case REFUNDED:
|
||||
break;
|
||||
|
||||
case FUNDED:{
|
||||
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
|
||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = digibyte.getUnspentOutputs(p2shAddressA);
|
||||
|
||||
// Determine receive address for refund
|
||||
String receiveAddress = digibyte.getUnusedReceiveAddress(tradeBotData.getForeignKey());
|
||||
Address receiving = Address.fromString(digibyte.getNetworkParameters(), receiveAddress);
|
||||
|
||||
Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(digibyte.getNetworkParameters(), refundAmount, refundKey,
|
||||
fundingOutputs, redeemScriptA, lockTimeA, receiving.getHash());
|
||||
|
||||
digibyte.broadcastTransaction(p2shRefundTransaction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDED,
|
||||
() -> String.format("LockTime-A reached. Refunded P2SH-A %s. Trade aborted", p2shAddressA));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if Alice finds AT unexpectedly cancelled, refunded, redeemed or locked to someone else.
|
||||
* <p>
|
||||
* Will automatically update trade-bot state to <tt>ALICE_REFUNDING_A</tt> or <tt>ALICE_DONE</tt> as necessary.
|
||||
*
|
||||
* @throws DataException
|
||||
* @throws ForeignBlockchainException
|
||||
*/
|
||||
private boolean aliceUnexpectedState(Repository repository, TradeBotData tradeBotData,
|
||||
ATData atData, CrossChainTradeData crossChainTradeData) throws DataException, ForeignBlockchainException {
|
||||
// This is OK
|
||||
if (!atData.getIsFinished() && crossChainTradeData.mode == AcctMode.OFFERING)
|
||||
return false;
|
||||
|
||||
boolean isAtLockedToUs = tradeBotData.getTradeNativeAddress().equals(crossChainTradeData.qortalPartnerAddress);
|
||||
|
||||
if (!atData.getIsFinished() && crossChainTradeData.mode == AcctMode.TRADING)
|
||||
if (isAtLockedToUs) {
|
||||
// AT is trading with us - OK
|
||||
return false;
|
||||
} else {
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A,
|
||||
() -> String.format("AT %s trading with someone else: %s. Refunding & aborting trade", tradeBotData.getAtAddress(), crossChainTradeData.qortalPartnerAddress));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (atData.getIsFinished() && crossChainTradeData.mode == AcctMode.REDEEMED && isAtLockedToUs) {
|
||||
// We've redeemed already?
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_DONE,
|
||||
() -> String.format("AT %s already redeemed by us. Trade completed", tradeBotData.getAtAddress()));
|
||||
} else {
|
||||
// Any other state is not good, so start defensive refund
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, State.ALICE_REFUNDING_A,
|
||||
() -> String.format("AT %s cancelled/refunded/redeemed by someone else/invalid state. Refunding & aborting trade", tradeBotData.getAtAddress()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private long calcFeeTimestamp(int lockTimeA, int tradeTimeout) {
|
||||
return (lockTimeA - tradeTimeout * 60) * 1000L;
|
||||
}
|
||||
|
||||
}
|
@@ -100,6 +100,7 @@ public class TradeBot implements Listener {
|
||||
acctTradeBotSuppliers.put(DogecoinACCTv1.class, DogecoinACCTv1TradeBot::getInstance);
|
||||
acctTradeBotSuppliers.put(DogecoinACCTv2.class, DogecoinACCTv2TradeBot::getInstance);
|
||||
acctTradeBotSuppliers.put(DogecoinACCTv3.class, DogecoinACCTv3TradeBot::getInstance);
|
||||
acctTradeBotSuppliers.put(DigibyteACCTv3.class, DigibyteACCTv3TradeBot::getInstance);
|
||||
}
|
||||
|
||||
private static TradeBot instance;
|
||||
@@ -239,6 +240,11 @@ public class TradeBot implements Listener {
|
||||
if (!(event instanceof Synchronizer.NewChainTipEvent))
|
||||
return;
|
||||
|
||||
// Don't process trade bots or broadcast presence timestamps if our chain is more than 30 minutes old
|
||||
final Long minLatestBlockTimestamp = NTP.getTime() - (30 * 60 * 1000L);
|
||||
if (!Controller.getInstance().isUpToDate(minLatestBlockTimestamp))
|
||||
return;
|
||||
|
||||
synchronized (this) {
|
||||
expireOldPresenceTimestamps();
|
||||
|
||||
@@ -397,7 +403,7 @@ public class TradeBot implements Listener {
|
||||
|
||||
long now = NTP.getTime();
|
||||
long newExpiry = generateExpiry(now);
|
||||
ByteArray pubkeyByteArray = ByteArray.of(tradeNativeAccount.getPublicKey());
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(tradeNativeAccount.getPublicKey());
|
||||
|
||||
// If map entry's timestamp is missing, or within early renewal period, use the new expiry - otherwise use existing timestamp.
|
||||
synchronized (this.ourTradePresenceTimestampsByPubkey) {
|
||||
@@ -484,7 +490,7 @@ public class TradeBot implements Listener {
|
||||
int knownCount = entriesUnknownToPeer.size();
|
||||
|
||||
for (TradePresenceData peersTradePresence : peersTradePresences) {
|
||||
ByteArray pubkeyByteArray = ByteArray.of(peersTradePresence.getPublicKey());
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(peersTradePresence.getPublicKey());
|
||||
|
||||
TradePresenceData ourEntry = entriesUnknownToPeer.get(pubkeyByteArray);
|
||||
|
||||
@@ -541,7 +547,7 @@ public class TradeBot implements Listener {
|
||||
continue;
|
||||
}
|
||||
|
||||
ByteArray pubkeyByteArray = ByteArray.of(peersTradePresence.getPublicKey());
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(peersTradePresence.getPublicKey());
|
||||
|
||||
// Ignore if we've previously verified this timestamp+publickey combo or sent timestamp is older
|
||||
TradePresenceData existingTradeData = this.safeAllTradePresencesByPubkey.get(pubkeyByteArray);
|
||||
@@ -584,7 +590,7 @@ public class TradeBot implements Listener {
|
||||
continue;
|
||||
}
|
||||
|
||||
ByteArray atCodeHash = new ByteArray(atData.getCodeHash());
|
||||
ByteArray atCodeHash = ByteArray.wrap(atData.getCodeHash());
|
||||
Supplier<ACCT> acctSupplier = acctSuppliersByCodeHash.get(atCodeHash);
|
||||
if (acctSupplier == null) {
|
||||
LOGGER.trace("Ignoring trade presence {} from peer {} as AT isn't a known ACCT?",
|
||||
@@ -637,7 +643,7 @@ public class TradeBot implements Listener {
|
||||
|
||||
public void bridgePresence(long timestamp, byte[] publicKey, byte[] signature, String atAddress) {
|
||||
long expiry = generateExpiry(timestamp);
|
||||
ByteArray pubkeyByteArray = ByteArray.of(publicKey);
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap(publicKey);
|
||||
|
||||
TradePresenceData fakeTradePresenceData = new TradePresenceData(expiry, publicKey, signature, atAddress);
|
||||
|
||||
|
@@ -42,30 +42,40 @@ public class Bitcoin extends Bitcoiny {
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("hodlers.beer", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||
//CLOSED new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrum.pabu.io", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("gd42.org", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("korea.electrum-server.com", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("prospero.bitsrc.net", 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.14.0 new Server("electrum.coinext.com.br", Server.ConnectionType.SSL, 50002),
|
||||
new Server("104.248.139.211", Server.ConnectionType.SSL, 50002),
|
||||
new Server("142.93.6.38", Server.ConnectionType.SSL, 50002),
|
||||
new Server("157.245.172.236", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.226.175", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.42.31", Server.ConnectionType.SSL, 50002),
|
||||
new Server("178.62.80.20", Server.ConnectionType.SSL, 50002),
|
||||
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
|
||||
new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002),
|
||||
new Server("xtrum.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.acinq.co", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.coinext.com.br", Server.ConnectionType.TCP, 50001),
|
||||
new Server("korea.electrum-server.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("eai.coincited.net", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum.coinext.com.br", Server.ConnectionType.SSL, 50002),
|
||||
new Server("node1.btccuracao.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("korea.electrum-server.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("68.183.188.105", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
|
||||
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("blkhub.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||
new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("prospero.bitsrc.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("gd42.org", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.pabu.io", Server.ConnectionType.SSL, 50002));
|
||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.dev", Server.ConnectionType.SSL, 50002),
|
||||
new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fortress.qtornado.com", 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("node1.btccuracao.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("xtrum.com", Server.ConnectionType.SSL, 50002));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
171
src/main/java/org/qortal/crosschain/Digibyte.java
Normal file
171
src/main/java/org/qortal/crosschain/Digibyte.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package org.qortal.crosschain;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
import org.bitcoinj.params.TestNet3Params;
|
||||
import org.libdohj.params.DigibyteMainNetParams;
|
||||
import org.qortal.crosschain.ElectrumX.Server;
|
||||
import org.qortal.crosschain.ElectrumX.Server.ConnectionType;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
public class Digibyte extends Bitcoiny {
|
||||
|
||||
public static final String CURRENCY_CODE = "DGB";
|
||||
|
||||
private static final Coin DEFAULT_FEE_PER_KB = Coin.valueOf(100000); // 0.001 DGB per 1000 bytes
|
||||
|
||||
private static final long MINIMUM_ORDER_AMOUNT = 1000000; // 0.01 DGB minimum order, to avoid dust errors
|
||||
|
||||
// Temporary values until a dynamic fee system is written.
|
||||
private static final long MAINNET_FEE = 10000L;
|
||||
private static final long NON_MAINNET_FEE = 10000L; // enough for TESTNET3 and should be OK for REGTEST
|
||||
|
||||
private static final Map<ConnectionType, Integer> DEFAULT_ELECTRUMX_PORTS = new EnumMap<>(ConnectionType.class);
|
||||
static {
|
||||
DEFAULT_ELECTRUMX_PORTS.put(ConnectionType.TCP, 50001);
|
||||
DEFAULT_ELECTRUMX_PORTS.put(ConnectionType.SSL, 50002);
|
||||
}
|
||||
|
||||
public enum DigibyteNet {
|
||||
MAIN {
|
||||
@Override
|
||||
public NetworkParameters getParams() {
|
||||
return DigibyteMainNetParams.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb
|
||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20059),
|
||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20059),
|
||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20059));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGenesisHash() {
|
||||
return "7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
// TODO: This will need to be replaced with something better in the near future!
|
||||
return MAINNET_FEE;
|
||||
}
|
||||
},
|
||||
TEST3 {
|
||||
@Override
|
||||
public NetworkParameters getParams() {
|
||||
return TestNet3Params.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(); // TODO: find testnet servers
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGenesisHash() {
|
||||
return "308ea0711d5763be2995670dd9ca9872753561285a84da1d58be58acaa822252";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return NON_MAINNET_FEE;
|
||||
}
|
||||
},
|
||||
REGTEST {
|
||||
@Override
|
||||
public NetworkParameters getParams() {
|
||||
return RegTestParams.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("localhost", ConnectionType.TCP, 50001),
|
||||
new Server("localhost", ConnectionType.SSL, 50002));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGenesisHash() {
|
||||
// This is unique to each regtest instance
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return NON_MAINNET_FEE;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract NetworkParameters getParams();
|
||||
public abstract Collection<Server> getServers();
|
||||
public abstract String getGenesisHash();
|
||||
public abstract long getP2shFee(Long timestamp) throws ForeignBlockchainException;
|
||||
}
|
||||
|
||||
private static Digibyte instance;
|
||||
|
||||
private final DigibyteNet digibyteNet;
|
||||
|
||||
// Constructors and instance
|
||||
|
||||
private Digibyte(DigibyteNet digibyteNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
|
||||
super(blockchain, bitcoinjContext, currencyCode);
|
||||
this.digibyteNet = digibyteNet;
|
||||
|
||||
LOGGER.info(() -> String.format("Starting Digibyte support using %s", this.digibyteNet.name()));
|
||||
}
|
||||
|
||||
public static synchronized Digibyte getInstance() {
|
||||
if (instance == null) {
|
||||
DigibyteNet digibyteNet = Settings.getInstance().getDigibyteNet();
|
||||
|
||||
BitcoinyBlockchainProvider electrumX = new ElectrumX("Digibyte-" + digibyteNet.name(), digibyteNet.getGenesisHash(), digibyteNet.getServers(), DEFAULT_ELECTRUMX_PORTS);
|
||||
Context bitcoinjContext = new Context(digibyteNet.getParams());
|
||||
|
||||
instance = new Digibyte(digibyteNet, electrumX, bitcoinjContext, CURRENCY_CODE);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Getters & setters
|
||||
|
||||
public static synchronized void resetForTesting() {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
// Actual useful methods for use by other classes
|
||||
|
||||
@Override
|
||||
public Coin getFeePerKb() {
|
||||
return DEFAULT_FEE_PER_KB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMinimumOrderAmount() {
|
||||
return MINIMUM_ORDER_AMOUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns estimated DGB fee, in sats per 1000bytes, optionally for historic timestamp.
|
||||
*
|
||||
* @param timestamp optional milliseconds since epoch, or null for 'now'
|
||||
* @return sats per 1000bytes, or throws ForeignBlockchainException if something went wrong
|
||||
*/
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) throws ForeignBlockchainException {
|
||||
return this.digibyteNet.getP2shFee(timestamp);
|
||||
}
|
||||
|
||||
}
|
858
src/main/java/org/qortal/crosschain/DigibyteACCTv3.java
Normal file
858
src/main/java/org/qortal/crosschain/DigibyteACCTv3.java
Normal file
@@ -0,0 +1,858 @@
|
||||
package org.qortal.crosschain;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.ciyam.at.*;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.at.QortalFunctionCode;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.ciyam.at.OpCode.calcOffset;
|
||||
|
||||
/**
|
||||
* Cross-chain trade AT
|
||||
*
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>Bob generates Digibyte & Qortal 'trade' keys
|
||||
* <ul>
|
||||
* <li>private key required to sign P2SH redeem tx</li>
|
||||
* <li>private key could be used to create 'secret' (e.g. double-SHA256)</li>
|
||||
* <li>encrypted private key could be stored in Qortal AT for access by Bob from any node</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Bob deploys Qortal AT
|
||||
* <ul>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Alice finds Qortal AT and wants to trade
|
||||
* <ul>
|
||||
* <li>Alice generates Digibyte & Qortal 'trade' keys</li>
|
||||
* <li>Alice funds Digibyte P2SH-A</li>
|
||||
* <li>Alice sends 'offer' MESSAGE to Bob from her Qortal trade address, containing:
|
||||
* <ul>
|
||||
* <li>hash-of-secret-A</li>
|
||||
* <li>her 'trade' Digibyte PKH</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Bob receives "offer" MESSAGE
|
||||
* <ul>
|
||||
* <li>Checks Alice's P2SH-A</li>
|
||||
* <li>Sends 'trade' MESSAGE to Qortal AT from his trade address, containing:
|
||||
* <ul>
|
||||
* <li>Alice's trade Qortal address</li>
|
||||
* <li>Alice's trade Digibyte PKH</li>
|
||||
* <li>hash-of-secret-A</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Alice checks Qortal AT to confirm it's locked to her
|
||||
* <ul>
|
||||
* <li>Alice sends 'redeem' MESSAGE to Qortal AT from her trade address, containing:
|
||||
* <ul>
|
||||
* <li>secret-A</li>
|
||||
* <li>Qortal receiving address of her chosing</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>AT's QORT funds are sent to Qortal receiving address</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>Bob checks AT, extracts secret-A
|
||||
* <ul>
|
||||
* <li>Bob redeems P2SH-A using his Digibyte trade key and secret-A</li>
|
||||
* <li>P2SH-A DGB funds end up at Digibyte address determined by redeem transaction output(s)</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
public class DigibyteACCTv3 implements ACCT {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(DigibyteACCTv3.class);
|
||||
|
||||
public static final String NAME = DigibyteACCTv3.class.getSimpleName();
|
||||
public static final byte[] CODE_BYTES_HASH = HashCode.fromString("e6a7dcd87296fae3ce7d80183bf7660c8e2cb4f8746c6a0421a17148f87a0e1d").asBytes(); // SHA256 of AT code bytes
|
||||
|
||||
public static final int SECRET_LENGTH = 32;
|
||||
|
||||
/** <b>Value</b> offset into AT segment where 'mode' variable (long) is stored. (Multiply by MachineState.VALUE_SIZE for byte offset). */
|
||||
private static final int MODE_VALUE_OFFSET = 61;
|
||||
/** <b>Byte</b> offset into AT state data where 'mode' variable (long) is stored. */
|
||||
public static final int MODE_BYTE_OFFSET = MachineState.HEADER_LENGTH + (MODE_VALUE_OFFSET * MachineState.VALUE_SIZE);
|
||||
|
||||
public static class OfferMessageData {
|
||||
public byte[] partnerDigibytePKH;
|
||||
public byte[] hashOfSecretA;
|
||||
public long lockTimeA;
|
||||
}
|
||||
public static final int OFFER_MESSAGE_LENGTH = 20 /*partnerDigibytePKH*/ + 20 /*hashOfSecretA*/ + 8 /*lockTimeA*/;
|
||||
public static final int TRADE_MESSAGE_LENGTH = 32 /*partner's Qortal trade address (padded from 25 to 32)*/
|
||||
+ 24 /*partner's Digibyte PKH (padded from 20 to 24)*/
|
||||
+ 8 /*AT trade timeout (minutes)*/
|
||||
+ 24 /*hash of secret-A (padded from 20 to 24)*/
|
||||
+ 8 /*lockTimeA*/;
|
||||
public static final int REDEEM_MESSAGE_LENGTH = 32 /*secret-A*/ + 32 /*partner's Qortal receiving address padded from 25 to 32*/;
|
||||
public static final int CANCEL_MESSAGE_LENGTH = 32 /*AT creator's Qortal address*/;
|
||||
|
||||
private static DigibyteACCTv3 instance;
|
||||
|
||||
private DigibyteACCTv3() {
|
||||
}
|
||||
|
||||
public static synchronized DigibyteACCTv3 getInstance() {
|
||||
if (instance == null)
|
||||
instance = new DigibyteACCTv3();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getCodeBytesHash() {
|
||||
return CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModeByteOffset() {
|
||||
return MODE_BYTE_OFFSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForeignBlockchain getBlockchain() {
|
||||
return Digibyte.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Qortal AT creation bytes for cross-chain trading AT.
|
||||
* <p>
|
||||
* <tt>tradeTimeout</tt> (minutes) is the time window for the trade partner to send the
|
||||
* 32-byte secret to the AT, before the AT automatically refunds the AT's creator.
|
||||
*
|
||||
* @param creatorTradeAddress AT creator's trade Qortal address
|
||||
* @param digibytePublicKeyHash 20-byte HASH160 of creator's trade Digibyte public key
|
||||
* @param qortAmount how much QORT to pay trade partner if they send correct 32-byte secrets to AT
|
||||
* @param digibyteAmount how much DGB the AT creator is expecting to trade
|
||||
* @param tradeTimeout suggested timeout for entire trade
|
||||
*/
|
||||
public static byte[] buildQortalAT(String creatorTradeAddress, byte[] digibytePublicKeyHash, long qortAmount, long digibyteAmount, int tradeTimeout) {
|
||||
if (digibytePublicKeyHash.length != 20)
|
||||
throw new IllegalArgumentException("Digibyte public key hash should be 20 bytes");
|
||||
|
||||
// Labels for data segment addresses
|
||||
int addrCounter = 0;
|
||||
|
||||
// Constants (with corresponding dataByteBuffer.put*() calls below)
|
||||
|
||||
final int addrCreatorTradeAddress1 = addrCounter++;
|
||||
final int addrCreatorTradeAddress2 = addrCounter++;
|
||||
final int addrCreatorTradeAddress3 = addrCounter++;
|
||||
final int addrCreatorTradeAddress4 = addrCounter++;
|
||||
|
||||
final int addrDigibytePublicKeyHash = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
final int addrQortAmount = addrCounter++;
|
||||
final int addrDigibyteAmount = addrCounter++;
|
||||
final int addrTradeTimeout = addrCounter++;
|
||||
|
||||
final int addrMessageTxnType = addrCounter++;
|
||||
final int addrExpectedTradeMessageLength = addrCounter++;
|
||||
final int addrExpectedRedeemMessageLength = addrCounter++;
|
||||
|
||||
final int addrCreatorAddressPointer = addrCounter++;
|
||||
final int addrQortalPartnerAddressPointer = addrCounter++;
|
||||
final int addrMessageSenderPointer = addrCounter++;
|
||||
|
||||
final int addrTradeMessagePartnerDigibytePKHOffset = addrCounter++;
|
||||
final int addrPartnerDigibytePKHPointer = addrCounter++;
|
||||
final int addrTradeMessageHashOfSecretAOffset = addrCounter++;
|
||||
final int addrHashOfSecretAPointer = addrCounter++;
|
||||
|
||||
final int addrRedeemMessageReceivingAddressOffset = addrCounter++;
|
||||
|
||||
final int addrMessageDataPointer = addrCounter++;
|
||||
final int addrMessageDataLength = addrCounter++;
|
||||
|
||||
final int addrPartnerReceivingAddressPointer = addrCounter++;
|
||||
|
||||
final int addrEndOfConstants = addrCounter;
|
||||
|
||||
// Variables
|
||||
|
||||
final int addrCreatorAddress1 = addrCounter++;
|
||||
final int addrCreatorAddress2 = addrCounter++;
|
||||
final int addrCreatorAddress3 = addrCounter++;
|
||||
final int addrCreatorAddress4 = addrCounter++;
|
||||
|
||||
final int addrQortalPartnerAddress1 = addrCounter++;
|
||||
final int addrQortalPartnerAddress2 = addrCounter++;
|
||||
final int addrQortalPartnerAddress3 = addrCounter++;
|
||||
final int addrQortalPartnerAddress4 = addrCounter++;
|
||||
|
||||
final int addrLockTimeA = addrCounter++;
|
||||
final int addrRefundTimeout = addrCounter++;
|
||||
final int addrRefundTimestamp = addrCounter++;
|
||||
final int addrLastTxnTimestamp = addrCounter++;
|
||||
final int addrBlockTimestamp = addrCounter++;
|
||||
final int addrTxnType = addrCounter++;
|
||||
final int addrResult = addrCounter++;
|
||||
|
||||
final int addrMessageSender1 = addrCounter++;
|
||||
final int addrMessageSender2 = addrCounter++;
|
||||
final int addrMessageSender3 = addrCounter++;
|
||||
final int addrMessageSender4 = addrCounter++;
|
||||
|
||||
final int addrMessageLength = addrCounter++;
|
||||
|
||||
final int addrMessageData = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
final int addrHashOfSecretA = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
final int addrPartnerDigibytePKH = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
final int addrPartnerReceivingAddress = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
final int addrMode = addrCounter++;
|
||||
assert addrMode == MODE_VALUE_OFFSET : String.format("addrMode %d does not match MODE_VALUE_OFFSET %d", addrMode, MODE_VALUE_OFFSET);
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
// AT creator's trade Qortal address, decoded from Base58
|
||||
assert dataByteBuffer.position() == addrCreatorTradeAddress1 * MachineState.VALUE_SIZE : "addrCreatorTradeAddress1 incorrect";
|
||||
byte[] creatorTradeAddressBytes = Base58.decode(creatorTradeAddress);
|
||||
dataByteBuffer.put(Bytes.ensureCapacity(creatorTradeAddressBytes, 32, 0));
|
||||
|
||||
// Digibyte public key hash
|
||||
assert dataByteBuffer.position() == addrDigibytePublicKeyHash * MachineState.VALUE_SIZE : "addrDigibytePublicKeyHash incorrect";
|
||||
dataByteBuffer.put(Bytes.ensureCapacity(digibytePublicKeyHash, 32, 0));
|
||||
|
||||
// Redeem Qort amount
|
||||
assert dataByteBuffer.position() == addrQortAmount * MachineState.VALUE_SIZE : "addrQortAmount incorrect";
|
||||
dataByteBuffer.putLong(qortAmount);
|
||||
|
||||
// Expected Digibyte amount
|
||||
assert dataByteBuffer.position() == addrDigibyteAmount * MachineState.VALUE_SIZE : "addrDigibyteAmount incorrect";
|
||||
dataByteBuffer.putLong(digibyteAmount);
|
||||
|
||||
// Suggested trade timeout (minutes)
|
||||
assert dataByteBuffer.position() == addrTradeTimeout * MachineState.VALUE_SIZE : "addrTradeTimeout incorrect";
|
||||
dataByteBuffer.putLong(tradeTimeout);
|
||||
|
||||
// We're only interested in MESSAGE transactions
|
||||
assert dataByteBuffer.position() == addrMessageTxnType * MachineState.VALUE_SIZE : "addrMessageTxnType incorrect";
|
||||
dataByteBuffer.putLong(API.ATTransactionType.MESSAGE.value);
|
||||
|
||||
// Expected length of 'trade' MESSAGE data from AT creator
|
||||
assert dataByteBuffer.position() == addrExpectedTradeMessageLength * MachineState.VALUE_SIZE : "addrExpectedTradeMessageLength incorrect";
|
||||
dataByteBuffer.putLong(TRADE_MESSAGE_LENGTH);
|
||||
|
||||
// Expected length of 'redeem' MESSAGE data from trade partner
|
||||
assert dataByteBuffer.position() == addrExpectedRedeemMessageLength * MachineState.VALUE_SIZE : "addrExpectedRedeemMessageLength incorrect";
|
||||
dataByteBuffer.putLong(REDEEM_MESSAGE_LENGTH);
|
||||
|
||||
// Index into data segment of AT creator's address, used by GET_B_IND
|
||||
assert dataByteBuffer.position() == addrCreatorAddressPointer * MachineState.VALUE_SIZE : "addrCreatorAddressPointer incorrect";
|
||||
dataByteBuffer.putLong(addrCreatorAddress1);
|
||||
|
||||
// Index into data segment of partner's Qortal address, used by SET_B_IND
|
||||
assert dataByteBuffer.position() == addrQortalPartnerAddressPointer * MachineState.VALUE_SIZE : "addrQortalPartnerAddressPointer incorrect";
|
||||
dataByteBuffer.putLong(addrQortalPartnerAddress1);
|
||||
|
||||
// Index into data segment of (temporary) transaction's sender's address, used by GET_B_IND
|
||||
assert dataByteBuffer.position() == addrMessageSenderPointer * MachineState.VALUE_SIZE : "addrMessageSenderPointer incorrect";
|
||||
dataByteBuffer.putLong(addrMessageSender1);
|
||||
|
||||
// Offset into 'trade' MESSAGE data payload for extracting partner's Digibyte PKH
|
||||
assert dataByteBuffer.position() == addrTradeMessagePartnerDigibytePKHOffset * MachineState.VALUE_SIZE : "addrTradeMessagePartnerDigibytePKHOffset incorrect";
|
||||
dataByteBuffer.putLong(32L);
|
||||
|
||||
// Index into data segment of partner's Digibyte PKH, used by GET_B_IND
|
||||
assert dataByteBuffer.position() == addrPartnerDigibytePKHPointer * MachineState.VALUE_SIZE : "addrPartnerDigibytePKHPointer incorrect";
|
||||
dataByteBuffer.putLong(addrPartnerDigibytePKH);
|
||||
|
||||
// Offset into 'trade' MESSAGE data payload for extracting hash-of-secret-A
|
||||
assert dataByteBuffer.position() == addrTradeMessageHashOfSecretAOffset * MachineState.VALUE_SIZE : "addrTradeMessageHashOfSecretAOffset incorrect";
|
||||
dataByteBuffer.putLong(64L);
|
||||
|
||||
// Index into data segment to hash of secret A, used by GET_B_IND
|
||||
assert dataByteBuffer.position() == addrHashOfSecretAPointer * MachineState.VALUE_SIZE : "addrHashOfSecretAPointer incorrect";
|
||||
dataByteBuffer.putLong(addrHashOfSecretA);
|
||||
|
||||
// Offset into 'redeem' MESSAGE data payload for extracting Qortal receiving address
|
||||
assert dataByteBuffer.position() == addrRedeemMessageReceivingAddressOffset * MachineState.VALUE_SIZE : "addrRedeemMessageReceivingAddressOffset incorrect";
|
||||
dataByteBuffer.putLong(32L);
|
||||
|
||||
// Source location and length for hashing any passed secret
|
||||
assert dataByteBuffer.position() == addrMessageDataPointer * MachineState.VALUE_SIZE : "addrMessageDataPointer incorrect";
|
||||
dataByteBuffer.putLong(addrMessageData);
|
||||
assert dataByteBuffer.position() == addrMessageDataLength * MachineState.VALUE_SIZE : "addrMessageDataLength incorrect";
|
||||
dataByteBuffer.putLong(32L);
|
||||
|
||||
// Pointer into data segment of where to save partner's receiving Qortal address, used by GET_B_IND
|
||||
assert dataByteBuffer.position() == addrPartnerReceivingAddressPointer * MachineState.VALUE_SIZE : "addrPartnerReceivingAddressPointer incorrect";
|
||||
dataByteBuffer.putLong(addrPartnerReceivingAddress);
|
||||
|
||||
assert dataByteBuffer.position() == addrEndOfConstants * MachineState.VALUE_SIZE : "dataByteBuffer position not at end of constants";
|
||||
|
||||
// Code labels
|
||||
Integer labelRefund = null;
|
||||
|
||||
Integer labelTradeTxnLoop = null;
|
||||
Integer labelCheckTradeTxn = null;
|
||||
Integer labelCheckCancelTxn = null;
|
||||
Integer labelNotTradeNorCancelTxn = null;
|
||||
Integer labelCheckNonRefundTradeTxn = null;
|
||||
Integer labelTradeTxnExtract = null;
|
||||
Integer labelRedeemTxnLoop = null;
|
||||
Integer labelCheckRedeemTxn = null;
|
||||
Integer labelCheckRedeemTxnSender = null;
|
||||
Integer labelPayout = null;
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(768);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
/* Initialization */
|
||||
|
||||
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTxnTimestamp));
|
||||
|
||||
/* NOP - to ensure DIGIBYTE ACCT is unique */
|
||||
codeByteBuffer.put(OpCode.NOP.compile());
|
||||
|
||||
// Load B register with AT creator's address so we can save it into addrCreatorAddress1-4
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B));
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrCreatorAddressPointer));
|
||||
|
||||
// Set restart position to after this opcode
|
||||
codeByteBuffer.put(OpCode.SET_PCS.compile());
|
||||
|
||||
/* Loop, waiting for message from AT creator's trade address containing trade partner details, or AT owner's address to cancel offer */
|
||||
|
||||
/* Transaction processing loop */
|
||||
labelTradeTxnLoop = codeByteBuffer.position();
|
||||
|
||||
/* Sleep until message arrives */
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.SLEEP_UNTIL_MESSAGE.value, addrLastTxnTimestamp));
|
||||
|
||||
// Find next transaction (if any) to this AT since the last one (referenced by addrLastTxnTimestamp)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp));
|
||||
// If no transaction found, A will be zero. If A is zero, set addrResult to 1, otherwise 0.
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult));
|
||||
// If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction
|
||||
codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckTradeTxn)));
|
||||
// Stop and wait for next block
|
||||
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
||||
|
||||
/* Check transaction */
|
||||
labelCheckTradeTxn = codeByteBuffer.position();
|
||||
|
||||
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp));
|
||||
// Extract transaction type (message/payment) from transaction and save type in addrTxnType
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType));
|
||||
// If transaction type is not MESSAGE type then go look for another transaction
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelTradeTxnLoop)));
|
||||
|
||||
/* Check transaction's sender. We're expecting AT creator's trade address for 'trade' message, or AT creator's own address for 'cancel' message. */
|
||||
|
||||
// Extract sender address from transaction into B register
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B));
|
||||
// Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer));
|
||||
// Compare each part of message sender's address with AT creator's trade address. If they don't match, check for cancel situation.
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorTradeAddress1, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorTradeAddress2, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorTradeAddress3, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorTradeAddress4, calcOffset(codeByteBuffer, labelCheckCancelTxn)));
|
||||
// Message sender's address matches AT creator's trade address so go process 'trade' message
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelCheckNonRefundTradeTxn == null ? 0 : labelCheckNonRefundTradeTxn));
|
||||
|
||||
/* Checking message sender for possible cancel message */
|
||||
labelCheckCancelTxn = codeByteBuffer.position();
|
||||
|
||||
// Compare each part of message sender's address with AT creator's address. If they don't match, look for another transaction.
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrCreatorAddress1, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrCreatorAddress2, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrCreatorAddress3, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrCreatorAddress4, calcOffset(codeByteBuffer, labelNotTradeNorCancelTxn)));
|
||||
// Partner address is AT creator's address, so cancel offer and finish.
|
||||
codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.CANCELLED.value));
|
||||
// We're finished forever (finishing auto-refunds remaining balance to AT creator)
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
|
||||
/* Not trade nor cancel message */
|
||||
labelNotTradeNorCancelTxn = codeByteBuffer.position();
|
||||
|
||||
// Loop to find another transaction
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop));
|
||||
|
||||
/* Possible switch-to-trade-mode message */
|
||||
labelCheckNonRefundTradeTxn = codeByteBuffer.position();
|
||||
|
||||
// Check 'trade' message we received has expected number of message bytes
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength));
|
||||
// If message length matches, branch to info extraction code
|
||||
codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedTradeMessageLength, calcOffset(codeByteBuffer, labelTradeTxnExtract)));
|
||||
// Message length didn't match - go back to finding another 'trade' MESSAGE transaction
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelTradeTxnLoop == null ? 0 : labelTradeTxnLoop));
|
||||
|
||||
/* Extracting info from 'trade' MESSAGE transaction */
|
||||
labelTradeTxnExtract = codeByteBuffer.position();
|
||||
|
||||
// Extract message from transaction into B register
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
|
||||
// Save B register into data segment starting at addrQortalPartnerAddress1 (as pointed to by addrQortalPartnerAddressPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrQortalPartnerAddressPointer));
|
||||
|
||||
// Extract trade partner's Digibyte public key hash (PKH) from message into B
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessagePartnerDigibytePKHOffset));
|
||||
// Store partner's Digibyte PKH (we only really use values from B1-B3)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerDigibytePKHPointer));
|
||||
// Extract AT trade timeout (minutes) (from B4)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrRefundTimeout));
|
||||
|
||||
// Grab next 32 bytes
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrTradeMessageHashOfSecretAOffset));
|
||||
|
||||
// Extract hash-of-secret-A (we only really use values from B1-B3)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrHashOfSecretAPointer));
|
||||
// Extract lockTime-A (from B4)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B4, addrLockTimeA));
|
||||
|
||||
// Calculate trade timeout refund 'timestamp' by adding addrRefundTimeout minutes to this transaction's 'timestamp', then save into addrRefundTimestamp
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTxnTimestamp, addrRefundTimeout));
|
||||
|
||||
/* We are in 'trade mode' */
|
||||
codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.TRADING.value));
|
||||
|
||||
// Set restart position to after this opcode
|
||||
codeByteBuffer.put(OpCode.SET_PCS.compile());
|
||||
|
||||
/* Loop, waiting for trade timeout or 'redeem' MESSAGE from Qortal trade partner */
|
||||
|
||||
// Fetch current block 'timestamp'
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp));
|
||||
// If we're not past refund 'timestamp' then look for next transaction
|
||||
codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||
// We're past refund 'timestamp' so go refund everything back to AT creator
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
|
||||
|
||||
/* Transaction processing loop */
|
||||
labelRedeemTxnLoop = codeByteBuffer.position();
|
||||
|
||||
// Find next transaction to this AT since the last one (if any)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTxnTimestamp));
|
||||
// If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0.
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrResult));
|
||||
// If addrResult is zero (i.e. A is non-zero, transaction was found) then go check transaction
|
||||
codeByteBuffer.put(OpCode.BZR_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelCheckRedeemTxn)));
|
||||
// Stop and wait for next block
|
||||
codeByteBuffer.put(OpCode.STP_IMD.compile());
|
||||
|
||||
/* Check transaction */
|
||||
labelCheckRedeemTxn = codeByteBuffer.position();
|
||||
|
||||
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTxnTimestamp));
|
||||
// Extract transaction type (message/payment) from transaction and save type in addrTxnType
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxnType));
|
||||
// If transaction type is not MESSAGE type then go look for another transaction
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxnType, addrMessageTxnType, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||
|
||||
/* Check message payload length */
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_MESSAGE_LENGTH_FROM_TX_IN_A.value, addrMessageLength));
|
||||
// If message length matches, branch to sender checking code
|
||||
codeByteBuffer.put(OpCode.BEQ_DAT.compile(addrMessageLength, addrExpectedRedeemMessageLength, calcOffset(codeByteBuffer, labelCheckRedeemTxnSender)));
|
||||
// Message length didn't match - go back to finding another 'redeem' MESSAGE transaction
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
|
||||
|
||||
/* Check transaction's sender */
|
||||
labelCheckRedeemTxnSender = codeByteBuffer.position();
|
||||
|
||||
// Extract sender address from transaction into B register
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B));
|
||||
// Save B register into data segment starting at addrMessageSender1 (as pointed to by addrMessageSenderPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageSenderPointer));
|
||||
// Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction.
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender1, addrQortalPartnerAddress1, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender2, addrQortalPartnerAddress2, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender3, addrQortalPartnerAddress3, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrMessageSender4, addrQortalPartnerAddress4, calcOffset(codeByteBuffer, labelRedeemTxnLoop)));
|
||||
|
||||
/* Check 'secret-A' in transaction's message */
|
||||
|
||||
// Extract secret-A from first 32 bytes of message from transaction into B register
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
|
||||
// Save B register into data segment starting at addrMessageData (as pointed to by addrMessageDataPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrMessageDataPointer));
|
||||
// Load B register with expected hash result (as pointed to by addrHashOfSecretAPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrHashOfSecretAPointer));
|
||||
// Perform HASH160 using source data at addrMessageData. (Location and length specified via addrMessageDataPointer and addrMessageDataLength).
|
||||
// Save the equality result (1 if they match, 0 otherwise) into addrResult.
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrResult, addrMessageDataPointer, addrMessageDataLength));
|
||||
// If hashes don't match, addrResult will be zero so go find another transaction
|
||||
codeByteBuffer.put(OpCode.BNZ_DAT.compile(addrResult, calcOffset(codeByteBuffer, labelPayout)));
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRedeemTxnLoop == null ? 0 : labelRedeemTxnLoop));
|
||||
|
||||
/* Success! Pay arranged amount to receiving address */
|
||||
labelPayout = codeByteBuffer.position();
|
||||
|
||||
// Extract Qortal receiving address from next 32 bytes of message from transaction into B register
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(QortalFunctionCode.PUT_PARTIAL_MESSAGE_FROM_TX_IN_A_INTO_B.value, addrRedeemMessageReceivingAddressOffset));
|
||||
// Save B register into data segment starting at addrPartnerReceivingAddress (as pointed to by addrPartnerReceivingAddressPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.GET_B_IND, addrPartnerReceivingAddressPointer));
|
||||
// Pay AT's balance to receiving address
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrQortAmount));
|
||||
// Set redeemed mode
|
||||
codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.REDEEMED.value));
|
||||
// We're finished forever (finishing auto-refunds remaining balance to AT creator)
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
|
||||
// Fall-through to refunding any remaining balance back to AT creator
|
||||
|
||||
/* Refund balance back to AT creator */
|
||||
labelRefund = codeByteBuffer.position();
|
||||
|
||||
// Set refunded mode
|
||||
codeByteBuffer.put(OpCode.SET_VAL.compile(addrMode, AcctMode.REFUNDED.value));
|
||||
// We're finished forever (finishing auto-refunds remaining balance to AT creator)
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile DGB-QORT ACCT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
assert Arrays.equals(Crypto.digest(codeBytes), DigibyteACCTv3.CODE_BYTES_HASH)
|
||||
: String.format("BTCACCT.CODE_BYTES_HASH mismatch: expected %s, actual %s", HashCode.fromBytes(CODE_BYTES_HASH), HashCode.fromBytes(Crypto.digest(codeBytes)));
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CrossChainTradeData with useful info extracted from AT.
|
||||
*/
|
||||
@Override
|
||||
public CrossChainTradeData populateTradeData(Repository repository, ATData atData) throws DataException {
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atData.getATAddress());
|
||||
return populateTradeData(repository, atData.getCreatorPublicKey(), atData.getCreation(), atStateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CrossChainTradeData with useful info extracted from AT.
|
||||
*/
|
||||
@Override
|
||||
public CrossChainTradeData populateTradeData(Repository repository, ATStateData atStateData) throws DataException {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atStateData.getATAddress());
|
||||
return populateTradeData(repository, atData.getCreatorPublicKey(), atData.getCreation(), atStateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CrossChainTradeData with useful info extracted from AT.
|
||||
*/
|
||||
public CrossChainTradeData populateTradeData(Repository repository, byte[] creatorPublicKey, long creationTimestamp, ATStateData atStateData) throws DataException {
|
||||
byte[] addressBytes = new byte[25]; // for general use
|
||||
String atAddress = atStateData.getATAddress();
|
||||
|
||||
CrossChainTradeData tradeData = new CrossChainTradeData();
|
||||
|
||||
tradeData.foreignBlockchain = SupportedBlockchain.DIGIBYTE.name();
|
||||
tradeData.acctName = NAME;
|
||||
|
||||
tradeData.qortalAtAddress = atAddress;
|
||||
tradeData.qortalCreator = Crypto.toAddress(creatorPublicKey);
|
||||
tradeData.creationTimestamp = creationTimestamp;
|
||||
|
||||
Account atAccount = new Account(repository, atAddress);
|
||||
tradeData.qortBalance = atAccount.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.wrap(stateData);
|
||||
dataByteBuffer.position(MachineState.HEADER_LENGTH);
|
||||
|
||||
/* Constants */
|
||||
|
||||
// Skip creator's trade address
|
||||
dataByteBuffer.get(addressBytes);
|
||||
tradeData.qortalCreatorTradeAddress = Base58.encode(addressBytes);
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length);
|
||||
|
||||
// Creator's Digibyte/foreign public key hash
|
||||
tradeData.creatorForeignPKH = new byte[20];
|
||||
dataByteBuffer.get(tradeData.creatorForeignPKH);
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - tradeData.creatorForeignPKH.length); // skip to 32 bytes
|
||||
|
||||
// We don't use secret-B
|
||||
tradeData.hashOfSecretB = null;
|
||||
|
||||
// Redeem payout
|
||||
tradeData.qortAmount = dataByteBuffer.getLong();
|
||||
|
||||
// Expected DGB amount
|
||||
tradeData.expectedForeignAmount = dataByteBuffer.getLong();
|
||||
|
||||
// Trade timeout
|
||||
tradeData.tradeTimeout = (int) dataByteBuffer.getLong();
|
||||
|
||||
// Skip MESSAGE transaction type
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip expected 'trade' message length
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip expected 'redeem' message length
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip pointer to creator's address
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip pointer to partner's Qortal trade address
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip pointer to message sender
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip 'trade' message data offset for partner's Digibyte PKH
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip pointer to partner's Digibyte PKH
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip 'trade' message data offset for hash-of-secret-A
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip pointer to hash-of-secret-A
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip 'redeem' message data offset for partner's Qortal receiving address
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip pointer to message data
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip message data length
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip pointer to partner's receiving address
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
/* End of constants / begin variables */
|
||||
|
||||
// Skip AT creator's address
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
|
||||
|
||||
// Partner's trade address (if present)
|
||||
dataByteBuffer.get(addressBytes);
|
||||
String qortalRecipient = Base58.encode(addressBytes);
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - addressBytes.length);
|
||||
|
||||
// Potential lockTimeA (if in trade mode)
|
||||
int lockTimeA = (int) dataByteBuffer.getLong();
|
||||
|
||||
// AT refund timeout (probably only useful for debugging)
|
||||
int refundTimeout = (int) dataByteBuffer.getLong();
|
||||
|
||||
// Trade-mode refund timestamp (AT 'timestamp' converted to Qortal block height)
|
||||
long tradeRefundTimestamp = dataByteBuffer.getLong();
|
||||
|
||||
// Skip last transaction timestamp
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip block timestamp
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip transaction type
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip temporary result
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip temporary message sender
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
|
||||
|
||||
// Skip message length
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8);
|
||||
|
||||
// Skip temporary message data
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 8 * 4);
|
||||
|
||||
// Potential hash160 of secret A
|
||||
byte[] hashOfSecretA = new byte[20];
|
||||
dataByteBuffer.get(hashOfSecretA);
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - hashOfSecretA.length); // skip to 32 bytes
|
||||
|
||||
// Potential partner's Digibyte PKH
|
||||
byte[] partnerDigibytePKH = new byte[20];
|
||||
dataByteBuffer.get(partnerDigibytePKH);
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerDigibytePKH.length); // skip to 32 bytes
|
||||
|
||||
// Partner's receiving address (if present)
|
||||
byte[] partnerReceivingAddress = new byte[25];
|
||||
dataByteBuffer.get(partnerReceivingAddress);
|
||||
dataByteBuffer.position(dataByteBuffer.position() + 32 - partnerReceivingAddress.length); // skip to 32 bytes
|
||||
|
||||
// Trade AT's 'mode'
|
||||
long modeValue = dataByteBuffer.getLong();
|
||||
AcctMode mode = AcctMode.valueOf((int) (modeValue & 0xffL));
|
||||
|
||||
/* End of variables */
|
||||
|
||||
if (mode != null && mode != AcctMode.OFFERING) {
|
||||
tradeData.mode = mode;
|
||||
tradeData.refundTimeout = refundTimeout;
|
||||
tradeData.tradeRefundHeight = new Timestamp(tradeRefundTimestamp).blockHeight;
|
||||
tradeData.qortalPartnerAddress = qortalRecipient;
|
||||
tradeData.hashOfSecretA = hashOfSecretA;
|
||||
tradeData.partnerForeignPKH = partnerDigibytePKH;
|
||||
tradeData.lockTimeA = lockTimeA;
|
||||
|
||||
if (mode == AcctMode.REDEEMED)
|
||||
tradeData.qortalPartnerReceivingAddress = Base58.encode(partnerReceivingAddress);
|
||||
} else {
|
||||
tradeData.mode = AcctMode.OFFERING;
|
||||
}
|
||||
|
||||
tradeData.duplicateDeprecated();
|
||||
|
||||
return tradeData;
|
||||
}
|
||||
|
||||
/** Returns 'offer' MESSAGE payload for trade partner to send to AT creator's trade address. */
|
||||
public static byte[] buildOfferMessage(byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA) {
|
||||
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
||||
return Bytes.concat(partnerBitcoinPKH, hashOfSecretA, lockTimeABytes);
|
||||
}
|
||||
|
||||
/** Returns info extracted from 'offer' MESSAGE payload sent by trade partner to AT creator's trade address, or null if not valid. */
|
||||
public static OfferMessageData extractOfferMessageData(byte[] messageData) {
|
||||
if (messageData == null || messageData.length != OFFER_MESSAGE_LENGTH)
|
||||
return null;
|
||||
|
||||
OfferMessageData offerMessageData = new OfferMessageData();
|
||||
offerMessageData.partnerDigibytePKH = Arrays.copyOfRange(messageData, 0, 20);
|
||||
offerMessageData.hashOfSecretA = Arrays.copyOfRange(messageData, 20, 40);
|
||||
offerMessageData.lockTimeA = BitTwiddling.longFromBEBytes(messageData, 40);
|
||||
|
||||
return offerMessageData;
|
||||
}
|
||||
|
||||
/** Returns 'trade' MESSAGE payload for AT creator to send to AT. */
|
||||
public static byte[] buildTradeMessage(String partnerQortalTradeAddress, byte[] partnerBitcoinPKH, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
byte[] data = new byte[TRADE_MESSAGE_LENGTH];
|
||||
byte[] partnerQortalAddressBytes = Base58.decode(partnerQortalTradeAddress);
|
||||
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
||||
byte[] refundTimeoutBytes = BitTwiddling.toBEByteArray((long) refundTimeout);
|
||||
|
||||
System.arraycopy(partnerQortalAddressBytes, 0, data, 0, partnerQortalAddressBytes.length);
|
||||
System.arraycopy(partnerBitcoinPKH, 0, data, 32, partnerBitcoinPKH.length);
|
||||
System.arraycopy(refundTimeoutBytes, 0, data, 56, refundTimeoutBytes.length);
|
||||
System.arraycopy(hashOfSecretA, 0, data, 64, hashOfSecretA.length);
|
||||
System.arraycopy(lockTimeABytes, 0, data, 88, lockTimeABytes.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Returns 'cancel' MESSAGE payload for AT creator to cancel trade AT. */
|
||||
@Override
|
||||
public byte[] buildCancelMessage(String creatorQortalAddress) {
|
||||
byte[] data = new byte[CANCEL_MESSAGE_LENGTH];
|
||||
byte[] creatorQortalAddressBytes = Base58.decode(creatorQortalAddress);
|
||||
|
||||
System.arraycopy(creatorQortalAddressBytes, 0, data, 0, creatorQortalAddressBytes.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Returns 'redeem' MESSAGE payload for trade partner to send to AT. */
|
||||
public static byte[] buildRedeemMessage(byte[] secretA, String qortalReceivingAddress) {
|
||||
byte[] data = new byte[REDEEM_MESSAGE_LENGTH];
|
||||
byte[] qortalReceivingAddressBytes = Base58.decode(qortalReceivingAddress);
|
||||
|
||||
System.arraycopy(secretA, 0, data, 0, secretA.length);
|
||||
System.arraycopy(qortalReceivingAddressBytes, 0, data, 32, qortalReceivingAddressBytes.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Returns refund timeout (minutes) based on trade partner's 'offer' MESSAGE timestamp and P2SH-A locktime. */
|
||||
public static int calcRefundTimeout(long offerMessageTimestamp, int lockTimeA) {
|
||||
// refund should be triggered halfway between offerMessageTimestamp and lockTimeA
|
||||
return (int) ((lockTimeA - (offerMessageTimestamp / 1000L)) / 2L / 60L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] findSecretA(Repository repository, CrossChainTradeData crossChainTradeData) throws DataException {
|
||||
String atAddress = crossChainTradeData.qortalAtAddress;
|
||||
String redeemerAddress = crossChainTradeData.qortalPartnerAddress;
|
||||
|
||||
// We don't have partner's public key so we check every message to AT
|
||||
List<MessageTransactionData> messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, atAddress, null, null, null);
|
||||
if (messageTransactionsData == null)
|
||||
return null;
|
||||
|
||||
// Find 'redeem' message
|
||||
for (MessageTransactionData messageTransactionData : messageTransactionsData) {
|
||||
// Check message payload type/encryption
|
||||
if (messageTransactionData.isText() || messageTransactionData.isEncrypted())
|
||||
continue;
|
||||
|
||||
// Check message payload size
|
||||
byte[] messageData = messageTransactionData.getData();
|
||||
if (messageData.length != REDEEM_MESSAGE_LENGTH)
|
||||
// Wrong payload length
|
||||
continue;
|
||||
|
||||
// Check sender
|
||||
if (!Crypto.toAddress(messageTransactionData.getSenderPublicKey()).equals(redeemerAddress))
|
||||
// Wrong sender;
|
||||
continue;
|
||||
|
||||
// Extract secretA
|
||||
byte[] secretA = new byte[32];
|
||||
System.arraycopy(messageData, 0, secretA, 0, secretA.length);
|
||||
|
||||
byte[] hashOfSecretA = Crypto.hash160(secretA);
|
||||
if (!Arrays.equals(hashOfSecretA, crossChainTradeData.hashOfSecretA))
|
||||
continue;
|
||||
|
||||
return secretA;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -45,9 +45,9 @@ public class Dogecoin extends Bitcoiny {
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("electrum1.cipig.net", ConnectionType.TCP, 10060),
|
||||
new Server("electrum2.cipig.net", ConnectionType.TCP, 10060),
|
||||
new Server("electrum3.cipig.net", ConnectionType.TCP, 10060));
|
||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20060),
|
||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20060),
|
||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20060));
|
||||
// TODO: add more mainnet servers. It's too centralized.
|
||||
}
|
||||
|
||||
|
@@ -44,23 +44,17 @@ public class Litecoin extends Bitcoiny {
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("electrum-ltc.someguy123.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("backup.electrum-ltc.org", Server.ConnectionType.TCP, 50001),
|
||||
// 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),
|
||||
//PHISHY new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002),
|
||||
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443),
|
||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.TCP, 50001),
|
||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum3.cipig.net", ConnectionType.TCP, 10063),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.TCP, 10063),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.TCP, 10063),
|
||||
new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
|
||||
new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022),
|
||||
new Server("electrum-ltc-bysh.me", Server.ConnectionType.TCP, 50002),
|
||||
new Server("electrum.jochen-hoenicke.de", Server.ConnectionType.TCP, 50005),
|
||||
new Server("node.ispol.sk", Server.ConnectionType.TCP, 50004));
|
||||
new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -57,12 +57,26 @@ public enum SupportedBlockchain {
|
||||
public ACCT getLatestAcct() {
|
||||
return DogecoinACCTv3.getInstance();
|
||||
}
|
||||
},
|
||||
|
||||
DIGIBYTE(Arrays.asList(
|
||||
Triple.valueOf(DigibyteACCTv3.NAME, DigibyteACCTv3.CODE_BYTES_HASH, DigibyteACCTv3::getInstance)
|
||||
)) {
|
||||
@Override
|
||||
public ForeignBlockchain getInstance() {
|
||||
return Digibyte.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ACCT getLatestAcct() {
|
||||
return DigibyteACCTv3.getInstance();
|
||||
}
|
||||
};
|
||||
|
||||
private static final Map<ByteArray, Supplier<ACCT>> supportedAcctsByCodeHash = Arrays.stream(SupportedBlockchain.values())
|
||||
.map(supportedBlockchain -> supportedBlockchain.supportedAccts)
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> new ByteArray(triple.getB()), Triple::getC));
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> ByteArray.wrap(triple.getB()), Triple::getC));
|
||||
|
||||
private static final Map<String, Supplier<ACCT>> supportedAcctsByName = Arrays.stream(SupportedBlockchain.values())
|
||||
.map(supportedBlockchain -> supportedBlockchain.supportedAccts)
|
||||
@@ -94,7 +108,7 @@ public enum SupportedBlockchain {
|
||||
return getAcctMap();
|
||||
|
||||
return blockchain.supportedAccts.stream()
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> new ByteArray(triple.getB()), Triple::getC));
|
||||
.collect(Collectors.toUnmodifiableMap(triple -> ByteArray.wrap(triple.getB()), Triple::getC));
|
||||
}
|
||||
|
||||
public static Map<ByteArray, Supplier<ACCT>> getFilteredAcctMap(String specificBlockchain) {
|
||||
@@ -109,7 +123,7 @@ public enum SupportedBlockchain {
|
||||
}
|
||||
|
||||
public static ACCT getAcctByCodeHash(byte[] codeHash) {
|
||||
ByteArray wrappedCodeHash = new ByteArray(codeHash);
|
||||
ByteArray wrappedCodeHash = ByteArray.wrap(codeHash);
|
||||
|
||||
Supplier<ACCT> acctInstanceSupplier = supportedAcctsByCodeHash.get(wrappedCodeHash);
|
||||
|
||||
|
@@ -23,6 +23,7 @@ public class GroupData {
|
||||
private ApprovalThreshold approvalThreshold;
|
||||
private int minimumBlockDelay;
|
||||
private int maximumBlockDelay;
|
||||
public int memberCount;
|
||||
|
||||
/** Reference to CREATE_GROUP or UPDATE_GROUP transaction, used to rebuild group during orphaning. */
|
||||
// No need to ever expose this via API
|
||||
|
@@ -48,6 +48,7 @@ public class UpdateNameTransactionData extends TransactionData {
|
||||
|
||||
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
||||
this.creatorPublicKey = this.ownerPublicKey;
|
||||
this.reducedNewName = this.newName != null ? Unicode.sanitize(this.newName) : null;
|
||||
}
|
||||
|
||||
/** From repository */
|
||||
@@ -62,7 +63,7 @@ public class UpdateNameTransactionData extends TransactionData {
|
||||
this.nameReference = nameReference;
|
||||
}
|
||||
|
||||
/** From network/API */
|
||||
/** From network */
|
||||
public UpdateNameTransactionData(BaseTransactionData baseTransactionData, String name, String newName, String newData) {
|
||||
this(baseTransactionData, name, newName, newData, Unicode.sanitize(newName), null);
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import java.awt.GraphicsEnvironment;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ServiceConfigurationError;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JOptionPane;
|
||||
@@ -46,12 +47,12 @@ public class Gui {
|
||||
this.splashFrame = SplashFrame.getInstance();
|
||||
}
|
||||
|
||||
protected static BufferedImage loadImage(String resourceName) {
|
||||
protected static BufferedImage loadImage(String resourceName) throws IOException {
|
||||
try (InputStream in = Gui.class.getResourceAsStream("/images/" + resourceName)) {
|
||||
return ImageIO.read(in);
|
||||
} catch (IllegalArgumentException | IOException e) {
|
||||
} catch (IllegalArgumentException | IOException | ServiceConfigurationError e) {
|
||||
LOGGER.warn(String.format("Couldn't locate image resource \"images/%s\"", resourceName));
|
||||
return null;
|
||||
throw new IOException(String.format("Couldn't locate image resource \"images/%s\"", resourceName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.qortal.gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.awt.image.BufferedImage;
|
||||
@@ -29,18 +30,23 @@ public class SplashFrame {
|
||||
private JLabel statusLabel;
|
||||
|
||||
public SplashPanel() {
|
||||
image = Gui.loadImage(defaultSplash);
|
||||
try {
|
||||
image = Gui.loadImage(defaultSplash);
|
||||
|
||||
// Add logo
|
||||
JLabel imageLabel = new JLabel(new ImageIcon(image));
|
||||
imageLabel.setSize(new Dimension(300, 300));
|
||||
add(imageLabel);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOGGER.warn("Unable to load splash panel image");
|
||||
}
|
||||
|
||||
setOpaque(true);
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
setBackground(Color.BLACK);
|
||||
|
||||
// Add logo
|
||||
JLabel imageLabel = new JLabel(new ImageIcon(image));
|
||||
imageLabel.setSize(new Dimension(300, 300));
|
||||
add(imageLabel);
|
||||
|
||||
// Add spacing
|
||||
add(Box.createRigidArea(new Dimension(0, 16)));
|
||||
|
||||
@@ -75,15 +81,20 @@ public class SplashFrame {
|
||||
|
||||
this.splashDialog = new JFrame();
|
||||
|
||||
List<Image> icons = new ArrayList<>();
|
||||
icons.add(Gui.loadImage("icons/icon16.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_synced.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing_time-alt.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_minting.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing.png"));
|
||||
icons.add(Gui.loadImage("icons/icon64.png"));
|
||||
icons.add(Gui.loadImage("icons/Qlogo_128.png"));
|
||||
this.splashDialog.setIconImages(icons);
|
||||
try {
|
||||
List<Image> icons = new ArrayList<>();
|
||||
icons.add(Gui.loadImage("icons/icon16.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_synced.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing_time-alt.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_minting.png"));
|
||||
icons.add(Gui.loadImage("icons/qortal_ui_tray_syncing.png"));
|
||||
icons.add(Gui.loadImage("icons/icon64.png"));
|
||||
icons.add(Gui.loadImage("icons/Qlogo_128.png"));
|
||||
this.splashDialog.setIconImages(icons);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOGGER.warn("Unable to load splash frame icons");
|
||||
}
|
||||
|
||||
this.splashPanel = new SplashPanel();
|
||||
this.splashDialog.getContentPane().add(this.splashPanel);
|
||||
|
@@ -61,7 +61,13 @@ public class SysTray {
|
||||
this.popupMenu = createJPopupMenu();
|
||||
|
||||
// Build TrayIcon without AWT PopupMenu (which doesn't support Unicode)...
|
||||
this.trayIcon = new TrayIcon(Gui.loadImage("icons/qortal_ui_tray_synced.png"), "qortal", null);
|
||||
try {
|
||||
this.trayIcon = new TrayIcon(Gui.loadImage("icons/qortal_ui_tray_synced.png"), "qortal", null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOGGER.warn("Unable to load system tray icon");
|
||||
return;
|
||||
}
|
||||
// ...and attach mouse listener instead so we can use JPopupMenu (which does support Unicode)
|
||||
this.trayIcon.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
|
@@ -76,6 +76,9 @@ public interface AccountRepository {
|
||||
*/
|
||||
public void setBlocksMintedAdjustment(AccountData accountData) throws DataException;
|
||||
|
||||
/** Returns account's minted block count or null if account not found. */
|
||||
public Integer getMintedBlockCount(String address) throws DataException;
|
||||
|
||||
/**
|
||||
* Saves account's minted block count and public key if present, in repository.
|
||||
* <p>
|
||||
|
@@ -419,8 +419,8 @@ public class Bootstrap {
|
||||
downloaded += bytesRead;
|
||||
|
||||
if (fileSize > 0) {
|
||||
int progress = (int)((double)downloaded / (double)fileSize * 100);
|
||||
SplashFrame.getInstance().updateStatus(String.format("Downloading %s bootstrap... (%d%%)", type, progress));
|
||||
double progress = (double)downloaded / (double)fileSize * 100;
|
||||
SplashFrame.getInstance().updateStatus(String.format("Downloading %s bootstrap... (%.1f%%)", type, progress));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -241,6 +241,20 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getMintedBlockCount(String address) throws DataException {
|
||||
String sql = "SELECT blocks_minted FROM Accounts WHERE account = ?";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
return resultSet.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch account's minted block count from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMintedBlockCount(AccountData accountData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
||||
|
@@ -26,6 +26,7 @@ import org.qortal.controller.arbitrary.ArbitraryDataStorageManager.*;
|
||||
import org.qortal.crosschain.Bitcoin.BitcoinNet;
|
||||
import org.qortal.crosschain.Litecoin.LitecoinNet;
|
||||
import org.qortal.crosschain.Dogecoin.DogecoinNet;
|
||||
import org.qortal.crosschain.Digibyte.DigibyteNet;
|
||||
import org.qortal.utils.EnumUtils;
|
||||
|
||||
// All properties to be converted to JSON via JAXB
|
||||
@@ -222,6 +223,7 @@ public class Settings {
|
||||
private BitcoinNet bitcoinNet = BitcoinNet.MAIN;
|
||||
private LitecoinNet litecoinNet = LitecoinNet.MAIN;
|
||||
private DogecoinNet dogecoinNet = DogecoinNet.MAIN;
|
||||
private DigibyteNet digibyteNet = DigibyteNet.MAIN;
|
||||
// Also crosschain-related:
|
||||
/** Whether to show SysTray pop-up notifications when trade-bot entries change state */
|
||||
private boolean tradebotSystrayEnabled = false;
|
||||
@@ -680,6 +682,10 @@ public class Settings {
|
||||
return this.dogecoinNet;
|
||||
}
|
||||
|
||||
public DigibyteNet getDigibyteNet() {
|
||||
return this.digibyteNet;
|
||||
}
|
||||
|
||||
public boolean isTradebotSystrayEnabled() {
|
||||
return this.tradebotSystrayEnabled;
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ public class ChatTransaction extends Transaction {
|
||||
public static final int MAX_DATA_SIZE = 256;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY_WITH_QORT = 8; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_NO_QORT = 14; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_NO_QORT = 12; // leading zero bits
|
||||
|
||||
// Constructors
|
||||
|
||||
|
@@ -185,7 +185,7 @@ public class PresenceTransaction extends Transaction {
|
||||
String signerAddress = Crypto.toAddress(this.transactionData.getCreatorPublicKey());
|
||||
|
||||
for (ATData atData : atsData) {
|
||||
ByteArray atCodeHash = new ByteArray(atData.getCodeHash());
|
||||
ByteArray atCodeHash = ByteArray.wrap(atData.getCodeHash());
|
||||
Supplier<ACCT> acctSupplier = acctSuppliersByCodeHash.get(atCodeHash);
|
||||
if (acctSupplier == null)
|
||||
continue;
|
||||
|
@@ -39,11 +39,7 @@ public class RegisterNameTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public long getUnitFee(Long timestamp) {
|
||||
// Use a higher unit fee after the fee increase timestamp
|
||||
if (timestamp > BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp()) {
|
||||
return BlockChain.getInstance().getNameRegistrationUnitFee();
|
||||
}
|
||||
return BlockChain.getInstance().getUnitFee();
|
||||
return BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(timestamp);
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
@@ -118,10 +118,13 @@ public class UpdateNameTransaction extends Transaction {
|
||||
if (!owner.getAddress().equals(nameData.getOwner()))
|
||||
return ValidationResult.INVALID_NAME_OWNER;
|
||||
|
||||
// Check new name isn't already taken, unless it is the same name (this allows for case-adjusting renames)
|
||||
NameData newNameData = this.repository.getNameRepository().fromReducedName(this.updateNameTransactionData.getReducedNewName());
|
||||
if (newNameData != null && !newNameData.getName().equals(nameData.getName()))
|
||||
return ValidationResult.NAME_ALREADY_REGISTERED;
|
||||
// Additional checks if transaction intends to change name
|
||||
if (!this.updateNameTransactionData.getNewName().isEmpty()) {
|
||||
// Check new name isn't already taken, unless it is the same name (this allows for case-adjusting renames)
|
||||
NameData newNameData = this.repository.getNameRepository().fromReducedName(this.updateNameTransactionData.getReducedNewName());
|
||||
if (newNameData != null && !newNameData.getName().equals(nameData.getName()))
|
||||
return ValidationResult.NAME_ALREADY_REGISTERED;
|
||||
}
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
@@ -8,12 +8,16 @@ public class ByteArray implements Comparable<ByteArray> {
|
||||
private int hash;
|
||||
public final byte[] value;
|
||||
|
||||
public ByteArray(byte[] value) {
|
||||
this.value = Objects.requireNonNull(value);
|
||||
private ByteArray(byte[] value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ByteArray of(byte[] value) {
|
||||
return new ByteArray(value);
|
||||
public static ByteArray wrap(byte[] value) {
|
||||
return new ByteArray(Objects.requireNonNull(value));
|
||||
}
|
||||
|
||||
public static ByteArray copyOf(byte[] value) {
|
||||
return new ByteArray(Arrays.copyOf(value, value.length));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,12 +40,7 @@ public class ByteArray implements Comparable<ByteArray> {
|
||||
byte[] val = this.value;
|
||||
|
||||
if (h == 0 && val.length > 0) {
|
||||
h = 1;
|
||||
|
||||
for (int i = 0; i < val.length; ++i)
|
||||
h = 31 * h + val[i];
|
||||
|
||||
this.hash = h;
|
||||
this.hash = h = Arrays.hashCode(val);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
@@ -53,24 +52,7 @@ public class ByteArray implements Comparable<ByteArray> {
|
||||
}
|
||||
|
||||
public int compareToPrimitive(byte[] otherValue) {
|
||||
byte[] val = this.value;
|
||||
|
||||
if (val.length < otherValue.length)
|
||||
return -1;
|
||||
|
||||
if (val.length > otherValue.length)
|
||||
return 1;
|
||||
|
||||
for (int i = 0; i < val.length; ++i) {
|
||||
int a = val[i] & 0xFF;
|
||||
int b = otherValue[i] & 0xFF;
|
||||
if (a < b)
|
||||
return -1;
|
||||
if (a > b)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Arrays.compareUnsigned(this.value, otherValue);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
@@ -18,6 +18,9 @@ import java.util.TreeMap;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
|
||||
import com.ibm.icu.text.CaseMap;
|
||||
import com.ibm.icu.text.Normalizer2;
|
||||
import com.ibm.icu.text.UnicodeSet;
|
||||
import net.codebox.homoglyph.HomoglyphBuilder;
|
||||
|
||||
public abstract class Unicode {
|
||||
@@ -31,6 +34,8 @@ public abstract class Unicode {
|
||||
public static final String ZERO_WIDTH_NO_BREAK_SPACE = "\ufeff";
|
||||
|
||||
public static final CharMatcher ZERO_WIDTH_CHAR_MATCHER = CharMatcher.anyOf(ZERO_WIDTH_SPACE + ZERO_WIDTH_NON_JOINER + ZERO_WIDTH_JOINER + WORD_JOINER + ZERO_WIDTH_NO_BREAK_SPACE);
|
||||
private static final UnicodeSet removableUniset = new UnicodeSet("[[:Mark:][:Other:]]").freeze();
|
||||
|
||||
|
||||
private static int[] homoglyphCodePoints;
|
||||
private static int[] reducedCodePoints;
|
||||
@@ -59,7 +64,7 @@ public abstract class Unicode {
|
||||
public static String normalize(String input) {
|
||||
String output;
|
||||
|
||||
// Normalize
|
||||
// Normalize using NFKC to recompose in canonical form
|
||||
output = Normalizer.normalize(input, Form.NFKC);
|
||||
|
||||
// Remove zero-width code-points, used for rendering
|
||||
@@ -91,8 +96,8 @@ public abstract class Unicode {
|
||||
public static String sanitize(String input) {
|
||||
String output;
|
||||
|
||||
// Normalize
|
||||
output = Normalizer.normalize(input, Form.NFKD);
|
||||
// Normalize using NFKD to decompose into individual combining code points
|
||||
output = Normalizer2.getNFKDInstance().normalize(input);
|
||||
|
||||
// Remove zero-width code-points, used for rendering
|
||||
output = removeZeroWidth(output);
|
||||
@@ -100,11 +105,11 @@ public abstract class Unicode {
|
||||
// Normalize whitespace
|
||||
output = CharMatcher.whitespace().trimAndCollapseFrom(output, ' ');
|
||||
|
||||
// Remove accents, combining marks
|
||||
output = output.replaceAll("[\\p{M}\\p{C}]", "");
|
||||
// Remove accents, combining marks - see https://www.unicode.org/reports/tr44/#GC_Values_Table
|
||||
output = removableUniset.stripFrom(output, true);
|
||||
|
||||
// Convert to lowercase
|
||||
output = output.toLowerCase(Locale.ROOT);
|
||||
output = CaseMap.toLower().apply(Locale.ROOT, output);
|
||||
|
||||
// Reduce homoglyphs
|
||||
output = reduceHomoglyphs(output);
|
||||
|
@@ -4,8 +4,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.001",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFeeTimestamp": 1645372800000,
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"useBrokenMD160ForAddresses": false,
|
||||
"requireGroupForApproval": false,
|
||||
"defaultGroupId": 0,
|
||||
|
@@ -81,4 +81,3 @@ ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
FILE_NOT_FOUND = Datei nicht gefunden
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
|
||||
|
@@ -68,7 +68,7 @@ ORDER_UNKNOWN = unknown asset order ID
|
||||
GROUP_UNKNOWN = group unknown
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blockchain or ElectrumX network issue
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
|
||||
@@ -80,8 +80,4 @@ ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
ORDER_SIZE_TOO_SMALL = order size too small
|
||||
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer didn't reply within the allowed time
|
||||
|
83
src/main/resources/i18n/ApiError_es.properties
Normal file
83
src/main/resources/i18n/ApiError_es.properties
Normal file
@@ -0,0 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "es",
|
||||
|
||||
### Common ###
|
||||
JSON = no se pudo analizar el mensaje JSON
|
||||
|
||||
INSUFFICIENT_BALANCE = saldo insuficiente
|
||||
|
||||
UNAUTHORIZED = Llamada API no autorizada
|
||||
|
||||
REPOSITORY_ISSUE = error de repositorio
|
||||
|
||||
NON_PRODUCTION = esta llamada API no está permitida para sistemas de producción
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = blockchain necesita sincronizarse primero
|
||||
|
||||
NO_TIME_SYNC = aún no hay sincronización de reloj
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = firma no válida
|
||||
|
||||
INVALID_ADDRESS = dirección no válida
|
||||
|
||||
INVALID_PUBLIC_KEY = clave pública no válida
|
||||
|
||||
INVALID_DATA = datos no válidos
|
||||
|
||||
INVALID_NETWORK_ADDRESS = dirección de red no válida
|
||||
|
||||
ADDRESS_UNKNOWN = dirección de cuenta desconocida
|
||||
|
||||
INVALID_CRITERIA = criterio de búsqueda no válido
|
||||
|
||||
INVALID_REFERENCE = referencia no válida
|
||||
|
||||
TRANSFORMATION_ERROR = no se pudo transformar JSON en transacción
|
||||
|
||||
INVALID_PRIVATE_KEY = clave privada no válida
|
||||
|
||||
INVALID_HEIGHT = altura de bloque no válida
|
||||
|
||||
CANNOT_MINT = la cuenta no puede acuñar
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = bloque desconocido
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = transacción desconocida
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = clave pública no encontrada
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = transacción no válida: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = nombre desconocido
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = ID de recurso no válido
|
||||
|
||||
INVALID_ORDER_ID = ID de pedido de activo no válido
|
||||
|
||||
ORDER_UNKNOWN = ID de pedido de activo desconocido
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = grupo desconocido
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = problema de cadena de bloques extranjera o red ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = saldo insuficiente en blockchain extranjera
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = demasiado pronto para transmitir transacciones de blockchain extranjeras (LockTime/mediana de tiempo de bloqueo)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = importe del pedido demasiado bajo
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = archivo no encontrado
|
||||
|
||||
NO_REPLY = el compañero no respondió dentro del tiempo permitido
|
@@ -1,10 +1,7 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# Kielen muuttaminen suomeksi tapahtuu settings.json-tiedostossa
|
||||
#
|
||||
# "localeLang": "fi",
|
||||
# muista pilkku lopussa jos komento ei ole viimeisellä rivillä
|
||||
|
||||
### Common ###
|
||||
JSON = JSON-viestin jaottelu epäonnistui
|
||||
@@ -83,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
NO_REPLY = peer did not reply with data
|
||||
|
@@ -1,24 +1,46 @@
|
||||
### Commun ###
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "fr",
|
||||
|
||||
### Commun ###
|
||||
JSON = échec de l'analyse du message JSON
|
||||
|
||||
INSUFFICIENT_BALANCE = balance insuffisante
|
||||
|
||||
UNAUTHORIZED = appel de l’API non autorisé
|
||||
|
||||
REPOSITORY_ISSUE = erreur de dépôt
|
||||
|
||||
NON_PRODUCTION = cet appel API n'est pas autorisé pour les systèmes en production
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = la blockchain doit d'abord être synchronisée
|
||||
|
||||
NO_TIME_SYNC = heure pas encore synchronisée
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = signature invalide
|
||||
|
||||
INVALID_ADDRESS = adresse invalide
|
||||
|
||||
INVALID_PUBLIC_KEY = clé publique invalide
|
||||
|
||||
INVALID_DATA = données invalides
|
||||
|
||||
INVALID_NETWORK_ADDRESS = adresse réseau invalide
|
||||
|
||||
ADDRESS_UNKNOWN = adresse de compte inconnue
|
||||
|
||||
INVALID_CRITERIA = critère de recherche invalide
|
||||
|
||||
INVALID_REFERENCE = référence invalide
|
||||
|
||||
TRANSFORMATION_ERROR = ne peut pas transformer JSON en transaction
|
||||
|
||||
INVALID_PRIVATE_KEY = clé privée invalide
|
||||
|
||||
INVALID_HEIGHT = hauteur de bloc invalide
|
||||
|
||||
CANNOT_MINT = le compte ne peut pas mint
|
||||
|
||||
### Blocks ###
|
||||
@@ -26,6 +48,7 @@ BLOCK_UNKNOWN = bloc inconnu
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = opération inconnue
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = clé publique introuvable
|
||||
|
||||
# celui-ci est spécial dans le sens où l'appelant doit passer deux chaînes supplémentaires, d'où les deux %s
|
||||
@@ -36,7 +59,9 @@ NAME_UNKNOWN = nom inconnu
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = identifiant d'actif invalide
|
||||
|
||||
INVALID_ORDER_ID = identifiant de commande d'actif non valide
|
||||
|
||||
ORDER_UNKNOWN = identifiant d'ordre d'actif inconnu
|
||||
|
||||
### Groupes ###
|
||||
@@ -44,7 +69,9 @@ GROUP_UNKNOWN = groupe inconnu
|
||||
|
||||
### Blockchain étrangère ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = Problème blokchain étrangère ou de réseau ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = solde insuffisant sur la blockchain étrangère
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = trop tôt pour diffuser la transaction sur la blockchain étrangère (temps de verrouillage/temps de bloc médian)
|
||||
|
||||
### Portail de trading ###
|
||||
@@ -52,4 +79,5 @@ ORDER_SIZE_TOO_SMALL = montant de commande trop bas
|
||||
|
||||
### Données ###
|
||||
FILE_NOT_FOUND = fichier introuvable
|
||||
NO_REPLY = le pair n'a pas renvoyé de données
|
||||
|
||||
NO_REPLY = le pair n'a pas renvoyé de données
|
||||
|
@@ -1,8 +1,5 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# Magyar myelvre forditotta: Szkíta (Scythian). 2021 Augusztus 7.
|
||||
# Az alkalmazás nyelvének magyarra való változtatása a settings.json oldalon történik.
|
||||
# Keys are from api.ApiError enum # Magyar nyelvre forditotta: Szkíta (Scythian). 2021 Augusztus 7.
|
||||
|
||||
# "localeLang": "hu",
|
||||
|
||||
@@ -15,7 +12,7 @@ UNAUTHORIZED = nem engedélyezett API-hívás
|
||||
|
||||
REPOSITORY_ISSUE = adattári hiba
|
||||
|
||||
NON_PRODUCTION = ez az API-hívás nem engedélyezett korlátozott rendszereken
|
||||
NON_PRODUCTION = ez az API-hívás nem engedélyezett éles rendszereken
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = a blokkláncnak még szinkronizálnia kell
|
||||
|
||||
@@ -24,15 +21,15 @@ NO_TIME_SYNC = az óraszinkronizálás még nem történt meg
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = érvénytelen aláírás
|
||||
|
||||
INVALID_ADDRESS = érvénytelen fiók cím
|
||||
INVALID_ADDRESS = érvénytelen fiókcím
|
||||
|
||||
INVALID_PUBLIC_KEY = érvénytelen nyilvános kulcs
|
||||
|
||||
INVALID_DATA = érvénytelen adat
|
||||
|
||||
INVALID_NETWORK_ADDRESS = érvénytelen hálózat cím
|
||||
INVALID_NETWORK_ADDRESS = érvénytelen hálózatcím
|
||||
|
||||
ADDRESS_UNKNOWN = ismeretlen fiók cím
|
||||
ADDRESS_UNKNOWN = ismeretlen fiókcím
|
||||
|
||||
INVALID_CRITERIA = érvénytelen keresési feltétel
|
||||
|
||||
@@ -83,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = rendelési összeg túl alacsony
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = fájl nem található
|
||||
|
||||
NO_REPLY = a másik csomópont nem válaszolt
|
||||
NO_REPLY = a másik csomópont nem válaszolt
|
||||
|
@@ -1,26 +1,22 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
# Italian translation by Pabs 2021
|
||||
# Keys are from api.ApiError enum # Italian translation by Pabs 2021
|
||||
|
||||
# La modifica della lingua dell'UI è fatta nel file Settings.json
|
||||
#
|
||||
# "localeLang": "it",
|
||||
# Si prega ricordare la virgola alla fine, se questo comando non è sull'ultima riga
|
||||
|
||||
### Common ###
|
||||
JSON = Impossibile analizzare il messaggio JSON
|
||||
|
||||
INSUFFICIENT_BALANCE = insufficient balance
|
||||
INSUFFICIENT_BALANCE = bilancio insufficiente
|
||||
|
||||
UNAUTHORIZED = Chiamata API non autorizzata
|
||||
|
||||
REPOSITORY_ISSUE = errore del repositorio
|
||||
REPOSITORY_ISSUE = errore del repository
|
||||
|
||||
NON_PRODUCTION = questa chiamata API non è consentita per i sistemi di produzione
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = blockchain deve prima sincronizzarsi
|
||||
BLOCKCHAIN_NEEDS_SYNC = la blockchain deve sincronizzarsi
|
||||
|
||||
NO_TIME_SYNC = nessuna sincronizzazione dell'orologio ancora
|
||||
NO_TIME_SYNC = nessuna sincronizzazione
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = firma non valida
|
||||
@@ -39,7 +35,7 @@ INVALID_CRITERIA = criteri di ricerca non validi
|
||||
|
||||
INVALID_REFERENCE = riferimento non valido
|
||||
|
||||
TRANSFORMATION_ERROR = non è stato possibile trasformare JSON in transazione
|
||||
TRANSFORMATION_ERROR = non è stato possibile trasformare il JSON
|
||||
|
||||
INVALID_PRIVATE_KEY = chiave privata non valida
|
||||
|
||||
@@ -62,26 +58,26 @@ TRANSACTION_INVALID = transazione non valida: %s (%s)
|
||||
NAME_UNKNOWN = nome sconosciuto
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = identificazione risorsa non valida
|
||||
INVALID_ASSET_ID = risorsa non valida
|
||||
|
||||
INVALID_ORDER_ID = identificazione di ordine di risorsa non valida
|
||||
INVALID_ORDER_ID = ordine di risorsa non valida
|
||||
|
||||
ORDER_UNKNOWN = identificazione di ordine di risorsa sconosciuta
|
||||
ORDER_UNKNOWN = ordine di risorsa sconosciuta
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = gruppo sconosciuto
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = problema nella blockchain esterna o nella rete ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = bilancio insufficiente nella blockchain esterna
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = too soon to broadcast foreign blockchain transaction (LockTime/median block time)
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = troppo presto per distribuire la transazione (sospensione LockTime/median)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
ORDER_SIZE_TOO_SMALL = quantità d'ordine troppo bassa
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
NO_REPLY = il peer non ha fornito dati
|
||||
|
@@ -41,7 +41,7 @@ INVALID_PRIVATE_KEY = ongeldige private key
|
||||
|
||||
INVALID_HEIGHT = ongeldige blokhoogte
|
||||
|
||||
CANNOT_MINT = account kan niet munten
|
||||
CANNOT_MINT = account kan niet minten
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = blok onbekend
|
||||
@@ -68,16 +68,16 @@ ORDER_UNKNOWN = onbekende asset order ID
|
||||
GROUP_UNKNOWN = onbekende groep
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = blockchain of ElectrumX network probleem
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = onvoldoende saldo blockchain
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = too soon to broadcast foreign blockchain transaction (LockTime/median block time)
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = nog niet gereed om de blockchain transactie uittevoeren (LockTime/median block time)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
ORDER_SIZE_TOO_SMALL = order bedrag te laag
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
FILE_NOT_FOUND = file niet gevonden
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
NO_REPLY = peer reageerd niet met data
|
||||
|
@@ -1,83 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "ru",
|
||||
|
||||
### Common ###
|
||||
JSON = не удалось разобрать сообщение json
|
||||
|
||||
INSUFFICIENT_BALANCE = insufficient balance
|
||||
|
||||
UNAUTHORIZED = вызов API не авторизован
|
||||
|
||||
REPOSITORY_ISSUE = ошибка репозитория
|
||||
|
||||
NON_PRODUCTION = этот вызов API не разрешен для производственных систем
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться
|
||||
|
||||
NO_TIME_SYNC = no clock synchronization yet
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = недействительная подпись
|
||||
|
||||
INVALID_ADDRESS = неизвестный адрес
|
||||
|
||||
INVALID_PUBLIC_KEY = недействительный открытый ключ
|
||||
|
||||
INVALID_DATA = неверные данные
|
||||
|
||||
INVALID_NETWORK_ADDRESS = неверный сетевой адрес
|
||||
|
||||
ADDRESS_UNKNOWN = неизвестная учетная запись
|
||||
|
||||
INVALID_CRITERIA = неверные критерии поиска
|
||||
|
||||
INVALID_REFERENCE = неверная ссылка
|
||||
|
||||
TRANSFORMATION_ERROR = не удалось преобразовать JSON в транзакцию
|
||||
|
||||
INVALID_PRIVATE_KEY = неверный приватный ключ
|
||||
|
||||
INVALID_HEIGHT = недопустимая высота блока
|
||||
|
||||
CANNOT_MINT = аккаунт не может чеканить
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = неизвестный блок
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = транзакция неизвестна
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = открытый ключ не найден
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = транзакция недействительна: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = имя неизвестно
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = неверный идентификатор актива
|
||||
|
||||
INVALID_ORDER_ID = неверный идентификатор заказа актива
|
||||
|
||||
ORDER_UNKNOWN = неизвестный идентификатор заказа актива
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = неизвестная группа
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = foreign blokchain or ElectrumX network issue
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = insufficient balance on foreign blockchain
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = too soon to broadcast foreign blockchain transaction (LockTime/median block time)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = order amount too low
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = file not found
|
||||
|
||||
NO_REPLY = peer did not reply with data
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "ru",
|
||||
|
||||
### Common ###
|
||||
JSON = не удалось разобрать сообщение json
|
||||
|
||||
INSUFFICIENT_BALANCE = недостаточный баланс
|
||||
|
||||
UNAUTHORIZED = вызов API не авторизован
|
||||
|
||||
REPOSITORY_ISSUE = ошибка репозитория
|
||||
|
||||
NON_PRODUCTION = этот вызов API не разрешен для производственных систем
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = блокчейн должен сначала синхронизироваться
|
||||
|
||||
NO_TIME_SYNC = пока нет синхронизации часов
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = недействительная подпись
|
||||
|
||||
INVALID_ADDRESS = неизвестный адрес
|
||||
|
||||
INVALID_PUBLIC_KEY = недействительный открытый ключ
|
||||
|
||||
INVALID_DATA = неверные данные
|
||||
|
||||
INVALID_NETWORK_ADDRESS = неверный адрес сети
|
||||
|
||||
ADDRESS_UNKNOWN = неизвестная учетная запись
|
||||
|
||||
INVALID_CRITERIA = неверные критерии поиска
|
||||
|
||||
INVALID_REFERENCE = неверная ссылка
|
||||
|
||||
TRANSFORMATION_ERROR = не удалось преобразовать JSON в транзакцию
|
||||
|
||||
INVALID_PRIVATE_KEY = неверный приватный ключ
|
||||
|
||||
INVALID_HEIGHT = недопустимая высота блока
|
||||
|
||||
CANNOT_MINT = аккаунт не может чеканить
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = неизвестный блок
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = транзакция неизвестна
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = открытый ключ не найден
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = транзакция недействительна: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = имя неизвестно
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = неверный идентификатор актива
|
||||
|
||||
INVALID_ORDER_ID = неверный идентификатор заказа актива
|
||||
|
||||
ORDER_UNKNOWN = неизвестный идентификатор заказа актива
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = неизвестная группа
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = проблема с внешним блокчейном или сетью ElectrumX
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = недостаточный баланс на внешнем блокчейне
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = слишком рано для трансляции транзакции во внений блокчей (время блокировки/среднее время блока)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = слишком маленькая сумма ордера
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = файл не найден
|
||||
|
||||
NO_REPLY = узел не ответил данными
|
||||
|
83
src/main/resources/i18n/ApiError_sv.properties
Normal file
83
src/main/resources/i18n/ApiError_sv.properties
Normal file
@@ -0,0 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "sv",
|
||||
|
||||
### Common ###
|
||||
JSON = misslyckades att tolka JSON meddelande
|
||||
|
||||
INSUFFICIENT_BALANCE = otillräcklig balans
|
||||
|
||||
UNAUTHORIZED = API obehörigt anrop
|
||||
|
||||
REPOSITORY_ISSUE = fel i lagret
|
||||
|
||||
NON_PRODUCTION = detta API-anrop är inte tillåtet för produktionssystem
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = blockchain måste synkroniseras först
|
||||
|
||||
NO_TIME_SYNC = ingen klocksynkronisering ännu
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = ogiltig signatur
|
||||
|
||||
INVALID_ADDRESS = ogiltig adress
|
||||
|
||||
INVALID_PUBLIC_KEY = ogiltig offentlig nyckel
|
||||
|
||||
INVALID_DATA = ogiltig data
|
||||
|
||||
INVALID_NETWORK_ADDRESS = ogiltig nätverksadress
|
||||
|
||||
ADDRESS_UNKNOWN = okänd kontoadress
|
||||
|
||||
INVALID_CRITERIA = ogiltiga sökkriterier
|
||||
|
||||
INVALID_REFERENCE = ogiltig referens
|
||||
|
||||
TRANSFORMATION_ERROR = kunde inte omvandla JSON till en transaktion
|
||||
|
||||
INVALID_PRIVATE_KEY = ogiltig privat nyckel
|
||||
|
||||
INVALID_HEIGHT = ogiltig blockhöjd
|
||||
|
||||
CANNOT_MINT = konto kan inte prägla QORT
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = okänt block
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = okänd transaktion
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = hittade inte en offentlig nyckel
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = ogiltig transaktion: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = okänt namn
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = ogiltigt tillgångs-ID
|
||||
|
||||
INVALID_ORDER_ID = ogiltigt tillgångsbeställnings-ID
|
||||
|
||||
ORDER_UNKNOWN = okänt tillgångsbeställnings-ID
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = okänd grupp
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = utländsk blockchain eller ElectrumX nätverksproblem
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = otillräcklig balans på utländsk blockchain
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = för tidigt för att sända utländsk blockkedjetransaktion (LockTime/medianblocktid)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = beställningssumman för låg
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = filen hittades inte
|
||||
|
||||
NO_REPLY = noden svarade inte inom den tillåtna tiden
|
83
src/main/resources/i18n/ApiError_zh_CN.properties
Normal file
83
src/main/resources/i18n/ApiError_zh_CN.properties
Normal file
@@ -0,0 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "zh_CN",
|
||||
|
||||
### Common ###
|
||||
JSON = 无法解析JSON文件信息
|
||||
|
||||
INSUFFICIENT_BALANCE = 钱包余额不足
|
||||
|
||||
UNAUTHORIZED = 未授权的API指令
|
||||
|
||||
REPOSITORY_ISSUE = 数据库错误
|
||||
|
||||
NON_PRODUCTION = 此API指令已被节点禁止
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = 请先同步区块链
|
||||
|
||||
NO_TIME_SYNC = 同步时间失败
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = 无效的签名
|
||||
|
||||
INVALID_ADDRESS = 无效的钱包地址
|
||||
|
||||
INVALID_PUBLIC_KEY = 无效的公共密钥
|
||||
|
||||
INVALID_DATA = 无效的数据
|
||||
|
||||
INVALID_NETWORK_ADDRESS = 无效的网络地址
|
||||
|
||||
ADDRESS_UNKNOWN = 未知的钱包地址
|
||||
|
||||
INVALID_CRITERIA = 无效的搜寻关键词
|
||||
|
||||
INVALID_REFERENCE = 无效的参考资料
|
||||
|
||||
TRANSFORMATION_ERROR = 未能将JSON文件转换成交易
|
||||
|
||||
INVALID_PRIVATE_KEY = 无效的私人密钥
|
||||
|
||||
INVALID_HEIGHT = 无效的区块链高度
|
||||
|
||||
CANNOT_MINT = 账号不能铸币
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = 未知的区块
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = 未知的交易
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = 找不到有效的公共密钥
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = 无效的交易: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = 未知的名称
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = 无效的资产ID
|
||||
|
||||
INVALID_ORDER_ID = 无效的资产交易ID
|
||||
|
||||
ORDER_UNKNOWN = 未知的资产交易ID
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = 未知的群组
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = 其他区块链网络出现异常
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = 请确保钱包余额足够(包含支付网络手续费)
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = 执行动作太快了 (LockTime/median block time)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = 交易数量太少
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = 档案不存在
|
||||
|
||||
NO_REPLY = 其他节点在指定时间内没有回应
|
83
src/main/resources/i18n/ApiError_zh_TW.properties
Normal file
83
src/main/resources/i18n/ApiError_zh_TW.properties
Normal file
@@ -0,0 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "zh_TW",
|
||||
|
||||
### Common ###
|
||||
JSON = 無法解析JSON文件信息
|
||||
|
||||
INSUFFICIENT_BALANCE = 錢包餘額不足
|
||||
|
||||
UNAUTHORIZED = 未授權的API指令
|
||||
|
||||
REPOSITORY_ISSUE = 數據庫錯誤
|
||||
|
||||
NON_PRODUCTION = 此API指令已被節點禁止
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = 請先同步區塊鏈
|
||||
|
||||
NO_TIME_SYNC = 同步時間失敗
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = 無效的簽名
|
||||
|
||||
INVALID_ADDRESS = 無效的錢包地址
|
||||
|
||||
INVALID_PUBLIC_KEY = 無效的公共密鑰
|
||||
|
||||
INVALID_DATA = 無效的數據
|
||||
|
||||
INVALID_NETWORK_ADDRESS = 無效的網絡地址
|
||||
|
||||
ADDRESS_UNKNOWN = 未知的錢包地址
|
||||
|
||||
INVALID_CRITERIA = 無效的搜尋關鍵詞
|
||||
|
||||
INVALID_REFERENCE = 無效的參考資料
|
||||
|
||||
TRANSFORMATION_ERROR = 未能將JSON文件轉換成交易
|
||||
|
||||
INVALID_PRIVATE_KEY = 無效的私人密鑰
|
||||
|
||||
INVALID_HEIGHT = 無效的區塊鏈高度
|
||||
|
||||
CANNOT_MINT = 賬號不能鑄幣
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = 未知的區塊
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = 未知的交易
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = 找不到有效的公共密鑰
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = 無效的交易: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = 未知的名稱
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = 無效的資產ID
|
||||
|
||||
INVALID_ORDER_ID = 無效的資產交易ID
|
||||
|
||||
ORDER_UNKNOWN = 未知的資產交易ID
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = 未知的群組
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = 其他區塊鏈網絡出現異常
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = 請確保錢包餘額足夠(包含支付網絡手續費)
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = 執行動作太快 (LockTime/median block time)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = 交易數量太少
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = 檔案不存在
|
||||
|
||||
NO_REPLY = 其他節點在指定時間内沒有回應
|
@@ -1,10 +1,10 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
AUTO_UPDATE = Automatisches Update
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Automatisches Update anwenden und neu starten …
|
||||
|
||||
AUTO_UPDATE = Automatisches Update
|
||||
|
||||
BLOCK_HEIGHT = height
|
||||
|
||||
BUILD_VERSION = Build-Version
|
||||
@@ -23,6 +23,8 @@ DB_BACKUP = Datenbank Backup
|
||||
|
||||
DB_CHECKPOINT = Datenbank Kontrollpunkt
|
||||
|
||||
DB_MAINTENANCE = Datenbank Instandhaltung
|
||||
|
||||
EXIT = Verlassen
|
||||
|
||||
MINTING_DISABLED = NOT minting
|
||||
@@ -33,6 +35,8 @@ OPEN_UI = Öffne UI
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Speichern nicht übergebener Datenbank Änderungen …
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Planmäßige Wartung durchführen...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Synchronisiere Uhr
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Synchronisierung
|
||||
|
@@ -1,10 +1,10 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
AUTO_UPDATE = Auto Update
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Applying automatic update and restarting...
|
||||
|
||||
AUTO_UPDATE = Auto Update
|
||||
|
||||
BLOCK_HEIGHT = height
|
||||
|
||||
BUILD_VERSION = Build version
|
||||
@@ -21,10 +21,10 @@ CREATING_BACKUP_OF_DB_FILES = Creating backup of database files...
|
||||
|
||||
DB_BACKUP = Database Backup
|
||||
|
||||
DB_MAINTENANCE = Database Maintenance
|
||||
|
||||
DB_CHECKPOINT = Database Checkpoint
|
||||
|
||||
DB_MAINTENANCE = Database Maintenance
|
||||
|
||||
EXIT = Exit
|
||||
|
||||
MINTING_DISABLED = NOT minting
|
||||
|
44
src/main/resources/i18n/SysTray_es.properties
Normal file
44
src/main/resources/i18n/SysTray_es.properties
Normal file
@@ -0,0 +1,44 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Aplicando actualización automática y reiniciando...
|
||||
|
||||
AUTO_UPDATE = Actualización automática
|
||||
|
||||
BLOCK_HEIGHT = altura
|
||||
|
||||
BUILD_VERSION = Versión de compilación
|
||||
|
||||
CHECK_TIME_ACCURACY = Comprobar la precisión del tiempo
|
||||
|
||||
CONNECTING = Conectando
|
||||
|
||||
CONNECTION = conexión
|
||||
|
||||
CONNECTIONS = conexiones
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Creando una copia de seguridad de los archivos de la base de datos...
|
||||
|
||||
DB_BACKUP = Copia de seguridad de la base de datos
|
||||
|
||||
DB_CHECKPOINT = Punto de control de la base de datos
|
||||
|
||||
DB_MAINTENANCE = Mantenimiento de la base de datos
|
||||
|
||||
EXIT = Salir
|
||||
|
||||
MINTING_DISABLED = Acuñación NO habilitada
|
||||
|
||||
MINTING_ENABLED = \u2714 Acuñación habilitada
|
||||
|
||||
OPEN_UI = IU abierta
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Guardando cambios de base de datos no confirmados...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Realizando mantenimiento programado...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Sincronizar reloj
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Sincronizando
|
||||
|
||||
SYNCHRONIZING_CLOCK = Sincronizando reloj
|
@@ -1,10 +1,10 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
AUTO_UPDATE = Automaattinen päivitys
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Automaattinen päivitys käynnissä, uudelleenkäynnistys seuraa...
|
||||
|
||||
AUTO_UPDATE = Automaattinen päivitys
|
||||
|
||||
BLOCK_HEIGHT = korkeus
|
||||
|
||||
BUILD_VERSION = Versio
|
||||
@@ -21,10 +21,10 @@ CREATING_BACKUP_OF_DB_FILES = Luodaan varmuuskopio tietokannan tiedostoista...
|
||||
|
||||
DB_BACKUP = Tietokannan varmuuskopio
|
||||
|
||||
DB_MAINTENANCE = Database Maintenance
|
||||
|
||||
DB_CHECKPOINT = Tietokannan varmistuspiste
|
||||
|
||||
DB_MAINTENANCE = Tietokannan ylläpito
|
||||
|
||||
EXIT = Pois
|
||||
|
||||
MINTING_DISABLED = EI lyö rahaa
|
||||
@@ -35,7 +35,7 @@ OPEN_UI = Avaa UI
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Tallentaa kommittoidut tietokantamuutokset...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance...
|
||||
PERFORMING_DB_MAINTENANCE = Suoritetaan määräaikaishuoltoa...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Synkronisoi kello
|
||||
|
||||
|
@@ -1,41 +1,44 @@
|
||||
AUTO_UPDATE = Mise à jour automatique
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Application de la mise à jour automatique et redémarrage...
|
||||
|
||||
BLOCK_HEIGHT = hauteur
|
||||
|
||||
BUILD_VERSION = Numéro de version
|
||||
|
||||
CHECK_TIME_ACCURACY = Vérifier l'heure
|
||||
|
||||
CONNECTING = Connexion en cours
|
||||
|
||||
CONNECTION = connexion
|
||||
|
||||
CONNECTIONS = connexions
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Création d'une sauvegarde des fichiers de la base de données...
|
||||
|
||||
DB_BACKUP = Sauvegarde de la base de données
|
||||
|
||||
DB_MAINTENANCE = Maintenance de la base de données
|
||||
|
||||
DB_CHECKPOINT = Point de contrôle de la base de données
|
||||
|
||||
EXIT = Quitter
|
||||
|
||||
MINTING_DISABLED = NE mint PAS
|
||||
|
||||
MINTING_ENABLED = \u2714 Minting
|
||||
|
||||
OPEN_UI = Ouvrir l'interface
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Enregistrement des modifications de base de données non validées...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Entrain d'effectuer la maintenance programmée...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Mettre l'heure à jour
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Synchronisation
|
||||
|
||||
SYNCHRONIZING_CLOCK = Synchronisation de l'heure
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Application de la mise à jour automatique et redémarrage...
|
||||
|
||||
AUTO_UPDATE = Mise à jour automatique
|
||||
|
||||
BLOCK_HEIGHT = hauteur
|
||||
|
||||
BUILD_VERSION = Numéro de version
|
||||
|
||||
CHECK_TIME_ACCURACY = Vérifier l'heure
|
||||
|
||||
CONNECTING = Connexion en cours
|
||||
|
||||
CONNECTION = connexion
|
||||
|
||||
CONNECTIONS = connexions
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Création d'une sauvegarde des fichiers de la base de données...
|
||||
|
||||
DB_BACKUP = Sauvegarde de la base de données
|
||||
|
||||
DB_CHECKPOINT = Point de contrôle de la base de données
|
||||
|
||||
DB_MAINTENANCE = Maintenance de la base de données
|
||||
|
||||
EXIT = Quitter
|
||||
|
||||
MINTING_DISABLED = NE mint PAS
|
||||
|
||||
MINTING_ENABLED = \u2714 Minting
|
||||
|
||||
OPEN_UI = Ouvrir l'interface
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Enregistrement des modifications de base de données non validées...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Entrain d'effectuer la maintenance programmée...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Mettre l'heure à jour
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Synchronisation
|
||||
|
||||
SYNCHRONIZING_CLOCK = Synchronisation de l'heure
|
||||
|
@@ -1,17 +1,15 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
# SysTray pop-up menu # Magyar myelvre forditotta: Szkíta (Scythian). 2021 Augusztus 7.
|
||||
|
||||
# Magyar myelvre forditotta: Szkíta (Scythian). 2021 Augusztus 7.
|
||||
APPLYING_UPDATE_AND_RESTARTING = Automatikus frissítés alkalmazása és újraindítás...
|
||||
|
||||
AUTO_UPDATE = Automatikus Frissítés
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Automatikus frissítés és újraindítás alkalmazása...
|
||||
|
||||
BLOCK_HEIGHT = blokkmagasság
|
||||
|
||||
BUILD_VERSION = Verzió
|
||||
|
||||
CHECK_TIME_ACCURACY = Idő pontosság ellenőrzése
|
||||
CHECK_TIME_ACCURACY = Óra pontosságának ellenőrzése
|
||||
|
||||
CONNECTING = Kapcsolódás
|
||||
|
||||
@@ -23,24 +21,24 @@ CREATING_BACKUP_OF_DB_FILES = Adatbázis fájlok biztonsági mentésének létre
|
||||
|
||||
DB_BACKUP = Adatbázis biztonsági mentése
|
||||
|
||||
DB_MAINTENANCE = Database Maintenance
|
||||
|
||||
DB_CHECKPOINT = Adatbázis-ellenőrzőpont
|
||||
|
||||
DB_MAINTENANCE = Adatbázis karbantartás
|
||||
|
||||
EXIT = Kilépés
|
||||
|
||||
MINTING_DISABLED = QORT-érmeverés jelenleg nincs folyamatban
|
||||
|
||||
MINTING_ENABLED = \u2714 QORT-érmeverés folyamatban
|
||||
|
||||
OPEN_UI = Felhasználói eszköz megnyitása
|
||||
OPEN_UI = Felhasználói felület megnyitása
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Mentetlen adatbázis-módosítások mentése...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance...
|
||||
PERFORMING_DB_MAINTENANCE = Ütemezett karbantartás...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Óra-szinkronizálás megkezdése
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Szinkronizálás
|
||||
|
||||
SYNCHRONIZING_CLOCK = Óra-szinkronizálás folyamatban
|
||||
SYNCHRONIZING_CLOCK = Óraszinkronizálás folyamatban
|
||||
|
@@ -1,8 +1,7 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
# Italian translation by Pabs 2021
|
||||
# SysTray pop-up menu # Italian translation by Pabs 2021
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Applicando aggiornamento automatico e riavviando...
|
||||
APPLYING_UPDATE_AND_RESTARTING = Aggiornamento automatico e riavvio...
|
||||
|
||||
AUTO_UPDATE = Aggiornamento automatico
|
||||
|
||||
@@ -12,34 +11,34 @@ BUILD_VERSION = Versione
|
||||
|
||||
CHECK_TIME_ACCURACY = Controlla la precisione dell'ora
|
||||
|
||||
CONNECTING = Collegando
|
||||
CONNECTING = Collegamento
|
||||
|
||||
CONNECTION = connessione
|
||||
|
||||
CONNECTIONS = connessioni
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Creazione di backup dei file di database...
|
||||
CREATING_BACKUP_OF_DB_FILES = Creazione del backup dei file di database...
|
||||
|
||||
DB_BACKUP = Backup del database
|
||||
|
||||
DB_MAINTENANCE = Database Maintenance
|
||||
DB_CHECKPOINT = Fase di controllo del database
|
||||
|
||||
DB_CHECKPOINT = Punto di controllo del database
|
||||
DB_MAINTENANCE = Manutenzione del database
|
||||
|
||||
EXIT = Uscita
|
||||
|
||||
MINTING_DISABLED = NON coniando
|
||||
MINTING_DISABLED = Conio disabilitato
|
||||
|
||||
MINTING_ENABLED = \u2714 Coniando
|
||||
MINTING_ENABLED = \u2714 Conio abilitato
|
||||
|
||||
OPEN_UI = Apri UI
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Salvataggio delle modifiche al database non salvate...
|
||||
PERFORMING_DB_CHECKPOINT = Salvataggio delle modifiche del database non salvate...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance...
|
||||
PERFORMING_DB_MAINTENANCE = Manutenzione programmata dell'efficienza...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Sincronizza orologio
|
||||
SYNCHRONIZE_CLOCK = Sincronizzare l'orologio
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Sincronizzando
|
||||
SYNCHRONIZING_BLOCKCHAIN = Sincronizzazione della blockchain
|
||||
|
||||
SYNCHRONIZING_CLOCK = Sincronizzando orologio
|
||||
SYNCHRONIZING_CLOCK = Sincronizzazione orologio
|
||||
|
@@ -1,13 +1,13 @@
|
||||
Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Automatische update en herstart worden uitgevoerd...
|
||||
|
||||
AUTO_UPDATE = Automatische Update
|
||||
|
||||
BLOCK_HEIGHT = hoogte
|
||||
BLOCK_HEIGHT = Block hoogte
|
||||
|
||||
BUILD_VERSION = Versie
|
||||
BUILD_VERSION = Versie nummer
|
||||
|
||||
CHECK_TIME_ACCURACY = Controleer accuraatheid van de tijd
|
||||
|
||||
@@ -23,17 +23,19 @@ DB_BACKUP = Database Backup
|
||||
|
||||
DB_CHECKPOINT = Database Controlepunt
|
||||
|
||||
DB_MAINTENANCE = Database Onderhoud
|
||||
|
||||
EXIT = Verlaten
|
||||
|
||||
MINTING_DISABLED = NIET muntend
|
||||
MINTING_DISABLED = Minten is uitgeschakeld
|
||||
|
||||
MINTING_ENABLED = \u2714 Muntend
|
||||
MINTING_ENABLED = \u2714 Minten is ingeschakeld
|
||||
|
||||
OPEN_UI = Open UI
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Nieuwe veranderingen aan database worden opgeslagen...
|
||||
PERFORMING_DB_CHECKPOINT = De database wordt gecontroleerd...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance...
|
||||
PERFORMING_DB_MAINTENANCE = Uitvoeren van gepland onderhoud...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Synchronizeer klok
|
||||
|
||||
|
@@ -1,42 +1,44 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуска...
|
||||
|
||||
AUTO_UPDATE = Автоматическое обновление
|
||||
|
||||
BLOCK_HEIGHT = Высота блока
|
||||
|
||||
BUILD_VERSION = Build version
|
||||
|
||||
CHECK_TIME_ACCURACY = Проверка точного времени
|
||||
|
||||
CONNECTING = Подключение
|
||||
|
||||
CONNECTION = Соединение
|
||||
|
||||
CONNECTIONS = Соединений
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Создание резервной копии файлов базы данных...
|
||||
|
||||
DB_BACKUP = Резервное копирование базы данных
|
||||
|
||||
DB_MAINTENANCE = Database Maintenance
|
||||
|
||||
EXIT = Выход
|
||||
|
||||
MINTING_DISABLED = Чеканка отключена
|
||||
|
||||
MINTING_ENABLED = Чеканка активна
|
||||
|
||||
OPEN_UI = Открыть пользовательский интерфейс
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Saving uncommitted database changes...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Синхронизировать время
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Синхронизация цепи
|
||||
|
||||
SYNCHRONIZING_CLOCK = Проверка времени
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Применение автоматического обновления и перезапуска...
|
||||
|
||||
AUTO_UPDATE = Автоматическое обновление
|
||||
|
||||
BLOCK_HEIGHT = Высота блока
|
||||
|
||||
BUILD_VERSION = Версия сборки
|
||||
|
||||
CHECK_TIME_ACCURACY = Проверка точного времени
|
||||
|
||||
CONNECTING = Подключение
|
||||
|
||||
CONNECTION = Соединение
|
||||
|
||||
CONNECTIONS = Соединения
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Создание резервной копии файлов базы данных...
|
||||
|
||||
DB_BACKUP = Резервное копирование базы данных
|
||||
|
||||
DB_CHECKPOINT = Контрольная точка базы данных
|
||||
|
||||
DB_MAINTENANCE = Обслуживание базы данных
|
||||
|
||||
EXIT = Выход
|
||||
|
||||
MINTING_DISABLED = Чеканка отключена
|
||||
|
||||
MINTING_ENABLED = \u2714 Чеканка активна
|
||||
|
||||
OPEN_UI = Открыть пользовательский интерфейс
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Сохранение незафиксированных изменений базы данных...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Выполнение планового технического обслуживания...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Синхронизировать время
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Синхронизация цепи
|
||||
|
||||
SYNCHRONIZING_CLOCK = Проверка времени
|
||||
|
44
src/main/resources/i18n/SysTray_sv.properties
Normal file
44
src/main/resources/i18n/SysTray_sv.properties
Normal file
@@ -0,0 +1,44 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Tillämpar automatisk uppdatering och startar om...
|
||||
|
||||
AUTO_UPDATE = Automatisk uppdatering
|
||||
|
||||
BLOCK_HEIGHT = höjd
|
||||
|
||||
BUILD_VERSION = Byggversion
|
||||
|
||||
CHECK_TIME_ACCURACY = Kontrollera tidens noggrannhet
|
||||
|
||||
CONNECTING = Ansluter
|
||||
|
||||
CONNECTION = anslutning
|
||||
|
||||
CONNECTIONS = anslutningar
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Skapar säkerhetskopia av databasfiler...
|
||||
|
||||
DB_BACKUP = Databas backup
|
||||
|
||||
DB_CHECKPOINT = Databaskontrollpunkt
|
||||
|
||||
DB_MAINTENANCE = Databasunderhåll
|
||||
|
||||
EXIT = Utgång
|
||||
|
||||
MINTING_DISABLED = Präglar INTE
|
||||
|
||||
MINTING_ENABLED = \u2714 Präglar
|
||||
|
||||
OPEN_UI = Öppna UI
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Sparar oengagerade databasändringar...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Utför schemalagt underhåll...
|
||||
|
||||
SYNCHRONIZE_CLOCK = Synkronisera klockan
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = Synkroniserar
|
||||
|
||||
SYNCHRONIZING_CLOCK = Synkroniserar klockan
|
@@ -1,39 +1,41 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
AUTO_UPDATE = Auto Update
|
||||
APPLYING_UPDATE_AND_RESTARTING = 正在自动更新并重新启动核心...
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Applying automatic update and restarting...
|
||||
AUTO_UPDATE = 自动更新
|
||||
|
||||
BLOCK_HEIGHT = 区块高度
|
||||
|
||||
BUILD_VERSION = Build version
|
||||
BUILD_VERSION = 版本
|
||||
|
||||
CHECK_TIME_ACCURACY = 检查时间准确性
|
||||
|
||||
CONNECTING = Connecting
|
||||
CONNECTING = 连线中
|
||||
|
||||
CONNECTION = 个链接
|
||||
|
||||
CONNECTIONS = 个链接
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Creating backup of database files...
|
||||
CREATING_BACKUP_OF_DB_FILES = 正在创建数据库备份资料...
|
||||
|
||||
DB_BACKUP = Database Backup
|
||||
DB_BACKUP = 数据库备份
|
||||
|
||||
DB_CHECKPOINT = Database Checkpoint
|
||||
DB_CHECKPOINT = 数据库检查点
|
||||
|
||||
DB_MAINTENANCE = 数据库维护
|
||||
|
||||
EXIT = 退出核心
|
||||
|
||||
MINTING_DISABLED = 没有铸币
|
||||
|
||||
MINTING_ENABLED = ✔ 铸币
|
||||
MINTING_ENABLED = \u2714 铸币
|
||||
|
||||
OPEN_UI = 开启Qortal界面
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Saving uncommitted database changes...
|
||||
PERFORMING_DB_CHECKPOINT = 正在保存未提交的数据库修订...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = Performing scheduled maintenance...
|
||||
PERFORMING_DB_MAINTENANCE = 正在执行定期数据库维护...
|
||||
|
||||
SYNCHRONIZE_CLOCK = 同步时钟
|
||||
|
||||
|
@@ -1,40 +1,44 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu
|
||||
|
||||
AUTO_UPDATE = Auto Update
|
||||
APPLYING_UPDATE_AND_RESTARTING = 正在自動更新並重新啓動核心...
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = Applying automatic update and restarting...
|
||||
AUTO_UPDATE = 自動更新
|
||||
|
||||
BLOCK_HEIGHT = 區塊高度
|
||||
|
||||
BUILD_VERSION = Build version
|
||||
BUILD_VERSION = 版本
|
||||
|
||||
CHECK_TIME_ACCURACY = 檢查時間準確性
|
||||
|
||||
CONNECTING = Connecting
|
||||
CONNECTING = 連線中
|
||||
|
||||
CONNECTION = 個鏈接
|
||||
|
||||
CONNECTIONS = 個鏈接
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = Creating backup of database files...
|
||||
CREATING_BACKUP_OF_DB_FILES = 正在創建數據庫備份資料...
|
||||
|
||||
DB_BACKUP = Database Backup
|
||||
DB_BACKUP = 數據庫備份
|
||||
|
||||
DB_CHECKPOINT = Database Checkpoint
|
||||
DB_CHECKPOINT = 數據庫檢查點
|
||||
|
||||
DB_MAINTENANCE = 數據庫維護
|
||||
|
||||
EXIT = 退出核心
|
||||
|
||||
MINTING_DISABLED = 沒有鑄幣
|
||||
|
||||
MINTING_ENABLED = ✔ 鑄幣
|
||||
MINTING_ENABLED = \u2714 鑄幣
|
||||
|
||||
OPEN_UI = 開啓Qortal界面
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = Saving uncommitted database changes...
|
||||
PERFORMING_DB_CHECKPOINT = 正在保存未提交的數據庫修訂...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = 正在執行數據庫定期維護...
|
||||
|
||||
SYNCHRONIZE_CLOCK = 同步時鐘
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = 正在同步區塊鏈
|
||||
|
||||
SYNCHRONIZING_CLOCK = 正在同步時鐘
|
||||
SYNCHRONIZING_CLOCK = 正在同步時鐘
|
||||
|
@@ -1,112 +1,32 @@
|
||||
OK = OK
|
||||
#
|
||||
|
||||
INVALID_ADDRESS = invalid address
|
||||
ACCOUNT_ALREADY_EXISTS = account already exists
|
||||
|
||||
NEGATIVE_AMOUNT = invalid/negative amount
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = account cannot reward-share
|
||||
|
||||
NEGATIVE_FEE = invalid/negative fee
|
||||
ADDRESS_ABOVE_RATE_LIMIT = address reached specified rate limit
|
||||
|
||||
NO_BALANCE = insufficient balance
|
||||
|
||||
INVALID_REFERENCE = invalid reference
|
||||
|
||||
INVALID_NAME_LENGTH = invalid name length
|
||||
|
||||
INVALID_VALUE_LENGTH = invalid 'value' length
|
||||
|
||||
NAME_ALREADY_REGISTERED = name already registered
|
||||
|
||||
NAME_DOES_NOT_EXIST = name does not exist
|
||||
|
||||
INVALID_NAME_OWNER = invalid name owner
|
||||
|
||||
NAME_ALREADY_FOR_SALE = name already for sale
|
||||
|
||||
NAME_NOT_FOR_SALE = name is not for sale
|
||||
|
||||
BUYER_ALREADY_OWNER = buyer is already owner
|
||||
|
||||
INVALID_AMOUNT = invalid amount
|
||||
|
||||
INVALID_SELLER = invalid seller
|
||||
|
||||
NAME_NOT_NORMALIZED = name not in Unicode 'normalized' form
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = invalid description length
|
||||
|
||||
INVALID_OPTIONS_COUNT = invalid options count
|
||||
|
||||
INVALID_OPTION_LENGTH = invalid options length
|
||||
|
||||
DUPLICATE_OPTION = duplicate option
|
||||
|
||||
POLL_ALREADY_EXISTS = poll already exists
|
||||
|
||||
POLL_DOES_NOT_EXIST = poll does not exist
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = poll option does not exist
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = already voted for that option
|
||||
|
||||
INVALID_DATA_LENGTH = invalid data length
|
||||
|
||||
INVALID_QUANTITY = invalid quantity
|
||||
|
||||
ASSET_DOES_NOT_EXIST = asset does not exist
|
||||
|
||||
INVALID_RETURN = invalid return
|
||||
|
||||
HAVE_EQUALS_WANT = have-asset is the same as want-asset
|
||||
|
||||
ORDER_DOES_NOT_EXIST = asset trade order does not exist
|
||||
|
||||
INVALID_ORDER_CREATOR = invalid order creator
|
||||
|
||||
INVALID_PAYMENTS_COUNT = invalid payments count
|
||||
|
||||
NEGATIVE_PRICE = invalid/negative price
|
||||
|
||||
INVALID_CREATION_BYTES = invalid creation bytes
|
||||
|
||||
INVALID_TAGS_LENGTH = invalid 'tags' length
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = invalid AT 'type' length
|
||||
|
||||
INVALID_AT_TRANSACTION = invalid AT transaction
|
||||
|
||||
INSUFFICIENT_FEE = insufficient fee
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = asset does not match AT's asset
|
||||
|
||||
ASSET_ALREADY_EXISTS = asset already exists
|
||||
|
||||
MISSING_CREATOR = missing creator
|
||||
|
||||
TIMESTAMP_TOO_OLD = timestamp too old
|
||||
|
||||
TIMESTAMP_TOO_NEW = timestamp too new
|
||||
|
||||
TOO_MANY_UNCONFIRMED = account has too many unconfirmed transactions pending
|
||||
|
||||
GROUP_ALREADY_EXISTS = group already exists
|
||||
|
||||
GROUP_DOES_NOT_EXIST = group does not exist
|
||||
|
||||
INVALID_GROUP_OWNER = invalid group owner
|
||||
|
||||
ALREADY_GROUP_MEMBER = already group member
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = group owner cannot leave group
|
||||
|
||||
NOT_GROUP_MEMBER = account is not a group member
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
|
||||
ALREADY_GROUP_ADMIN = already group admin
|
||||
|
||||
NOT_GROUP_ADMIN = account is not a group admin
|
||||
ALREADY_GROUP_MEMBER = already group member
|
||||
|
||||
INVALID_LIFETIME = invalid lifetime
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = already voted for that option
|
||||
|
||||
INVITE_UNKNOWN = group invite unknown
|
||||
ASSET_ALREADY_EXISTS = asset already exists
|
||||
|
||||
ASSET_DOES_NOT_EXIST = asset does not exist
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = asset does not match AT's asset
|
||||
|
||||
ASSET_NOT_SPENDABLE = asset is not spendable
|
||||
|
||||
AT_ALREADY_EXISTS = AT already exists
|
||||
|
||||
AT_IS_FINISHED = AT has finished
|
||||
|
||||
AT_UNKNOWN = AT unknown
|
||||
|
||||
BAN_EXISTS = ban already exists
|
||||
|
||||
@@ -114,80 +34,162 @@ BAN_UNKNOWN = ban unknown
|
||||
|
||||
BANNED_FROM_GROUP = banned from group
|
||||
|
||||
JOIN_REQUEST_EXISTS = group join request already exists
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = invalid group-approval threshold
|
||||
|
||||
GROUP_ID_MISMATCH = group ID mismatch
|
||||
|
||||
INVALID_GROUP_ID = invalid group ID
|
||||
|
||||
TRANSACTION_UNKNOWN = transaction unknown
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = transaction has already confirmed
|
||||
|
||||
INVALID_TX_GROUP_ID = invalid transaction group ID
|
||||
|
||||
TX_GROUP_ID_MISMATCH = transaction's group ID does not match
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = multiple registered names per account is forbidden
|
||||
|
||||
INVALID_ASSET_OWNER = invalid asset owner
|
||||
|
||||
AT_IS_FINISHED = AT has finished
|
||||
|
||||
NO_FLAG_PERMISSION = account does not have that permission
|
||||
|
||||
NOT_MINTING_ACCOUNT = account cannot mint
|
||||
|
||||
REWARD_SHARE_UNKNOWN = reward-share unknown
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = invalid reward-share percent
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = public key unknown
|
||||
|
||||
INVALID_PUBLIC_KEY = invalid public key
|
||||
|
||||
AT_UNKNOWN = AT unknown
|
||||
|
||||
AT_ALREADY_EXISTS = AT already exists
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = group-approval not required
|
||||
|
||||
GROUP_APPROVAL_DECIDED = group-approval already decided
|
||||
|
||||
MAXIMUM_REWARD_SHARES = already at maximum number of reward-shares for this account
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = transaction already exists
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = node's blockchain currently busy
|
||||
|
||||
ORDER_ALREADY_CLOSED = asset trade order is already closed
|
||||
BUYER_ALREADY_OWNER = buyer is already owner
|
||||
|
||||
CLOCK_NOT_SYNCED = clock not synchronized
|
||||
|
||||
ASSET_NOT_SPENDABLE = asset is not spendable
|
||||
DUPLICATE_MESSAGE = address sent duplicate message
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = account cannot reward-share
|
||||
DUPLICATE_OPTION = duplicate option
|
||||
|
||||
SELF_SHARE_EXISTS = self-share (reward-share) already exists
|
||||
GROUP_ALREADY_EXISTS = group already exists
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = account already exists
|
||||
GROUP_APPROVAL_DECIDED = group-approval already decided
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = invalid group-approval block delay
|
||||
GROUP_APPROVAL_NOT_REQUIRED = group-approval not required
|
||||
|
||||
GROUP_DOES_NOT_EXIST = group does not exist
|
||||
|
||||
GROUP_ID_MISMATCH = group ID mismatch
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = group owner cannot leave group
|
||||
|
||||
HAVE_EQUALS_WANT = have-asset is the same as want-asset
|
||||
|
||||
INCORRECT_NONCE = incorrect PoW nonce
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = invalid timestamp signature
|
||||
INSUFFICIENT_FEE = insufficient fee
|
||||
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
INVALID_ADDRESS = invalid address
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
INVALID_AMOUNT = invalid amount
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = address reached specified rate limit
|
||||
INVALID_ASSET_OWNER = invalid asset owner
|
||||
|
||||
DUPLICATE_MESSAGE = address sent duplicate message
|
||||
INVALID_AT_TRANSACTION = invalid AT transaction
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = invalid AT 'type' length
|
||||
|
||||
INVALID_BUT_OK = invalid but OK
|
||||
|
||||
INVALID_CREATION_BYTES = invalid creation bytes
|
||||
|
||||
INVALID_DATA_LENGTH = invalid data length
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = invalid description length
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = invalid group-approval threshold
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = invalid group-approval block delay
|
||||
|
||||
INVALID_GROUP_ID = invalid group ID
|
||||
|
||||
INVALID_GROUP_OWNER = invalid group owner
|
||||
|
||||
INVALID_LIFETIME = invalid lifetime
|
||||
|
||||
INVALID_NAME_LENGTH = invalid name length
|
||||
|
||||
INVALID_NAME_OWNER = invalid name owner
|
||||
|
||||
INVALID_OPTION_LENGTH = invalid options length
|
||||
|
||||
INVALID_OPTIONS_COUNT = invalid options count
|
||||
|
||||
INVALID_ORDER_CREATOR = invalid order creator
|
||||
|
||||
INVALID_PAYMENTS_COUNT = invalid payments count
|
||||
|
||||
INVALID_PUBLIC_KEY = invalid public key
|
||||
|
||||
INVALID_QUANTITY = invalid quantity
|
||||
|
||||
INVALID_REFERENCE = invalid reference
|
||||
|
||||
INVALID_RETURN = invalid return
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = invalid reward-share percent
|
||||
|
||||
INVALID_SELLER = invalid seller
|
||||
|
||||
INVALID_TAGS_LENGTH = invalid 'tags' length
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = invalid timestamp signature
|
||||
|
||||
INVALID_TX_GROUP_ID = invalid transaction group ID
|
||||
|
||||
INVALID_VALUE_LENGTH = invalid 'value' length
|
||||
|
||||
INVITE_UNKNOWN = group invite unknown
|
||||
|
||||
JOIN_REQUEST_EXISTS = group join request already exists
|
||||
|
||||
MAXIMUM_REWARD_SHARES = already at maximum number of reward-shares for this account
|
||||
|
||||
MISSING_CREATOR = missing creator
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = multiple registered names per account is forbidden
|
||||
|
||||
NAME_ALREADY_FOR_SALE = name already for sale
|
||||
|
||||
NAME_ALREADY_REGISTERED = name already registered
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
|
||||
NAME_DOES_NOT_EXIST = name does not exist
|
||||
|
||||
NAME_NOT_FOR_SALE = name is not for sale
|
||||
|
||||
NAME_NOT_NORMALIZED = name not in Unicode 'normalized' form
|
||||
|
||||
NEGATIVE_AMOUNT = invalid/negative amount
|
||||
|
||||
NEGATIVE_FEE = invalid/negative fee
|
||||
|
||||
NEGATIVE_PRICE = invalid/negative price
|
||||
|
||||
NO_BALANCE = insufficient balance
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = node's blockchain currently busy
|
||||
|
||||
NO_FLAG_PERMISSION = account does not have that permission
|
||||
|
||||
NOT_GROUP_ADMIN = account is not a group admin
|
||||
|
||||
NOT_GROUP_MEMBER = account is not a group member
|
||||
|
||||
NOT_MINTING_ACCOUNT = account cannot mint
|
||||
|
||||
NOT_YET_RELEASED = feature not yet released
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = asset trade order is already closed
|
||||
|
||||
ORDER_DOES_NOT_EXIST = asset trade order does not exist
|
||||
|
||||
POLL_ALREADY_EXISTS = poll already exists
|
||||
|
||||
POLL_DOES_NOT_EXIST = poll does not exist
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = poll option does not exist
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = public key unknown
|
||||
|
||||
REWARD_SHARE_UNKNOWN = reward-share unknown
|
||||
|
||||
SELF_SHARE_EXISTS = self-share (reward-share) already exists
|
||||
|
||||
TIMESTAMP_TOO_NEW = timestamp too new
|
||||
|
||||
TIMESTAMP_TOO_OLD = timestamp too old
|
||||
|
||||
TOO_MANY_UNCONFIRMED = account has too many unconfirmed transactions pending
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = transaction has already confirmed
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = transaction already exists
|
||||
|
||||
TRANSACTION_UNKNOWN = transaction unknown
|
||||
|
||||
TX_GROUP_ID_MISMATCH = transaction's group ID does not match
|
||||
|
195
src/main/resources/i18n/TransactionValidity_es.properties
Normal file
195
src/main/resources/i18n/TransactionValidity_es.properties
Normal file
@@ -0,0 +1,195 @@
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = la cuenta ya existe
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = la cuenta no puede compartir recompensas
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = la dirección alcanzó el límite de velocidad especificado
|
||||
|
||||
ADDRESS_BLOCKED = esta dirección está bloqueada
|
||||
|
||||
ALREADY_GROUP_ADMIN = ya es administrador del grupo
|
||||
|
||||
ALREADY_GROUP_MEMBER = ya es miembro del grupo
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = ya voté por esa opción
|
||||
|
||||
ASSET_ALREADY_EXISTS = el activo ya existe
|
||||
|
||||
ASSET_DOES_NOT_EXIST = el activo no existe
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = el activo no coincide con el activo de AT
|
||||
|
||||
ASSET_NOT_SPENDABLE = el activo no se puede gastar
|
||||
|
||||
AT_ALREADY_EXISTS = AT ya existe
|
||||
|
||||
AT_IS_FINISHED = AT ha terminado
|
||||
|
||||
AT_UNKNOWN = AT desconocido
|
||||
|
||||
BAN_EXISTS = la prohibición ya existe
|
||||
|
||||
BAN_UNKNOWN = bloqueo desconocido
|
||||
|
||||
BANNED_FROM_GROUP = prohibido del grupo
|
||||
|
||||
BUYER_ALREADY_OWNER = el comprador ya es propietario
|
||||
|
||||
CLOCK_NOT_SYNCED = reloj no sincronizado
|
||||
|
||||
DUPLICATE_MESSAGE = dirección enviada mensaje duplicado
|
||||
|
||||
DUPLICATE_OPTION = opción duplicada
|
||||
|
||||
GROUP_ALREADY_EXISTS = el grupo ya existe
|
||||
|
||||
GROUP_APPROVAL_DECIDED = aprobación del grupo ya decidida
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = no se requiere aprobación de grupo
|
||||
|
||||
GROUP_DOES_NOT_EXIST = el grupo no existe
|
||||
|
||||
GROUP_ID_MISMATCH = discrepancia de ID de grupo
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = el propietario del grupo no puede abandonar el grupo
|
||||
|
||||
HAVE_EQUALS_WANT = tener-activo es lo mismo que querer-activo
|
||||
|
||||
INCORRECT_NONCE = PoW nonce incorrecto
|
||||
|
||||
INSUFFICIENT_FEE = tarifa insuficiente
|
||||
|
||||
INVALID_ADDRESS = dirección no válida
|
||||
|
||||
INVALID_AMOUNT = monto no válido
|
||||
|
||||
INVALID_ASSET_OWNER = propietario de activo no válido
|
||||
|
||||
INVALID_AT_TRANSACTION = transacción AT no válida
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = longitud de 'tipo' de AT no válida
|
||||
|
||||
INVALID_BUT_OK = inválido pero correcto
|
||||
|
||||
INVALID_CREATION_BYTES = bytes de creación no válidos
|
||||
|
||||
INVALID_DATA_LENGTH = longitud de datos no válida
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = longitud de descripción no válida
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = umbral de aprobación de grupo no válido
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = retraso de bloqueo de aprobación de grupo no válido
|
||||
|
||||
INVALID_GROUP_ID = ID de grupo no válido
|
||||
|
||||
INVALID_GROUP_OWNER = propietario del grupo no válido
|
||||
|
||||
INVALID_LIFETIME = tiempo de vida no válido
|
||||
|
||||
INVALID_NAME_LENGTH = longitud de nombre no válida
|
||||
|
||||
INVALID_NAME_OWNER = propietario de nombre no válido
|
||||
|
||||
INVALID_OPTION_LENGTH = longitud de opciones no válida
|
||||
|
||||
INVALID_OPTIONS_COUNT = recuento de opciones no válidas
|
||||
|
||||
INVALID_ORDER_CREATOR = creador de pedido no válido
|
||||
|
||||
INVALID_PAYMENTS_COUNT = recuento de pagos no válidos
|
||||
|
||||
INVALID_PUBLIC_KEY = clave pública no válida
|
||||
|
||||
INVALID_QUANTITY = cantidad no válida
|
||||
|
||||
INVALID_REFERENCE = referencia no válida
|
||||
|
||||
INVALID_RETURN = devolución no válida
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = porcentaje de recompensa compartida no válido
|
||||
|
||||
INVALID_SELLER = vendedor no válido
|
||||
|
||||
INVALID_TAGS_LENGTH = longitud de 'etiquetas' no válida
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = firma de marca de tiempo no válida
|
||||
|
||||
INVALID_TX_GROUP_ID = ID de grupo de transacciones no válido
|
||||
|
||||
INVALID_VALUE_LENGTH = longitud de 'valor' no válida
|
||||
|
||||
INVITE_UNKNOWN = invitación de grupo desconocida
|
||||
|
||||
JOIN_REQUEST_EXISTS = la solicitud para unirse al grupo ya existe
|
||||
|
||||
MAXIMUM_REWARD_SHARES = ya en el número máximo de acciones de recompensa para esta cuenta
|
||||
|
||||
MISSING_CREATOR = creador faltante
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = múltiples nombres registrados por cuenta están prohibidos
|
||||
|
||||
NAME_ALREADY_FOR_SALE = nombre ya a la venta
|
||||
|
||||
NAME_ALREADY_REGISTERED = nombre ya registrado
|
||||
|
||||
NAME_BLOCKED = este nombre está bloqueado
|
||||
|
||||
NAME_DOES_NOT_EXIST = el nombre no existe
|
||||
|
||||
NAME_NOT_FOR_SALE = el nombre no está a la venta
|
||||
|
||||
NAME_NOT_NORMALIZED = nombre no en formato Unicode 'normalizado'
|
||||
|
||||
NEGATIVE_AMOUNT = importe no válido/negativo
|
||||
|
||||
NEGATIVE_FEE = tarifa no válida/negativa
|
||||
|
||||
NEGATIVE_PRICE = precio no válido/negativo
|
||||
|
||||
NO_BALANCE = saldo insuficiente
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = la cadena de bloques del nodo está actualmente ocupada
|
||||
|
||||
NO_FLAG_PERMISSION = la cuenta no tiene ese permiso
|
||||
|
||||
NOT_GROUP_ADMIN = la cuenta no es un administrador de grupo
|
||||
|
||||
NOT_GROUP_MEMBER = la cuenta no es miembro del grupo
|
||||
|
||||
NOT_MINTING_ACCOUNT = la cuenta no puede acuñar
|
||||
|
||||
NOT_YET_RELEASED = función aún no lanzada
|
||||
|
||||
OK = Aceptar
|
||||
|
||||
ORDER_ALREADY_CLOSED = la orden comercial de activos ya está cerrada
|
||||
|
||||
ORDER_DOES_NOT_EXIST = la orden comercial de activos no existe
|
||||
|
||||
POLL_ALREADY_EXISTS = la encuesta ya existe
|
||||
|
||||
POLL_DOES_NOT_EXIST = la encuesta no existe
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = la opción de encuesta no existe
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = clave pública desconocida
|
||||
|
||||
REWARD_SHARE_UNKNOWN = Recompensa compartida desconocida
|
||||
|
||||
SELF_SHARE_EXISTS = autocompartir (recompensa compartida) ya existe
|
||||
|
||||
TIMESTAMP_TOO_NEW = marca de tiempo demasiado nueva
|
||||
|
||||
TIMESTAMP_TOO_OLD = marca de tiempo demasiado antigua
|
||||
|
||||
TOO_MANY_UNCONFIRMED = la cuenta tiene demasiadas transacciones pendientes sin confirmar
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = la transacción ya ha sido confirmada
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = la transacción ya existe
|
||||
|
||||
TRANSACTION_UNKNOWN = transacción desconocida
|
||||
|
||||
TX_GROUP_ID_MISMATCH = el ID de grupo de la transacción no coincide
|
@@ -1,7 +1,13 @@
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = tili on jo olemassa
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = tili ei voi palkinto-jakaa
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = address reached specified rate limit
|
||||
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
|
||||
ALREADY_GROUP_ADMIN = on jo ryhmän admin
|
||||
|
||||
ALREADY_GROUP_MEMBER = on jo ryhmän jäsen
|
||||
@@ -10,7 +16,7 @@ ALREADY_VOTED_FOR_THAT_OPTION = on jo äänestänyt vaihtoehtoa
|
||||
|
||||
ASSET_ALREADY_EXISTS = resurssi on jo olemassa
|
||||
|
||||
ASSET_DOES_NOT_EXIST = resurssia ei ole olemassa
|
||||
ASSET_DOES_NOT_EXIST = resurssia ei ole olemassa
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = resurssi ei vastaa AT:n resurssia
|
||||
|
||||
@@ -22,16 +28,18 @@ AT_IS_FINISHED = AT on päättynyt
|
||||
|
||||
AT_UNKNOWN = AT on tuntematon
|
||||
|
||||
BANNED_FROM_GROUP = on evätty ryhmän jäsenyydestä
|
||||
|
||||
BAN_EXISTS = eväys on jo olemassa
|
||||
|
||||
BAN_UNKNOWN = tuntematon eväys
|
||||
|
||||
BANNED_FROM_GROUP = on evätty ryhmän jäsenyydestä
|
||||
|
||||
BUYER_ALREADY_OWNER = ostaja on jo omistaja
|
||||
|
||||
CLOCK_NOT_SYNCED = kello on synkronisoimatta
|
||||
|
||||
DUPLICATE_MESSAGE = address sent duplicate message
|
||||
|
||||
DUPLICATE_OPTION = kahdennettu valinta
|
||||
|
||||
GROUP_ALREADY_EXISTS = ryhmä on jo olemassa
|
||||
@@ -62,6 +70,8 @@ INVALID_AT_TRANSACTION = kelvoton AT-transaktio
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = kelvoton AT 'tyypin' pituus
|
||||
|
||||
INVALID_BUT_OK = Invalid but OK
|
||||
|
||||
INVALID_CREATION_BYTES = kelvoton luodun tavumäärä
|
||||
|
||||
INVALID_DATA_LENGTH = kelvoton datan pituus
|
||||
@@ -82,10 +92,10 @@ INVALID_NAME_LENGTH = kelvoton nimen pituus
|
||||
|
||||
INVALID_NAME_OWNER = kelvoton nimen omistaja
|
||||
|
||||
INVALID_OPTIONS_COUNT = kelvoton valintojen lkm
|
||||
|
||||
INVALID_OPTION_LENGTH = kelvoton valintojen pituus
|
||||
|
||||
INVALID_OPTIONS_COUNT = kelvoton valintojen lkm
|
||||
|
||||
INVALID_ORDER_CREATOR = kelvoton tilauksen luoja
|
||||
|
||||
INVALID_PAYMENTS_COUNT = kelvoton maksujen lkm
|
||||
@@ -104,6 +114,8 @@ INVALID_SELLER = kelvoton myyjä
|
||||
|
||||
INVALID_TAGS_LENGTH = kelvoton 'tagin' pituus
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = Invalid timestamp signature
|
||||
|
||||
INVALID_TX_GROUP_ID = kelvoton transaktion ryhmä-ID
|
||||
|
||||
INVALID_VALUE_LENGTH = kelvoton 'arvon' pituus
|
||||
@@ -122,6 +134,8 @@ NAME_ALREADY_FOR_SALE = nimi on jo myynnissä
|
||||
|
||||
NAME_ALREADY_REGISTERED = nimi on jo rekisteröity
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
|
||||
NAME_DOES_NOT_EXIST = nimeä ei ole
|
||||
|
||||
NAME_NOT_FOR_SALE = nimi ei ole kaupan
|
||||
@@ -134,6 +148,12 @@ NEGATIVE_FEE = kelvoton/negatiivinen kulu
|
||||
|
||||
NEGATIVE_PRICE = kelvoton/negatiivinen hinta
|
||||
|
||||
NO_BALANCE = riittämätön saldo
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = solmun lohkoketju on juuri nyt varattuna
|
||||
|
||||
NO_FLAG_PERMISSION = tilillä ei ole lupaa tuohon
|
||||
|
||||
NOT_GROUP_ADMIN = tili ei ole ryhmän admin
|
||||
|
||||
NOT_GROUP_MEMBER = tili ei ole ryhmän jäsen
|
||||
@@ -142,12 +162,6 @@ NOT_MINTING_ACCOUNT = tili ei voi lyödä rahaa
|
||||
|
||||
NOT_YET_RELEASED = ominaisuutta ei ole vielä julkistettu
|
||||
|
||||
NO_BALANCE = riittämätön saldo
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = solmun lohkoketju on juuri nyt varattuna
|
||||
|
||||
NO_FLAG_PERMISSION = tilillä ei ole lupaa tuohon
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = resurssin määräys kauppaan on jo suljettu
|
||||
@@ -179,15 +193,3 @@ TRANSACTION_ALREADY_EXISTS = transaktio on jo olemassa
|
||||
TRANSACTION_UNKNOWN = tuntematon transaktio
|
||||
|
||||
TX_GROUP_ID_MISMATCH = transaktion ryhmä-ID:n vastaavuusvirhe
|
||||
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = address reached specified rate limit
|
||||
|
||||
DUPLICATE_MESSAGE = address sent duplicate message
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = Invalid timestamp signature
|
||||
|
||||
INVALID_BUT_OK = Invalid but OK
|
||||
|
@@ -1,151 +1,195 @@
|
||||
OK = OK
|
||||
|
||||
INVALID_ADDRESS = adresse invalide
|
||||
|
||||
NEGATIVE_AMOUNT = montant invalide/négatif
|
||||
|
||||
NEGATIVE_FEE = frais invalides/négatifs
|
||||
|
||||
NO_BALANCE = solde insuffisant
|
||||
|
||||
INVALID_REFERENCE = référence invalide
|
||||
|
||||
INVALID_NAME_LENGTH = longueur de nom invalide
|
||||
|
||||
INVALID_VALUE_LENGTH = longueur de 'valeur' invalide
|
||||
|
||||
NAME_ALREADY_REGISTERED = le nom est déjà enregistré
|
||||
|
||||
NAME_DOES_NOT_EXIST = le nom n'existe pas
|
||||
|
||||
INVALID_NAME_OWNER = le nom du propriétaire est invalide
|
||||
|
||||
NAME_ALREADY_FOR_SALE = le nom est déjà en vente
|
||||
|
||||
NAME_NOT_FOR_SALE = le nom n'est pas à vendre
|
||||
|
||||
BUYER_ALREADY_OWNER = l'acheteur est déjà le propriétaire
|
||||
|
||||
INVALID_AMOUNT = montant invalide
|
||||
|
||||
INVALID_SELLER = vendeur invalide
|
||||
|
||||
NAME_NOT_NORMALIZED = le nom n'est pas sous la forme 'normalisée' Unicode
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = longueur de description invalide
|
||||
|
||||
INVALID_OPTIONS_COUNT = nombre d'options invalides
|
||||
|
||||
INVALID_OPTION_LENGTH = longueur des options invalide
|
||||
|
||||
DUPLICATE_OPTION = option dupliquée
|
||||
|
||||
POLL_ALREADY_EXISTS = le scrutin existe déjà
|
||||
|
||||
POLL_DOES_NOT_EXIST = le scrutin n'existe pas
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = Ce choix de scrutin n'existe pas
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = Vous avez déjà voté pour ce choix
|
||||
|
||||
INVALID_DATA_LENGTH = longueur de données invalide
|
||||
|
||||
INVALID_QUANTITY = quantité invalide
|
||||
|
||||
ASSET_DOES_NOT_EXIST = l'actif n'existe pas
|
||||
|
||||
INVALID_RETURN = retour invalide
|
||||
|
||||
HAVE_EQUALS_WANT = l'actif désiré est le même que l'actif possédé
|
||||
|
||||
ORDER_DOES_NOT_EXIST = l'ordre d'échange d'actifs n'existe pas
|
||||
|
||||
INVALID_ORDER_CREATOR = créateur d'ordre invalide
|
||||
|
||||
INVALID_PAYMENTS_COUNT = nombre de paiements invalides
|
||||
|
||||
NEGATIVE_PRICE = prix invalide/négatif
|
||||
|
||||
INVALID_CREATION_BYTES = octets de création invalides
|
||||
|
||||
INVALID_TAGS_LENGTH = longueur de 'tags' invalide
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = longueur 'type' AT invalide
|
||||
|
||||
INVALID_AT_TRANSACTION = transaction AT invalide
|
||||
|
||||
INSUFFICIENT_FEE = frais insuffisant
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = l'actif ne correspond pas à l'actif d'AT
|
||||
ASSET_ALREADY_EXISTS = l'actif existe déjà
|
||||
MISSING_CREATOR = créateur manquant
|
||||
TIMESTAMP_TOO_OLD = horodatage trop ancien
|
||||
TIMESTAMP_TOO_NEW = horodatage trop récent
|
||||
TOO_MANY_UNCONFIRMED = le compte a trop de transactions non confirmées en attente
|
||||
GROUP_ALREADY_EXISTS = le groupe existe déjà
|
||||
GROUP_DOES_NOT_EXIST = le groupe n'existe pas
|
||||
INVALID_GROUP_OWNER = propriétaire de groupe invalide
|
||||
ALREADY_GROUP_MEMBER = vous êtes déjà un(e) membre du groupe
|
||||
GROUP_OWNER_CANNOT_LEAVE = le propriétaire du groupe ne peut pas quitter le groupe
|
||||
NOT_GROUP_MEMBER = le compte n'est pas membre du groupe
|
||||
ALREADY_GROUP_ADMIN = vous êtes déjà l'administrateur(trice) du groupe
|
||||
NOT_GROUP_ADMIN = le compte n'est pas un administrateur du groupe
|
||||
INVALID_LIFETIME = durée de vie invalide
|
||||
INVITE_UNKNOWN = invitation de groupe inconnue
|
||||
BAN_EXISTS = déjà banni
|
||||
BAN_UNKNOWN = bannissement inconnu
|
||||
BANNED_FROM_GROUP = banned from group
|
||||
JOIN_REQUEST_EXISTS = la demande d'adhésion au groupe existe déjà
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = seuil d'approbation de groupe non valide
|
||||
GROUP_ID_MISMATCH = identifiant de groupe non-concorde
|
||||
INVALID_GROUP_ID = identifiant de groupe invalide
|
||||
TRANSACTION_UNKNOWN = transaction inconnue
|
||||
TRANSACTION_ALREADY_CONFIRMED = la transaction a déjà été confirmée
|
||||
INVALID_TX_GROUP_ID = identifiant du groupe de transactions invalide
|
||||
TX_GROUP_ID_MISMATCH = l'identifiant du groupe de transaction ne correspond pas
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = l'enregistrement de plusieurs noms par compte est interdit
|
||||
|
||||
INVALID_ASSET_OWNER = propriétaire de l'actif invalide
|
||||
|
||||
AT_IS_FINISHED = l'AT est fini
|
||||
|
||||
NO_FLAG_PERMISSION = le compte n'a pas cette autorisation
|
||||
|
||||
NOT_MINTING_ACCOUNT = le compte ne peut pas mint
|
||||
|
||||
REWARD_SHARE_UNKNOWN = partage de récompense inconnu
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = pourcentage du partage de récompense invalide
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = clé publique inconnue
|
||||
|
||||
INVALID_PUBLIC_KEY = clé publique invalide
|
||||
|
||||
AT_UNKNOWN = AT inconnu
|
||||
|
||||
AT_ALREADY_EXISTS = AT déjà existante
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = approbation de groupe non requise
|
||||
|
||||
GROUP_APPROVAL_DECIDED = approbation de groupe déjà décidée
|
||||
|
||||
MAXIMUM_REWARD_SHARES = déjà au nombre maximum de récompense pour ce compte
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = la transaction existe déjà
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = nœud de la blockchain actuellement occupé
|
||||
ORDER_ALREADY_CLOSED = l'ordre d'échange d'actifs est déjà fermé
|
||||
CLOCK_NOT_SYNCED = horloge non synchronisée
|
||||
ASSET_NOT_SPENDABLE = l'actif n'est pas dépensable
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = le compte ne peut pas récompenser
|
||||
SELF_SHARE_EXISTS = l'auto-partage (récompense) existe déjà
|
||||
ACCOUNT_ALREADY_EXISTS = Le compte existe déjà
|
||||
INVALID_GROUP_BLOCK_DELAY = délai de blocage d'approbation de groupe invalide
|
||||
INCORRECT_NONCE = PoW nonce incorrect
|
||||
INVALID_TIMESTAMP_SIGNATURE = signature d'horodatage invalide
|
||||
ADDRESS_IN_BLACKLIST = cette adresse est dans votre liste noire
|
||||
ADDRESS_ABOVE_RATE_LIMIT = l'adresse a atteint la limite de débit spécifiée
|
||||
DUPLICATE_MESSAGE = l'adresse a envoyé un message en double
|
||||
INVALID_BUT_OK = invalide mais OK
|
||||
NOT_YET_RELEASED = fonctionnalité pas encore publiée
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = Le compte existe déjà
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = le compte ne peut pas récompenser
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = l'adresse a atteint la limite de débit spécifiée
|
||||
|
||||
ADDRESS_BLOCKED = cette adresse est bloquée
|
||||
|
||||
ALREADY_GROUP_ADMIN = vous êtes déjà l'administrateur(trice) du groupe
|
||||
|
||||
ALREADY_GROUP_MEMBER = vous êtes déjà un(e) membre du groupe
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = Vous avez déjà voté pour ce choix
|
||||
|
||||
ASSET_ALREADY_EXISTS = l'actif existe déjà
|
||||
|
||||
ASSET_DOES_NOT_EXIST = l'actif n'existe pas
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = l'actif ne correspond pas à l'actif d'AT
|
||||
|
||||
ASSET_NOT_SPENDABLE = l'actif n'est pas dépensable
|
||||
|
||||
AT_ALREADY_EXISTS = AT déjà existante
|
||||
|
||||
AT_IS_FINISHED = l'AT est fini
|
||||
|
||||
AT_UNKNOWN = AT inconnu
|
||||
|
||||
BAN_EXISTS = déjà banni
|
||||
|
||||
BAN_UNKNOWN = bannissement inconnu
|
||||
|
||||
BANNED_FROM_GROUP = banned from group
|
||||
|
||||
BUYER_ALREADY_OWNER = l'acheteur est déjà le propriétaire
|
||||
|
||||
CLOCK_NOT_SYNCED = horloge non synchronisée
|
||||
|
||||
DUPLICATE_MESSAGE = l'adresse a envoyé un message en double
|
||||
|
||||
DUPLICATE_OPTION = option dupliquée
|
||||
|
||||
GROUP_ALREADY_EXISTS = le groupe existe déjà
|
||||
|
||||
GROUP_APPROVAL_DECIDED = approbation de groupe déjà décidée
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = approbation de groupe non requise
|
||||
|
||||
GROUP_DOES_NOT_EXIST = le groupe n'existe pas
|
||||
|
||||
GROUP_ID_MISMATCH = identifiant de groupe non-concorde
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = le propriétaire du groupe ne peut pas quitter le groupe
|
||||
|
||||
HAVE_EQUALS_WANT = l'actif désiré est le même que l'actif possédé
|
||||
|
||||
INCORRECT_NONCE = PoW nonce incorrect
|
||||
|
||||
INSUFFICIENT_FEE = frais insuffisant
|
||||
|
||||
INVALID_ADDRESS = adresse invalide
|
||||
|
||||
INVALID_AMOUNT = montant invalide
|
||||
|
||||
INVALID_ASSET_OWNER = propriétaire de l'actif invalide
|
||||
|
||||
INVALID_AT_TRANSACTION = transaction AT invalide
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = longueur 'type' AT invalide
|
||||
|
||||
INVALID_BUT_OK = invalide mais OK
|
||||
|
||||
INVALID_CREATION_BYTES = octets de création invalides
|
||||
|
||||
INVALID_DATA_LENGTH = longueur de données invalide
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = longueur de description invalide
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = seuil d'approbation de groupe non valide
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = délai de blocage d'approbation de groupe invalide
|
||||
|
||||
INVALID_GROUP_ID = identifiant de groupe invalide
|
||||
|
||||
INVALID_GROUP_OWNER = propriétaire de groupe invalide
|
||||
|
||||
INVALID_LIFETIME = durée de vie invalide
|
||||
|
||||
INVALID_NAME_LENGTH = longueur de nom invalide
|
||||
|
||||
INVALID_NAME_OWNER = le nom du propriétaire est invalide
|
||||
|
||||
INVALID_OPTION_LENGTH = longueur des options invalide
|
||||
|
||||
INVALID_OPTIONS_COUNT = nombre d'options invalides
|
||||
|
||||
INVALID_ORDER_CREATOR = créateur d'ordre invalide
|
||||
|
||||
INVALID_PAYMENTS_COUNT = nombre de paiements invalides
|
||||
|
||||
INVALID_PUBLIC_KEY = clé publique invalide
|
||||
|
||||
INVALID_QUANTITY = quantité invalide
|
||||
|
||||
INVALID_REFERENCE = référence invalide
|
||||
|
||||
INVALID_RETURN = retour invalide
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = pourcentage du partage de récompense invalide
|
||||
|
||||
INVALID_SELLER = vendeur invalide
|
||||
|
||||
INVALID_TAGS_LENGTH = longueur de 'tags' invalide
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = signature d'horodatage invalide
|
||||
|
||||
INVALID_TX_GROUP_ID = identifiant du groupe de transactions invalide
|
||||
|
||||
INVALID_VALUE_LENGTH = longueur de 'valeur' invalide
|
||||
|
||||
INVITE_UNKNOWN = invitation de groupe inconnue
|
||||
|
||||
JOIN_REQUEST_EXISTS = la demande d'adhésion au groupe existe déjà
|
||||
|
||||
MAXIMUM_REWARD_SHARES = déjà au nombre maximum de récompense pour ce compte
|
||||
|
||||
MISSING_CREATOR = créateur manquant
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = l'enregistrement de plusieurs noms par compte est interdit
|
||||
|
||||
NAME_ALREADY_FOR_SALE = le nom est déjà en vente
|
||||
|
||||
NAME_ALREADY_REGISTERED = le nom est déjà enregistré
|
||||
|
||||
NAME_BLOCKED = ce nom est bloqué
|
||||
|
||||
NAME_DOES_NOT_EXIST = le nom n'existe pas
|
||||
|
||||
NAME_NOT_FOR_SALE = le nom n'est pas à vendre
|
||||
|
||||
NAME_NOT_NORMALIZED = le nom n'est pas sous la forme 'normalisée' Unicode
|
||||
|
||||
NEGATIVE_AMOUNT = montant invalide/négatif
|
||||
|
||||
NEGATIVE_FEE = frais invalides/négatifs
|
||||
|
||||
NEGATIVE_PRICE = prix invalide/négatif
|
||||
|
||||
NO_BALANCE = solde insuffisant
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = nœud de la blockchain actuellement occupé
|
||||
|
||||
NO_FLAG_PERMISSION = le compte n'a pas cette autorisation
|
||||
|
||||
NOT_GROUP_ADMIN = le compte n'est pas un administrateur du groupe
|
||||
|
||||
NOT_GROUP_MEMBER = le compte n'est pas membre du groupe
|
||||
|
||||
NOT_MINTING_ACCOUNT = le compte ne peut pas mint
|
||||
|
||||
NOT_YET_RELEASED = fonctionnalité pas encore publiée
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = l'ordre d'échange d'actifs est déjà fermé
|
||||
|
||||
ORDER_DOES_NOT_EXIST = l'ordre d'échange d'actifs n'existe pas
|
||||
|
||||
POLL_ALREADY_EXISTS = le scrutin existe déjà
|
||||
|
||||
POLL_DOES_NOT_EXIST = le scrutin n'existe pas
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = Ce choix de scrutin n'existe pas
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = clé publique inconnue
|
||||
|
||||
REWARD_SHARE_UNKNOWN = partage de récompense inconnu
|
||||
|
||||
SELF_SHARE_EXISTS = l'auto-partage (récompense) existe déjà
|
||||
|
||||
TIMESTAMP_TOO_NEW = horodatage trop récent
|
||||
|
||||
TIMESTAMP_TOO_OLD = horodatage trop ancien
|
||||
|
||||
TOO_MANY_UNCONFIRMED = le compte a trop de transactions non confirmées en attente
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = la transaction a déjà été confirmée
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = la transaction existe déjà
|
||||
|
||||
TRANSACTION_UNKNOWN = transaction inconnue
|
||||
|
||||
TX_GROUP_ID_MISMATCH = l'identifiant du groupe de transaction ne correspond pas
|
||||
|
@@ -1,114 +1,32 @@
|
||||
# Magyar myelvre forditotta: Szkíta (Scythian). 2021 Augusztus 7.
|
||||
|
||||
OK = OK
|
||||
ACCOUNT_ALREADY_EXISTS = ez a fiók már létezik
|
||||
|
||||
INVALID_ADDRESS = érvénytelen név vagy cím
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = ez a fiók nem vehet részt jutalék-megosztásban
|
||||
|
||||
NEGATIVE_AMOUNT = negatív összeg
|
||||
ADDRESS_ABOVE_RATE_LIMIT = ez a cím elérte a megengedett mérték korlátot
|
||||
|
||||
NEGATIVE_FEE = érvénytelen/negatív tranzakciós díj
|
||||
|
||||
NO_BALANCE = elégtelen egyenleg
|
||||
|
||||
INVALID_REFERENCE = érvénytelen hivatkozás
|
||||
|
||||
INVALID_NAME_LENGTH = érvénytelen névhossz
|
||||
|
||||
INVALID_VALUE_LENGTH = érvénytelen értékhossz
|
||||
|
||||
NAME_ALREADY_REGISTERED = ez a név már regisztrált
|
||||
|
||||
NAME_DOES_NOT_EXIST = ez a név nem létezik
|
||||
|
||||
INVALID_NAME_OWNER = érvénytelen név tulajdonos
|
||||
|
||||
NAME_ALREADY_FOR_SALE = ez a név már eladó
|
||||
|
||||
NAME_NOT_FOR_SALE = ez a név nem eladó
|
||||
|
||||
BUYER_ALREADY_OWNER = ez a vevő már a tulajdonos
|
||||
|
||||
INVALID_AMOUNT = érvénytelen összeg
|
||||
|
||||
INVALID_SELLER = érvénytelen eladó
|
||||
|
||||
NAME_NOT_NORMALIZED = ez a név nincs "normalizált" Unicode formátumban
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = érvénytelen leíráshossz
|
||||
|
||||
INVALID_OPTIONS_COUNT = invalid options count
|
||||
|
||||
INVALID_OPTION_LENGTH = érvénytelen opciókszám
|
||||
|
||||
DUPLICATE_OPTION = ez a lehetőség már létezik
|
||||
|
||||
POLL_ALREADY_EXISTS = ez a szavazás már létezik
|
||||
|
||||
POLL_DOES_NOT_EXIST = ez a szavazás nem létezik
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = ez a szavazási lehetőség nem létezik
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = erre a lehetőségre már szavaztál
|
||||
|
||||
INVALID_DATA_LENGTH = érvénytelen adathossz
|
||||
|
||||
INVALID_QUANTITY = érvénytelen mennyiség
|
||||
|
||||
ASSET_DOES_NOT_EXIST = tőke nem létezik
|
||||
|
||||
INVALID_RETURN = érvénytelen csere tőke
|
||||
|
||||
HAVE_EQUALS_WANT = saját tőke egyenlő a csere tőkével
|
||||
|
||||
ORDER_DOES_NOT_EXIST = tőke rendelés nem létezik
|
||||
|
||||
INVALID_ORDER_CREATOR = érvénytelen rendelés létrehozó
|
||||
|
||||
INVALID_PAYMENTS_COUNT = a kifizetések száma érvénytelen
|
||||
|
||||
NEGATIVE_PRICE = érvénytelen/negatív ár
|
||||
|
||||
INVALID_CREATION_BYTES = érvénytelen létrehozási bájtok
|
||||
|
||||
INVALID_TAGS_LENGTH = érvénytelen cimkehossz
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = érvénytelen AT "típus" hossz
|
||||
|
||||
INVALID_AT_TRANSACTION = érvénytelen AT tranzakció
|
||||
|
||||
INSUFFICIENT_FEE = elégtelen díj
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = a tőke nem egyezik az AT tőkéjével
|
||||
|
||||
ASSET_ALREADY_EXISTS = ez a tőke már létezik
|
||||
|
||||
MISSING_CREATOR = hiányzó létrehozó
|
||||
|
||||
TIMESTAMP_TOO_OLD = időbélyeg túl régi
|
||||
|
||||
TIMESTAMP_TOO_NEW = időbélyeg túl korai
|
||||
|
||||
TOO_MANY_UNCONFIRMED = ennek a fióknak túl sok meg nem erősített tranzakciója van folyamatban
|
||||
|
||||
GROUP_ALREADY_EXISTS = ez a csoport már létezik
|
||||
|
||||
GROUP_DOES_NOT_EXIST = ez a csoport nem létezik
|
||||
|
||||
INVALID_GROUP_OWNER = érvénytelen csoport tulajdonos
|
||||
|
||||
ALREADY_GROUP_MEMBER = már csoporttag
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = a csoport tulajdonos nem tudja elhagyni a csoportot
|
||||
|
||||
NOT_GROUP_MEMBER = ez a tag nem csoporttag
|
||||
ADDRESS_BLOCKED = ez a cím le van tiltva
|
||||
|
||||
ALREADY_GROUP_ADMIN = már csoport adminisztrátor
|
||||
|
||||
NOT_GROUP_ADMIN = ez a tag nem csoport adminisztrátor
|
||||
ALREADY_GROUP_MEMBER = már csoporttag
|
||||
|
||||
INVALID_LIFETIME = érvénytelen élettartam
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = erre a lehetőségre már szavaztál
|
||||
|
||||
INVITE_UNKNOWN = ismeretlen csoport meghívás
|
||||
ASSET_ALREADY_EXISTS = ez a tőke már létezik
|
||||
|
||||
ASSET_DOES_NOT_EXIST = tőke nem létezik
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = a tőke nem egyezik az AT tőkéjével
|
||||
|
||||
ASSET_NOT_SPENDABLE = ez a tőke nem értékesíthető
|
||||
|
||||
AT_ALREADY_EXISTS = az AT már létezik
|
||||
|
||||
AT_IS_FINISHED = az AT végzett
|
||||
|
||||
AT_UNKNOWN = az AT ismeretlen
|
||||
|
||||
BAN_EXISTS = már ki van tiltva
|
||||
|
||||
@@ -116,80 +34,162 @@ BAN_UNKNOWN = kitiltás nem létezik
|
||||
|
||||
BANNED_FROM_GROUP = ki van tiltva a csoportból
|
||||
|
||||
JOIN_REQUEST_EXISTS = a csoporthoz való csatlakozási kérelem már megtöretént
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = érvénytelen jóváhagyási küszöbérték
|
||||
|
||||
GROUP_ID_MISMATCH = csoportazonosító nem egyezik
|
||||
|
||||
INVALID_GROUP_ID = csoportazonosító érvénytelen
|
||||
|
||||
TRANSACTION_UNKNOWN = ismeretlen tranzakció
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = ez a tranzakció már meg van erősítve
|
||||
|
||||
INVALID_TX_GROUP_ID = a tranzakció csoportazonosítója érvénytelen
|
||||
|
||||
TX_GROUP_ID_MISMATCH = a tranzakció csoportazonosítója nem egyezik
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = fiókonként több név regisztrálása tilos
|
||||
|
||||
INVALID_ASSET_OWNER = érvénytelen tőke tulajdonos
|
||||
|
||||
AT_IS_FINISHED = az AT végzett
|
||||
|
||||
NO_FLAG_PERMISSION = ez a fiók nem rendelkezik ezzel az engedéllyel
|
||||
|
||||
NOT_MINTING_ACCOUNT = ez a fiók nem tud QORT-ot verni
|
||||
|
||||
REWARD_SHARE_UNKNOWN = ez a jutalék-megosztás ismeretlen
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = ez a jutalék-megosztási arány érvénytelen
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = ismeretlen nyilvános kulcs
|
||||
|
||||
INVALID_PUBLIC_KEY = érvénytelen nyilvános kulcs
|
||||
|
||||
AT_UNKNOWN = az AT ismeretlen
|
||||
|
||||
AT_ALREADY_EXISTS = az AT már létezik
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = csoport általi jóváhagyás nem szükséges
|
||||
|
||||
GROUP_APPROVAL_DECIDED = csoport általi jóváhagyás el van döntve
|
||||
|
||||
MAXIMUM_REWARD_SHARES = ez a fiókcím már elérte a maximális lehetséges jutalék-megosztási részesedést
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = ez a tranzakció már létezik
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = csomópont blokklánca jelenleg elfoglalt
|
||||
|
||||
ORDER_ALREADY_CLOSED = ez a tőke értékesítés már befejeződött
|
||||
BUYER_ALREADY_OWNER = ez a vevő már a tulajdonos
|
||||
|
||||
CLOCK_NOT_SYNCED = az óra nincs szinkronizálva
|
||||
|
||||
ASSET_NOT_SPENDABLE = ez a tőke nem értékesíthető
|
||||
DUPLICATE_MESSAGE = ez a cím duplikált üzenetet küldött
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = ez a fiók nem vehet részt jutalék-megosztásban
|
||||
DUPLICATE_OPTION = ez a lehetőség már létezik
|
||||
|
||||
SELF_SHARE_EXISTS = önrészes jutalék-megosztás már létezik
|
||||
GROUP_ALREADY_EXISTS = ez a csoport már létezik
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = ez a fiók már létezik
|
||||
GROUP_APPROVAL_DECIDED = csoport általi jóváhagyás el van döntve
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = invalid group-approval block delay
|
||||
GROUP_APPROVAL_NOT_REQUIRED = csoport általi jóváhagyás nem szükséges
|
||||
|
||||
GROUP_DOES_NOT_EXIST = ez a csoport nem létezik
|
||||
|
||||
GROUP_ID_MISMATCH = csoportazonosító nem egyezik
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = a csoport tulajdonosa nem tudja elhagyni a csoportot
|
||||
|
||||
HAVE_EQUALS_WANT = saját tőke egyenlő a csere tőkével
|
||||
|
||||
INCORRECT_NONCE = helytelen Proof-of-Work Nonce
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = érvénytelen időbélyeg aláírás
|
||||
INSUFFICIENT_FEE = elégtelen díj
|
||||
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
INVALID_ADDRESS = érvénytelen név vagy cím
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
INVALID_AMOUNT = érvénytelen összeg
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = ez a cím elérte a megengedett mérték korlátot
|
||||
INVALID_ASSET_OWNER = érvénytelen tőke tulajdonos
|
||||
|
||||
DUPLICATE_MESSAGE = ez a cím duplikált üzenetet küldött
|
||||
INVALID_AT_TRANSACTION = érvénytelen AT tranzakció
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = érvénytelen AT típushossz
|
||||
|
||||
INVALID_BUT_OK = érvénytelen de elfogadva
|
||||
|
||||
INVALID_CREATION_BYTES = érvénytelen létrehozási bájtok
|
||||
|
||||
INVALID_DATA_LENGTH = érvénytelen adathossz
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = érvénytelen leíráshossz
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = érvénytelen jóváhagyási küszöbérték
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = invalid group-approval block delay
|
||||
|
||||
INVALID_GROUP_ID = csoportazonosító érvénytelen
|
||||
|
||||
INVALID_GROUP_OWNER = érvénytelen csoporttulajdonos
|
||||
|
||||
INVALID_LIFETIME = érvénytelen élettartam
|
||||
|
||||
INVALID_NAME_LENGTH = érvénytelen névhossz
|
||||
|
||||
INVALID_NAME_OWNER = érvénytelen névtulajdonos
|
||||
|
||||
INVALID_OPTION_LENGTH = érvénytelen opciószám
|
||||
|
||||
INVALID_OPTIONS_COUNT = invalid options count
|
||||
|
||||
INVALID_ORDER_CREATOR = érvénytelen a rendelés létrehozója
|
||||
|
||||
INVALID_PAYMENTS_COUNT = a kifizetések száma érvénytelen
|
||||
|
||||
INVALID_PUBLIC_KEY = érvénytelen nyilvános kulcs
|
||||
|
||||
INVALID_QUANTITY = érvénytelen mennyiség
|
||||
|
||||
INVALID_REFERENCE = érvénytelen hivatkozás
|
||||
|
||||
INVALID_RETURN = érvénytelen csere tőke
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = ez a jutalék-megosztási arány érvénytelen
|
||||
|
||||
INVALID_SELLER = érvénytelen eladó
|
||||
|
||||
INVALID_TAGS_LENGTH = érvénytelen cimkehossz
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = érvénytelen időbélyeg-aláírás
|
||||
|
||||
INVALID_TX_GROUP_ID = a tranzakció csoportazonosítója érvénytelen
|
||||
|
||||
INVALID_VALUE_LENGTH = érvénytelen értékhossz
|
||||
|
||||
INVITE_UNKNOWN = ismeretlen csoport meghívás
|
||||
|
||||
JOIN_REQUEST_EXISTS = a csoporthoz való csatlakozási kérelem már létezik
|
||||
|
||||
MAXIMUM_REWARD_SHARES = ez a fiókcím már elérte a maximális lehetséges jutalék-megosztási részesedést
|
||||
|
||||
MISSING_CREATOR = hiányzó létrehozó
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = fiókonként több név regisztrálása tilos
|
||||
|
||||
NAME_ALREADY_FOR_SALE = ez a név már eladó
|
||||
|
||||
NAME_ALREADY_REGISTERED = ez a név már regisztrált
|
||||
|
||||
NAME_BLOCKED = ez a név tiltva van
|
||||
|
||||
NAME_DOES_NOT_EXIST = ez a név nem létezik
|
||||
|
||||
NAME_NOT_FOR_SALE = ez a név nem eladó
|
||||
|
||||
NAME_NOT_NORMALIZED = ez a név nincs "normalizált" Unicode formátumban
|
||||
|
||||
NEGATIVE_AMOUNT = érvénytelen/negatív összeg
|
||||
|
||||
NEGATIVE_FEE = érvénytelen/negatív tranzakciós díj
|
||||
|
||||
NEGATIVE_PRICE = érvénytelen/negatív ár
|
||||
|
||||
NO_BALANCE = elégtelen egyenleg
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = csomópont blokklánca jelenleg foglalt
|
||||
|
||||
NO_FLAG_PERMISSION = ez a fiók nem rendelkezik ezzel az engedéllyel
|
||||
|
||||
NOT_GROUP_ADMIN = ez a tag nem csoportadminisztrátor
|
||||
|
||||
NOT_GROUP_MEMBER = ez a tag nem csoporttag
|
||||
|
||||
NOT_MINTING_ACCOUNT = ez a fiók nem tud QORT-ot verni
|
||||
|
||||
NOT_YET_RELEASED = ez a funkció még nem került kiadásra
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = ez a tőkeértékesítés már befejeződött
|
||||
|
||||
ORDER_DOES_NOT_EXIST = tőkerendelés nem létezik
|
||||
|
||||
POLL_ALREADY_EXISTS = ez a szavazás már létezik
|
||||
|
||||
POLL_DOES_NOT_EXIST = ez a szavazás nem létezik
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = ez a válaszlehetőség nem létezik
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = ismeretlen nyilvános kulcs
|
||||
|
||||
REWARD_SHARE_UNKNOWN = ez a jutalék-megosztás ismeretlen
|
||||
|
||||
SELF_SHARE_EXISTS = önrészes jutalék-megosztás már létezik
|
||||
|
||||
TIMESTAMP_TOO_NEW = időbélyeg túl friss
|
||||
|
||||
TIMESTAMP_TOO_OLD = időbélyeg túl régi
|
||||
|
||||
TOO_MANY_UNCONFIRMED = ennek a fióknak túl sok meg nem erősített tranzakciója van folyamatban
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = ez a tranzakció már meg van erősítve
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = ez a tranzakció már létezik
|
||||
|
||||
TRANSACTION_UNKNOWN = ismeretlen tranzakció
|
||||
|
||||
TX_GROUP_ID_MISMATCH = a tranzakció csoportazonosítója nem egyezik
|
||||
|
@@ -1,12 +1,16 @@
|
||||
# Italian translation by Pabs 2021
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = l'account gia esiste
|
||||
ACCOUNT_ALREADY_EXISTS = l'account esiste già
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = l'account non può fare la condivisione di ricompensa
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = l'account non può ricevere ricompense
|
||||
|
||||
ALREADY_GROUP_ADMIN = è già amministratore del gruppo
|
||||
ADDRESS_ABOVE_RATE_LIMIT = l'indirizzo ha raggiunto il limite stabilito
|
||||
|
||||
ALREADY_GROUP_MEMBER = è già membro del gruppo
|
||||
ADDRESS_BLOCKED = questo indirizzo è bloccato
|
||||
|
||||
ALREADY_GROUP_ADMIN = già amministratore del gruppo
|
||||
|
||||
ALREADY_GROUP_MEMBER = già membro del gruppo
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = già votato per questa opzione
|
||||
|
||||
@@ -24,31 +28,33 @@ AT_IS_FINISHED = AT ha finito
|
||||
|
||||
AT_UNKNOWN = AT sconosciuto
|
||||
|
||||
BANNED_FROM_GROUP = divietato dal gruppo
|
||||
|
||||
BAN_EXISTS = il divieto esiste già
|
||||
|
||||
BAN_UNKNOWN = divieto sconosciuto
|
||||
|
||||
BANNED_FROM_GROUP = vietato dal gruppo
|
||||
|
||||
BUYER_ALREADY_OWNER = l'acquirente è già proprietario
|
||||
|
||||
CLOCK_NOT_SYNCED = orologio non sincronizzato
|
||||
|
||||
DUPLICATE_MESSAGE = l'indirizzo ha inviato un messaggio duplicato
|
||||
|
||||
DUPLICATE_OPTION = opzione duplicata
|
||||
|
||||
GROUP_ALREADY_EXISTS = gruppo già esistente
|
||||
|
||||
GROUP_APPROVAL_DECIDED = approvazione di gruppo già decisa
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = approvazione di gruppo non richiesto
|
||||
GROUP_APPROVAL_NOT_REQUIRED = approvazione di gruppo non richiesta
|
||||
|
||||
GROUP_DOES_NOT_EXIST = gruppo non esiste
|
||||
GROUP_DOES_NOT_EXIST = il gruppo non esiste
|
||||
|
||||
GROUP_ID_MISMATCH = identificazione di gruppo non corrispondente
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = il proprietario del gruppo non può lasciare il gruppo
|
||||
|
||||
HAVE_EQUALS_WANT = la risorsa avere è uguale a la risorsa volere
|
||||
HAVE_EQUALS_WANT = la richiesta è uguale a ciò che è già posseduto
|
||||
|
||||
INCORRECT_NONCE = PoW nonce sbagliato
|
||||
|
||||
@@ -64,9 +70,11 @@ INVALID_AT_TRANSACTION = transazione AT non valida
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = lunghezza di "tipo" AT non valida
|
||||
|
||||
INVALID_CREATION_BYTES = byte di creazione non validi
|
||||
INVALID_BUT_OK = non valido ma accettato
|
||||
|
||||
INVALID_DATA_LENGTH = lunghezza di dati non valida
|
||||
INVALID_CREATION_BYTES = creazione di byte non validi
|
||||
|
||||
INVALID_DATA_LENGTH = lunghezza dati non valida
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = lunghezza della descrizione non valida
|
||||
|
||||
@@ -78,16 +86,16 @@ INVALID_GROUP_ID = identificazione di gruppo non valida
|
||||
|
||||
INVALID_GROUP_OWNER = proprietario di gruppo non valido
|
||||
|
||||
INVALID_LIFETIME = durata della vita non valida
|
||||
INVALID_LIFETIME = durata non valida
|
||||
|
||||
INVALID_NAME_LENGTH = lunghezza del nome non valida
|
||||
|
||||
INVALID_NAME_OWNER = proprietario del nome non valido
|
||||
|
||||
INVALID_OPTIONS_COUNT = conteggio di opzioni non validi
|
||||
|
||||
INVALID_OPTION_LENGTH = lunghezza di opzioni non valida
|
||||
|
||||
INVALID_OPTIONS_COUNT = conteggio di opzioni non validi
|
||||
|
||||
INVALID_ORDER_CREATOR = creatore dell'ordine non valido
|
||||
|
||||
INVALID_PAYMENTS_COUNT = conteggio pagamenti non validi
|
||||
@@ -98,7 +106,7 @@ INVALID_QUANTITY = quantità non valida
|
||||
|
||||
INVALID_REFERENCE = riferimento non valido
|
||||
|
||||
INVALID_RETURN = ritorno non valido
|
||||
INVALID_RETURN = risposta non valida
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = percentuale condivisione di ricompensa non valida
|
||||
|
||||
@@ -106,6 +114,8 @@ INVALID_SELLER = venditore non valido
|
||||
|
||||
INVALID_TAGS_LENGTH = lunghezza dei "tag" non valida
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = firma timestamp non valida
|
||||
|
||||
INVALID_TX_GROUP_ID = identificazione di gruppo di transazioni non valida
|
||||
|
||||
INVALID_VALUE_LENGTH = lunghezza "valore" non valida
|
||||
@@ -114,16 +124,18 @@ INVITE_UNKNOWN = invito di gruppo sconosciuto
|
||||
|
||||
JOIN_REQUEST_EXISTS = la richiesta di iscrizione al gruppo già esiste
|
||||
|
||||
MAXIMUM_REWARD_SHARES = numero massimo di condivisione di ricompensa raggiunto per l'account
|
||||
MAXIMUM_REWARD_SHARES = numero massimo di condivisione di ricompensa raggiunto per l'account
|
||||
|
||||
MISSING_CREATOR = creatore mancante
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = è vietata la registrazione di multipli nomi per account
|
||||
MULTIPLE_NAMES_FORBIDDEN = è vietata la registrazione di nomi multipli per singolo account
|
||||
|
||||
NAME_ALREADY_FOR_SALE = nome già in vendita
|
||||
|
||||
NAME_ALREADY_REGISTERED = nome già registrato
|
||||
|
||||
NAME_BLOCKED = questo nome è bloccato
|
||||
|
||||
NAME_DOES_NOT_EXIST = il nome non esiste
|
||||
|
||||
NAME_NOT_FOR_SALE = il nome non è in vendita
|
||||
@@ -136,6 +148,12 @@ NEGATIVE_FEE = tariffa non valida / negativa
|
||||
|
||||
NEGATIVE_PRICE = prezzo non valido / negativo
|
||||
|
||||
NO_BALANCE = bilancio insufficiente
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = nodo di blockchain attualmente occupato
|
||||
|
||||
NO_FLAG_PERMISSION = l'account non dispone di questa autorizzazione
|
||||
|
||||
NOT_GROUP_ADMIN = l'account non è un amministratore di gruppo
|
||||
|
||||
NOT_GROUP_MEMBER = l'account non è un membro del gruppo
|
||||
@@ -144,17 +162,11 @@ NOT_MINTING_ACCOUNT = l'account non può coniare
|
||||
|
||||
NOT_YET_RELEASED = funzione non ancora rilasciata
|
||||
|
||||
NO_BALANCE = equilibrio insufficiente
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = nodo di blockchain attualmente occupato
|
||||
|
||||
NO_FLAG_PERMISSION = l'account non dispone di questa autorizzazione
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = l'ordine di scambio di risorsa è già chiuso
|
||||
|
||||
ORDER_DOES_NOT_EXIST = l'ordine di scambio di risorsa non esiste
|
||||
ORDER_DOES_NOT_EXIST = l'ordine di scambio di risorsa non esiste
|
||||
|
||||
POLL_ALREADY_EXISTS = il sondaggio già esiste
|
||||
|
||||
@@ -166,7 +178,7 @@ PUBLIC_KEY_UNKNOWN = chiave pubblica sconosciuta
|
||||
|
||||
REWARD_SHARE_UNKNOWN = condivisione di ricompensa sconosciuta
|
||||
|
||||
SELF_SHARE_EXISTS = condivisione di sé (condivisione di ricompensa) già esiste
|
||||
SELF_SHARE_EXISTS = condivisione di ricompensa già esiste
|
||||
|
||||
TIMESTAMP_TOO_NEW = timestamp troppo nuovo
|
||||
|
||||
@@ -181,15 +193,3 @@ TRANSACTION_ALREADY_EXISTS = la transazione già esiste
|
||||
TRANSACTION_UNKNOWN = transazione sconosciuta
|
||||
|
||||
TX_GROUP_ID_MISMATCH = identificazione di gruppo della transazione non corrisponde
|
||||
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = address reached specified rate limit
|
||||
|
||||
DUPLICATE_MESSAGE = address sent duplicate message
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = Invalid timestamp signature
|
||||
|
||||
INVALID_BUT_OK = Invalid but OK
|
||||
|
@@ -1,10 +1,16 @@
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = account bestaat al
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = account kan geen beloningen delen
|
||||
|
||||
ALREADY_GROUP_ADMIN = reeds groepsadministrator
|
||||
ADDRESS_ABOVE_RATE_LIMIT = adres heeft een waarde limiet bereikt
|
||||
|
||||
ALREADY_GROUP_MEMBER = reeds groepslid
|
||||
ADDRESS_BLOCKED = adres is geblokkeerd
|
||||
|
||||
ALREADY_GROUP_ADMIN = groeps administrator bestaat al
|
||||
|
||||
ALREADY_GROUP_MEMBER = groeps lid bestaat al
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = reeds gestemd voor die optie
|
||||
|
||||
@@ -12,9 +18,9 @@ ASSET_ALREADY_EXISTS = asset bestaat al
|
||||
|
||||
ASSET_DOES_NOT_EXIST = asset bestaat niet
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = asset matcht niet met de asset van de AT
|
||||
ASSET_DOES_NOT_MATCH_AT = asset komt niet overeen met de asset van de AT
|
||||
|
||||
ASSET_NOT_SPENDABLE = asset is niet uitgeefbaar
|
||||
ASSET_NOT_SPENDABLE = asset is niet toerijkend
|
||||
|
||||
AT_ALREADY_EXISTS = AT bestaat al
|
||||
|
||||
@@ -22,31 +28,33 @@ AT_IS_FINISHED = AT is afgelopen
|
||||
|
||||
AT_UNKNOWN = AT onbekend
|
||||
|
||||
BANNED_FROM_GROUP = verbannen uit groep
|
||||
BAN_EXISTS = hiervoor bestaat een verbod
|
||||
|
||||
BAN_EXISTS = ban bestaat al
|
||||
BAN_UNKNOWN = onbekend verbod
|
||||
|
||||
BAN_UNKNOWN = ban onbekend
|
||||
BANNED_FROM_GROUP = verbod om deel te nemen aan de groep
|
||||
|
||||
BUYER_ALREADY_OWNER = koper is al eigenaar
|
||||
BUYER_ALREADY_OWNER = koper is al de eigenaar
|
||||
|
||||
CLOCK_NOT_SYNCED = klok is niet gesynchronizeerd
|
||||
|
||||
DUPLICATE_MESSAGE = dubbel adres bericht
|
||||
|
||||
DUPLICATE_OPTION = dubbele optie
|
||||
|
||||
GROUP_ALREADY_EXISTS = groep bestaat reeds
|
||||
|
||||
GROUP_APPROVAL_DECIDED = groepsgoedkeuring reeds afgewezen
|
||||
GROUP_APPROVAL_DECIDED = groeps goedkeuring afgewezen
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = groepsgoedkeuring niet vereist
|
||||
GROUP_APPROVAL_NOT_REQUIRED = groeps goedkeuring niet vereist
|
||||
|
||||
GROUP_DOES_NOT_EXIST = groep bestaat niet
|
||||
|
||||
GROUP_ID_MISMATCH = ongeldige match met groep-ID
|
||||
GROUP_ID_MISMATCH = groeps ID komt niet overeen
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = groepseigenaar kan de groep niet verlaten
|
||||
GROUP_OWNER_CANNOT_LEAVE = groep eigenaar kan de groep niet verlaten
|
||||
|
||||
HAVE_EQUALS_WANT = have-asset is gelijk aan want-asset
|
||||
HAVE_EQUALS_WANT = asset is gelijk aan Want-asset
|
||||
|
||||
INCORRECT_NONCE = incorrecte PoW nonce
|
||||
|
||||
@@ -60,21 +68,23 @@ INVALID_ASSET_OWNER = ongeldige asset-eigenaar
|
||||
|
||||
INVALID_AT_TRANSACTION = ongeldige AT-transactie
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = ongeldige lengte voor AT 'type'
|
||||
INVALID_AT_TYPE_LENGTH = ongeldige lengte voor AT type
|
||||
|
||||
INVALID_CREATION_BYTES = ongeldige creation bytes
|
||||
INVALID_BUT_OK = ongeldig maar is in orde
|
||||
|
||||
INVALID_DATA_LENGTH = ongeldige lengte voor data
|
||||
INVALID_CREATION_BYTES = ongeldige gecreerde bytes
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = ongeldige lengte voor beschrijving
|
||||
INVALID_DATA_LENGTH = ongeldige data lengte
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = ongeldige lengte voor de beschrijving
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = ongeldige drempelwaarde voor groepsgoedkeuring
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = ongeldige groepsgoedkeuring voor blokvertraging
|
||||
INVALID_GROUP_BLOCK_DELAY = ongeldige groep blok vertraging
|
||||
|
||||
INVALID_GROUP_ID = ongeldige groep-ID
|
||||
|
||||
INVALID_GROUP_OWNER = ongeldige groepseigenaar
|
||||
INVALID_GROUP_OWNER = ongeldige groep eigenaar
|
||||
|
||||
INVALID_LIFETIME = ongeldige levensduur
|
||||
|
||||
@@ -82,13 +92,13 @@ INVALID_NAME_LENGTH = ongeldige lengte voor naam
|
||||
|
||||
INVALID_NAME_OWNER = ongeldige naam voor eigenaar
|
||||
|
||||
INVALID_OPTIONS_COUNT = ongeldige hoeveelheid opties
|
||||
|
||||
INVALID_OPTION_LENGTH = ongeldige lengte voor opties
|
||||
|
||||
INVALID_ORDER_CREATOR = ongeldige aanmaker voor order
|
||||
INVALID_OPTIONS_COUNT = ongeldige hoeveelheid opties
|
||||
|
||||
INVALID_PAYMENTS_COUNT = ongeldige hoeveelheid betalingen
|
||||
INVALID_ORDER_CREATOR = ongeldige gebruiker voor deze order
|
||||
|
||||
INVALID_PAYMENTS_COUNT = ongeldige betalings waarde
|
||||
|
||||
INVALID_PUBLIC_KEY = ongeldige public key
|
||||
|
||||
@@ -98,21 +108,23 @@ INVALID_REFERENCE = ongeldige verwijzing
|
||||
|
||||
INVALID_RETURN = ongeldige return
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = ongeldig percentage voor beloningsdeling
|
||||
INVALID_REWARD_SHARE_PERCENT = ongeldig belonings percentage
|
||||
|
||||
INVALID_SELLER = ongeldige verkoper
|
||||
|
||||
INVALID_TAGS_LENGTH = ongeldige lengte voor 'tags'
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = ongeldig tijd aanduiding
|
||||
|
||||
INVALID_TX_GROUP_ID = ongeldige transactiegroep-ID
|
||||
|
||||
INVALID_VALUE_LENGTH = ongeldige lengte voor 'waarde'
|
||||
INVALID_VALUE_LENGTH = ongeldige lengte voor de waarde
|
||||
|
||||
INVITE_UNKNOWN = onbekende groepsuitnodiging
|
||||
|
||||
JOIN_REQUEST_EXISTS = aanvraag om lid van groep te worden bestaat al
|
||||
|
||||
MAXIMUM_REWARD_SHARES = limiet aan beloningsdelingen voor dit account is bereikt
|
||||
MAXIMUM_REWARD_SHARES = limiet aan belonen voor dit account bereikt
|
||||
|
||||
MISSING_CREATOR = ontbrekende aanmaker
|
||||
|
||||
@@ -122,33 +134,35 @@ NAME_ALREADY_FOR_SALE = naam reeds te koop
|
||||
|
||||
NAME_ALREADY_REGISTERED = naam reeds geregistreerd
|
||||
|
||||
NAME_BLOCKED = deze naam is geblokkeerd
|
||||
|
||||
NAME_DOES_NOT_EXIST = naam bestaat niet
|
||||
|
||||
NAME_NOT_FOR_SALE = naam is niet te koop
|
||||
|
||||
NAME_NOT_NORMALIZED = naam is niet in 'genormalizeerde' Unicode-vorm
|
||||
NAME_NOT_NORMALIZED = naam voldoet niet aan Unicode-vorm
|
||||
|
||||
NEGATIVE_AMOUNT = ongeldige/negatieve hoeveelheid
|
||||
NEGATIVE_AMOUNT = negatieve hoeveelheid
|
||||
|
||||
NEGATIVE_FEE = ongeldige/negatieve vergoeding
|
||||
NEGATIVE_FEE = negatieve vergoeding
|
||||
|
||||
NEGATIVE_PRICE = ongeldige/negatieve prijs
|
||||
NEGATIVE_PRICE = negatieve prijs
|
||||
|
||||
NO_BALANCE = onvoldoende balans
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = geen blockchain slot
|
||||
|
||||
NO_FLAG_PERMISSION = account heeft hier geen toestemming voor
|
||||
|
||||
NOT_GROUP_ADMIN = account is geen groepsadministrator
|
||||
|
||||
NOT_GROUP_MEMBER = account is geen groepslid
|
||||
|
||||
NOT_MINTING_ACCOUNT = account kan niet munten
|
||||
NOT_MINTING_ACCOUNT = account kan niet minten
|
||||
|
||||
NOT_YET_RELEASED = functie nog niet uitgebracht
|
||||
|
||||
NO_BALANCE = onvoldoende balans
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = blockchain van node is momenteel bezig
|
||||
|
||||
NO_FLAG_PERMISSION = account heeft hier geen toestemming voor
|
||||
|
||||
OK = Oke
|
||||
OK = Ok
|
||||
|
||||
ORDER_ALREADY_CLOSED = asset handelsorder is al gesloten
|
||||
|
||||
@@ -162,9 +176,9 @@ POLL_OPTION_DOES_NOT_EXIST = peilingsoptie bestaat niet
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = public key onbekend
|
||||
|
||||
REWARD_SHARE_UNKNOWN = beloningsdeling onbekend
|
||||
REWARD_SHARE_UNKNOWN = beloning vergoeding onbekend
|
||||
|
||||
SELF_SHARE_EXISTS = zelfdeling (beloningsdeling) bestaat reeds
|
||||
SELF_SHARE_EXISTS = zelf vergoeding bestaat reeds
|
||||
|
||||
TIMESTAMP_TOO_NEW = tijdstempel te nieuw
|
||||
|
||||
@@ -178,16 +192,4 @@ TRANSACTION_ALREADY_EXISTS = transactie bestaat al
|
||||
|
||||
TRANSACTION_UNKNOWN = transactie onbekend
|
||||
|
||||
TX_GROUP_ID_MISMATCH = groep-ID van transactie matcht niet
|
||||
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = address reached specified rate limit
|
||||
|
||||
DUPLICATE_MESSAGE = address sent duplicate message
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = Invalid timestamp signature
|
||||
|
||||
INVALID_BUT_OK = Invalid but OK
|
||||
TX_GROUP_ID_MISMATCH = groep ID komt niet overeen
|
||||
|
@@ -1,187 +1,195 @@
|
||||
ACCOUNT_ALREADY_EXISTS = аккаунт уже существует
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = аккаунт не может делиться вознаграждением
|
||||
|
||||
ALREADY_GROUP_ADMIN = уже администратор группы
|
||||
|
||||
ALREADY_GROUP_MEMBER = уже член группы
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = уже проголосовали за этот вариант
|
||||
|
||||
ASSET_ALREADY_EXISTS = актив уже существует
|
||||
|
||||
ASSET_DOES_NOT_EXIST = Актив не существует
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = актив не совпадает с АТ
|
||||
|
||||
ASSET_NOT_SPENDABLE = актив не подлежит расходованию
|
||||
|
||||
AT_ALREADY_EXISTS = AT уже существует
|
||||
|
||||
AT_IS_FINISHED = AT в завершении
|
||||
|
||||
AT_UNKNOWN = не известный АТ
|
||||
|
||||
BANNED_FROM_GROUP = исключен из группы
|
||||
|
||||
BAN_EXISTS = Бан
|
||||
|
||||
BAN_UNKNOWN = не известный бан
|
||||
|
||||
BUYER_ALREADY_OWNER = покупатель уже собственник
|
||||
|
||||
CLOCK_NOT_SYNCED = часы не синхронизированы
|
||||
|
||||
DUPLICATE_OPTION = дублировать вариант
|
||||
|
||||
GROUP_ALREADY_EXISTS = группа уже существует
|
||||
|
||||
GROUP_APPROVAL_DECIDED = гуппа одобрена
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = гупповое одобрение не требуется
|
||||
|
||||
GROUP_DOES_NOT_EXIST = группа не существует
|
||||
|
||||
GROUP_ID_MISMATCH = не соответствие идентификатора группы
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = владелец группы не может уйти
|
||||
|
||||
HAVE_EQUALS_WANT = иммеются равные желания
|
||||
|
||||
INSUFFICIENT_FEE = недостаточная плата
|
||||
|
||||
INVALID_ADDRESS = недействительный адрес
|
||||
|
||||
INVALID_AMOUNT = недопустимая сумма
|
||||
|
||||
INVALID_ASSET_OWNER = недействительный владелец актива
|
||||
|
||||
INVALID_AT_TRANSACTION = недействительная АТ транзакция
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = недействительно для типа длины AT
|
||||
|
||||
INVALID_CREATION_BYTES = недопустимые байты создания
|
||||
|
||||
INVALID_DATA_LENGTH = недопустимая длина данных
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = недопустимая длина описания
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = недопустимый порог утверждения группы
|
||||
|
||||
INVALID_GROUP_ID = недопустимый идентификатор группы
|
||||
|
||||
INVALID_GROUP_OWNER = недопу владелец группы
|
||||
|
||||
INVALID_LIFETIME = недопу срок службы
|
||||
|
||||
INVALID_NAME_LENGTH = недопустимая длина группы
|
||||
|
||||
INVALID_NAME_OWNER = недопустимое имя владельца
|
||||
|
||||
INVALID_OPTIONS_COUNT = неверное количество опций
|
||||
|
||||
INVALID_OPTION_LENGTH = недопустимая длина опции
|
||||
|
||||
INVALID_ORDER_CREATOR = недопустимый создатель заказа
|
||||
|
||||
INVALID_PAYMENTS_COUNT = неверный подсчет платежей
|
||||
|
||||
INVALID_PUBLIC_KEY = недействительный открытый ключ
|
||||
|
||||
INVALID_QUANTITY = недопустимое количество
|
||||
|
||||
INVALID_REFERENCE = неверная ссылка
|
||||
|
||||
INVALID_RETURN = недопустимый возврат
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = недействительный процент награждения
|
||||
|
||||
INVALID_SELLER = недействительный продавец
|
||||
|
||||
INVALID_TAGS_LENGTH = недействительная длина тэгов
|
||||
|
||||
INVALID_TX_GROUP_ID = недействительный идентификатор группы передачи
|
||||
|
||||
INVALID_VALUE_LENGTH = недопустимое значение длины
|
||||
|
||||
INVITE_UNKNOWN = приглашать неизветсных
|
||||
|
||||
JOIN_REQUEST_EXISTS = запрос на присоединение существует
|
||||
|
||||
MAXIMUM_REWARD_SHARES = максимальное вознаграждение
|
||||
|
||||
MISSING_CREATOR = отсутствующий создатель
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = несколько имен запрещено
|
||||
|
||||
NAME_ALREADY_FOR_SALE = имя уже в продаже
|
||||
|
||||
NAME_ALREADY_REGISTERED = имя уже зарегистрировано
|
||||
|
||||
NAME_DOES_NOT_EXIST = имя не существует
|
||||
|
||||
NAME_NOT_FOR_SALE = имя не продается
|
||||
|
||||
NAME_NOT_LOWER_CASE = иммя не должно содержать строчный регистр
|
||||
|
||||
NEGATIVE_AMOUNT = недостаточная сумма
|
||||
|
||||
NEGATIVE_FEE = недостаточная комиссия
|
||||
|
||||
NEGATIVE_PRICE = недостаточная стоимость
|
||||
|
||||
NOT_GROUP_ADMIN = не администратор группы
|
||||
|
||||
NOT_GROUP_MEMBER = не член группы
|
||||
|
||||
NOT_MINTING_ACCOUNT = счет не чеканит
|
||||
|
||||
NOT_YET_RELEASED = еще не выпущено
|
||||
|
||||
NO_BALANCE = нет баланса
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = блокчейн узла в настоящее время занят
|
||||
|
||||
NO_FLAG_PERMISSION = нет разрешения на флаг
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = заказ закрыт
|
||||
|
||||
ORDER_DOES_NOT_EXIST = заказа не существует
|
||||
|
||||
POLL_ALREADY_EXISTS = опрос уже существует
|
||||
|
||||
POLL_DOES_NOT_EXIST = опроса не существует
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = вариантов ответа не существует
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = открытый ключ неизвестен
|
||||
|
||||
SELF_SHARE_EXISTS = поделиться долей
|
||||
|
||||
TIMESTAMP_TOO_NEW = новая метка времени
|
||||
|
||||
TIMESTAMP_TOO_OLD = старая метка времени
|
||||
|
||||
TOO_MANY_UNCONFIRMED = много не подтвержденных
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = транзакция уже подтверждена
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = транзакция существует
|
||||
|
||||
TRANSACTION_UNKNOWN = неизвестная транзакция
|
||||
|
||||
TX_GROUP_ID_MISMATCH = не соответствие идентификатора группы c хэш транзации
|
||||
|
||||
ADDRESS_BLOCKED = this address is blocked
|
||||
|
||||
NAME_BLOCKED = this name is blocked
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = address reached specified rate limit
|
||||
|
||||
DUPLICATE_MESSAGE = address sent duplicate message
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = Invalid timestamp signature
|
||||
|
||||
INVALID_BUT_OK = Invalid but OK
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = аккаунт уже существует
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = аккаунт не может делиться вознаграждением
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = адрес достиг указанного предела скорости
|
||||
|
||||
ADDRESS_BLOCKED = этот адрес заблокирован
|
||||
|
||||
ALREADY_GROUP_ADMIN = уже администратор группы
|
||||
|
||||
ALREADY_GROUP_MEMBER = уже член группы
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = уже проголосовали за этот вариант
|
||||
|
||||
ASSET_ALREADY_EXISTS = актив уже существует
|
||||
|
||||
ASSET_DOES_NOT_EXIST = Актив не существует
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = актив не совпадает с АТ
|
||||
|
||||
ASSET_NOT_SPENDABLE = актив не подлежит расходованию
|
||||
|
||||
AT_ALREADY_EXISTS = AT уже существует
|
||||
|
||||
AT_IS_FINISHED = AT в завершении
|
||||
|
||||
AT_UNKNOWN = неизвестный АТ
|
||||
|
||||
BAN_EXISTS = Бан
|
||||
|
||||
BAN_UNKNOWN = неизвестный бан
|
||||
|
||||
BANNED_FROM_GROUP = исключен из группы
|
||||
|
||||
BUYER_ALREADY_OWNER = покупатель уже собственник
|
||||
|
||||
CLOCK_NOT_SYNCED = часы не синхронизированы
|
||||
|
||||
DUPLICATE_MESSAGE = адрес отправил дубликат сообщения
|
||||
|
||||
DUPLICATE_OPTION = дублировать вариант
|
||||
|
||||
GROUP_ALREADY_EXISTS = группа уже существует
|
||||
|
||||
GROUP_APPROVAL_DECIDED = группа одобрена
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = групповое одобрение не требуется
|
||||
|
||||
GROUP_DOES_NOT_EXIST = группа не существует
|
||||
|
||||
GROUP_ID_MISMATCH = несоответствие идентификатора группы
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = владелец группы не может уйти
|
||||
|
||||
HAVE_EQUALS_WANT = имеются равные желания
|
||||
|
||||
INCORRECT_NONCE = неправильный одноразовый номер PoW
|
||||
|
||||
INSUFFICIENT_FEE = недостаточная плата
|
||||
|
||||
INVALID_ADDRESS = недействительный адрес
|
||||
|
||||
INVALID_AMOUNT = недопустимая сумма
|
||||
|
||||
INVALID_ASSET_OWNER = недействительный владелец актива
|
||||
|
||||
INVALID_AT_TRANSACTION = недействительная АТ транзакция
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = недействительно для типа длины AT
|
||||
|
||||
INVALID_BUT_OK = Недействительно, но ОК
|
||||
|
||||
INVALID_CREATION_BYTES = недопустимые байты создания
|
||||
|
||||
INVALID_DATA_LENGTH = недопустимая длина данных
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = недопустимая длина описания
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = недопустимый порог утверждения группы
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = недопустимая задержка блокировки группового утверждения
|
||||
|
||||
INVALID_GROUP_ID = недопустимый идентификатор группы
|
||||
|
||||
INVALID_GROUP_OWNER = недопустимый владелец группы
|
||||
|
||||
INVALID_LIFETIME = недопустимый срок службы
|
||||
|
||||
INVALID_NAME_LENGTH = недопустимая длина группы
|
||||
|
||||
INVALID_NAME_OWNER = недопустимое имя владельца
|
||||
|
||||
INVALID_OPTION_LENGTH = недопустимая длина опции
|
||||
|
||||
INVALID_OPTIONS_COUNT = неверное количество опций
|
||||
|
||||
INVALID_ORDER_CREATOR = недопустимый создатель заказа
|
||||
|
||||
INVALID_PAYMENTS_COUNT = неверный подсчет платежей
|
||||
|
||||
INVALID_PUBLIC_KEY = недействительный открытый ключ
|
||||
|
||||
INVALID_QUANTITY = недопустимое количество
|
||||
|
||||
INVALID_REFERENCE = неверная ссылка
|
||||
|
||||
INVALID_RETURN = недопустимый возврат
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = недопустимый процент доли вознаграждения
|
||||
|
||||
INVALID_SELLER = недействительный продавец
|
||||
|
||||
INVALID_TAGS_LENGTH = недействительная длина тэгов
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = неверная подпись временной метки
|
||||
|
||||
INVALID_TX_GROUP_ID = недействительный идентификатор группы передачи
|
||||
|
||||
INVALID_VALUE_LENGTH = недопустимое значение длины
|
||||
|
||||
INVITE_UNKNOWN = приглашать неизветсных
|
||||
|
||||
JOIN_REQUEST_EXISTS = запрос на присоединение существует
|
||||
|
||||
MAXIMUM_REWARD_SHARES = максимальное вознаграждение
|
||||
|
||||
MISSING_CREATOR = отсутствующий создатель
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = несколько имен запрещено
|
||||
|
||||
NAME_ALREADY_FOR_SALE = имя уже в продаже
|
||||
|
||||
NAME_ALREADY_REGISTERED = имя уже зарегистрировано
|
||||
|
||||
NAME_BLOCKED = это имя заблокировано
|
||||
|
||||
NAME_DOES_NOT_EXIST = имя не существует
|
||||
|
||||
NAME_NOT_FOR_SALE = имя не продается
|
||||
|
||||
NAME_NOT_LOWER_CASE = имя не должно содержать строчный регистр
|
||||
|
||||
NEGATIVE_AMOUNT = недостаточная сумма
|
||||
|
||||
NEGATIVE_FEE = недостаточная комиссия
|
||||
|
||||
NEGATIVE_PRICE = недостаточная стоимость
|
||||
|
||||
NO_BALANCE = нет баланса
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = блокчейн узла в настоящее время занят
|
||||
|
||||
NO_FLAG_PERMISSION = нет разрешения на флаг
|
||||
|
||||
NOT_GROUP_ADMIN = не администратор группы
|
||||
|
||||
NOT_GROUP_MEMBER = не член группы
|
||||
|
||||
NOT_MINTING_ACCOUNT = счет не чеканит
|
||||
|
||||
NOT_YET_RELEASED = еще не выпущено
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = заказ закрыт
|
||||
|
||||
ORDER_DOES_NOT_EXIST = заказа не существует
|
||||
|
||||
POLL_ALREADY_EXISTS = опрос уже существует
|
||||
|
||||
POLL_DOES_NOT_EXIST = опроса не существует
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = вариантов ответа не существует
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = открытый ключ неизвестен
|
||||
|
||||
REWARD_SHARE_UNKNOWN = доля награды неизвестна
|
||||
|
||||
SELF_SHARE_EXISTS = поделиться долей
|
||||
|
||||
TIMESTAMP_TOO_NEW = новая метка времени
|
||||
|
||||
TIMESTAMP_TOO_OLD = старая метка времени
|
||||
|
||||
TOO_MANY_UNCONFIRMED = много не подтвержденных
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = транзакция уже подтверждена
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = транзакция существует
|
||||
|
||||
TRANSACTION_UNKNOWN = неизвестная транзакция
|
||||
|
||||
TX_GROUP_ID_MISMATCH = не соответствие идентификатора группы в хэш транзации
|
||||
|
195
src/main/resources/i18n/TransactionValidity_sv.properties
Normal file
195
src/main/resources/i18n/TransactionValidity_sv.properties
Normal file
@@ -0,0 +1,195 @@
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = kontot finns redan
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = kontot kan inte dela ut en belöning
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = adressen nådde angiven gräns
|
||||
|
||||
ADDRESS_BLOCKED = denna adress är blockerad
|
||||
|
||||
ALREADY_GROUP_ADMIN = redan gruppadministratör
|
||||
|
||||
ALREADY_GROUP_MEMBER = redan gruppmedlem
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = redan röstat för det alternativet
|
||||
|
||||
ASSET_ALREADY_EXISTS = tillgången finns redan
|
||||
|
||||
ASSET_DOES_NOT_EXIST = tillgången finns inte
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = tillgången matchar inte AT:s tillgång
|
||||
|
||||
ASSET_NOT_SPENDABLE = tillgången är inte förbrukbar
|
||||
|
||||
AT_ALREADY_EXISTS = AT finns redan
|
||||
|
||||
AT_IS_FINISHED = AT har avslutats
|
||||
|
||||
AT_UNKNOWN = okänd AT
|
||||
|
||||
BAN_EXISTS = förbud finns redan
|
||||
|
||||
BAN_UNKNOWN = okänt förbud
|
||||
|
||||
BANNED_FROM_GROUP = avstängd från gruppen
|
||||
|
||||
BUYER_ALREADY_OWNER = köparen är redan ägare
|
||||
|
||||
CLOCK_NOT_SYNCED = klockan inte synkroniserad
|
||||
|
||||
DUPLICATE_MESSAGE = adress skickade dubblettmeddelande
|
||||
|
||||
DUPLICATE_OPTION = dubblettalternativ
|
||||
|
||||
GROUP_ALREADY_EXISTS = grupp finns redan
|
||||
|
||||
GROUP_APPROVAL_DECIDED = gruppgodkännande redan beslutat
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = gruppgodkännande krävs inte
|
||||
|
||||
GROUP_DOES_NOT_EXIST = grupp finns inte
|
||||
|
||||
GROUP_ID_MISMATCH = grupp-ID överensstämmer inte
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = gruppägare kan inte lämna gruppen
|
||||
|
||||
HAVE_EQUALS_WANT = ha-tillgång är detsamma som vill-tillgång
|
||||
|
||||
INCORRECT_NONCE = felaktig PoW nonce
|
||||
|
||||
INSUFFICIENT_FEE = otillräcklig avgift
|
||||
|
||||
INVALID_ADDRESS = ogiltig adress
|
||||
|
||||
INVALID_AMOUNT = ogiltigt belopp
|
||||
|
||||
INVALID_ASSET_OWNER = ogiltig tillgångsägare
|
||||
|
||||
INVALID_AT_TRANSACTION = ogiltig AT-transaktion
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = ogiltig AT-typlängd
|
||||
|
||||
INVALID_BUT_OK = ogiltig men OK
|
||||
|
||||
INVALID_CREATION_BYTES = ogiltiga skapande bytes
|
||||
|
||||
INVALID_DATA_LENGTH = ogiltig datalängd
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = ogiltig beskrivningslängd
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = ogiltig tröskel för gruppgodkännande
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = ogiltig grupp-godkännande fördröjning
|
||||
|
||||
INVALID_GROUP_ID = ogiltigt grupp-ID
|
||||
|
||||
INVALID_GROUP_OWNER = ogiltig gruppägare
|
||||
|
||||
INVALID_LIFETIME = ogiltig livstid
|
||||
|
||||
INVALID_NAME_LENGTH = ogiltig namnlängd
|
||||
|
||||
INVALID_NAME_OWNER = ogiltig namnägare
|
||||
|
||||
INVALID_OPTION_LENGTH = ogiltig alternativlängd
|
||||
|
||||
INVALID_OPTIONS_COUNT = ogiltigt antal alternativ
|
||||
|
||||
INVALID_ORDER_CREATOR = ogiltig orderskapare
|
||||
|
||||
INVALID_PAYMENTS_COUNT = ogiltiga antal betalningar
|
||||
|
||||
INVALID_PUBLIC_KEY = ogiltig offentlig nyckel
|
||||
|
||||
INVALID_QUANTITY = ogiltig kvantitet
|
||||
|
||||
INVALID_REFERENCE = ogiltig referens
|
||||
|
||||
INVALID_RETURN = ogiltig retur
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = ogiltig belöningsandelsprocent
|
||||
|
||||
INVALID_SELLER = ogiltig säljare
|
||||
|
||||
INVALID_TAGS_LENGTH = ogiltig 'tagg'-längd
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = ogiltig tidsstämpelsignatur
|
||||
|
||||
INVALID_TX_GROUP_ID = ogiltigt transaktionsgrupp-ID
|
||||
|
||||
INVALID_VALUE_LENGTH = ogiltig 'värdes'-längd
|
||||
|
||||
INVITE_UNKNOWN = okänd gruppinbjudan
|
||||
|
||||
JOIN_REQUEST_EXISTS = gruppanslutning finns redan
|
||||
|
||||
MAXIMUM_REWARD_SHARES = redan vid maximalt antal belöningsandelar för detta konto
|
||||
|
||||
MISSING_CREATOR = saknad skapare
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = flera registrerade namn per konto är förbjudet
|
||||
|
||||
NAME_ALREADY_FOR_SALE = namn redan till salu
|
||||
|
||||
NAME_ALREADY_REGISTERED = namn redan registrerat
|
||||
|
||||
NAME_BLOCKED = detta namn är blockerat
|
||||
|
||||
NAME_DOES_NOT_EXIST = namnet finns inte
|
||||
|
||||
NAME_NOT_FOR_SALE = namnet är inte till salu
|
||||
|
||||
NAME_NOT_NORMALIZED = namn inte i Unicode 'normaliserad' form
|
||||
|
||||
NEGATIVE_AMOUNT = ogiltigt/negativt belopp
|
||||
|
||||
NEGATIVE_FEE = ogiltig/negativ avgift
|
||||
|
||||
NEGATIVE_PRICE = ogiltigt/negativt pris
|
||||
|
||||
NO_BALANCE = otillräcklig balans
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = nodens blockchain för närvarande upptagen
|
||||
|
||||
NO_FLAG_PERMISSION = kontot har inte den behörigheten
|
||||
|
||||
NOT_GROUP_ADMIN = kontot är inte en gruppadministratör
|
||||
|
||||
NOT_GROUP_MEMBER = kontot är inte en gruppmedlem
|
||||
|
||||
NOT_MINTING_ACCOUNT = konto kan inte präglas
|
||||
|
||||
NOT_YET_RELEASED = funktionen ännu inte släppt
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = handelsorder för tillgångar är redan stängd
|
||||
|
||||
ORDER_DOES_NOT_EXIST = handelsorder för tillgångar finns inte
|
||||
|
||||
POLL_ALREADY_EXISTS = omröstning finns redan
|
||||
|
||||
POLL_DOES_NOT_EXIST = omröstning finns inte
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = omröstningsalternativet finns inte
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = offentlig nyckel okänd
|
||||
|
||||
REWARD_SHARE_UNKNOWN = okänd belöningsandel
|
||||
|
||||
SELF_SHARE_EXISTS = egenandel (belöningsandel) finns redan
|
||||
|
||||
TIMESTAMP_TOO_NEW = tidsstämpeln är för ny
|
||||
|
||||
TIMESTAMP_TOO_OLD = tidsstämpeln är för gammal
|
||||
|
||||
TOO_MANY_UNCONFIRMED = kontot har för många obekräftade transaktioner som väntar
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = transaktionen har redan bekräftats
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = transaktionen finns redan
|
||||
|
||||
TRANSACTION_UNKNOWN = okänd transaktion
|
||||
|
||||
TX_GROUP_ID_MISMATCH = transaktionens grupp-ID matchar inte
|
195
src/main/resources/i18n/TransactionValidity_zh_CN.properties
Normal file
195
src/main/resources/i18n/TransactionValidity_zh_CN.properties
Normal file
@@ -0,0 +1,195 @@
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = 账号已存在
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = 账户不能创建奖励分享
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = 账号已达到指定限制
|
||||
|
||||
ADDRESS_BLOCKED = 此账号已被封锁
|
||||
|
||||
ALREADY_GROUP_ADMIN = 你已是群组管理员
|
||||
|
||||
ALREADY_GROUP_MEMBER = 你已是群组的成员
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = 你已投票该选项
|
||||
|
||||
ASSET_ALREADY_EXISTS = 资产已存在
|
||||
|
||||
ASSET_DOES_NOT_EXIST = 资产不存在
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = 选择的资产与AT资产不吻合
|
||||
|
||||
ASSET_NOT_SPENDABLE = 资产不能使用
|
||||
|
||||
AT_ALREADY_EXISTS = AT已存在
|
||||
|
||||
AT_IS_FINISHED = AT已完成
|
||||
|
||||
AT_UNKNOWN = 未知的AT
|
||||
|
||||
BAN_EXISTS = 禁止功能已存在
|
||||
|
||||
BAN_UNKNOWN = 未知的禁止动作
|
||||
|
||||
BANNED_FROM_GROUP = 已被群组禁止进入
|
||||
|
||||
BUYER_ALREADY_OWNER = 购买者已是名称拥有者
|
||||
|
||||
CLOCK_NOT_SYNCED = 节点时间未同步
|
||||
|
||||
DUPLICATE_MESSAGE = 账号传送重复信息
|
||||
|
||||
DUPLICATE_OPTION = 重复的选项
|
||||
|
||||
GROUP_ALREADY_EXISTS = 群组已存在
|
||||
|
||||
GROUP_APPROVAL_DECIDED = 已审批
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = 不需审批
|
||||
|
||||
GROUP_DOES_NOT_EXIST = 群组不存在
|
||||
|
||||
GROUP_ID_MISMATCH = 群组ID不吻合
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = 群组拥有者不能离开群组
|
||||
|
||||
HAVE_EQUALS_WANT = 已存在相同的资产名称
|
||||
|
||||
INCORRECT_NONCE = 错误的PoW数值
|
||||
|
||||
INSUFFICIENT_FEE = 手续费不足
|
||||
|
||||
INVALID_ADDRESS = 无效的钱包地址
|
||||
|
||||
INVALID_AMOUNT = 无效的数量
|
||||
|
||||
INVALID_ASSET_OWNER = 无效的资产拥有者
|
||||
|
||||
INVALID_AT_TRANSACTION = 无效的AT交易
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = 无效的AT种类长度
|
||||
|
||||
INVALID_BUT_OK = 无效但不影响
|
||||
|
||||
INVALID_CREATION_BYTES = 无效的创建字节
|
||||
|
||||
INVALID_DATA_LENGTH = 无效的数据长度
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = 无效的描述长度
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = 无效的群组审批门槛
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = 无效的群组审批延迟值
|
||||
|
||||
INVALID_GROUP_ID = 无效的群组ID
|
||||
|
||||
INVALID_GROUP_OWNER = 无效的群组创建者
|
||||
|
||||
INVALID_LIFETIME = 无效的周期
|
||||
|
||||
INVALID_NAME_LENGTH = 无效的名称长度
|
||||
|
||||
INVALID_NAME_OWNER = 无效的名称拥有者
|
||||
|
||||
INVALID_OPTION_LENGTH = 无效的选项长度
|
||||
|
||||
INVALID_OPTIONS_COUNT = 无效的选项
|
||||
|
||||
INVALID_ORDER_CREATOR = 无效的交易创建者
|
||||
|
||||
INVALID_PAYMENTS_COUNT = 无效的付款
|
||||
|
||||
INVALID_PUBLIC_KEY = 无效的公共密钥
|
||||
|
||||
INVALID_QUANTITY = 无效的数量
|
||||
|
||||
INVALID_REFERENCE = 无效的参考资料
|
||||
|
||||
INVALID_RETURN = 无效的回应
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = 无效的奖励分享百分比
|
||||
|
||||
INVALID_SELLER = 无效的卖家
|
||||
|
||||
INVALID_TAGS_LENGTH = 无效的“标记”长度
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = 无效的时间戳签名
|
||||
|
||||
INVALID_TX_GROUP_ID = 无效的群组ID交易
|
||||
|
||||
INVALID_VALUE_LENGTH = 无效的数值长度
|
||||
|
||||
INVITE_UNKNOWN = 未知的群组邀请
|
||||
|
||||
JOIN_REQUEST_EXISTS = 加入群组的请求已存在
|
||||
|
||||
MAXIMUM_REWARD_SHARES = 奖励分享次数已达到上限
|
||||
|
||||
MISSING_CREATOR = 请填写创建者
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = 禁止注册多个名称
|
||||
|
||||
NAME_ALREADY_FOR_SALE = 名称可被购买
|
||||
|
||||
NAME_ALREADY_REGISTERED = 名称已被注册
|
||||
|
||||
NAME_BLOCKED = 此名称已被封锁
|
||||
|
||||
NAME_DOES_NOT_EXIST = 名称不存在
|
||||
|
||||
NAME_NOT_FOR_SALE = 名称为非卖品
|
||||
|
||||
NAME_NOT_NORMALIZED = 名称必须使用Unicode标准格式
|
||||
|
||||
NEGATIVE_AMOUNT = 无效数量或负数
|
||||
|
||||
NEGATIVE_FEE = 无效或负数的手续费
|
||||
|
||||
NEGATIVE_PRICE = 无效/负数标价
|
||||
|
||||
NO_BALANCE = 余额不足
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = 节点现时忙碌
|
||||
|
||||
NO_FLAG_PERMISSION = 此账号没有该权限
|
||||
|
||||
NOT_GROUP_ADMIN = 此地址不是群组管理员
|
||||
|
||||
NOT_GROUP_MEMBER = 此地址不是群组成员
|
||||
|
||||
NOT_MINTING_ACCOUNT = 账号不能铸币
|
||||
|
||||
NOT_YET_RELEASED = 功能未启用
|
||||
|
||||
OK = 确定
|
||||
|
||||
ORDER_ALREADY_CLOSED = 资产交易已关闭
|
||||
|
||||
ORDER_DOES_NOT_EXIST = 资产交易不存在
|
||||
|
||||
POLL_ALREADY_EXISTS = 投票已存在
|
||||
|
||||
POLL_DOES_NOT_EXIST = 投票不存在
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = 投票选项不存在
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = 未知的公共密钥
|
||||
|
||||
REWARD_SHARE_UNKNOWN = 未知的奖励分享
|
||||
|
||||
SELF_SHARE_EXISTS = 奖励分享已存在
|
||||
|
||||
TIMESTAMP_TOO_NEW = 时间戳太新
|
||||
|
||||
TIMESTAMP_TOO_OLD = 时间戳太久
|
||||
|
||||
TOO_MANY_UNCONFIRMED = 钱包有太多未确认的交易等待进行
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = 此交易已确认
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = 此交易已存在
|
||||
|
||||
TRANSACTION_UNKNOWN = 未知的交易
|
||||
|
||||
TX_GROUP_ID_MISMATCH = 群组ID交易不吻合
|
195
src/main/resources/i18n/TransactionValidity_zh_TW.properties
Normal file
195
src/main/resources/i18n/TransactionValidity_zh_TW.properties
Normal file
@@ -0,0 +1,195 @@
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = 賬號已存在
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = 賬戶不能創建獎勵分享
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = 賬號已達到指定限制
|
||||
|
||||
ADDRESS_BLOCKED = 此賬號已被封鎖
|
||||
|
||||
ALREADY_GROUP_ADMIN = 你已是群組管理員
|
||||
|
||||
ALREADY_GROUP_MEMBER = 你已是群組的成員
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = 你已投票該選項
|
||||
|
||||
ASSET_ALREADY_EXISTS = 資產已存在
|
||||
|
||||
ASSET_DOES_NOT_EXIST = 資產不存在
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = 選擇的資產與AT資產不吻合
|
||||
|
||||
ASSET_NOT_SPENDABLE = 資產不能使用
|
||||
|
||||
AT_ALREADY_EXISTS = AT已存在
|
||||
|
||||
AT_IS_FINISHED = AT已完成
|
||||
|
||||
AT_UNKNOWN = 未知的AT
|
||||
|
||||
BAN_EXISTS = 禁止功能已存在
|
||||
|
||||
BAN_UNKNOWN = 未知的禁止動作
|
||||
|
||||
BANNED_FROM_GROUP = 已被群組禁止進入
|
||||
|
||||
BUYER_ALREADY_OWNER = 購買者已是名稱擁有者
|
||||
|
||||
CLOCK_NOT_SYNCED = 節點時間未同步
|
||||
|
||||
DUPLICATE_MESSAGE = 賬號傳送重複信息
|
||||
|
||||
DUPLICATE_OPTION = 重複的選項
|
||||
|
||||
GROUP_ALREADY_EXISTS = 群組已存在
|
||||
|
||||
GROUP_APPROVAL_DECIDED = 已審批
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = 不需審批
|
||||
|
||||
GROUP_DOES_NOT_EXIST = 群組不存在
|
||||
|
||||
GROUP_ID_MISMATCH = 群組ID不吻合
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = 群組擁有者不能離開群組
|
||||
|
||||
HAVE_EQUALS_WANT = 已存在相同的資產名稱
|
||||
|
||||
INCORRECT_NONCE = 錯誤的PoW數值
|
||||
|
||||
INSUFFICIENT_FEE = 手續費不足
|
||||
|
||||
INVALID_ADDRESS = 無效的錢包地址
|
||||
|
||||
INVALID_AMOUNT = 無效的數量
|
||||
|
||||
INVALID_ASSET_OWNER = 無效的資產擁有者
|
||||
|
||||
INVALID_AT_TRANSACTION = 無效的AT交易
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = 無效的AT種類長度
|
||||
|
||||
INVALID_BUT_OK = 無效但不影響
|
||||
|
||||
INVALID_CREATION_BYTES = 無效的創建字節
|
||||
|
||||
INVALID_DATA_LENGTH = 無效的數據長度
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = 無效的描述長度
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = 無效的群組審批門檻
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = 無效的群組審批延遲值
|
||||
|
||||
INVALID_GROUP_ID = 無效的群組ID
|
||||
|
||||
INVALID_GROUP_OWNER = 無效的群組創建者
|
||||
|
||||
INVALID_LIFETIME = 無效的周期
|
||||
|
||||
INVALID_NAME_LENGTH = 無效的名稱長度
|
||||
|
||||
INVALID_NAME_OWNER = 無效的名稱擁有者
|
||||
|
||||
INVALID_OPTION_LENGTH = 無效的選項長度
|
||||
|
||||
INVALID_OPTIONS_COUNT = 無效的選項
|
||||
|
||||
INVALID_ORDER_CREATOR = 無效的交易創建者
|
||||
|
||||
INVALID_PAYMENTS_COUNT = 無效的付款
|
||||
|
||||
INVALID_PUBLIC_KEY = 無效的公共密鑰
|
||||
|
||||
INVALID_QUANTITY = 無效的數量
|
||||
|
||||
INVALID_REFERENCE = 無效的參考資料
|
||||
|
||||
INVALID_RETURN = 無效的回應
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = 無效的獎勵分享百分比
|
||||
|
||||
INVALID_SELLER = 無效的賣家
|
||||
|
||||
INVALID_TAGS_LENGTH = 無效的“標記”長度
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = 無效的時間戳簽名
|
||||
|
||||
INVALID_TX_GROUP_ID = 無效的群組ID交易
|
||||
|
||||
INVALID_VALUE_LENGTH = 無效的數值長度
|
||||
|
||||
INVITE_UNKNOWN = 未知的群組邀請
|
||||
|
||||
JOIN_REQUEST_EXISTS = 加入群組的請求已存在
|
||||
|
||||
MAXIMUM_REWARD_SHARES = 獎勵分享次數已達到上限
|
||||
|
||||
MISSING_CREATOR = 請填寫創建者
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = 禁止註冊多個名稱
|
||||
|
||||
NAME_ALREADY_FOR_SALE = 名稱可被購買
|
||||
|
||||
NAME_ALREADY_REGISTERED = 名稱已被註冊
|
||||
|
||||
NAME_BLOCKED = 此名稱已被封鎖
|
||||
|
||||
NAME_DOES_NOT_EXIST = 名稱不存在
|
||||
|
||||
NAME_NOT_FOR_SALE = 名稱為非賣品
|
||||
|
||||
NAME_NOT_NORMALIZED = 名稱必須使用Unicode標準格式
|
||||
|
||||
NEGATIVE_AMOUNT = 無效數量或負數
|
||||
|
||||
NEGATIVE_FEE = 無效或負數的手續費
|
||||
|
||||
NEGATIVE_PRICE = 無效/負數標價
|
||||
|
||||
NO_BALANCE = 餘額不足
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = 節點現時忙碌
|
||||
|
||||
NO_FLAG_PERMISSION = 此賬號沒有該權限
|
||||
|
||||
NOT_GROUP_ADMIN = 此地址不是群組管理員
|
||||
|
||||
NOT_GROUP_MEMBER = 此地址不是群組成員
|
||||
|
||||
NOT_MINTING_ACCOUNT = 賬號不能鑄幣
|
||||
|
||||
NOT_YET_RELEASED = 功能未啓用
|
||||
|
||||
OK = 確定
|
||||
|
||||
ORDER_ALREADY_CLOSED = 資產交易已關閉
|
||||
|
||||
ORDER_DOES_NOT_EXIST = 資產交易不存在
|
||||
|
||||
POLL_ALREADY_EXISTS = 投票已存在
|
||||
|
||||
POLL_DOES_NOT_EXIST = 投票不存在
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = 投票選項不存在
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = 未知的公共密鑰
|
||||
|
||||
REWARD_SHARE_UNKNOWN = 未知的獎勵分享
|
||||
|
||||
SELF_SHARE_EXISTS = 獎勵分享已存在
|
||||
|
||||
TIMESTAMP_TOO_NEW = 時間戳太新
|
||||
|
||||
TIMESTAMP_TOO_OLD = 時間戳太久
|
||||
|
||||
TOO_MANY_UNCONFIRMED = 錢包有太多未確認的交易等待進行
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = 此交易已確認
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = 此交易已存在
|
||||
|
||||
TRANSACTION_UNKNOWN = 未知的交易
|
||||
|
||||
TX_GROUP_ID_MISMATCH = 群組ID交易不吻合
|
@@ -17,6 +17,8 @@ import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
@@ -206,6 +208,37 @@ public class BootstrapTests extends Common {
|
||||
assertEquals(uniqueHosts.size(), Arrays.asList(bootstrapHosts).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBootstrapHosts() throws IOException {
|
||||
String[] bootstrapHosts = Settings.getInstance().getBootstrapHosts();
|
||||
String[] bootstrapTypes = { "archive", "toponly" };
|
||||
|
||||
for (String host : bootstrapHosts) {
|
||||
for (String type : bootstrapTypes) {
|
||||
String bootstrapFilename = String.format("bootstrap-%s.7z", type);
|
||||
String bootstrapUrl = String.format("%s/%s", host, bootstrapFilename);
|
||||
|
||||
// Make a HEAD request to check the status of each bootstrap file
|
||||
URL url = new URL(bootstrapUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("HEAD");
|
||||
connection.connect();
|
||||
long fileSize = connection.getContentLengthLong();
|
||||
long lastModified = connection.getLastModified();
|
||||
connection.disconnect();
|
||||
|
||||
// Ensure the bootstrap exists and has a size greated than 100MiB
|
||||
System.out.println(String.format("%s %s size is %d bytes", host, type, fileSize));
|
||||
assertTrue("Bootstrap size must be at least 100MiB", fileSize > 100*1024*1024L);
|
||||
|
||||
// Ensure the bootstrap has been published recently (in the last 3 days)
|
||||
long minimumLastMofifiedTimestamp = NTP.getTime() - (3 * 24 * 60 * 60 * 1000L);
|
||||
System.out.println(String.format("%s %s last modified timestamp is %d", host, type, lastModified));
|
||||
assertTrue("Bootstrap last modified date must be in the last 3 days", lastModified > minimumLastMofifiedTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteBootstraps() throws IOException {
|
||||
try {
|
||||
Path archivePath = Paths.get(String.format("%s%s", Settings.getInstance().getBootstrapFilenamePrefix(), "bootstrap-archive.7z"));
|
||||
|
@@ -32,19 +32,40 @@ public class ByteArrayTests {
|
||||
|
||||
private static void fillMap(Map<ByteArray, String> map) {
|
||||
for (byte[] testValue : testValues)
|
||||
map.put(new ByteArray(testValue), String.valueOf(map.size()));
|
||||
map.put(ByteArray.wrap(testValue), String.valueOf(map.size()));
|
||||
}
|
||||
|
||||
private static byte[] dup(byte[] value) {
|
||||
return Arrays.copyOf(value, value.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
public void testOriginatingIssue() {
|
||||
Map<byte[], String> testMap = new HashMap<>();
|
||||
|
||||
byte[] someValue = testValues.get(3);
|
||||
testMap.put(someValue, "someValue");
|
||||
|
||||
byte[] copiedValue = dup(someValue);
|
||||
|
||||
// Show that a byte[] with same values is not found
|
||||
System.out.printf("byte[] hashCode: 0x%08x%n", someValue.hashCode());
|
||||
System.out.printf("duplicated byte[] hashCode: 0x%08x%n", copiedValue.hashCode());
|
||||
|
||||
/*
|
||||
* Unfortunately this doesn't work because HashMap::containsKey compares hashCodes first,
|
||||
* followed by object references, and copiedValue.hashCode() will never match someValue.hashCode().
|
||||
*/
|
||||
assertFalse("byte[] with same values, but difference reference, not found", testMap.containsKey(copiedValue));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameContentReference() {
|
||||
// Create two objects, which will have different references, but same content.
|
||||
// Create two objects, which will have different references, but same content references.
|
||||
byte[] testValue = testValues.get(0);
|
||||
ByteArray ba1 = new ByteArray(testValue);
|
||||
ByteArray ba2 = new ByteArray(testValue);
|
||||
ByteArray ba1 = ByteArray.wrap(testValue);
|
||||
ByteArray ba2 = ByteArray.wrap(testValue);
|
||||
|
||||
// Confirm JVM-assigned references are different
|
||||
assertNotSame(ba1, ba2);
|
||||
@@ -58,13 +79,31 @@ public class ByteArrayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameContentValue() {
|
||||
// Create two objects, which will have different references, but same content.
|
||||
public void testSameWrappedContentValue() {
|
||||
// Create two objects, which will have different references, and different content references, but same content values.
|
||||
byte[] testValue = testValues.get(0);
|
||||
ByteArray ba1 = new ByteArray(testValue);
|
||||
ByteArray ba1 = ByteArray.wrap(testValue);
|
||||
|
||||
byte[] copiedValue = dup(testValue);
|
||||
ByteArray ba2 = new ByteArray(copiedValue);
|
||||
ByteArray ba2 = ByteArray.wrap(copiedValue);
|
||||
|
||||
// Confirm JVM-assigned references are different
|
||||
assertNotSame(ba1, ba2);
|
||||
|
||||
// Confirm "equals" works as intended
|
||||
assertTrue("equals did not return true", ba1.equals(ba2));
|
||||
assertEquals("ba1 not equal to ba2", ba1, ba2);
|
||||
|
||||
// Confirm "hashCode" results match
|
||||
assertEquals("hashCodes do not match", ba1.hashCode(), ba2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameCopiedContentValue() {
|
||||
// Create two objects, which will have different references, and different content references, but same content values.
|
||||
byte[] testValue = testValues.get(0);
|
||||
ByteArray ba1 = ByteArray.wrap(testValue);
|
||||
ByteArray ba2 = ByteArray.copyOf(testValue);
|
||||
|
||||
// Confirm JVM-assigned references are different
|
||||
assertNotSame(ba1, ba2);
|
||||
@@ -81,13 +120,17 @@ public class ByteArrayTests {
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
public void testCompareBoxedWithPrimitive() {
|
||||
byte[] testValue = testValues.get(0);
|
||||
ByteArray ba1 = new ByteArray(testValue);
|
||||
ByteArray wrappedByteArray = ByteArray.wrap(testValue);
|
||||
|
||||
byte[] copiedValue = dup(testValue);
|
||||
ByteArray copiedByteArray = ByteArray.copyOf(copiedValue);
|
||||
|
||||
// Confirm "equals" works as intended
|
||||
assertTrue("equals did not return true", ba1.equals(copiedValue));
|
||||
assertEquals("boxed not equal to primitive", ba1, copiedValue);
|
||||
assertTrue("equals did not return true", wrappedByteArray.equals(copiedValue));
|
||||
assertEquals("boxed not equal to primitive", wrappedByteArray, copiedValue);
|
||||
|
||||
assertTrue("equals did not return true", copiedByteArray.equals(testValue));
|
||||
assertEquals("boxed not equal to primitive", copiedByteArray, testValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,7 +141,7 @@ public class ByteArrayTests {
|
||||
|
||||
// Create new ByteArray object with an existing value.
|
||||
byte[] copiedValue = dup(testValues.get(3));
|
||||
ByteArray ba = new ByteArray(copiedValue);
|
||||
ByteArray ba = ByteArray.wrap(copiedValue);
|
||||
|
||||
// Confirm object can be found in map
|
||||
assertTrue("ByteArray not found in map", testMap.containsKey(ba));
|
||||
@@ -120,7 +163,7 @@ public class ByteArrayTests {
|
||||
|
||||
// Create new ByteArray object with an existing value.
|
||||
byte[] copiedValue = dup(testValues.get(3));
|
||||
ByteArray ba = new ByteArray(copiedValue);
|
||||
ByteArray ba = ByteArray.wrap(copiedValue);
|
||||
|
||||
// Confirm object can be found in map
|
||||
assertTrue("ByteArray not found in map", testMap.containsKey(ba));
|
||||
@@ -128,7 +171,7 @@ public class ByteArrayTests {
|
||||
assertTrue("boxed not equal to primitive", ba.equals(copiedValue));
|
||||
|
||||
/*
|
||||
* Unfortunately this doesn't work because TreeMap::containsKey(x) wants to cast x to
|
||||
* Unfortunately this doesn't work because TreeMap::containsKey(byte[]) wants to cast byte[] to
|
||||
* Comparable<? super ByteArray> and byte[] does not fit <? super ByteArray>
|
||||
* so this throws a ClassCastException.
|
||||
*/
|
||||
@@ -145,7 +188,7 @@ public class ByteArrayTests {
|
||||
public void testArrayListContains() {
|
||||
// Create new ByteArray object with an existing value.
|
||||
byte[] copiedValue = dup(testValues.get(3));
|
||||
ByteArray ba = new ByteArray(copiedValue);
|
||||
ByteArray ba = ByteArray.wrap(copiedValue);
|
||||
|
||||
// Confirm object can be found in list
|
||||
assertTrue("ByteArray not found in map", testValues.contains(ba));
|
||||
@@ -154,7 +197,7 @@ public class ByteArrayTests {
|
||||
|
||||
/*
|
||||
* Unfortunately this doesn't work because ArrayList::contains performs
|
||||
* copiedValue.equals(x) for each x in testValues, and byte[].equals()
|
||||
* copiedValue.equals(byte[]) for each byte[] in testValues, and byte[].equals()
|
||||
* simply compares object references, so will never match any ByteArray.
|
||||
*/
|
||||
assertFalse("Primitive shouldn't be found in ArrayList", testValues.contains(copiedValue));
|
||||
@@ -163,23 +206,25 @@ public class ByteArrayTests {
|
||||
@Test
|
||||
public void debugBoxedVersusPrimitive() {
|
||||
byte[] testValue = testValues.get(0);
|
||||
ByteArray ba1 = new ByteArray(testValue);
|
||||
ByteArray ba1 = ByteArray.wrap(testValue);
|
||||
|
||||
byte[] copiedValue = dup(testValue);
|
||||
|
||||
System.out.println(String.format("Primitive hashCode: 0x%08x", testValue.hashCode()));
|
||||
System.out.println(String.format("Boxed hashCode: 0x%08x", ba1.hashCode()));
|
||||
System.out.println(String.format("Duplicated primitive hashCode: 0x%08x", copiedValue.hashCode()));
|
||||
System.out.printf("Primitive hashCode: 0x%08x%n", testValue.hashCode());
|
||||
System.out.printf("Boxed hashCode: 0x%08x%n", ba1.hashCode());
|
||||
System.out.printf("Duplicated primitive hashCode: 0x%08x%n", copiedValue.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareTo() {
|
||||
ByteArray testValue0 = new ByteArray(new byte[] { 0x00 });
|
||||
ByteArray testValue1 = new ByteArray(new byte[] { 0x01 });
|
||||
ByteArray testValue0 = ByteArray.wrap(new byte[] { 0x00 });
|
||||
ByteArray testValue1 = ByteArray.wrap(new byte[] { 0x01 });
|
||||
ByteArray testValueFf = ByteArray.wrap(new byte[] {(byte) 0xFF});
|
||||
|
||||
assertEquals("0 should be the same as 0", 0, testValue0.compareTo(testValue0));
|
||||
assertEquals("0 should be before 1", -1, testValue0.compareTo(testValue1));
|
||||
assertEquals("1 should be after 0", 1, testValue1.compareTo(testValue0));
|
||||
assertTrue("0 should be the same as 0", testValue0.compareTo(testValue0) == 0);
|
||||
assertTrue("0 should be before 1", testValue0.compareTo(testValue1) < 0);
|
||||
assertTrue("1 should be after 0", testValue1.compareTo(testValue0) > 0);
|
||||
assertTrue("FF should be after 0", testValueFf.compareTo(testValue0) > 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -35,4 +35,41 @@ public class UnicodeTests {
|
||||
assertEquals("strings should match", Unicode.sanitize(input1), Unicode.sanitize(input2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmojis() {
|
||||
/*
|
||||
* Emojis shouldn't reduce down to empty strings.
|
||||
*
|
||||
* 🥳 Face with Party Horn and Party Hat Emoji U+1F973
|
||||
*/
|
||||
String emojis = "\uD83E\uDD73";
|
||||
|
||||
assertFalse(Unicode.sanitize(emojis).isBlank());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanitize() {
|
||||
/*
|
||||
* Check various code points that should be stripped out when sanitizing / reducing
|
||||
*/
|
||||
String enclosingCombiningMark = "\u1100\u1161\u20DD"; // \u20DD is an enclosing combining mark and should be removed
|
||||
String spacingMark = "\u0A39\u0A3f"; // \u0A3f is spacing combining mark and should be removed
|
||||
String nonspacingMark = "c\u0302"; // \u0302 is a non-spacing combining mark and should be removed
|
||||
|
||||
assertNotSame(enclosingCombiningMark, Unicode.sanitize(enclosingCombiningMark));
|
||||
assertNotSame(spacingMark, Unicode.sanitize(spacingMark));
|
||||
assertNotSame(nonspacingMark, Unicode.sanitize(nonspacingMark));
|
||||
|
||||
String control = "\u001B\u009E"; // \u001B and \u009E are a control codes
|
||||
String format = "\u202A\u2062"; // \u202A and \u2062 are zero-width formatting codes
|
||||
String surrogate = "\uD800\uDFFF"; // surrogates
|
||||
String privateUse = "\uE1E0"; // \uE000 - \uF8FF is private use area
|
||||
String unassigned = "\uFAFA"; // \uFAFA is currently unassigned
|
||||
|
||||
assertTrue(Unicode.sanitize(control).isBlank());
|
||||
assertTrue(Unicode.sanitize(format).isBlank());
|
||||
assertTrue(Unicode.sanitize(surrogate).isBlank());
|
||||
assertTrue(Unicode.sanitize(privateUse).isBlank());
|
||||
assertTrue(Unicode.sanitize(unassigned).isBlank());
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,118 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleChunkWithMetadata() throws DataException, IOException, MissingDataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String publicKey58 = Base58.encode(alice.getPublicKey());
|
||||
String name = "TEST"; // Can be anything for this test
|
||||
String identifier = null; // Not used for this test
|
||||
Service service = Service.ARBITRARY_DATA;
|
||||
int chunkSize = 1000;
|
||||
int dataLength = 10; // Actual data length will be longer due to encryption
|
||||
|
||||
String title = "Test title";
|
||||
String description = "Test description";
|
||||
List<String> tags = Arrays.asList("Test", "tag", "another tag");
|
||||
Category category = Category.QORTAL;
|
||||
|
||||
// Register the name to Alice
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
|
||||
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
assertEquals(0, arbitraryDataFile.chunkCount());
|
||||
|
||||
// Check the metadata is correct
|
||||
assertEquals(title, arbitraryDataFile.getMetadata().getTitle());
|
||||
assertEquals(description, arbitraryDataFile.getMetadata().getDescription());
|
||||
assertEquals(tags, arbitraryDataFile.getMetadata().getTags());
|
||||
assertEquals(category, arbitraryDataFile.getMetadata().getCategory());
|
||||
|
||||
// Now build the latest data state for this name
|
||||
ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(name, ResourceIdType.NAME, service, identifier);
|
||||
arbitraryDataReader.loadSynchronously(true);
|
||||
Path initialLayerPath = arbitraryDataReader.getFilePath();
|
||||
ArbitraryDataDigest initialLayerDigest = new ArbitraryDataDigest(initialLayerPath);
|
||||
initialLayerDigest.compute();
|
||||
|
||||
// Its directory hash should match the original directory hash
|
||||
ArbitraryDataDigest path1Digest = new ArbitraryDataDigest(path1);
|
||||
path1Digest.compute();
|
||||
assertEquals(path1Digest.getHash58(), initialLayerDigest.getHash58());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleNonLocalChunkWithMetadata() throws DataException, IOException, MissingDataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String publicKey58 = Base58.encode(alice.getPublicKey());
|
||||
String name = "TEST"; // Can be anything for this test
|
||||
String identifier = null; // Not used for this test
|
||||
Service service = Service.ARBITRARY_DATA;
|
||||
int chunkSize = 1000;
|
||||
int dataLength = 10; // Actual data length will be longer due to encryption
|
||||
|
||||
String title = "Test title";
|
||||
String description = "Test description";
|
||||
List<String> tags = Arrays.asList("Test", "tag", "another tag");
|
||||
Category category = Category.QORTAL;
|
||||
|
||||
// Register the name to Alice
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
|
||||
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
assertEquals(0, arbitraryDataFile.chunkCount());
|
||||
|
||||
// Check the metadata is correct
|
||||
assertEquals(title, arbitraryDataFile.getMetadata().getTitle());
|
||||
assertEquals(description, arbitraryDataFile.getMetadata().getDescription());
|
||||
assertEquals(tags, arbitraryDataFile.getMetadata().getTags());
|
||||
assertEquals(category, arbitraryDataFile.getMetadata().getCategory());
|
||||
|
||||
// Delete the file, to simulate that it hasn't been fetched from the network yet
|
||||
arbitraryDataFile.delete();
|
||||
|
||||
boolean missingDataExceptionCaught = false;
|
||||
boolean ioExceptionCaught = false;
|
||||
|
||||
// Now build the latest data state for this name
|
||||
ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(name, ResourceIdType.NAME, service, identifier);
|
||||
try {
|
||||
arbitraryDataReader.loadSynchronously(true);
|
||||
}
|
||||
catch (MissingDataException e) {
|
||||
missingDataExceptionCaught = true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
ioExceptionCaught = true;
|
||||
}
|
||||
|
||||
// We expect a MissingDataException, not an IOException.
|
||||
// This is because MissingDataException means that the core has correctly identified a file is missing,
|
||||
// whereas an IOException would be due to trying to build without first having everything that is needed.
|
||||
assertTrue(missingDataExceptionCaught);
|
||||
assertFalse(ioExceptionCaught);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDescriptiveMetadata() throws DataException, IOException, MissingDataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
@@ -0,0 +1,186 @@
|
||||
package org.qortal.test.at.qortalfunctioncodes;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.ciyam.at.CompilationException;
|
||||
import org.ciyam.at.FunctionCode;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.at.QortalFunctionCode;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class GetAccountBlocksMintedTests extends Common {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final long fundingAmount = 1_00000000L;
|
||||
|
||||
private Repository repository = null;
|
||||
private byte[] creationBytes = null;
|
||||
private PrivateKeyAccount deployer;
|
||||
private DeployAtTransaction deployAtTransaction;
|
||||
private String atAddress;
|
||||
|
||||
@Before
|
||||
public void before() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
|
||||
this.repository = RepositoryManager.getRepository();
|
||||
this.deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws DataException {
|
||||
if (this.repository != null)
|
||||
this.repository.close();
|
||||
|
||||
this.repository = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountBlocksMintedFromAddress() throws DataException {
|
||||
Account alice = Common.getTestAccount(repository, "alice");
|
||||
byte[] accountBytes = Bytes.ensureCapacity(Base58.decode(alice.getAddress()), 32, 0);
|
||||
|
||||
this.creationBytes = buildGetAccountBlocksMintedAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run - Alice's blocksMinted is incremented AFTER block is processed / AT is run
|
||||
Integer expectedBlocksMinted = alice.getBlocksMinted();
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedBlocksMinted = extractBlocksMinted(repository, atAddress);
|
||||
assertEquals(expectedBlocksMinted, extractedBlocksMinted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountBlocksMintedFromPublicKey() throws DataException {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
byte[] accountBytes = alice.getPublicKey();
|
||||
|
||||
this.creationBytes = buildGetAccountBlocksMintedAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run - Alice's blocksMinted is incremented AFTER block is processed / AT is run
|
||||
Integer expectedBlocksMinted = alice.getBlocksMinted();
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedBlocksMinted = extractBlocksMinted(repository, atAddress);
|
||||
assertEquals(expectedBlocksMinted, extractedBlocksMinted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknownAccountBlocksMinted() throws DataException {
|
||||
byte[] accountBytes = new byte[32];
|
||||
RANDOM.nextBytes(accountBytes);
|
||||
|
||||
this.creationBytes = buildGetAccountBlocksMintedAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run - Alice's blocksMinted is incremented AFTER block is processed / AT is run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedBlocksMinted = extractBlocksMinted(repository, atAddress);
|
||||
assertNull(extractedBlocksMinted);
|
||||
}
|
||||
|
||||
private static byte[] buildGetAccountBlocksMintedAT(byte[] accountBytes) {
|
||||
// Labels for data segment addresses
|
||||
int addrCounter = 0;
|
||||
|
||||
// Beginning of data segment for easy extraction
|
||||
final int addrBlocksMinted = addrCounter++;
|
||||
|
||||
// accountBytes
|
||||
final int addrAccountBytes = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
// Pointer to accountBytes so we can load them into B
|
||||
final int addrAccountBytesPointer = addrCounter++;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
// Write accountBytes
|
||||
dataByteBuffer.position(addrAccountBytes * MachineState.VALUE_SIZE);
|
||||
dataByteBuffer.put(accountBytes);
|
||||
|
||||
// Store pointer to addrAccountbytes at addrAccountBytesPointer
|
||||
assertEquals(addrAccountBytesPointer * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.putLong(addrAccountBytes);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
/* Initialization */
|
||||
|
||||
// Copy accountBytes from data segment into B, starting at addrAccountBytes (as pointed to by addrAccountBytesPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAccountBytesPointer));
|
||||
|
||||
// Get account's blocks minted count and save into addrBlocksMinted
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_BLOCKS_MINTED_FROM_ACCOUNT_IN_B.value, addrBlocksMinted));
|
||||
|
||||
// We're done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
private Integer extractBlocksMinted(Repository repository, String atAddress) throws DataException {
|
||||
// Check AT result
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
|
||||
byte[] dataBytes = MachineState.extractDataBytes(stateData);
|
||||
|
||||
Long blocksMintedValue = BitTwiddling.longFromBEBytes(dataBytes, 0);
|
||||
if (blocksMintedValue == -1)
|
||||
return null;
|
||||
|
||||
return blocksMintedValue.intValue();
|
||||
}
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
package org.qortal.test.at.qortalfunctioncodes;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.ciyam.at.CompilationException;
|
||||
import org.ciyam.at.FunctionCode;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.at.QortalFunctionCode;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.AtUtils;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class GetAccountLevelTests extends Common {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final long fundingAmount = 1_00000000L;
|
||||
|
||||
private Repository repository = null;
|
||||
private byte[] creationBytes = null;
|
||||
private PrivateKeyAccount deployer;
|
||||
private DeployAtTransaction deployAtTransaction;
|
||||
private String atAddress;
|
||||
|
||||
@Before
|
||||
public void before() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
|
||||
this.repository = RepositoryManager.getRepository();
|
||||
this.deployer = Common.getTestAccount(repository, "alice");
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws DataException {
|
||||
if (this.repository != null)
|
||||
this.repository.close();
|
||||
|
||||
this.repository = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountLevelFromAddress() throws DataException {
|
||||
Account dilbert = Common.getTestAccount(repository, "dilbert");
|
||||
byte[] accountBytes = Bytes.ensureCapacity(Base58.decode(dilbert.getAddress()), 32, 0);
|
||||
|
||||
this.creationBytes = buildGetAccountLevelAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedAccountLevel = extractAccountLevel(repository, atAddress);
|
||||
assertEquals(dilbert.getLevel(), extractedAccountLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountLevelFromPublicKey() throws DataException {
|
||||
TestAccount dilbert = Common.getTestAccount(repository, "dilbert");
|
||||
byte[] accountBytes = dilbert.getPublicKey();
|
||||
|
||||
this.creationBytes = buildGetAccountLevelAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedAccountLevel = extractAccountLevel(repository, atAddress);
|
||||
assertEquals(dilbert.getLevel(), extractedAccountLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknownAccountLevel() throws DataException {
|
||||
byte[] accountBytes = new byte[32];
|
||||
RANDOM.nextBytes(accountBytes);
|
||||
|
||||
this.creationBytes = buildGetAccountLevelAT(accountBytes);
|
||||
|
||||
this.deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
this.atAddress = deployAtTransaction.getATAccount().getAddress();
|
||||
|
||||
// Mint a block to allow AT to run
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
Integer extractedAccountLevel = extractAccountLevel(repository, atAddress);
|
||||
assertNull(extractedAccountLevel);
|
||||
}
|
||||
|
||||
private static byte[] buildGetAccountLevelAT(byte[] accountBytes) {
|
||||
// Labels for data segment addresses
|
||||
int addrCounter = 0;
|
||||
|
||||
// Beginning of data segment for easy extraction
|
||||
final int addrAccountLevel = addrCounter++;
|
||||
|
||||
// accountBytes
|
||||
final int addrAccountBytes = addrCounter;
|
||||
addrCounter += 4;
|
||||
|
||||
// Pointer to accountBytes so we can load them into B
|
||||
final int addrAccountBytesPointer = addrCounter++;
|
||||
|
||||
// Data segment
|
||||
ByteBuffer dataByteBuffer = ByteBuffer.allocate(addrCounter * MachineState.VALUE_SIZE);
|
||||
|
||||
// Write accountBytes
|
||||
dataByteBuffer.position(addrAccountBytes * MachineState.VALUE_SIZE);
|
||||
dataByteBuffer.put(accountBytes);
|
||||
|
||||
// Store pointer to addrAccountbytes at addrAccountBytesPointer
|
||||
assertEquals(addrAccountBytesPointer * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.putLong(addrAccountBytes);
|
||||
|
||||
ByteBuffer codeByteBuffer = ByteBuffer.allocate(512);
|
||||
|
||||
// Two-pass version
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
codeByteBuffer.clear();
|
||||
|
||||
try {
|
||||
/* Initialization */
|
||||
|
||||
// Copy accountBytes from data segment into B, starting at addrAccountBytes (as pointed to by addrAccountBytesPointer)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAccountBytesPointer));
|
||||
|
||||
// Get account level and save into addrAccountLevel
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(QortalFunctionCode.GET_ACCOUNT_LEVEL_FROM_ACCOUNT_IN_B.value, addrAccountLevel));
|
||||
|
||||
// We're done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.compile());
|
||||
} catch (CompilationException e) {
|
||||
throw new IllegalStateException("Unable to compile AT?", e);
|
||||
}
|
||||
}
|
||||
|
||||
codeByteBuffer.flip();
|
||||
|
||||
byte[] codeBytes = new byte[codeByteBuffer.limit()];
|
||||
codeByteBuffer.get(codeBytes);
|
||||
|
||||
final short ciyamAtVersion = 2;
|
||||
final short numCallStackPages = 0;
|
||||
final short numUserStackPages = 0;
|
||||
final long minActivationAmount = 0L;
|
||||
|
||||
return MachineState.toCreationBytes(ciyamAtVersion, codeBytes, dataByteBuffer.array(), numCallStackPages, numUserStackPages, minActivationAmount);
|
||||
}
|
||||
|
||||
private Integer extractAccountLevel(Repository repository, String atAddress) throws DataException {
|
||||
// Check AT result
|
||||
ATStateData atStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
byte[] stateData = atStateData.getStateData();
|
||||
|
||||
byte[] dataBytes = MachineState.extractDataBytes(stateData);
|
||||
|
||||
Long accountLevelValue = BitTwiddling.longFromBEBytes(dataBytes, 0);
|
||||
if (accountLevelValue == -1)
|
||||
return null;
|
||||
|
||||
return accountLevelValue.intValue();
|
||||
}
|
||||
}
|
115
src/test/java/org/qortal/test/crosschain/DigibyteTests.java
Normal file
115
src/test/java/org/qortal/test/crosschain/DigibyteTests.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Digibyte;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class DigibyteTests extends Common {
|
||||
|
||||
private Digibyte digibyte;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
digibyte = Digibyte.getInstance();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
Digibyte.resetForTesting();
|
||||
digibyte = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(digibyte, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = digibyte.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = digibyte.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = digibyte.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(digibyte.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = digibyte.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(digibyte.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = digibyte.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
@@ -55,7 +55,7 @@ public class TradeBotPresenceTests {
|
||||
|
||||
@Test
|
||||
public void testEnforceLatestTimestamp() {
|
||||
ByteArray pubkeyByteArray = ByteArray.of("publickey".getBytes(StandardCharsets.UTF_8));
|
||||
ByteArray pubkeyByteArray = ByteArray.wrap("publickey".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Map<ByteArray, Long> timestampsByPublicKey = new HashMap<>();
|
||||
|
||||
|
@@ -0,0 +1,769 @@
|
||||
package org.qortal.test.crosschain.digibytev3;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.crosschain.AcctMode;
|
||||
import org.qortal.crosschain.DigibyteACCTv3;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.utils.Amounts;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DigibyteACCTv3Tests extends Common {
|
||||
|
||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
||||
public static final byte[] digibytePublicKeyHash = 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 digibyteAmount = 864200L; // 0.00864200 DGB
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeAccount.getAddress(), digibytePublicKeyHash, redeemAmount, digibyteAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeploy() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
|
||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = fundingAmount;
|
||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = partnersInitialBalance;
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
expectedBalance = deployersInitialBalance;
|
||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = 0;
|
||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = partnersInitialBalance;
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testOfferCancel() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
// Send creator's address to AT, instead of typical partner's address
|
||||
byte[] messageData = DigibyteACCTv3.getInstance().buildCancelMessage(deployer.getAddress());
|
||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||
|
||||
// AT should process 'cancel' message in next block
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in CANCELLED mode
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
||||
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testOfferCancelInvalidLength() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
||||
byte[] messageData = new byte[7];
|
||||
RANDOM.nextBytes(messageData);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||
|
||||
// AT should process 'cancel' message in next block
|
||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in CANCELLED mode
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testTradingInfoProcessing() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
// AT should be in TRADE mode
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
// Check hashOfSecretA was extracted correctly
|
||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
||||
|
||||
// Check trade partner Qortal address was extracted correctly
|
||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
||||
|
||||
// Check trade partner's digibyte PKH was extracted correctly
|
||||
assertTrue(Arrays.equals(digibytePublicKeyHash, tradeData.partnerForeignPKH));
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance;
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testIncorrectTradeSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
long expectedBalance = partnersInitialBalance;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
// AT should still be in OFFER mode
|
||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testAutomaticTradeRefund() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||
|
||||
// Check refund
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in REFUNDED mode
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
||||
|
||||
// Test orphaning
|
||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = deployersPostDeploymentBalance;
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretCorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secret to AT, from correct account
|
||||
messageData = DigibyteACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertTrue(atData.getIsFinished());
|
||||
|
||||
// AT should be in REDEEMED mode
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Orphan redeem
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
// Check balances
|
||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check AT state
|
||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
|
||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secret to AT, but from wrong account
|
||||
messageData = DigibyteACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should still be in TRADE mode
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
// Check balances
|
||||
long expectedBalance = partnersInitialBalance;
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check eventual refund
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send incorrect secret to AT, from correct account
|
||||
byte[] wrongSecret = new byte[32];
|
||||
RANDOM.nextBytes(wrongSecret);
|
||||
messageData = DigibyteACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress());
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should still be in TRADE mode
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
|
||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||
|
||||
// Check eventual refund
|
||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
Account at = deployAtTransaction.getATAccount();
|
||||
String atAddress = at.getAddress();
|
||||
|
||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
|
||||
// Send trade info to AT
|
||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||
|
||||
// Give AT time to process message
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
||||
messageData = Bytes.concat(secretA);
|
||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||
|
||||
// AT should NOT send funds in the next block
|
||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
|
||||
// Check AT is NOT finished
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
assertFalse(atData.getIsFinished());
|
||||
|
||||
// AT should be in TRADING mode
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
public void testDescribeDeployed() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
|
||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||
|
||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
||||
|
||||
for (ATData atData : executableAts) {
|
||||
String atAddress = atData.getATAddress();
|
||||
byte[] codeBytes = atData.getCodeBytes();
|
||||
byte[] codeHash = Crypto.digest(codeBytes);
|
||||
|
||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
||||
atAddress,
|
||||
codeBytes.length,
|
||||
(codeBytes.length != 1 ? "s": ""),
|
||||
HashCode.fromBytes(codeHash)));
|
||||
|
||||
// Not one of ours?
|
||||
if (!Arrays.equals(codeHash, DigibyteACCTv3.CODE_BYTES_HASH))
|
||||
continue;
|
||||
|
||||
describeAt(repository, atAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calcTestLockTimeA(long messageTimestamp) {
|
||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
||||
}
|
||||
|
||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
||||
byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeAddress, digibytePublicKeyHash, redeemAmount, digibyteAmount, 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-DGB cross-chain trade";
|
||||
String description = String.format("Qortal-Digibyte cross-chain trade");
|
||||
String atType = "ACCT";
|
||||
String tags = "QORT-DGB 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) throws DataException {
|
||||
long txTimestamp = System.currentTimeMillis();
|
||||
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;
|
||||
}
|
||||
|
||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
||||
|
||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
||||
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
|
||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||
}
|
||||
|
||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
|
||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
System.out.print(String.format("%s:\n"
|
||||
+ "\tmode: %s\n"
|
||||
+ "\tcreator: %s,\n"
|
||||
+ "\tcreation timestamp: %s,\n"
|
||||
+ "\tcurrent balance: %s QORT,\n"
|
||||
+ "\tis finished: %b,\n"
|
||||
+ "\tredeem payout: %s QORT,\n"
|
||||
+ "\texpected digibyte: %s DGB,\n"
|
||||
+ "\tcurrent block height: %d,\n",
|
||||
tradeData.qortalAtAddress,
|
||||
tradeData.mode,
|
||||
tradeData.qortalCreator,
|
||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
||||
Amounts.prettyAmount(tradeData.qortBalance),
|
||||
atData.getIsFinished(),
|
||||
Amounts.prettyAmount(tradeData.qortAmount),
|
||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
||||
currentBlockHeight));
|
||||
|
||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
||||
+ "\trefund height: block %d,\n"
|
||||
+ "\tHASH160 of secret-A: %s,\n"
|
||||
+ "\tDigibyte P2SH-A nLockTime: %d (%s),\n"
|
||||
+ "\ttrade partner: %s\n"
|
||||
+ "\tpartner's receiving address: %s",
|
||||
tradeData.refundTimeout,
|
||||
tradeData.tradeRefundHeight,
|
||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
||||
tradeData.qortalPartnerAddress,
|
||||
tradeData.qortalPartnerReceivingAddress));
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
||||
// We actually use a known test account with funds to avoid PoW compute
|
||||
return Common.getTestAccount(repository, "alice");
|
||||
}
|
||||
|
||||
}
|
@@ -1,20 +1,26 @@
|
||||
package org.qortal.test.naming;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
||||
import org.qortal.data.naming.NameData;
|
||||
import org.qortal.data.transaction.*;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryFactory;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.RegisterNameTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.NTP;
|
||||
import org.qortal.utils.Unicode;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
@@ -50,31 +56,83 @@ public class IntegrityTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
// Test integrity check after renaming to something else and then back again
|
||||
// This was originally confusing the rebuildName() code and creating a loop
|
||||
@Test
|
||||
public void testBlankReducedName() throws DataException {
|
||||
public void testUpdateNameLoop() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "\uD83E\uDD73"; // Translates to a reducedName of ""
|
||||
String data = "\uD83E\uDD73";
|
||||
String initialName = "initial-name";
|
||||
String initialData = "{\"age\":30}";
|
||||
String initialReducedName = "initia1-name";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
|
||||
initialTransactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(initialTransactionData.getTimestamp()));
|
||||
TransactionUtils.signAndMint(repository, initialTransactionData, alice);
|
||||
|
||||
// Ensure the name exists and the data is correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
// Check initial name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Ensure the reducedName is blank
|
||||
assertEquals("", repository.getNameRepository().fromName(name).getReducedName());
|
||||
// Update the name to something new
|
||||
String newName = "new-name";
|
||||
String newData = "";
|
||||
String newReducedName = "new-name";
|
||||
TransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
|
||||
TransactionUtils.signAndMint(repository, updateTransactionData, alice);
|
||||
|
||||
// Run the database integrity check for this name
|
||||
// Check old name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(initialName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check new name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(newReducedName));
|
||||
|
||||
// Check updated timestamp is correct
|
||||
assertEquals((Long) updateTransactionData.getTimestamp(), repository.getNameRepository().fromName(newName).getUpdated());
|
||||
|
||||
// Update the name to another new name
|
||||
String newName2 = "new-name-2";
|
||||
String newData2 = "";
|
||||
String newReducedName2 = "new-name-2";
|
||||
TransactionData updateTransactionData2 = new UpdateNameTransactionData(TestTransaction.generateBase(alice), newName, newName2, newData2);
|
||||
TransactionUtils.signAndMint(repository, updateTransactionData2, alice);
|
||||
|
||||
// Check old name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(newName));
|
||||
assertNull(repository.getNameRepository().fromReducedName(newReducedName));
|
||||
|
||||
// Check new name exists
|
||||
assertTrue(repository.getNameRepository().nameExists(newName2));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(newReducedName2));
|
||||
|
||||
// Check updated timestamp is correct
|
||||
assertEquals((Long) updateTransactionData2.getTimestamp(), repository.getNameRepository().fromName(newName2).getUpdated());
|
||||
|
||||
// Update the name back to the initial name
|
||||
TransactionData updateTransactionData3 = new UpdateNameTransactionData(TestTransaction.generateBase(alice), newName2, initialName, initialData);
|
||||
TransactionUtils.signAndMint(repository, updateTransactionData3, alice);
|
||||
|
||||
// Check previous name no longer exists
|
||||
assertFalse(repository.getNameRepository().nameExists(newName2));
|
||||
assertNull(repository.getNameRepository().fromReducedName(newReducedName2));
|
||||
|
||||
// Check initial name exists again
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName));
|
||||
|
||||
// Check updated timestamp is correct
|
||||
assertEquals((Long) updateTransactionData3.getTimestamp(), repository.getNameRepository().fromName(initialName).getUpdated());
|
||||
|
||||
// Run the database integrity check for the initial name, to ensure it doesn't get into a loop
|
||||
NamesDatabaseIntegrityCheck integrityCheck = new NamesDatabaseIntegrityCheck();
|
||||
assertEquals(1, integrityCheck.rebuildName(name, repository));
|
||||
assertEquals(2, integrityCheck.rebuildName(initialName, repository));
|
||||
|
||||
// Ensure the name still exists and the data is still correct
|
||||
assertEquals(data, repository.getNameRepository().fromName(name).getData());
|
||||
assertEquals("", repository.getNameRepository().fromName(name).getReducedName());
|
||||
// Ensure the new name still exists and the data is still correct
|
||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||
assertEquals(initialData, repository.getNameRepository().fromName(initialName).getData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,4 +506,46 @@ public class IntegrityTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Checks 'live' repository")
|
||||
@Test
|
||||
public void testRepository() throws DataException {
|
||||
Settings.fileInstance("settings.json"); // use 'live' settings
|
||||
|
||||
String repositoryUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=false;hsqldb.full_log_replay=true";
|
||||
String connectionUrl = String.format(repositoryUrlTemplate, Settings.getInstance().getRepositoryPath());
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<NameData> names = repository.getNameRepository().getAllNames();
|
||||
|
||||
for (NameData nameData : names) {
|
||||
String reReduced = Unicode.sanitize(nameData.getName());
|
||||
|
||||
if (reReduced.isBlank()) {
|
||||
System.err.println(String.format("Name '%s' reduced to blank",
|
||||
nameData.getName()
|
||||
));
|
||||
}
|
||||
|
||||
if (!nameData.getReducedName().equals(reReduced)) {
|
||||
System.out.println(String.format("Name '%s' reduced form was '%s' but is now '%s'",
|
||||
nameData.getName(),
|
||||
nameData.getReducedName(),
|
||||
reReduced
|
||||
));
|
||||
|
||||
// ...but does another name already have this reduced form?
|
||||
names.stream()
|
||||
.filter(tmpNameData -> tmpNameData.getReducedName().equals(reReduced))
|
||||
.forEach(tmpNameData ->
|
||||
System.err.println(String.format("Name '%s' new reduced form also matches name '%s'",
|
||||
nameData.getName(),
|
||||
tmpNameData.getName()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,21 +2,22 @@ package org.qortal.test.naming;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.api.AmountTypeAdapter;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.block.BlockChain.*;
|
||||
import org.qortal.controller.BlockMinter;
|
||||
import org.qortal.data.transaction.*;
|
||||
import org.qortal.naming.Name;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.test.common.*;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.RegisterNameTransaction;
|
||||
@@ -325,17 +326,20 @@ public class MiscTests extends Common {
|
||||
|
||||
// test name registration fee increase
|
||||
@Test
|
||||
public void testRegisterNameFeeIncrease() throws DataException, IllegalAccessException {
|
||||
public void testRegisterNameFeeIncrease() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set nameRegistrationUnitFeeTimestamp to a time far in the future
|
||||
long futureTimestamp = 9999999999999L; // 20 Nov 2286
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFeeTimestamp", futureTimestamp, true);
|
||||
assertEquals(futureTimestamp, BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp());
|
||||
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("5");
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(futureFeeIncrease), true);
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getUnitFee()); // 0.1 QORT
|
||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFee()); // 5 QORT
|
||||
assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 QORT
|
||||
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
@@ -349,8 +353,11 @@ public class MiscTests extends Common {
|
||||
|
||||
// Set nameRegistrationUnitFeeTimestamp to a time in the past
|
||||
Long now = NTP.getTime();
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFeeTimestamp", now - 1000L, true);
|
||||
assertEquals(now - 1000L, BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp());
|
||||
UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp();
|
||||
pastFeeIncrease.timestamp = now - 1000L; // 1 second ago
|
||||
pastFeeIncrease.fee = new AmountTypeAdapter().unmarshal("3");
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease), true);
|
||||
assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
|
||||
|
||||
// Register a different name
|
||||
// First try with the default unit fee
|
||||
@@ -365,7 +372,7 @@ public class MiscTests extends Common {
|
||||
// Now try using correct fee (this is specified by the UI, via the /transaction/unitfee API endpoint)
|
||||
transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name2, data);
|
||||
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(500000000L, transactionData.getFee().longValue());
|
||||
assertEquals(300000000L, transactionData.getFee().longValue());
|
||||
transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
result = transaction.importAsUnconfirmed();
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -5,7 +5,9 @@
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFee": "5",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
|
@@ -95,7 +95,6 @@ cat <<__JAR__
|
||||
|
||||
### [${project}.jar](${git_url}/releases/download/${git_tag}/${project}.jar)
|
||||
|
||||
If built using OpenJDK 11:
|
||||
__JAR__
|
||||
3hash target/${project}*.jar
|
||||
|
||||
|
Reference in New Issue
Block a user