Compare commits

...

77 Commits

Author SHA1 Message Date
CalDescent
a95a37277c Removed DigibyteACCTv1 and v2
Also removed CrossChainDigibyteACCTv1Resource, since this is unused, and it seems excessive to maintain support of this for every coin (and potentially every ACCT version).
2022-05-01 16:12:12 +01:00
QuickMythril
8aed84e6af add Digibyte ACCTs 2022-04-26 11:40:42 -04:00
CalDescent
b1f184c493 Use DigibyteMainNetParams 2022-04-22 16:31:44 +01:00
CalDescent
d66dd51bf6 Switched to Qortal fork of altcoinj, with Digibyte support 2022-04-22 16:31:32 +01:00
QuickMythril
0baed55a44 add DGB wallet 2022-04-21 11:40:17 -04:00
CalDescent
311f41c610 Attempt to fix core startup problems on some systems (GNOME Desktop?) by adding defensiveness to GUI elements. 2022-04-20 08:41:37 +01:00
CalDescent
0a156c76a2 Fix for NPE observed on the EPC-fixes branch (but putting the fix on master in case unrelated) 2022-04-20 08:38:59 +01:00
CalDescent
337b03aa68 Catch java.util.ServiceConfigurationError in Gui.loadImage() 2022-04-17 17:59:29 +01:00
CalDescent
3d99f86630 Improved logging 2022-04-16 20:50:00 +01:00
CalDescent
8d1a58ec06 POW_DIFFICULTY_NO_QORT reduced from 14 to 12 (around 4x faster) 2022-04-16 12:36:32 +01:00
CalDescent
2e5a7cb5a1 Adapted Blockchain.java to use lookup table for name registration fees, to more easily support fee adjustments.
This is currently for name registration transactions only, but can be adapted (or duplicated) for other transaction types when needed.

Note: this switches from a greater-than (>) to a greater-than-or-equal (>=) timestamp comparison, as it makes more sense this way. It shouldn't affect the previous transition since there were no REGISTER_NAME transactions at that exact timestamp.
2022-04-16 12:20:03 +01:00
CalDescent
895f02f178 Remove peers with unknown height, lower height or same height and same block signature (unless we don't have their block signature)
Adapted from code originally written by catbref from before genesis, and essentially prevents syncing backwards. This needs significant testing on testnet.
2022-04-16 11:30:07 +01:00
CalDescent
c59869982b Fix for system-wide QDN issues occuring when the metadata file has an empty chunks array.
It is quite likely that existing resources with both metadata and an empty chunks array will need to be republished, because this bug may have led to incorrect file deletions.
2022-04-16 11:25:44 +01:00
CalDescent
3b3368f950 Merge pull request #85 from QuickMythril/member-count
Add member count to each group returned by GET /member/{address}
2022-04-16 11:00:35 +01:00
QuickMythril
3f02c760c2 Add member count to each group returned by GET /member/{address} 2022-04-15 06:23:10 -04:00
CalDescent
fee603e500 Add member count to each group returned by GET /groups (expanded on code written by QuickMythril) 2022-04-15 10:19:43 +01:00
QuickMythril
ad31d8014d get memberCount with Group Data
works for lookup by groupId
2022-04-14 22:08:52 -04:00
CalDescent
58a0ac74d2 Merge pull request #84 from catbref/ByteArray
Improvements to ByteArray to leverage Java 11 'native' Arrays methods
2022-04-14 21:30:59 +01:00
QuickMythril
8388aa9c23 update Russian translation
credit: Alexander45 & malina
2022-04-10 15:50:29 -04:00
catbref
c1894d8c00 Improvements to ByteArray to leverage Java 11 'native' Arrays.hashCode and Arrays.compareUnsigned for speed.
Also modified ambiguous ByteArray::new and ByteArray::of to ByteArray::wrap and ByteArray::copyOf.
Modifications to other classes that use ByteArray.
2022-04-10 16:38:02 +01:00
QuickMythril
f7f9cdc518 Merge pull request #83 from aldum/feature/hungarian_translation
fixup grammar; add missing translations
2022-04-09 00:10:37 -04:00
QuickMythril
850d7f8220 add/update translations
credit: johnnyfg (sv), schizo (it), IsBe (nl), Eduardo9999 (es)
2022-04-08 23:57:54 -04:00
aldum
051043283c fixup grammar; add missing translations 2022-04-06 23:21:49 +02:00
QuickMythril
15bc69de01 Merge pull request #82 from JaymenChou/patch-8
Update SysTray_zh_CN.properties
2022-04-05 13:38:13 -04:00
QuickMythril
ee3cfa4d6d fix typo 2022-04-05 13:26:02 -04:00
QuickMythril
df1f3079a5 Merge pull request #81 from JaymenChou/patch-7
Update SysTray_zh_TW.properties
2022-04-05 13:25:06 -04:00
QuickMythril
d9ae8a5552 Merge branch 'master' into patch-8 2022-04-05 13:23:19 -04:00
QuickMythril
2326c31ee7 Merge branch 'master' into patch-7 2022-04-05 13:11:14 -04:00
QuickMythril
91cb0f30dd Updated TransactionValidity translations
added some missing entries, and sorted alphabetically.
2022-04-05 12:51:49 -04:00
QuickMythril
c0307c352c Updated ApiError translations
removed some duplicate entries, and standardized the order
2022-04-05 11:46:32 -04:00
QuickMythril
8fd7c1b313 formatting fix 2022-04-05 11:09:30 -04:00
QuickMythril
b8147659b1 Updated SysTray translations
added some missing entries, and sorted alphabetically.
2022-04-05 10:48:43 -04:00
JaymenChou
7a1bac682f Update SysTray_zh_TW.properties
Add the missing term "PERFORMING_DB_MAINTENANCE" and translate it to Traditional Chinese
2022-04-04 20:36:48 +08:00
JaymenChou
9fdb7c977f Update SysTray_zh_CN.properties
Translate remaining terms to Simple Chinese
2022-04-04 20:33:59 +08:00
JaymenChou
4f3948323b Update SysTray_zh_TW.properties
Translate the remaining terms to Traditional Chinese
2022-04-04 20:31:19 +08:00
QuickMythril
70fcc1f712 Merge pull request #78 from JaymenChou/patch-4
Create ApiError_zh_CN.properties
2022-04-04 02:49:00 -04:00
JaymenChou
f20fe9199f Update ApiError_zh_CN.properties 2022-04-04 14:36:55 +08:00
QuickMythril
91dee4a3b8 Merge pull request #80 from JaymenChou/patch-6
Create TransactionValidity_zh_CN.properties
2022-04-04 02:17:35 -04:00
QuickMythril
0b89b8084e Merge pull request #79 from JaymenChou/patch-5
Create TransactionValidity_zh_TW.properties
2022-04-04 02:17:24 -04:00
QuickMythril
a5a80302b2 Merge pull request #77 from JaymenChou/patch-3
Create ApiError_zh_TW.properties
2022-04-04 02:17:02 -04:00
QuickMythril
e61a24ee7b removed electrum-ltc.bysh.me
this server often gives a false positive for phishing by some antivirus software.
2022-04-03 22:32:57 -04:00
JaymenChou
55ed342b59 Create TransactionValidity_zh_CN.properties
Add Simple Chinese For better understanding of logs
2022-04-03 13:27:52 +08:00
JaymenChou
3c6f79eec0 Create TransactionValidity_zh_TW.properties
Add Traditional Chinese For TransactionValidity Logs.
2022-04-03 13:25:32 +08:00
JaymenChou
590800ac1d Create ApiError_zh_CN.properties
Add Simple Chinese Support For API Error Message 
Hope it helps in understanding the API !
2022-04-03 12:43:18 +08:00
JaymenChou
95c412b946 Create ApiError_zh_TW.properties
Add Traditional Chinese support to API Responses
2022-04-03 12:40:27 +08:00
CalDescent
a232395750 Merge branch 'master' of github.com:Qortal/qortal 2022-04-01 11:24:56 +01:00
QuickMythril
6edbc8b6a5 add decimal precision to download progress 2022-03-31 13:46:40 -04:00
QuickMythril
f8ffeed302 updated BTC electrumx servers
added new and removed TCP, closed servers, and versions older than 1.16.0
2022-03-31 11:32:55 -04:00
QuickMythril
e2ee68427c removed TCP electrumx servers 2022-03-31 11:29:54 -04:00
QuickMythril
74ff23239d removed TCP electrumx servers 2022-03-31 11:27:56 -04:00
QuickMythril
f1fa2ba2f6 added SSL electrumx servers 2022-03-31 10:02:31 -04:00
QuickMythril
e1522cec94 updated LTC electrumx servers 2022-03-31 09:58:53 -04:00
QuickMythril
8841b3cbb1 add spanish translations 2022-03-31 08:44:33 -04:00
CalDescent
94260bd93f Decreased the number of retries for missing metadata, to reduce broadcast spam. 2022-03-30 08:23:22 +01:00
CalDescent
15ff8af7ac Don't process trade bots or broadcast presence timestamps if our chain is more than 30 minutes old 2022-03-30 08:11:02 +01:00
CalDescent
d420033b36 Revert "Revert "Add Qortal AT FunctionCodes for getting account level / blocks minted + tests""
This reverts commit 59025b8f47.
2022-03-30 08:07:07 +01:00
CalDescent
bda63f0310 Removed hardcoded "qortal-backup/TradeBotStates.json" from POST /admin/repository/data API, as it's no longer needed now that API keys are required. 2022-03-30 08:06:09 +01:00
QuickMythril
54add26ccb fixed typo 2022-03-25 23:39:41 -04:00
CalDescent
089b068362 Updated AdvancedInstaller project for v3.2.3 2022-03-19 22:38:58 +00:00
CalDescent
fe474b4507 Bump version to 3.2.3 2022-03-19 20:44:41 +00:00
CalDescent
bbe15b563c Added unit test to simulate recent issue.
This fails with the 3.2.2 code but now passes when using the latest fixes.
2022-03-19 20:41:38 +00:00
CalDescent
59025b8f47 Revert "Add Qortal AT FunctionCodes for getting account level / blocks minted + tests"
This reverts commit eb9b94b9c6.
2022-03-19 19:52:14 +00:00
CalDescent
1b42c5edb1 Fixed NPE in runIntegrityCheck()
This feature is disabled by default so can be tidied up later. For now, the unhandled scenario is logged and the checking continues on.

One name's transactions are too complex for the current integrity check code to verify (MangoSalsa), but it has been verified manually. All other names pass the automated test.
2022-03-19 19:22:16 +00:00
CalDescent
362335913d Fixed infinite loop in name rebuilding.
If an account is renamed and then at some point renamed back to one of the original names, it confused the names rebuilding code. The current solution is to track the linked names that have already been rebuilt, and then break out of the loop once a name is encountered a second time.
2022-03-19 18:55:19 +00:00
CalDescent
4340dac595 Fixed recently introduced issue in name rebuilding code causing transactions to be unordered.
This is the likely cause of inconsistent name entries across different nodes, as we can't guarantee that every environment will return the same transaction order from the SQL queries.
2022-03-19 18:44:16 +00:00
CalDescent
f3e1fc884c Merge pull request #63 from catbref/master
Add Qortal AT FunctionCodes for getting account level / blocks minted
2022-03-19 11:32:39 +00:00
CalDescent
39c06d8817 Merge pull request #75 from catbref/name-unicode
Unicode / NAME updates.
2022-03-19 11:32:22 +00:00
CalDescent
91cee36c21 Catch and log all exceptions in addStatusToResources()
Some users are seeing 500 errors deriving from this code. This should hopefully allow more info to be obtained, as well as causing it to omit the status for resources that encounter problems.
2022-03-19 11:08:42 +00:00
CalDescent
6bef883942 Removed OpenJDK 11 reference in build-release.sh, as it seems that checksums will not match by default due to timestamps and file orderings.
See: https://dzone.com/articles/reproducible-builds-in-java
2022-03-19 11:05:51 +00:00
CalDescent
25ba2406c0 Updated AdvancedInstaller project for v3.2.2 2022-03-16 19:53:22 +00:00
CalDescent
e4dc8f85a7 Bump version to 3.2.2 2022-03-15 19:57:02 +00:00
CalDescent
12a4a260c8 Handle new sync result case. 2022-03-14 22:04:11 +00:00
CalDescent
268f02b5c3 Added automated test to ensure that the core's default bootstrap hosts are functional.
Whilst not strictly a unit test, this should allow issues with the core's bootstrap servers to be caught quickly.
2022-03-14 21:52:54 +00:00
CalDescent
13eff43b87 Fixed synchronizer issues which caused large re-orgs
Peers without a recent block are removed at the start of the sync process, however, due to the time lag involved in fetching block summaries and comparing the list of peers, some of these could subsequently drop back to a non-recent block and still be chosen as the next peer to sync with. The end result being that nodes could unnecessarily orphan as many as 20 blocks due to syncing with a peer that doesn't have a recent block (but has a couple of high weight blocks after the common block).

This commit adds some additional filtering to avoid this situation.

1) Peers without a recent block are removed as candidates in comparePeers(), allowing for alternate peers to be chosen.
2) After comparePeers() completes, the list is filtered a second time to make sure that all are still recent.
3) Finally, the peer's state is checked one last time in syncToPeerChain(), just before any orphaning takes place.

Whilst just one of the above would probably have been sufficient, the consequences of this bug are so severe that it makes sense to be very thorough.

The only exception to the above is when the node is in "recovery mode", in which case peers without recent blocks are allowed to be included. Items 1 and 3 above do not apply in recovery mode. Item 2 does apply, since the entire comparePeers() functionality is already skipped in a recovery situation due to our chain being out of date.
2022-03-14 21:47:37 +00:00
catbref
e604a19bce Unicode / NAME updates.
Fix UPDATE_NAME not processing empty 'newName' transactions correctly.
Fix some emoji code-points not being processed correctly.
Updated tests.
Now included ICU4J v70.1 - WARNING: this could add around 10MB to JAR size!
Bumped homoglyph to v1.2.1.
2022-03-14 08:45:32 +00:00
CalDescent
e63e39fe9a Updated AdvancedInstaller project for v3.2.1 2022-03-13 19:39:58 +00:00
catbref
eb9b94b9c6 Add Qortal AT FunctionCodes for getting account level / blocks minted + tests 2021-12-04 16:36:05 +00:00
96 changed files with 6530 additions and 1283 deletions

View File

@@ -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
View File

@@ -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>

View File

@@ -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>

View File

@@ -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() {
}
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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 {
}
}
}
}

View File

@@ -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()) {

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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

View 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);
}
}

View 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;
}
}

View File

@@ -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.
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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>

View File

@@ -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));
}
}

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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 lAPI 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = узел не ответил данными

View 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

View 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 = 其他节点在指定时间内没有回应

View 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 = 其他節點在指定時間内沒有回應

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = Проверка времени

View 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

View File

@@ -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 = 同步时钟

View File

@@ -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 = 正在同步時鐘

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = не соответствие идентификатора группы в хэш транзации

View 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

View 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交易不吻合

View 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交易不吻合

View File

@@ -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"));

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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()) {

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View 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);
}
}

View File

@@ -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<>();

View File

@@ -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");
}
}

View File

@@ -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()
))
);
}
}
}
}
}

View File

@@ -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();

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -5,7 +5,9 @@
"maxBlockSize": 2097152,
"maxBytesPerUnitFee": 1024,
"unitFee": "0.1",
"nameRegistrationUnitFee": "5",
"nameRegistrationUnitFees": [
{ "timestamp": 1645372800000, "fee": "5" }
],
"requireGroupForApproval": false,
"minAccountLevelToRewardShare": 5,
"maxRewardSharesPerMintingAccount": 20,

View File

@@ -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