Compare commits

...

44 Commits

Author SHA1 Message Date
AlphaX-Projects
a02d1cec75 Bump version to 4.5.2 2024-06-30 18:41:04 +02:00
AlphaX-Projects
2d9f1d6d81 Update minimum peer version 2024-06-30 18:38:34 +02:00
crowetic
c1da495dd1 Merge pull request #197 from AlphaX-Projects/master
Bug fixes and additions
2024-06-30 08:48:11 -07:00
AlphaX-Projects
533df9f2b8 Updated dependencies 2024-06-29 15:01:36 +02:00
AlphaX-Projects
44b4b08117 Added block minter thread check 2024-06-29 13:17:07 +02:00
AlphaX-Projects
a140805c36 Updated group transactions (owner check) 2024-06-29 12:58:58 +02:00
AlphaX-Projects
ea1d4dd962 Updated electrumx with non null objects 2024-06-29 12:48:06 +02:00
AlphaX-Projects
a6cbdaafd8 Updated to bouncycastle 1.70 (AT and Core) 2024-06-29 12:23:57 +02:00
AlphaX-Projects
5db808e300 Replaced reference method with qualifier (websockets) 2024-06-29 12:18:52 +02:00
AlphaX-Projects
cc95106019 Removed unnecessary continue (last statement in a loop) 2024-06-29 12:15:55 +02:00
AlphaX-Projects
64537ad705 Fixed synchronization on a non-final field 2024-06-29 12:13:12 +02:00
AlphaX-Projects
806dc6d056 Replaced string.equals() with string.isEmpty() 2024-06-29 12:11:38 +02:00
AlphaX-Projects
d58fbab1b5 Removed pointless boolean Expression 2024-06-29 11:58:25 +02:00
AlphaX-Projects
61ede811cd Replaced size() == / > 0 with isEmpty() 2024-06-29 11:54:41 +02:00
AlphaX-Projects
95d42db773 Removed unnecessary semicolons 2024-06-29 11:52:22 +02:00
AlphaX-Projects
ef07a444d6 Removed unnecessary imports 2024-06-29 11:49:19 +02:00
crowetic
5910ceff80 Merge pull request #194 from kennycud/master
Adding in foreign blockchain server configuration add and remove capa…
2024-05-31 17:44:50 -07:00
kennycud
f916d3581b Corrected incorrect language in the comments. This was copied and pasted 6 times. 2024-05-27 10:10:45 -07:00
kennycud
498f409346 Adding in foreign blockchain server configuration add and remove capabilities. Also adding in connection recording and connection setting capabilities. 2024-05-26 11:08:07 -07:00
crowetic
d54f840265 Merge pull request #185 from kennycud/master
Server Info Current Server & Ordering
2024-03-25 09:15:41 -07:00
crowetic
cc740cceec Merge pull request #187 from AlphaX-Projects/master
Update QDN file management
2024-03-25 09:13:02 -07:00
AlphaX-Projects
df39819de0 Update QDN file management
- Allow files to move around the network more quickly
- Ensuring that metadata isn't saved for blocked names
- More selective disk usage
- Minor fixes
2024-03-25 07:34:26 +01:00
kennycud
e83b2263f0 some simple error logging 2024-03-23 09:42:33 -07:00
kennycud
5c90c4bc61 Merge remote-tracking branch 'origin/master' 2024-03-23 06:28:02 -07:00
kennycud
f7793443f3 call current blockchain height to ensure the current server is set to the blockchain provider and sort the servers to ensure the current server is listed first 2024-03-23 06:25:31 -07:00
crowetic
f6b91df7b6 Merge pull request #181 from kennycud/master
Foreign Coin Trade Fees & Summaries
2024-02-27 12:02:24 -08:00
kennycud
92d589a1ca Merge branch 'Qortal:master' into master 2024-02-19 04:56:33 -08:00
AlphaX-Projects
c4a7fb3b92 Bump version to 4.5.1 2024-02-18 18:14:33 +01:00
AlphaX-Projects
2d27901f9f Adding new algos 2024-02-18 18:12:43 +01:00
AlphaX-Projects
ce8fb002cc Merge pull request #179 from lgedgar/log-invalid-timestamps
Add debug logging when invalid timestamp is encountered
2024-02-14 15:41:09 +01:00
kennycud
3f29116b47 Foreign coin trade transaction summaries 2024-02-12 06:31:17 -08:00
Lance Edgar
d05359dfa9 Add debug logging when invalid timestamp is encountered 2024-02-07 16:52:12 -06:00
kennycud
587b063e6a Merge branch 'Qortal:master' into master 2024-02-06 15:22:51 -08:00
AlphaX-Projects
9e001dfc16 Remove penalty fix 2024-02-03 19:51:51 +01:00
AlphaX-Projects
55f941467f Remove fix in order for the chain history to remain valid 2024-02-03 14:05:11 +01:00
AlphaX-Projects
4f9a4a2091 Disable fix in order for the chain history to remain valid 2024-02-02 16:40:00 +01:00
crowetic
d579606d2d Merge pull request #174 from QuickMythril/fix-unit-tests
Update ElectrumX servers & fix unit tests
2024-01-31 19:32:20 -08:00
crowetic
dcedcf8685 Merge pull request #176 from QuickMythril/votes-api-fix
Fixed vote weight results API call
2024-01-31 19:27:01 -08:00
QuickMythril
f78764880c Fixed vote weight results API call
blocksMintedPenalty is a negative value, so it should be added to blocksMinted, not subtracted.
2024-01-31 21:13:19 -05:00
QuickMythril
6051b85e52 Remove out of service 2024-01-21 08:42:05 -05:00
QuickMythril
9c62740f44 Merge branch 'master' into fix-unit-tests 2024-01-21 08:36:41 -05:00
kennycud
21c45535be Enabled fee updates for foreign coin trade transactions. 2024-01-17 18:19:09 -08:00
QuickMythril
15ae32efd9 Fix crosschain tests 2024-01-17 06:56:06 -05:00
QuickMythril
03ba36729b Update ElectrumX servers 2024-01-17 06:08:09 -05:00
143 changed files with 6836 additions and 5380 deletions

Binary file not shown.

View File

@@ -0,0 +1,123 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ciyam</groupId>
<artifactId>AT</artifactId>
<version>1.4.2</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>false</skipTests>
<bouncycastle.version>1.70</bouncycastle.version>
<junit.version>4.13.2</junit.version>
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<maven-source-plugin.version>3.3.0</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.6.3</maven-javadoc-plugin.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.4.1</maven-jar-plugin.version>
</properties>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>${skipTests}</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<executions>
<execution>
<id>attach-javadoc</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -3,13 +3,14 @@
<groupId>org.ciyam</groupId>
<artifactId>AT</artifactId>
<versioning>
<release>1.4.1</release>
<release>1.4.2</release>
<versions>
<version>1.3.7</version>
<version>1.3.8</version>
<version>1.4.0</version>
<version>1.4.1</version>
<version>1.4.2</version>
</versions>
<lastUpdated>20231212092227</lastUpdated>
<lastUpdated>20240426084210</lastUpdated>
</versioning>
</metadata>

88
pom.xml
View File

@@ -3,58 +3,60 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId>
<artifactId>qortal</artifactId>
<version>4.5.0</version>
<version>4.5.2</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>true</skipTests>
<altcoinj.version>7dc8c6f</altcoinj.version>
<bitcoinj.version>0.15.10</bitcoinj.version>
<bouncycastle.version>1.69</bouncycastle.version>
<build-helper-maven-plugin.version>3.5.0</build-helper-maven-plugin.version>
<bouncycastle.version>1.70</bouncycastle.version>
<build.timestamp>${maven.build.timestamp}</build.timestamp>
<ciyam-at.version>1.4.1</ciyam-at.version>
<ciyam-at.version>1.4.2</ciyam-at.version>
<commons-net.version>3.8.0</commons-net.version>
<commons-text.version>1.11.0</commons-text.version>
<commons-io.version>2.11.0</commons-io.version>
<commons-compress.version>1.25.0</commons-compress.version>
<commons-text.version>1.12.0</commons-text.version>
<commons-io.version>2.16.1</commons-io.version>
<commons-compress.version>1.26.2</commons-compress.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
<dagger.version>1.2.2</dagger.version>
<extendedset.version>0.12.3</extendedset.version>
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
<grpc.version>1.61.0</grpc.version>
<guava.version>33.0.0-jre</guava.version>
<grpc.version>1.65.0</grpc.version>
<guava.version>33.2.1-jre</guava.version>
<hamcrest-library.version>2.2</hamcrest-library.version>
<homoglyph.version>1.2.1</homoglyph.version>
<hsqldb.version>2.5.1</hsqldb.version>
<icu4j.version>74.2</icu4j.version>
<icu4j.version>75.1</icu4j.version>
<java-diff-utils.version>4.12</java-diff-utils.version>
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
<jaxb-runtime.version>2.3.9</jaxb-runtime.version>
<jersey.version>2.41</jersey.version>
<jetty.version>9.4.53.v20231009</jetty.version>
<jersey.version>2.42</jersey.version>
<jetty.version>9.4.54.v20240208</jetty.version>
<json-simple.version>1.1.1</json-simple.version>
<json.version>20231013</json.version>
<json.version>20240303</json.version>
<jsoup.version>1.17.2</jsoup.version>
<junit-jupiter-engine.version>5.10.0</junit-jupiter-engine.version>
<junit-jupiter-engine.version>5.11.0-M2</junit-jupiter-engine.version>
<lifecycle-mapping.version>1.0.0</lifecycle-mapping.version>
<log4j.version>2.22.1</log4j.version>
<log4j.version>2.23.1</log4j.version>
<mail.version>1.5.0-b01</mail.version>
<maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-build-helper-plugin.version>3.6.0</maven-build-helper-plugin.version>
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<maven-package-info-plugin.version>1.1.0</maven-package-info-plugin.version>
<maven-plugin.version>2.16.2</maven-plugin.version>
<maven-reproducible-build-plugin.version>0.16</maven-reproducible-build-plugin.version>
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
<maven-shade-plugin.version>3.5.1</maven-shade-plugin.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<package-info-maven-plugin.version>1.1.0</package-info-maven-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<protobuf.version>3.25.1</protobuf.version>
<maven-shade-plugin.version>3.6.0</maven-shade-plugin.version>
<maven-surefire-plugin.version>3.3.0</maven-surefire-plugin.version>
<protobuf.version>3.25.3</protobuf.version>
<replacer.version>1.5.3</replacer.version>
<reproducible-build-maven-plugin.version>0.16</reproducible-build-maven-plugin.version>
<simplemagic.version>1.17</simplemagic.version>
<slf4j.version>1.7.36</slf4j.version>
<swagger-api.version>2.0.10</swagger-api.version>
<swagger-ui.version>5.10.3</swagger-ui.version>
<swagger-ui.version>5.17.14</swagger-ui.version>
<upnp.version>1.2</upnp.version>
<versions-maven-plugin.version>2.16.2</versions-maven-plugin.version>
<xz.version>1.9</xz.version>
</properties>
<build>
@@ -70,7 +72,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>${versions-maven-plugin.version}</version>
<version>${maven-plugin.version}</version>
<configuration>
<generateBackupPoms>false</generateBackupPoms>
</configuration>
@@ -238,7 +240,7 @@
<plugin>
<groupId>com.github.bohnman</groupId>
<artifactId>package-info-maven-plugin</artifactId>
<version>${package-info-maven-plugin.version}</version>
<version>${maven-package-info-plugin.version}</version>
<configuration>
<packages>
<package>
@@ -268,7 +270,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin.version}</version>
<version>${maven-build-helper-plugin.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
@@ -353,7 +355,7 @@
<plugin>
<groupId>io.github.zlika</groupId>
<artifactId>reproducible-build-maven-plugin</artifactId>
<version>${reproducible-build-maven-plugin.version}</version>
<version>${maven-reproducible-build-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
@@ -388,15 +390,9 @@
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-dependency-plugin
</artifactId>
<versionRange>
[3.6.0,)
</versionRange>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
<goals>
<goal>unpack</goal>
</goals>
@@ -407,15 +403,9 @@
</pluginExecution>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
com.google.code.maven-replacer-plugin
</groupId>
<artifactId>
replacer
</artifactId>
<versionRange>
[1.5.3,)
</versionRange>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>${replacer.version}</version>
<goals>
<goal>replace</goal>
</goals>
@@ -448,7 +438,7 @@
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin.version}</version>
<version>${maven-build-helper-plugin.version}</version>
<scope>provided</scope>
<!-- needed for build, not for runtime -->
</dependency>
@@ -456,7 +446,7 @@
<dependency>
<groupId>com.github.bohnman</groupId>
<artifactId>package-info-maven-plugin</artifactId>
<version>${package-info-maven-plugin.version}</version>
<version>${maven-package-info-plugin.version}</version>
<scope>provided</scope>
<!-- needed for build, not for runtime -->
</dependency>

View File

@@ -5290,7 +5290,7 @@ public final class Service {
if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(vendor_)) {
com.google.protobuf.GeneratedMessageV3.writeString(output, 2, vendor_);
}
if (taddrSupport_ != false) {
if (taddrSupport_) {
output.writeBool(3, taddrSupport_);
}
if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(chainName_)) {
@@ -5341,7 +5341,7 @@ public final class Service {
if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(vendor_)) {
size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, vendor_);
}
if (taddrSupport_ != false) {
if (taddrSupport_) {
size += com.google.protobuf.CodedOutputStream
.computeBoolSize(3, taddrSupport_);
}
@@ -5729,7 +5729,7 @@ public final class Service {
vendor_ = other.vendor_;
onChanged();
}
if (other.getTaddrSupport() != false) {
if (other.getTaddrSupport()) {
setTaddrSupport(other.getTaddrSupport());
}
if (!other.getChainName().isEmpty()) {

View File

@@ -340,7 +340,7 @@ public class SelfSponsorshipAlgoV1 {
return true;
}
transactionDataList.removeIf(t -> t.getTimestamp() >= this.snapshotTimestamp);
return transactionDataList.size() == 0;
return transactionDataList.isEmpty();
}
private static List<TransactionData> fetchTransactions(Repository repository, List<TransactionType> txTypes, String address, boolean reverse) throws DataException {

View File

@@ -0,0 +1,249 @@
package org.qortal.account;
import org.qortal.api.resource.TransactionsResource;
import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.data.account.AccountData;
import org.qortal.data.transaction.*;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.transaction.Transaction.TransactionType;
import java.util.*;
public class SelfSponsorshipAlgoV2 {
private final long snapshotTimestampV1 = BlockChain.getInstance().getSelfSponsorshipAlgoV1SnapshotTimestamp();
private final long snapshotTimestampV2 = BlockChain.getInstance().getSelfSponsorshipAlgoV2SnapshotTimestamp();
private final long referenceTimestamp = BlockChain.getInstance().getReferenceTimestampBlock();
private final boolean override;
private final Repository repository;
private final String address;
private int recentAssetSendCount = 0;
private int recentSponsorshipCount = 0;
private final Set<String> assetAddresses = new LinkedHashSet<>();
private final Set<String> penaltyAddresses = new LinkedHashSet<>();
private final Set<String> sponsorAddresses = new LinkedHashSet<>();
private List<RewardShareTransactionData> sponsorshipRewardShares = new ArrayList<>();
private List<TransferAssetTransactionData> transferAssetForAddress = new ArrayList<>();
public SelfSponsorshipAlgoV2(Repository repository, String address, boolean override) {
this.repository = repository;
this.address = address;
this.override = override;
}
public String getAddress() {
return this.address;
}
public Set<String> getPenaltyAddresses() {
return this.penaltyAddresses;
}
public void run() throws DataException {
if (!override) {
this.getAccountPrivs(this.address);
}
if (override) {
this.fetchTransferAssetForAddress(this.address);
this.findRecentAssetSendCount();
if (this.recentAssetSendCount >= 6) {
this.penaltyAddresses.add(this.address);
this.penaltyAddresses.addAll(this.assetAddresses);
}
}
}
private void getAccountPrivs(String address) throws DataException {
AccountData accountData = this.repository.getAccountRepository().getAccount(address);
List<TransactionData> transferPrivsTransactions = fetchTransferPrivsForAddress(address);
transferPrivsTransactions.removeIf(t -> t.getTimestamp() > this.referenceTimestamp || accountData.getAddress().equals(t.getRecipient()));
if (transferPrivsTransactions.isEmpty()) {
// Nothing to do
return;
}
for (TransactionData transactionData : transferPrivsTransactions) {
TransferPrivsTransactionData transferPrivsTransactionData = (TransferPrivsTransactionData) transactionData;
this.penaltyAddresses.add(transferPrivsTransactionData.getRecipient());
this.fetchSponsorshipRewardShares(transferPrivsTransactionData.getRecipient());
this.findRecentSponsorshipCount();
if (this.recentSponsorshipCount >= 1) {
this.penaltyAddresses.addAll(this.sponsorAddresses);
}
String newAddress = this.getDestinationAccount(transferPrivsTransactionData.getRecipient());
while (newAddress != null) {
// Found destination account
this.penaltyAddresses.add(newAddress);
this.fetchSponsorshipRewardShares(newAddress);
this.findRecentSponsorshipCount();
if (this.recentSponsorshipCount >= 1) {
this.penaltyAddresses.addAll(this.sponsorAddresses);
}
newAddress = this.getDestinationAccount(newAddress);
}
}
}
private String getDestinationAccount(String address) throws DataException {
AccountData accountData = this.repository.getAccountRepository().getAccount(address);
List<TransactionData> transferPrivsTransactions = fetchTransferPrivsForAddress(address);
transferPrivsTransactions.removeIf(t -> t.getTimestamp() > this.referenceTimestamp || accountData.getAddress().equals(t.getRecipient()));
if (transferPrivsTransactions.isEmpty()) {
return null;
}
if (accountData == null) {
return null;
}
for (TransactionData transactionData : transferPrivsTransactions) {
TransferPrivsTransactionData transferPrivsTransactionData = (TransferPrivsTransactionData) transactionData;
if (Arrays.equals(transferPrivsTransactionData.getSenderPublicKey(), accountData.getPublicKey())) {
return transferPrivsTransactionData.getRecipient();
}
}
return null;
}
private void fetchSponsorshipRewardShares(String address) throws DataException {
AccountData accountDataRs = this.repository.getAccountRepository().getAccount(address);
List<RewardShareTransactionData> sponsorshipRewardShares = new ArrayList<>();
// Define relevant transactions
List<TransactionType> txTypes = List.of(TransactionType.REWARD_SHARE);
List<TransactionData> transactionDataList = fetchTransactions(repository, txTypes, address, false);
for (TransactionData transactionData : transactionDataList) {
if (transactionData.getType() != TransactionType.REWARD_SHARE) {
continue;
}
RewardShareTransactionData rewardShareTransactionData = (RewardShareTransactionData) transactionData;
// Skip removals
if (rewardShareTransactionData.getSharePercent() < 0) {
continue;
}
// Skip if not sponsored by this account
if (!Arrays.equals(rewardShareTransactionData.getCreatorPublicKey(), accountDataRs.getPublicKey())) {
continue;
}
// Skip self shares
if (Objects.equals(rewardShareTransactionData.getRecipient(), address)) {
continue;
}
boolean duplicateFound = false;
for (RewardShareTransactionData existingRewardShare : sponsorshipRewardShares) {
if (Objects.equals(existingRewardShare.getRecipient(), rewardShareTransactionData.getRecipient())) {
// Duplicate
duplicateFound = true;
break;
}
}
if (!duplicateFound) {
sponsorshipRewardShares.add(rewardShareTransactionData);
this.sponsorAddresses.add(rewardShareTransactionData.getRecipient());
}
}
this.sponsorshipRewardShares = sponsorshipRewardShares;
}
private void fetchTransferAssetForAddress(String address) throws DataException {
List<TransferAssetTransactionData> transferAssetForAddress = new ArrayList<>();
// Define relevant transactions
List<TransactionType> txTypes = List.of(TransactionType.TRANSFER_ASSET);
List<TransactionData> transactionDataList = fetchTransactions(repository, txTypes, address, false);
transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV2);
for (TransactionData transactionData : transactionDataList) {
if (transactionData.getType() != TransactionType.TRANSFER_ASSET) {
continue;
}
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) transactionData;
if (transferAssetTransactionData.getAssetId() == Asset.QORT) {
if (!Objects.equals(transferAssetTransactionData.getRecipient(), address)) {
// Outgoing transfer asset for this account
transferAssetForAddress.add(transferAssetTransactionData);
this.assetAddresses.add(transferAssetTransactionData.getRecipient());
}
}
}
this.transferAssetForAddress = transferAssetForAddress;
}
private void findRecentSponsorshipCount() {
int recentSponsorshipCount = 0;
for (RewardShareTransactionData rewardShare : sponsorshipRewardShares) {
if (rewardShare.getTimestamp() >= this.snapshotTimestampV1) {
recentSponsorshipCount++;
}
}
this.recentSponsorshipCount = recentSponsorshipCount;
}
private void findRecentAssetSendCount() {
int recentAssetSendCount = 0;
for (TransferAssetTransactionData assetSend : transferAssetForAddress) {
if (assetSend.getTimestamp() >= this.snapshotTimestampV1) {
recentAssetSendCount++;
}
}
this.recentAssetSendCount = recentAssetSendCount;
}
private List<TransactionData> fetchTransferPrivsForAddress(String address) throws DataException {
return fetchTransactions(repository,
List.of(TransactionType.TRANSFER_PRIVS),
address, true);
}
private static List<TransactionData> fetchTransactions(Repository repository, List<TransactionType> txTypes, String address, boolean reverse) throws DataException {
// Fetch all relevant transactions for this account
List<byte[]> signatures = repository.getTransactionRepository()
.getSignaturesMatchingCriteria(null, null, null, txTypes,
null, null, address, TransactionsResource.ConfirmationStatus.CONFIRMED,
null, null, reverse);
List<TransactionData> transactionDataList = new ArrayList<>();
for (byte[] signature : signatures) {
// Fetch transaction data
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
if (transactionData == null) {
continue;
}
transactionDataList.add(transactionData);
}
return transactionDataList;
}
}

View File

@@ -0,0 +1,370 @@
package org.qortal.account;
import org.qortal.api.resource.TransactionsResource;
import org.qortal.asset.Asset;
import org.qortal.data.account.AccountData;
import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.*;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.transaction.Transaction.TransactionType;
import java.util.*;
import java.util.stream.Collectors;
public class SelfSponsorshipAlgoV3 {
private final Repository repository;
private final String address;
private final AccountData accountData;
private final long snapshotTimestampV1;
private final long snapshotTimestampV3;
private final boolean override;
private int registeredNameCount = 0;
private int suspiciousCount = 0;
private int suspiciousPercent = 0;
private int consolidationCount = 0;
private int bulkIssuanceCount = 0;
private int recentSponsorshipCount = 0;
private List<RewardShareTransactionData> sponsorshipRewardShares = new ArrayList<>();
private final Map<String, List<TransactionData>> paymentsByAddress = new HashMap<>();
private final Set<String> sponsees = new LinkedHashSet<>();
private Set<String> consolidatedAddresses = new LinkedHashSet<>();
private final Set<String> zeroTransactionAddreses = new LinkedHashSet<>();
private final Set<String> penaltyAddresses = new LinkedHashSet<>();
public SelfSponsorshipAlgoV3(Repository repository, String address, long snapshotTimestampV1, long snapshotTimestampV3, boolean override) throws DataException {
this.repository = repository;
this.address = address;
this.accountData = this.repository.getAccountRepository().getAccount(this.address);
this.snapshotTimestampV1 = snapshotTimestampV1;
this.snapshotTimestampV3 = snapshotTimestampV3;
this.override = override;
}
public String getAddress() {
return this.address;
}
public Set<String> getPenaltyAddresses() {
return this.penaltyAddresses;
}
public void run() throws DataException {
if (this.accountData == null) {
// Nothing to do
return;
}
this.fetchSponsorshipRewardShares();
if (this.sponsorshipRewardShares.isEmpty()) {
// Nothing to do
return;
}
this.findConsolidatedRewards();
this.findBulkIssuance();
this.findRegisteredNameCount();
this.findRecentSponsorshipCount();
int score = this.calculateScore();
if (score <= 0 && !override) {
return;
}
String newAddress = this.getDestinationAccount(this.address);
while (newAddress != null) {
// Found destination account
this.penaltyAddresses.add(newAddress);
// Run algo for this address, but in "override" mode because it has already been flagged
SelfSponsorshipAlgoV3 algoV3 = new SelfSponsorshipAlgoV3(this.repository, newAddress, this.snapshotTimestampV1, this.snapshotTimestampV3, true);
algoV3.run();
this.penaltyAddresses.addAll(algoV3.getPenaltyAddresses());
newAddress = this.getDestinationAccount(newAddress);
}
this.penaltyAddresses.add(this.address);
if (this.override || this.recentSponsorshipCount < 20) {
this.penaltyAddresses.addAll(this.consolidatedAddresses);
this.penaltyAddresses.addAll(this.zeroTransactionAddreses);
}
else {
this.penaltyAddresses.addAll(this.sponsees);
}
}
private String getDestinationAccount(String address) throws DataException {
List<TransactionData> transferPrivsTransactions = fetchTransferPrivsForAddress(address);
if (transferPrivsTransactions.isEmpty()) {
// No TRANSFER_PRIVS transactions for this address
return null;
}
AccountData accountData = this.repository.getAccountRepository().getAccount(address);
if (accountData == null) {
return null;
}
for (TransactionData transactionData : transferPrivsTransactions) {
TransferPrivsTransactionData transferPrivsTransactionData = (TransferPrivsTransactionData) transactionData;
if (Arrays.equals(transferPrivsTransactionData.getSenderPublicKey(), accountData.getPublicKey())) {
return transferPrivsTransactionData.getRecipient();
}
}
return null;
}
private void findConsolidatedRewards() throws DataException {
List<String> sponseesThatSentRewards = new ArrayList<>();
Map<String, Integer> paymentRecipients = new HashMap<>();
// Collect outgoing payments of each sponsee
for (String sponseeAddress : this.sponsees) {
// Firstly fetch all payments for address, since the functions below depend on this data
this.fetchPaymentsForAddress(sponseeAddress);
// Check if the address has zero relevant transactions
if (this.hasZeroTransactions(sponseeAddress)) {
this.zeroTransactionAddreses.add(sponseeAddress);
}
// Get payment recipients
List<String> allPaymentRecipients = this.fetchOutgoingPaymentRecipientsForAddress(sponseeAddress);
if (allPaymentRecipients.isEmpty()) {
continue;
}
sponseesThatSentRewards.add(sponseeAddress);
List<String> addressesPaidByThisSponsee = new ArrayList<>();
for (String paymentRecipient : allPaymentRecipients) {
if (addressesPaidByThisSponsee.contains(paymentRecipient)) {
// We already tracked this association - don't allow multiple to stack up
continue;
}
addressesPaidByThisSponsee.add(paymentRecipient);
// Increment count for this recipient, or initialize to 1 if not present
if (paymentRecipients.computeIfPresent(paymentRecipient, (k, v) -> v + 1) == null) {
paymentRecipients.put(paymentRecipient, 1);
}
}
}
// Exclude addresses with a low number of payments
Map<String, Integer> filteredPaymentRecipients = paymentRecipients.entrySet().stream()
.filter(p -> p.getValue() != null && p.getValue() >= 10)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// Now check how many sponsees have sent to this subset of addresses
Map<String, Integer> sponseesThatConsolidatedRewards = new HashMap<>();
for (String sponseeAddress : sponseesThatSentRewards) {
List<String> allPaymentRecipients = this.fetchOutgoingPaymentRecipientsForAddress(sponseeAddress);
// Remove any that aren't to one of the flagged recipients (i.e. consolidation)
allPaymentRecipients.removeIf(r -> !filteredPaymentRecipients.containsKey(r));
int count = allPaymentRecipients.size();
if (count == 0) {
continue;
}
if (sponseesThatConsolidatedRewards.computeIfPresent(sponseeAddress, (k, v) -> v + count) == null) {
sponseesThatConsolidatedRewards.put(sponseeAddress, count);
}
}
// Remove sponsees that have only sent a low number of payments to the filtered addresses
Map<String, Integer> filteredSponseesThatConsolidatedRewards = sponseesThatConsolidatedRewards.entrySet().stream()
.filter(p -> p.getValue() != null && p.getValue() >= 2)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
this.consolidationCount = sponseesThatConsolidatedRewards.size();
this.consolidatedAddresses = new LinkedHashSet<>(filteredSponseesThatConsolidatedRewards.keySet());
this.suspiciousCount = this.consolidationCount + this.zeroTransactionAddreses.size();
this.suspiciousPercent = (int)(this.suspiciousCount / (float) this.sponsees.size() * 100);
}
private void findBulkIssuance() {
Long lastTimestamp = null;
for (RewardShareTransactionData rewardShareTransactionData : sponsorshipRewardShares) {
long timestamp = rewardShareTransactionData.getTimestamp();
if (timestamp >= this.snapshotTimestampV3) {
continue;
}
if (lastTimestamp != null) {
if (timestamp - lastTimestamp < 3*60*1000L) {
this.bulkIssuanceCount++;
}
}
lastTimestamp = timestamp;
}
}
private void findRegisteredNameCount() throws DataException {
int registeredNameCount = 0;
for (String sponseeAddress : sponsees) {
List<NameData> names = repository.getNameRepository().getNamesByOwner(sponseeAddress);
for (NameData name : names) {
if (name.getRegistered() < this.snapshotTimestampV3) {
registeredNameCount++;
break;
}
}
}
this.registeredNameCount = registeredNameCount;
}
private void findRecentSponsorshipCount() {
int recentSponsorshipCount = 0;
for (RewardShareTransactionData rewardShare : sponsorshipRewardShares) {
if (rewardShare.getTimestamp() >= this.snapshotTimestampV1) {
recentSponsorshipCount++;
}
}
this.recentSponsorshipCount = recentSponsorshipCount;
}
private int calculateScore() {
final int suspiciousMultiplier = (this.suspiciousCount >= 100) ? this.suspiciousPercent : 1;
final int nameMultiplier = (this.sponsees.size() >= 25 && this.registeredNameCount <= 1) ? 21 :
(this.sponsees.size() >= 15 && this.registeredNameCount <= 1) ? 11 :
(this.sponsees.size() >= 5 && this.registeredNameCount <= 1) ? 5 : 1;
final int consolidationMultiplier = Math.max(this.consolidationCount, 1);
final int bulkIssuanceMultiplier = Math.max(this.bulkIssuanceCount / 2, 1);
final int offset = 20;
return suspiciousMultiplier * nameMultiplier * consolidationMultiplier * bulkIssuanceMultiplier - offset;
}
private void fetchSponsorshipRewardShares() throws DataException {
List<RewardShareTransactionData> sponsorshipRewardShares = new ArrayList<>();
// Define relevant transactions
List<TransactionType> txTypes = List.of(TransactionType.REWARD_SHARE);
List<TransactionData> transactionDataList = fetchTransactions(repository, txTypes, this.address, false);
transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV3);
for (TransactionData transactionData : transactionDataList) {
if (transactionData.getType() != TransactionType.REWARD_SHARE) {
continue;
}
RewardShareTransactionData rewardShareTransactionData = (RewardShareTransactionData) transactionData;
// Skip removals
if (rewardShareTransactionData.getSharePercent() < 0) {
continue;
}
// Skip if not sponsored by this account
if (!Arrays.equals(rewardShareTransactionData.getCreatorPublicKey(), accountData.getPublicKey())) {
continue;
}
// Skip self shares
if (Objects.equals(rewardShareTransactionData.getRecipient(), this.address)) {
continue;
}
boolean duplicateFound = false;
for (RewardShareTransactionData existingRewardShare : sponsorshipRewardShares) {
if (Objects.equals(existingRewardShare.getRecipient(), rewardShareTransactionData.getRecipient())) {
// Duplicate
duplicateFound = true;
break;
}
}
if (!duplicateFound) {
sponsorshipRewardShares.add(rewardShareTransactionData);
this.sponsees.add(rewardShareTransactionData.getRecipient());
}
}
this.sponsorshipRewardShares = sponsorshipRewardShares;
}
private List<TransactionData> fetchTransferPrivsForAddress(String address) throws DataException {
return fetchTransactions(repository,
List.of(TransactionType.TRANSFER_PRIVS),
address, true);
}
private void fetchPaymentsForAddress(String address) throws DataException {
List<TransactionData> payments = fetchTransactions(repository,
Arrays.asList(TransactionType.PAYMENT, TransactionType.TRANSFER_ASSET),
address, false);
this.paymentsByAddress.put(address, payments);
}
private List<String> fetchOutgoingPaymentRecipientsForAddress(String address) {
List<String> outgoingPaymentRecipients = new ArrayList<>();
List<TransactionData> transactionDataList = this.paymentsByAddress.get(address);
if (transactionDataList == null) transactionDataList = new ArrayList<>();
transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV3);
for (TransactionData transactionData : transactionDataList) {
switch (transactionData.getType()) {
case PAYMENT:
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
if (!Objects.equals(paymentTransactionData.getRecipient(), address)) {
// Outgoing payment from this account
outgoingPaymentRecipients.add(paymentTransactionData.getRecipient());
}
break;
case TRANSFER_ASSET:
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) transactionData;
if (transferAssetTransactionData.getAssetId() == Asset.QORT) {
if (!Objects.equals(transferAssetTransactionData.getRecipient(), address)) {
// Outgoing payment from this account
outgoingPaymentRecipients.add(transferAssetTransactionData.getRecipient());
}
}
break;
default:
break;
}
}
return outgoingPaymentRecipients;
}
private boolean hasZeroTransactions(String address) {
List<TransactionData> transactionDataList = this.paymentsByAddress.get(address);
if (transactionDataList == null) {
return true;
}
transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV3);
return transactionDataList.isEmpty();
}
private static List<TransactionData> fetchTransactions(Repository repository, List<TransactionType> txTypes, String address, boolean reverse) throws DataException {
// Fetch all relevant transactions for this account
List<byte[]> signatures = repository.getTransactionRepository()
.getSignaturesMatchingCriteria(null, null, null, txTypes,
null, null, address, TransactionsResource.ConfirmationStatus.CONFIRMED,
null, null, reverse);
List<TransactionData> transactionDataList = new ArrayList<>();
for (byte[] signature : signatures) {
// Fetch transaction data
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
if (transactionData == null) {
continue;
}
transactionDataList.add(transactionData);
}
return transactionDataList;
}
}

View File

@@ -141,7 +141,7 @@ public class ApiRequest {
}
String resultString = result.toString();
return resultString.length() > 0 ? resultString.substring(0, resultString.length() - 1) : resultString;
return !resultString.isEmpty() ? resultString.substring(0, resultString.length() - 1) : resultString;
}
/**

View File

@@ -82,7 +82,7 @@ public class HTMLParser {
}
public static boolean isHtmlFile(String path) {
if (path.endsWith(".html") || path.endsWith(".htm") || path.equals("")) {
if (path.endsWith(".html") || path.endsWith(".htm") || path.isEmpty()) {
return true;
}
return false;

View File

@@ -2,5 +2,5 @@ package org.qortal.api;
public enum SearchMode {
LATEST,
ALL;
ALL
}

View File

@@ -41,7 +41,7 @@ public class GatewayResource {
private ArbitraryResourceStatus getStatus(Service service, String name, String identifier, Boolean build) {
// If "build=true" has been specified in the query string, build the resource before returning its status
if (build != null && build == true) {
if (build != null && build) {
try {
ArbitraryDataReader reader = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, null);
if (!reader.isBuilding()) {
@@ -80,7 +80,7 @@ public class GatewayResource {
private HttpServletResponse parsePath(String inPath, String qdnContext, String secret58, boolean includeResourceIdInPrefix, boolean async) {
if (inPath == null || inPath.equals("")) {
if (inPath == null || inPath.isEmpty()) {
// Assume not a real file
return ArbitraryDataRenderer.getResponse(response, 404, "Error 404: File Not Found");
}
@@ -140,7 +140,7 @@ public class GatewayResource {
}
String prefix = StringUtils.join(prefixParts, "/");
if (prefix != null && prefix.length() > 0) {
if (prefix != null && !prefix.isEmpty()) {
prefix = "/" + prefix;
}

View File

@@ -17,7 +17,7 @@ public class ConnectedPeer {
public enum Direction {
INBOUND,
OUTBOUND;
OUTBOUND
}
public Direction direction;

View File

@@ -233,8 +233,7 @@ public class AddressesResource {
}
} catch (DataException e) {
continue;
}
}
}
// Sort by level

View File

@@ -119,7 +119,7 @@ public class ArbitraryResource {
// Ensure that "default" and "identifier" parameters cannot coexist
boolean defaultRes = Boolean.TRUE.equals(defaultResource);
if (defaultRes == true && identifier != null) {
if (defaultRes && identifier != null) {
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "identifier cannot be specified when requesting a default resource");
}
@@ -491,7 +491,7 @@ public class ArbitraryResource {
List<ArbitraryTransactionData> transactionDataList;
if (query == null || query.equals("")) {
if (query == null || query.isEmpty()) {
transactionDataList = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository, limit, offset);
} else {
transactionDataList = ArbitraryDataStorageManager.getInstance().searchHostedTransactions(repository,query, limit, offset);
@@ -1258,7 +1258,7 @@ public class ArbitraryResource {
}
// Finish here if user has requested a preview
if (preview != null && preview == true) {
if (preview != null && preview) {
return this.preview(path, service);
}

View File

@@ -86,7 +86,7 @@ public class BlocksResource {
// Check the database first
BlockData blockData = repository.getBlockRepository().fromSignature(signature);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
@@ -95,7 +95,7 @@ public class BlocksResource {
// Not found, so try the block archive
blockData = repository.getBlockArchiveRepository().fromSignature(signature);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
@@ -304,7 +304,7 @@ public class BlocksResource {
try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().getLastBlock();
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}
@@ -474,7 +474,7 @@ public class BlocksResource {
// Firstly check the database
BlockData blockData = repository.getBlockRepository().fromHeight(height);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
@@ -483,7 +483,7 @@ public class BlocksResource {
// Not found, so try the archive
blockData = repository.getBlockArchiveRepository().fromHeight(height);
if (blockData != null) {
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
@@ -596,7 +596,7 @@ public class BlocksResource {
if (height > 1) {
// Found match in Blocks table
blockData = repository.getBlockRepository().fromHeight(height);
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}
return blockData;
@@ -614,7 +614,7 @@ public class BlocksResource {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
}
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}
@@ -651,7 +651,7 @@ public class BlocksResource {
@QueryParam("includeOnlineSignatures") Boolean includeOnlineSignatures) {
try (final Repository repository = RepositoryManager.getRepository()) {
List<BlockData> blocks = new ArrayList<>();
boolean shouldReverse = (reverse != null && reverse == true);
boolean shouldReverse = (reverse != null && reverse);
int i = 0;
while (i < count) {
@@ -664,7 +664,7 @@ public class BlocksResource {
break;
}
}
if (includeOnlineSignatures == null || includeOnlineSignatures == false) {
if (includeOnlineSignatures == null || !includeOnlineSignatures) {
blockData.setOnlineAccountsSignatures(null);
}

View File

@@ -17,7 +17,11 @@ import org.qortal.api.model.crosschain.AddressRequest;
import org.qortal.api.model.crosschain.BitcoinSendRequest;
import org.qortal.crosschain.AddressInfo;
import org.qortal.crosschain.Bitcoin;
import org.qortal.crosschain.ChainableServer;
import org.qortal.crosschain.ElectrumX;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.ServerConnectionInfo;
import org.qortal.crosschain.ServerInfo;
import org.qortal.crosschain.SimpleTransaction;
import org.qortal.crosschain.ServerConfigurationInfo;
@@ -265,4 +269,292 @@ public class CrossChainBitcoinResource {
return CrossChainUtils.buildServerConfigurationInfo(Bitcoin.getInstance());
}
@GET
@Path("/serverconnectionhistory")
@Operation(
summary = "Returns Bitcoin server connection history",
description = "Returns Bitcoin server connection history",
responses = {
@ApiResponse(
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
)
}
)
public List<ServerConnectionInfo> getServerConnectionHistory() {
return CrossChainUtils.buildServerConnectionHistory(Bitcoin.getInstance());
}
@POST
@Path("/addserver")
@Operation(
summary = "Add server to list of Bitcoin servers",
description = "Add server to list of Bitcoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if added, false if not added",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.addServer( Bitcoin.getInstance(), server )) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/removeserver")
@Operation(
summary = "Remove server from list of Bitcoin servers",
description = "Remove server from list of Bitcoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if removed, otherwise",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.removeServer( Bitcoin.getInstance(), server ) ) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/setcurrentserver")
@Operation(
summary = "Set current Bitcoin server",
description = "Set current Bitcoin server",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "connection info",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerConnectionInfo.class
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
if( serverInfo.getConnectionType() == null ||
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try {
return CrossChainUtils.setCurrentServer( Bitcoin.getInstance(), serverInfo );
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return new ServerConnectionInfo(
serverInfo,
CrossChainUtils.CORE_API_CALL,
true,
false,
System.currentTimeMillis(),
CrossChainUtils.getNotes(e));
}
}
@GET
@Path("/feekb")
@Operation(
summary = "Returns Bitcoin fee per Kb.",
description = "Returns Bitcoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getBitcoinFeePerKb() {
Bitcoin bitcoin = Bitcoin.getInstance();
return String.valueOf(bitcoin.getFeePerKb().value);
}
@POST
@Path("/updatefeekb")
@Operation(
summary = "Sets Bitcoin fee per Kb.",
description = "Sets Bitcoin fee per Kb.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee per Kb",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setBitcoinFeePerKb(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Bitcoin bitcoin = Bitcoin.getInstance();
try {
return CrossChainUtils.setFeePerKb(bitcoin, fee);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
@GET
@Path("/feeceiling")
@Operation(
summary = "Returns Bitcoin fee per Kb.",
description = "Returns Bitcoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getBitcoinFeeCeiling() {
Bitcoin bitcoin = Bitcoin.getInstance();
return String.valueOf(bitcoin.getFeeCeiling());
}
@POST
@Path("/updatefeeceiling")
@Operation(
summary = "Sets Bitcoin fee ceiling.",
description = "Sets Bitcoin fee ceiling.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setBitcoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Bitcoin bitcoin = Bitcoin.getInstance();
try {
return CrossChainUtils.setFeeCeiling(bitcoin, fee);
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
}

View File

@@ -16,8 +16,12 @@ import org.qortal.api.Security;
import org.qortal.api.model.crosschain.AddressRequest;
import org.qortal.api.model.crosschain.DigibyteSendRequest;
import org.qortal.crosschain.AddressInfo;
import org.qortal.crosschain.Digibyte;
import org.qortal.crosschain.ChainableServer;
import org.qortal.crosschain.ElectrumX;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.Digibyte;
import org.qortal.crosschain.ServerConnectionInfo;
import org.qortal.crosschain.ServerInfo;
import org.qortal.crosschain.SimpleTransaction;
import org.qortal.crosschain.ServerConfigurationInfo;
@@ -265,4 +269,292 @@ public class CrossChainDigibyteResource {
return CrossChainUtils.buildServerConfigurationInfo(Digibyte.getInstance());
}
}
@GET
@Path("/serverconnectionhistory")
@Operation(
summary = "Returns Digibyte server connection history",
description = "Returns Digibyte server connection history",
responses = {
@ApiResponse(
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
)
}
)
public List<ServerConnectionInfo> getServerConnectionHistory() {
return CrossChainUtils.buildServerConnectionHistory(Digibyte.getInstance());
}
@POST
@Path("/addserver")
@Operation(
summary = "Add server to list of Digibyte servers",
description = "Add server to list of Digibyte servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if added, false if not added",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.addServer( Digibyte.getInstance(), server )) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/removeserver")
@Operation(
summary = "Remove server from list of Digibyte servers",
description = "Remove server from list of Digibyte servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if removed, otherwise",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.removeServer( Digibyte.getInstance(), server ) ) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/setcurrentserver")
@Operation(
summary = "Set current Digibyte server",
description = "Set current Digibyte server",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "connection info",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerConnectionInfo.class
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
if( serverInfo.getConnectionType() == null ||
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try {
return CrossChainUtils.setCurrentServer( Digibyte.getInstance(), serverInfo );
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return new ServerConnectionInfo(
serverInfo,
CrossChainUtils.CORE_API_CALL,
true,
false,
System.currentTimeMillis(),
CrossChainUtils.getNotes(e));
}
}
@GET
@Path("/feekb")
@Operation(
summary = "Returns Digibyte fee per Kb.",
description = "Returns Digibyte fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getDigibyteFeePerKb() {
Digibyte digibyte = Digibyte.getInstance();
return String.valueOf(digibyte.getFeePerKb().value);
}
@POST
@Path("/updatefeekb")
@Operation(
summary = "Sets Digibyte fee per Kb.",
description = "Sets Digibyte fee per Kb.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee per Kb",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setDigibyteFeePerKb(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Digibyte digibyte = Digibyte.getInstance();
try {
return CrossChainUtils.setFeePerKb(digibyte, fee);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
@GET
@Path("/feeceiling")
@Operation(
summary = "Returns Digibyte fee per Kb.",
description = "Returns Digibyte fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getDigibyteFeeCeiling() {
Digibyte digibyte = Digibyte.getInstance();
return String.valueOf(digibyte.getFeeCeiling());
}
@POST
@Path("/updatefeeceiling")
@Operation(
summary = "Sets Digibyte fee ceiling.",
description = "Sets Digibyte fee ceiling.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setDigibyteFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Digibyte digibyte = Digibyte.getInstance();
try {
return CrossChainUtils.setFeeCeiling(digibyte, fee);
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
}

View File

@@ -16,8 +16,12 @@ import org.qortal.api.Security;
import org.qortal.api.model.crosschain.AddressRequest;
import org.qortal.api.model.crosschain.DogecoinSendRequest;
import org.qortal.crosschain.AddressInfo;
import org.qortal.crosschain.Dogecoin;
import org.qortal.crosschain.ChainableServer;
import org.qortal.crosschain.ElectrumX;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.Dogecoin;
import org.qortal.crosschain.ServerConnectionInfo;
import org.qortal.crosschain.ServerInfo;
import org.qortal.crosschain.SimpleTransaction;
import org.qortal.crosschain.ServerConfigurationInfo;
@@ -265,4 +269,292 @@ public class CrossChainDogecoinResource {
return CrossChainUtils.buildServerConfigurationInfo(Dogecoin.getInstance());
}
}
@GET
@Path("/serverconnectionhistory")
@Operation(
summary = "Returns Dogecoin server connection history",
description = "Returns Dogecoin server connection history",
responses = {
@ApiResponse(
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
)
}
)
public List<ServerConnectionInfo> getServerConnectionHistory() {
return CrossChainUtils.buildServerConnectionHistory(Dogecoin.getInstance());
}
@POST
@Path("/addserver")
@Operation(
summary = "Add server to list of Dogecoin servers",
description = "Add server to list of Dogecoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if added, false if not added",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.addServer( Dogecoin.getInstance(), server )) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/removeserver")
@Operation(
summary = "Remove server from list of Dogecoin servers",
description = "Remove server from list of Dogecoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if removed, otherwise",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.removeServer( Dogecoin.getInstance(), server ) ) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/setcurrentserver")
@Operation(
summary = "Set current Dogecoin server",
description = "Set current Dogecoin server",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "connection info",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerConnectionInfo.class
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
if( serverInfo.getConnectionType() == null ||
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try {
return CrossChainUtils.setCurrentServer( Dogecoin.getInstance(), serverInfo );
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return new ServerConnectionInfo(
serverInfo,
CrossChainUtils.CORE_API_CALL,
true,
false,
System.currentTimeMillis(),
CrossChainUtils.getNotes(e));
}
}
@GET
@Path("/feekb")
@Operation(
summary = "Returns Dogecoin fee per Kb.",
description = "Returns Dogecoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getDogecoinFeePerKb() {
Dogecoin dogecoin = Dogecoin.getInstance();
return String.valueOf(dogecoin.getFeePerKb().value);
}
@POST
@Path("/updatefeekb")
@Operation(
summary = "Sets Dogecoin fee per Kb.",
description = "Sets Dogecoin fee per Kb.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee per Kb",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setDogecoinFeePerKb(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Dogecoin dogecoin = Dogecoin.getInstance();
try {
return CrossChainUtils.setFeePerKb(dogecoin, fee);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
@GET
@Path("/feeceiling")
@Operation(
summary = "Returns Dogecoin fee per Kb.",
description = "Returns Dogecoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getDogecoinFeeCeiling() {
Dogecoin dogecoin = Dogecoin.getInstance();
return String.valueOf(dogecoin.getFeeCeiling());
}
@POST
@Path("/updatefeeceiling")
@Operation(
summary = "Sets Dogecoin fee ceiling.",
description = "Sets Dogecoin fee ceiling.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setDogecoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Dogecoin dogecoin = Dogecoin.getInstance();
try {
return CrossChainUtils.setFeeCeiling(dogecoin, fee);
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
}

View File

@@ -16,8 +16,12 @@ import org.qortal.api.Security;
import org.qortal.api.model.crosschain.AddressRequest;
import org.qortal.api.model.crosschain.LitecoinSendRequest;
import org.qortal.crosschain.AddressInfo;
import org.qortal.crosschain.ChainableServer;
import org.qortal.crosschain.ElectrumX;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.Litecoin;
import org.qortal.crosschain.ServerConnectionInfo;
import org.qortal.crosschain.ServerInfo;
import org.qortal.crosschain.SimpleTransaction;
import org.qortal.crosschain.ServerConfigurationInfo;
@@ -266,6 +270,180 @@ public class CrossChainLitecoinResource {
return CrossChainUtils.buildServerConfigurationInfo(Litecoin.getInstance());
}
@GET
@Path("/serverconnectionhistory")
@Operation(
summary = "Returns Litecoin server connection history",
description = "Returns Litecoin server connection history",
responses = {
@ApiResponse(
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
)
}
)
public List<ServerConnectionInfo> getServerConnectionHistory() {
return CrossChainUtils.buildServerConnectionHistory(Litecoin.getInstance());
}
@POST
@Path("/addserver")
@Operation(
summary = "Add server to list of Litecoin servers",
description = "Add server to list of Litecoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if added, false if not added",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.addServer( Litecoin.getInstance(), server )) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/removeserver")
@Operation(
summary = "Remove server from list of Litecoin servers",
description = "Remove server from list of Litecoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if removed, otherwise",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.removeServer( Litecoin.getInstance(), server ) ) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/setcurrentserver")
@Operation(
summary = "Set current Litecoin server",
description = "Set current Litecoin server",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "connection info",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerConnectionInfo.class
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
if( serverInfo.getConnectionType() == null ||
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try {
return CrossChainUtils.setCurrentServer( Litecoin.getInstance(), serverInfo );
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return new ServerConnectionInfo(
serverInfo,
CrossChainUtils.CORE_API_CALL,
true,
false,
System.currentTimeMillis(),
CrossChainUtils.getNotes(e));
}
}
@POST
@Path("/repair")
@Operation(
@@ -304,4 +482,117 @@ public class CrossChainLitecoinResource {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE);
}
}
@GET
@Path("/feekb")
@Operation(
summary = "Returns Litecoin fee per Kb.",
description = "Returns Litecoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getLitecoinFeePerKb() {
Litecoin litecoin = Litecoin.getInstance();
return String.valueOf(litecoin.getFeePerKb().value);
}
@POST
@Path("/updatefeekb")
@Operation(
summary = "Sets Litecoin fee per Kb.",
description = "Sets Litecoin fee per Kb.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee per Kb",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setLitecoinFeePerKb(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Litecoin litecoin = Litecoin.getInstance();
try {
return CrossChainUtils.setFeePerKb(litecoin, fee);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
@GET
@Path("/feeceiling")
@Operation(
summary = "Returns Litecoin fee per Kb.",
description = "Returns Litecoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getLitecoinFeeCeiling() {
Litecoin litecoin = Litecoin.getInstance();
return String.valueOf(litecoin.getFeeCeiling());
}
@POST
@Path("/updatefeeceiling")
@Operation(
summary = "Sets Litecoin fee ceiling.",
description = "Sets Litecoin fee ceiling.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setLitecoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Litecoin litecoin = Litecoin.getInstance();
try {
return CrossChainUtils.setFeeCeiling(litecoin, fee);
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
}

View File

@@ -13,8 +13,12 @@ import org.qortal.api.ApiErrors;
import org.qortal.api.ApiExceptionFactory;
import org.qortal.api.Security;
import org.qortal.api.model.crosschain.PirateChainSendRequest;
import org.qortal.crosschain.ChainableServer;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.PirateChain;
import org.qortal.crosschain.PirateLightClient;
import org.qortal.crosschain.ServerConnectionInfo;
import org.qortal.crosschain.ServerInfo;
import org.qortal.crosschain.SimpleTransaction;
import org.qortal.crosschain.ServerConfigurationInfo;
@@ -351,4 +355,291 @@ public class CrossChainPirateChainResource {
return CrossChainUtils.buildServerConfigurationInfo(PirateChain.getInstance());
}
@GET
@Path("/serverconnectionhistory")
@Operation(
summary = "Returns Pirate Chain server connection history",
description = "Returns Pirate Chain server connection history",
responses = {
@ApiResponse(
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
)
}
)
public List<ServerConnectionInfo> getServerConnectionHistory() {
return CrossChainUtils.buildServerConnectionHistory(PirateChain.getInstance());
}
@POST
@Path("/addserver")
@Operation(
summary = "Add server to list of Pirate Chain servers",
description = "Add server to list of Pirate Chain servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if added, false if not added",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String addServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
PirateLightClient.Server server = new PirateLightClient.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.addServer( PirateChain.getInstance(), server )) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/removeserver")
@Operation(
summary = "Remove server from list of Pirate Chain servers",
description = "Remove server from list of Pirate Chain servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if removed, otherwise",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String removeServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
PirateLightClient.Server server = new PirateLightClient.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.removeServer( PirateChain.getInstance(), server ) ) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/setcurrentserver")
@Operation(
summary = "Set current Pirate Chain server",
description = "Set current Pirate Chain server",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "connection info",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerConnectionInfo.class
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public ServerConnectionInfo setCurrentServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
if( serverInfo.getConnectionType() == null ||
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try {
return CrossChainUtils.setCurrentServer( PirateChain.getInstance(), serverInfo );
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return new ServerConnectionInfo(
serverInfo,
CrossChainUtils.CORE_API_CALL,
true,
false,
System.currentTimeMillis(),
CrossChainUtils.getNotes(e));
}
}
@GET
@Path("/feekb")
@Operation(
summary = "Returns PirateChain fee per Kb.",
description = "Returns PirateChain fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getPirateChainFeePerKb() {
PirateChain pirateChain = PirateChain.getInstance();
return String.valueOf(pirateChain.getFeePerKb().value);
}
@POST
@Path("/updatefeekb")
@Operation(
summary = "Sets PirateChain fee per Kb.",
description = "Sets PirateChain fee per Kb.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee per Kb",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setPirateChainFeePerKb(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
PirateChain pirateChain = PirateChain.getInstance();
try {
return CrossChainUtils.setFeePerKb(pirateChain, fee);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
@GET
@Path("/feeceiling")
@Operation(
summary = "Returns PirateChain fee per Kb.",
description = "Returns PirateChain fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getPirateChainFeeCeiling() {
PirateChain pirateChain = PirateChain.getInstance();
return String.valueOf(pirateChain.getFeeCeiling());
}
@POST
@Path("/updatefeeceiling")
@Operation(
summary = "Sets PirateChain fee ceiling.",
description = "Sets PirateChain fee ceiling.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setPirateChainFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
PirateChain pirateChain = PirateChain.getInstance();
try {
return CrossChainUtils.setFeeCeiling(pirateChain, fee);
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
}

View File

@@ -16,8 +16,12 @@ import org.qortal.api.Security;
import org.qortal.api.model.crosschain.AddressRequest;
import org.qortal.api.model.crosschain.RavencoinSendRequest;
import org.qortal.crosschain.AddressInfo;
import org.qortal.crosschain.ChainableServer;
import org.qortal.crosschain.ElectrumX;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.Ravencoin;
import org.qortal.crosschain.ServerConnectionInfo;
import org.qortal.crosschain.ServerInfo;
import org.qortal.crosschain.SimpleTransaction;
import org.qortal.crosschain.ServerConfigurationInfo;
@@ -265,4 +269,292 @@ public class CrossChainRavencoinResource {
return CrossChainUtils.buildServerConfigurationInfo(Ravencoin.getInstance());
}
@GET
@Path("/serverconnectionhistory")
@Operation(
summary = "Returns Ravencoin server connection history",
description = "Returns Ravencoin server connection history",
responses = {
@ApiResponse(
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
)
}
)
public List<ServerConnectionInfo> getServerConnectionHistory() {
return CrossChainUtils.buildServerConnectionHistory(Ravencoin.getInstance());
}
@POST
@Path("/addserver")
@Operation(
summary = "Add server to list of Ravencoin servers",
description = "Add server to list of Ravencoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if added, false if not added",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.addServer( Ravencoin.getInstance(), server )) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/removeserver")
@Operation(
summary = "Remove server from list of Ravencoin servers",
description = "Remove server from list of Ravencoin servers",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "true if removed, otherwise",
content = @Content(
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
try {
ElectrumX.Server server = new ElectrumX.Server(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
if( CrossChainUtils.removeServer( Ravencoin.getInstance(), server ) ) {
return "true";
}
else {
return "false";
}
}
catch (IllegalArgumentException | NullPointerException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return "false";
}
}
@POST
@Path("/setcurrentserver")
@Operation(
summary = "Set current Ravencoin server",
description = "Set current Ravencoin server",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerInfo.class
)
)
),
responses = {
@ApiResponse(
description = "connection info",
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = ServerConnectionInfo.class
)
)
)
}
)
@ApiErrors({ApiError.INVALID_DATA})
@SecurityRequirement(name = "apiKey")
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
Security.checkApiCallAllowed(request);
if( serverInfo.getConnectionType() == null ||
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
try {
return CrossChainUtils.setCurrentServer( Ravencoin.getInstance(), serverInfo );
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
catch (Exception e) {
return new ServerConnectionInfo(
serverInfo,
CrossChainUtils.CORE_API_CALL,
true,
false,
System.currentTimeMillis(),
CrossChainUtils.getNotes(e));
}
}
@GET
@Path("/feekb")
@Operation(
summary = "Returns Ravencoin fee per Kb.",
description = "Returns Ravencoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getRavencoinFeePerKb() {
Ravencoin ravencoin = Ravencoin.getInstance();
return String.valueOf(ravencoin.getFeePerKb().value);
}
@POST
@Path("/updatefeekb")
@Operation(
summary = "Sets Ravencoin fee per Kb.",
description = "Sets Ravencoin fee per Kb.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee per Kb",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setRavencoinFeePerKb(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Ravencoin ravencoin = Ravencoin.getInstance();
try {
return CrossChainUtils.setFeePerKb(ravencoin, fee);
} catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
@GET
@Path("/feeceiling")
@Operation(
summary = "Returns Ravencoin fee per Kb.",
description = "Returns Ravencoin fee per Kb.",
responses = {
@ApiResponse(
content = @Content(
schema = @Schema(
type = "number"
)
)
)
}
)
public String getRavencoinFeeCeiling() {
Ravencoin ravencoin = Ravencoin.getInstance();
return String.valueOf(ravencoin.getFeeCeiling());
}
@POST
@Path("/updatefeeceiling")
@Operation(
summary = "Sets Ravencoin fee ceiling.",
description = "Sets Ravencoin fee ceiling.",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "number",
description = "the fee",
example = "100"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "number", description = "fee"))
)
}
)
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
public String setRavencoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
Security.checkApiCallAllowed(request);
Ravencoin ravencoin = Ravencoin.getInstance();
try {
return CrossChainUtils.setFeeCeiling(ravencoin, fee);
}
catch (IllegalArgumentException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
}
}

View File

@@ -19,11 +19,14 @@ import org.qortal.api.model.CrossChainTradeSummary;
import org.qortal.controller.tradebot.TradeBot;
import org.qortal.crosschain.ACCT;
import org.qortal.crosschain.AcctMode;
import org.qortal.crosschain.Bitcoiny;
import org.qortal.crosschain.ForeignBlockchainException;
import org.qortal.crosschain.SupportedBlockchain;
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.crosschain.TransactionSummary;
import org.qortal.data.transaction.BaseTransactionData;
import org.qortal.data.transaction.MessageTransactionData;
import org.qortal.data.transaction.TransactionData;
@@ -47,6 +50,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@Path("/crosschain")
@Tag(name = "Cross-Chain")
@@ -372,7 +376,7 @@ public class CrossChainResource {
int maximumCount = maxtrades != null ? maxtrades : 10;
long minimumPeriod = 4 * 60 * 60 * 1000L; // ms
Boolean isFinished = Boolean.TRUE;
boolean useInversePrice = (inverse != null && inverse == true);
boolean useInversePrice = (inverse != null && inverse);
try (final Repository repository = RepositoryManager.getRepository()) {
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getFilteredAcctMap(foreignBlockchain);
@@ -497,6 +501,111 @@ public class CrossChainResource {
}
}
@POST
@Path("/p2sh")
@Operation(
summary = "Returns P2SH Address",
description = "Get the P2SH address to lock foreign coin in a cross chain trade for QORT",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "string",
description = "the AT address",
example = "AKFnu9yBp7tUAc5HAphhfCxRZTYoeKXgUy"
)
)
),
responses = {
@ApiResponse(
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", description = "address"))
)
}
)
@ApiErrors({ApiError.ADDRESS_UNKNOWN, ApiError.INVALID_CRITERIA})
@SecurityRequirement(name = "apiKey")
public String getForeignP2SH(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String atAddress) {
Security.checkApiCallAllowed(request);
try (final Repository repository = RepositoryManager.getRepository()) {
ATData atData = repository.getATRepository().fromATAddress(atAddress);
if (atData == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
ACCT acct = SupportedBlockchain.getAcctByCodeHash(atData.getCodeHash());
if( acct == null || !(acct.getBlockchain() instanceof Bitcoiny) )
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
CrossChainTradeData crossChainTradeData = acct.populateTradeData(repository, atData);
Optional<String> p2sh
= CrossChainUtils.getP2ShAddressForAT(atAddress, repository, bitcoiny, crossChainTradeData);
if(p2sh.isPresent()){
return p2sh.get();
}
else{
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
}
}
catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage());
}
}
@POST
@Path("/txactivity")
@Operation(
summary = "Returns Foreign Transaction Activity",
description = "Get the activity related to foreign coin trading",
responses = {
@ApiResponse(
content = @Content(
array = @ArraySchema(
schema = @Schema(
implementation = TransactionSummary.class
)
)
)
)
}
)
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE})
@SecurityRequirement(name = "apiKey")
public List<TransactionSummary> getForeignTransactionActivity(@HeaderParam(Security.API_KEY_HEADER) String apiKey, @Parameter(
description = "Limit to specific blockchain",
example = "LITECOIN",
schema = @Schema(implementation = SupportedBlockchain.class)
) @QueryParam("foreignBlockchain") SupportedBlockchain foreignBlockchain) {
Security.checkApiCallAllowed(request);
if (!(foreignBlockchain.getInstance() instanceof Bitcoiny))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
Bitcoiny bitcoiny = (Bitcoiny) foreignBlockchain.getInstance() ;
org.bitcoinj.core.Context.propagate( bitcoiny.getBitcoinjContext() );
try (final Repository repository = RepositoryManager.getRepository()) {
// sort from last lock to first lock
return CrossChainUtils
.getForeignTradeSummaries(foreignBlockchain, repository, bitcoiny).stream()
.sorted(Comparator.comparing(TransactionSummary::getLockingTimestamp).reversed())
.collect(Collectors.toList());
}
catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.REPOSITORY_ISSUE, e.getMessage());
}
catch (ForeignBlockchainException e) {
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE, e.getMessage());
}
}
private ATData fetchAtDataWithChecking(Repository repository, String atAddress) throws DataException {
ATData atData = repository.getATRepository().fromATAddress(atAddress);
if (atData == null)

View File

@@ -2,22 +2,43 @@ package org.qortal.api.resource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.crosschain.*;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.qortal.crosschain.*;
import org.qortal.data.at.ATData;
import org.qortal.data.crosschain.*;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class CrossChainUtils {
private static final Logger LOGGER = LogManager.getLogger(CrossChainUtils.class);
public static final String CORE_API_CALL = "Core API Call";
public static ServerConfigurationInfo buildServerConfigurationInfo(Bitcoiny blockchain) {
BitcoinyBlockchainProvider blockchainProvider = blockchain.getBlockchainProvider();
// the only reason this is called is to ensure the current server is set on the blockchain provider,
// if there is an exception, then ignore it
try {
blockchainProvider.getCurrentHeight();
} catch (ForeignBlockchainException e) {
LOGGER.warn("Problems getting block height before building server configuration infos");
}
ChainableServer currentServer = blockchainProvider.getCurrentServer();
return new ServerConfigurationInfo(
buildInfos(blockchainProvider.getServers(), currentServer),
buildInfos(blockchainProvider.getServers(), currentServer).stream()
.sorted(Comparator.comparing(ServerInfo::isCurrent).reversed())
.collect(Collectors.toList()),
buildInfos(blockchainProvider.getRemainingServers(), currentServer),
buildInfos(blockchainProvider.getUselessServers(), currentServer)
);
@@ -44,4 +65,484 @@ public class CrossChainUtils {
return infos;
}
}
/**
* Set Fee Per Kb
*
* @param bitcoiny the blockchain support
* @param fee the fee in satoshis
*
* @return the fee if valid
*
* @throws IllegalArgumentException if invalid
*/
public static String setFeePerKb(Bitcoiny bitcoiny, String fee) throws IllegalArgumentException {
long satoshis = Long.parseLong(fee);
if( satoshis < 0 ) throw new IllegalArgumentException("can't set fee to negative number");
bitcoiny.setFeePerKb(Coin.valueOf(satoshis) );
return String.valueOf(bitcoiny.getFeePerKb().value);
}
/**
* Set Fee Ceiling
*
* @param bitcoiny the blockchain support
* @param fee the fee in satoshis
*
* @return the fee if valid
*
* @throws IllegalArgumentException if invalid
*/
public static String setFeeCeiling(Bitcoiny bitcoiny, String fee) throws IllegalArgumentException{
long satoshis = Long.parseLong(fee);
if( satoshis < 0 ) throw new IllegalArgumentException("can't set fee to negative number");
bitcoiny.setFeeCeiling( Long.parseLong(fee));
return String.valueOf(bitcoiny.getFeeCeiling());
}
/**
* Get P2Sh Address For AT
*
* @param atAddress the AT address
* @param repository the repository
* @param bitcoiny the blockchain data
* @param crossChainTradeData the trade data
*
* @return the p2sh address for the trade, if there is one
*
* @throws DataException
*/
public static Optional<String> getP2ShAddressForAT(
String atAddress,
Repository repository,
Bitcoiny bitcoiny,
CrossChainTradeData crossChainTradeData) throws DataException {
// get the trade bot data for the AT address
Optional<TradeBotData> tradeBotDataOptional
= repository.getCrossChainRepository()
.getAllTradeBotData().stream()
.filter(data -> data.getAtAddress().equals(atAddress))
.findFirst();
if( tradeBotDataOptional.isEmpty() )
return Optional.empty();
TradeBotData tradeBotData = tradeBotDataOptional.get();
// return the p2sh address from the trade bot
return getP2ShFromTradeBot(bitcoiny, crossChainTradeData, tradeBotData);
}
/**
* Get Foreign Trade Summaries
*
* @param foreignBlockchain the blockchain traded on
* @param repository the repository
* @param bitcoiny data for the blockchain trade on
* @return
* @throws DataException
* @throws ForeignBlockchainException
*/
public static List<TransactionSummary> getForeignTradeSummaries(
SupportedBlockchain foreignBlockchain,
Repository repository,
Bitcoiny bitcoiny) throws DataException, ForeignBlockchainException {
// get all the AT address for the given blockchain
List<String> atAddresses
= repository.getCrossChainRepository().getAllTradeBotData().stream()
.filter(data -> foreignBlockchain.name().toLowerCase().equals(data.getForeignBlockchain().toLowerCase()))
//.filter( data -> data.getForeignKey().equals( xpriv )) // TODO
.map(data -> data.getAtAddress())
.collect(Collectors.toList());
List<TransactionSummary> summaries = new ArrayList<>( atAddresses.size() * 2 );
// for each AT address, gather the data and get foreign trade summary
for( String atAddress: atAddresses) {
ATData atData = repository.getATRepository().fromATAddress(atAddress);
CrossChainTradeData crossChainTradeData = foreignBlockchain.getLatestAcct().populateTradeData(repository, atData);
Optional<String> address = getP2ShAddressForAT(atAddress,repository, bitcoiny, crossChainTradeData);
if( address.isPresent()){
summaries.add( getForeignTradeSummary( bitcoiny, address.get(), atAddress ) );
}
}
return summaries;
}
/**
* Add Server
*
* Add foreign blockchain server to list of candidates.
*
* @param bitcoiny the foreign blockchain
* @param server the server
*
* @return true if the add was successful, otherwise false
*/
public static boolean addServer(Bitcoiny bitcoiny, ChainableServer server) {
return bitcoiny.getBlockchainProvider().addServer(server);
}
/**
* Remove Server
*
* Remove foreign blockchain server from list of candidates.
*
* @param bitcoiny the foreign blockchain
* @param server the server
*
* @return true if the removal was successful, otherwise false
*/
public static boolean removeServer(Bitcoiny bitcoiny, ChainableServer server){
return bitcoiny.getBlockchainProvider().removeServer(server);
}
/**
* Set Current Server
*
* Set the server to use the intended foreign blockchain.
*
* @param bitcoiny the foreign blockchain
* @param serverInfo the server configuration information
*
* @return the server connection information
*/
public static ServerConnectionInfo setCurrentServer(Bitcoiny bitcoiny, ServerInfo serverInfo) throws ForeignBlockchainException {
final BitcoinyBlockchainProvider blockchainProvider = bitcoiny.getBlockchainProvider();
ChainableServer server = blockchainProvider.getServer(
serverInfo.getHostName(),
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
serverInfo.getPort()
);
ChainableServerConnection connection = blockchainProvider.setCurrentServer(server, CORE_API_CALL).get();
return new ServerConnectionInfo(
new ServerInfo(
0,
serverInfo.getHostName(),
serverInfo.getPort(),
serverInfo.getConnectionType(),
connection.isSuccess()
),
CORE_API_CALL,
true,
connection.isSuccess() ,
System.currentTimeMillis(),
connection.getNotes()
);
}
/**
* Get P2Sh From Trade Bot
*
* Get P2Sh address from the trade bot
*
* @param bitcoiny the blockchain for the trade
* @param crossChainTradeData the cross cahin data for the trade
* @param tradeBotData the data from the trade bot
*
* @return the address, original format
*/
private static Optional<String> getP2ShFromTradeBot(
Bitcoiny bitcoiny,
CrossChainTradeData crossChainTradeData,
TradeBotData tradeBotData) {
// Pirate Chain does not support this
if( SupportedBlockchain.PIRATECHAIN.name().equals(tradeBotData.getForeignBlockchain())) return Optional.empty();
// need to get the trade PKH from the trade bot
if( tradeBotData.getTradeForeignPublicKeyHash() == null ) return Optional.empty();
// need to get the lock time from the trade bot
if( tradeBotData.getLockTimeA() == null ) return Optional.empty();
// need to get the creator PKH from the trade bot
if( crossChainTradeData.creatorForeignPKH == null ) return Optional.empty();
// need to get the secret from the trade bot
if( tradeBotData.getHashOfSecret() == null ) return Optional.empty();
// if we have the necessary data from the trade bot,
// then build the redeem script necessary to facilitate the trade
byte[] redeemScriptBytes
= BitcoinyHTLC.buildScript(
tradeBotData.getTradeForeignPublicKeyHash(),
tradeBotData.getLockTimeA(),
crossChainTradeData.creatorForeignPKH,
tradeBotData.getHashOfSecret()
);
String p2shAddress = bitcoiny.deriveP2shAddress(redeemScriptBytes);
return Optional.of(p2shAddress);
}
/**
* Get Foreign Trade Summary
*
* @param bitcoiny the blockchain the trade occurred on
* @param p2shAddress the p2sh address
* @param atAddress the AT address the p2sh address is derived from
*
* @return the summary
*
* @throws ForeignBlockchainException
*/
public static TransactionSummary getForeignTradeSummary(Bitcoiny bitcoiny, String p2shAddress, String atAddress)
throws ForeignBlockchainException {
Script outputScript = ScriptBuilder.createOutputScript(
Address.fromString(bitcoiny.getNetworkParameters(), p2shAddress));
List<TransactionHash> hashes
= bitcoiny.getAddressTransactions( outputScript.getProgram(), true);
TransactionSummary summary;
if(hashes.isEmpty()){
summary
= new TransactionSummary(
atAddress,
p2shAddress,
"N/A",
"N/A",
0,
0,
0,
0,
"N/A",
0,
0,
0,
0);
}
else if( hashes.size() == 1) {
AtomicTransactionData data = buildTransactionData(bitcoiny, hashes.get(0));
summary = new TransactionSummary(
atAddress,
p2shAddress,
"N/A",
data.hash.txHash,
data.timestamp,
data.totalAmount,
getTotalInput(bitcoiny, data.inputs) - data.totalAmount,
data.size,
"N/A",
0,
0,
0,
0);
}
// otherwise assuming there is 2 and only 2 hashes
else {
List<AtomicTransactionData> atomicTransactionDataList = new ArrayList<>(2);
// hashes -> data
for( TransactionHash hash : hashes){
atomicTransactionDataList.add(buildTransactionData(bitcoiny,hash));
}
// sort the transaction data by time
List<AtomicTransactionData> sorted
= atomicTransactionDataList.stream()
.sorted((data1, data2) -> data1.timestamp.compareTo(data2.timestamp))
.collect(Collectors.toList());
// build the summary using the first 2 transactions
summary = buildForeignTradeSummary(atAddress, p2shAddress, sorted.get(0), sorted.get(1), bitcoiny);
}
return summary;
}
/**
* Build Foreign Trade Summary
*
* @param p2shValue the p2sh address, original format
* @param lockingTransaction the transaction lock the foreighn coin
* @param unlockingTransaction the transaction to unlock the foreign coin
* @param bitcoiny the blockchain the trade occurred on
*
* @return
*
* @throws ForeignBlockchainException
*/
private static TransactionSummary buildForeignTradeSummary(
String atAddress,
String p2shValue,
AtomicTransactionData lockingTransaction,
AtomicTransactionData unlockingTransaction,
Bitcoiny bitcoiny) throws ForeignBlockchainException {
// get sum of the relevant inputs for each transaction
long lockingTotalInput = getTotalInput(bitcoiny, lockingTransaction.inputs);
long unlockingTotalInput = getTotalInput(bitcoiny, unlockingTransaction.inputs);
// find the address that has output that matches the total input
Optional<Map.Entry<List<String>, Long>> addressValue
= lockingTransaction.valueByAddress.entrySet().stream()
.filter(entry -> entry.getValue() == unlockingTotalInput).findFirst();
// set that matching address, if found
String p2shAddress;
if( addressValue.isPresent() && addressValue.get().getKey().size() == 1 ){
p2shAddress = addressValue.get().getKey().get(0);
}
else {
p2shAddress = "N/A";
}
// build summaries with prepared values
// the fees are the total amount subtracted by the total transaction input
return new TransactionSummary(
atAddress,
p2shValue,
p2shAddress,
lockingTransaction.hash.txHash,
lockingTransaction.timestamp,
lockingTransaction.totalAmount,
lockingTotalInput - lockingTransaction.totalAmount,
lockingTransaction.size,
unlockingTransaction.hash.txHash,
unlockingTransaction.timestamp,
unlockingTransaction.totalAmount,
unlockingTotalInput - unlockingTransaction.totalAmount,
unlockingTransaction.size
);
}
/**
* Build Transaction Data
*
* @param bitcoiny the coin for the transaction
* @param hash the hash for the transaction
*
* @return the data for the transaction
*
* @throws ForeignBlockchainException
*/
private static AtomicTransactionData buildTransactionData( Bitcoiny bitcoiny, TransactionHash hash)
throws ForeignBlockchainException {
BitcoinyTransaction transaction = bitcoiny.getTransaction(hash.txHash);
// destination address list -> value
Map<List<String>, Long> valueByAddress = new HashMap<>();
// for each output in the transaction, index by address list
for( BitcoinyTransaction.Output output : transaction.outputs) {
valueByAddress.put(output.addresses, output.value);
}
return new AtomicTransactionData(
hash,
transaction.timestamp,
transaction.inputs,
valueByAddress,
transaction.totalAmount,
transaction.size);
}
/**
* Get Total Input
*
* Get the sum of all the inputs used in a list of inputs.
*
* @param bitcoiny the coin the inputs belong to
* @param inputs the inputs
*
* @return the sum
*
* @throws ForeignBlockchainException
*/
private static long getTotalInput(Bitcoiny bitcoiny, List<BitcoinyTransaction.Input> inputs)
throws ForeignBlockchainException {
long totalInputOut = 0;
// for each input, add to total input,
// get the indexed transaction output value and add to total value
for( BitcoinyTransaction.Input input : inputs){
BitcoinyTransaction inputOut = bitcoiny.getTransaction(input.outputTxHash);
BitcoinyTransaction.Output output = inputOut.outputs.get(input.outputVout);
totalInputOut += output.value;
}
return totalInputOut;
}
/**
* Get Notes
*
* Build notes from an exception thrown.
*
* @param e the exception
*
* @return the exception message or the exception class name
*/
public static String getNotes(Exception e) {
return e.getMessage() + " (" + e.getClass().getSimpleName() + ")";
}
/**
* Build Server Connection History
*
* @param bitcoiny the foreign blockchain
*
* @return the history of connections from latest to first
*/
public static List<ServerConnectionInfo> buildServerConnectionHistory(Bitcoiny bitcoiny) {
return bitcoiny.getBlockchainProvider().getServerConnections().stream()
.sorted(Comparator.comparing(ChainableServerConnection::getCurrentTimeMillis).reversed())
.map(
connection -> new ServerConnectionInfo(
serverToServerInfo( connection.getServer()),
connection.getRequestedBy(),
connection.isOpen(),
connection.isSuccess(),
connection.getCurrentTimeMillis(),
connection.getNotes()
)
)
.collect(Collectors.toList());
}
/**
* Server To Server Info
*
* Make a server info object from a server object.
*
* @param server the server
*
* @return the server info
*/
private static ServerInfo serverToServerInfo(ChainableServer server) {
return new ServerInfo(
0,
server.getHostName(),
server.getPort(),
server.getConnectionType().toString(),
false);
}
}

View File

@@ -142,7 +142,7 @@ public class PollsResource {
for (VoteOnPollData vote : votes) {
String voter = Crypto.toAddress(vote.getVoterPublicKey());
AccountData voterData = repository.getAccountRepository().getAccount(voter);
int voteWeight = voterData.getBlocksMinted() - voterData.getBlocksMintedPenalty();
int voteWeight = voterData.getBlocksMinted() + voterData.getBlocksMintedPenalty();
if (voteWeight < 0) voteWeight = 0;
totalWeight += voteWeight;

View File

@@ -330,8 +330,8 @@ public class TransactionsResource {
public enum ConfirmationStatus {
CONFIRMED,
UNCONFIRMED,
BOTH;
}
BOTH
}
@GET
@Path("/search")

View File

@@ -31,7 +31,7 @@ public class AdminStatusWebSocket extends ApiWebSocket implements Listener {
return;
}
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@@ -28,7 +28,7 @@ public class BlocksWebSocket extends ApiWebSocket implements Listener {
public void configure(WebSocketServletFactory factory) {
factory.register(BlocksWebSocket.class);
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@@ -86,7 +86,7 @@ public class PresenceWebSocket extends ApiWebSocket implements Listener {
return;
}
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@@ -43,7 +43,7 @@ public class TradeBotWebSocket extends ApiWebSocket implements Listener {
// No output this time
}
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@@ -67,7 +67,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
return;
}
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@@ -29,7 +29,7 @@ public class TradePresenceWebSocket extends ApiWebSocket implements Listener {
populateCurrentInfo();
EventBus.INSTANCE.addListener(this::listen);
EventBus.INSTANCE.addListener(this);
}
@Override

View File

@@ -104,7 +104,7 @@ public class ArbitraryDataBuilder {
if (latestPut.getMethod() != Method.PUT) {
throw new DataException("Expected PUT but received PATCH");
}
if (transactionDataList.size() == 0) {
if (transactionDataList.isEmpty()) {
throw new DataException(String.format("No transactions found for name %s, service %s, " +
"identifier: %s, since %d", name, service, this.identifierString(), latestPut.getTimestamp()));
}
@@ -176,7 +176,7 @@ public class ArbitraryDataBuilder {
}
private void findLatestSignature() throws DataException {
if (this.transactions.size() == 0) {
if (this.transactions.isEmpty()) {
throw new DataException("Unable to find latest signature from empty transaction list");
}

View File

@@ -58,6 +58,9 @@ public class ArbitraryDataFile {
public static int SHORT_DIGEST_LENGTH = 8;
protected Path filePath;
protected byte[] fileContent;
private boolean useTemporaryFile;
protected String hash58;
protected byte[] signature;
private ArrayList<ArbitraryDataFileChunk> chunks;
@@ -90,8 +93,14 @@ public class ArbitraryDataFile {
this.signature = signature;
LOGGER.trace(String.format("File digest: %s, size: %d bytes", this.hash58, fileContent.length));
this.fileContent = fileContent;
this.useTemporaryFile = useTemporaryFile;
}
public void save() throws DataException {
Path outputFilePath;
if (useTemporaryFile) {
if (this.useTemporaryFile) {
try {
outputFilePath = Files.createTempFile("qortalRawData", null);
outputFilePath.toFile().deleteOnExit();
@@ -149,6 +158,7 @@ public class ArbitraryDataFile {
case RAW_DATA:
arbitraryDataFile = ArbitraryDataFile.fromRawData(data, signature);
arbitraryDataFile.save();
break;
}
@@ -324,6 +334,7 @@ public class ArbitraryDataFile {
out.flush();
ArbitraryDataFileChunk chunk = new ArbitraryDataFileChunk(out.toByteArray(), this.signature);
chunk.save();
ValidationResult validationResult = chunk.isValid();
if (validationResult == ValidationResult.OK) {
this.chunks.add(chunk);
@@ -343,7 +354,7 @@ public class ArbitraryDataFile {
public boolean join() {
// Ensure we have chunks
if (this.chunks != null && this.chunks.size() > 0) {
if (this.chunks != null && !this.chunks.isEmpty()) {
// Create temporary path for joined file
// Use the user-specified temp dir, as it is deterministic, and is more likely to be located on reusable storage hardware
@@ -406,6 +417,10 @@ public class ArbitraryDataFile {
}
public boolean delete(int attempts) {
if (this.filePath == null) {
return false;
}
// Keep trying to delete the data until it is deleted, or we reach 10 attempts
for (int i=0; i<attempts; i++) {
if (this.delete()) {
@@ -424,7 +439,7 @@ public class ArbitraryDataFile {
boolean success = false;
// Delete the individual chunks
if (this.chunks != null && this.chunks.size() > 0) {
if (this.chunks != null && !this.chunks.isEmpty()) {
Iterator iterator = this.chunks.iterator();
while (iterator.hasNext()) {
ArbitraryDataFileChunk chunk = (ArbitraryDataFileChunk) iterator.next();
@@ -467,6 +482,10 @@ public class ArbitraryDataFile {
}
public byte[] getBytes() {
if (this.fileContent != null) {
return this.fileContent;
}
try {
return Files.readAllBytes(this.filePath);
} catch (IOException e) {
@@ -690,7 +709,7 @@ public class ArbitraryDataFile {
}
public byte[] chunkHashes() throws DataException {
if (this.chunks != null && this.chunks.size() > 0) {
if (this.chunks != null && !this.chunks.isEmpty()) {
// Return null if we only have one chunk, with the same hash as the parent
if (Arrays.equals(this.digest(), this.chunks.get(0).digest())) {
return null;
@@ -717,7 +736,7 @@ public class ArbitraryDataFile {
public List<byte[]> chunkHashList() {
List<byte[]> chunks = new ArrayList<>();
if (this.chunks != null && this.chunks.size() > 0) {
if (this.chunks != null && !this.chunks.isEmpty()) {
// Return null if we only have one chunk, with the same hash as the parent
if (Arrays.equals(this.digest(), this.chunks.get(0).digest())) {
return null;
@@ -801,7 +820,7 @@ public class ArbitraryDataFile {
String outputString = "";
if (this.chunkCount() > 0) {
for (ArbitraryDataFileChunk chunk : this.chunks) {
if (outputString.length() > 0) {
if (!outputString.isEmpty()) {
outputString = outputString.concat(",");
}
outputString = outputString.concat(chunk.digest58());

View File

@@ -73,7 +73,7 @@ public class ArbitraryDataReader {
}
// If identifier is a blank string, or reserved keyword "default", treat it as null
if (identifier == null || identifier.equals("") || identifier.equals("default")) {
if (identifier == null || identifier.isEmpty() || identifier.equals("default")) {
identifier = null;
}

View File

@@ -199,7 +199,7 @@ public class ArbitraryDataRenderer {
}
private String getFilename(String directory, String userPath) {
if (userPath == null || userPath.endsWith("/") || userPath.equals("")) {
if (userPath == null || userPath.endsWith("/") || userPath.isEmpty()) {
// Locate index file
List<String> indexFiles = ArbitraryDataRenderer.indexFiles();
for (String indexFile : indexFiles) {

View File

@@ -52,7 +52,7 @@ public class ArbitraryDataResource {
this.service = service;
// If identifier is a blank string, or reserved keyword "default", treat it as null
if (identifier == null || identifier.equals("") || identifier.equals("default")) {
if (identifier == null || identifier.isEmpty() || identifier.equals("default")) {
identifier = null;
}
this.identifier = identifier;

View File

@@ -81,7 +81,7 @@ public class ArbitraryDataTransactionBuilder {
this.service = service;
// If identifier is a blank string, or reserved keyword "default", treat it as null
if (identifier == null || identifier.equals("") || identifier.equals("default")) {
if (identifier == null || identifier.isEmpty() || identifier.equals("default")) {
identifier = null;
}
this.identifier = identifier;

View File

@@ -78,7 +78,7 @@ public class ArbitraryDataWriter {
this.compression = compression;
// If identifier is a blank string, or reserved keyword "default", treat it as null
if (identifier == null || identifier.equals("") || identifier.equals("default")) {
if (identifier == null || identifier.isEmpty() || identifier.equals("default")) {
identifier = null;
}
this.identifier = identifier;

View File

@@ -132,7 +132,7 @@ public class AT {
// Nothing happened?
if (state.getSteps() == 0 && Arrays.equals(stateHash, latestAtStateData.getStateHash()))
// We currently want to execute frozen ATs, to maintain backwards support.
if (state.isFrozen() == false)
if (!state.isFrozen())
// this.atStateData will be null
return Collections.emptyList();

View File

@@ -1061,8 +1061,10 @@ public class Block {
return ValidationResult.MINTER_NOT_ACCEPTED;
long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getMinterPublicKey(), minterLevel);
if (this.blockData.getTimestamp() != expectedTimestamp)
if (this.blockData.getTimestamp() != expectedTimestamp) {
LOGGER.debug(String.format("timestamp mismatch! block had %s but we expected %s", this.blockData.getTimestamp(), expectedTimestamp));
return ValidationResult.TIMESTAMP_INCORRECT;
}
return ValidationResult.OK;
}
@@ -1084,7 +1086,7 @@ public class Block {
// Online accounts should only be included in designated blocks; all others must be empty
if (!this.isOnlineAccountsBlock()) {
if (this.blockData.getOnlineAccountsCount() != 0 || accountIndexes.size() != 0) {
if (this.blockData.getOnlineAccountsCount() != 0 || !accountIndexes.isEmpty()) {
return ValidationResult.ONLINE_ACCOUNTS_INVALID;
}
// Not a designated online accounts block and account count is 0. Everything is correct so no need to validate further.
@@ -1557,9 +1559,12 @@ public class Block {
SelfSponsorshipAlgoV1Block.processAccountPenalties(this);
}
if (this.blockData.getHeight() == BlockChain.getInstance().getPenaltyFixHeight()) {
// Apply fix for penalties
PenaltyFix.processPenaltiesFix(this);
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) {
SelfSponsorshipAlgoV2Block.processAccountPenalties(this);
}
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) {
SelfSponsorshipAlgoV3Block.processAccountPenalties(this);
}
}
@@ -1854,11 +1859,14 @@ public class Block {
SelfSponsorshipAlgoV1Block.orphanAccountPenalties(this);
}
if (this.blockData.getHeight() == BlockChain.getInstance().getPenaltyFixHeight()) {
// Revert fix for penalties
PenaltyFix.orphanPenaltiesFix(this);
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) {
SelfSponsorshipAlgoV2Block.orphanAccountPenalties(this);
}
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) {
SelfSponsorshipAlgoV3Block.orphanAccountPenalties(this);
}
// Account levels and block rewards are only processed/orphaned on block reward distribution blocks
if (this.isRewardDistributionBlock()) {
// Block rewards, including transaction fees, removed after transactions undone
@@ -2556,4 +2564,4 @@ public class Block {
LOGGER.info(() -> String.format("Unable to log block debugging info: %s", e.getMessage()));
}
}
}
}

View File

@@ -74,13 +74,13 @@ public class BlockChain {
onlineAccountMinterLevelValidationHeight,
selfSponsorshipAlgoV1Height,
selfSponsorshipAlgoV2Height,
selfSponsorshipAlgoV3Height,
feeValidationFixTimestamp,
chatReferenceTimestamp,
arbitraryOptionalFeeTimestamp,
unconfirmableRewardSharesHeight,
disableTransferPrivsTimestamp,
enableTransferPrivsTimestamp,
penaltyFixHeight
enableTransferPrivsTimestamp
}
// Custom transaction fees
@@ -217,6 +217,12 @@ public class BlockChain {
/** Snapshot timestamp for self sponsorship algo V2 */
private long selfSponsorshipAlgoV2SnapshotTimestamp;
/** Snapshot timestamp for self sponsorship algo V3 */
private long selfSponsorshipAlgoV3SnapshotTimestamp;
/** Reference timestamp for self sponsorship algo V1 block height */
private long referenceTimestampBlock;
/** Feature-trigger timestamp to modify behaviour of various transactions that support mempow */
private long mempowTransactionUpdatesTimestamp;
@@ -419,6 +425,15 @@ public class BlockChain {
return this.selfSponsorshipAlgoV2SnapshotTimestamp;
}
// Self sponsorship algo V3
public long getSelfSponsorshipAlgoV3SnapshotTimestamp() {
return this.selfSponsorshipAlgoV3SnapshotTimestamp;
}
// Self sponsorship algo V3
public long getReferenceTimestampBlock() {
return this.referenceTimestampBlock;
}
// Feature-trigger timestamp to modify behaviour of various transactions that support mempow
public long getMemPoWTransactionUpdatesTimestamp() {
return this.mempowTransactionUpdatesTimestamp;
@@ -563,6 +578,10 @@ public class BlockChain {
return this.featureTriggers.get(FeatureTrigger.selfSponsorshipAlgoV2Height.name()).intValue();
}
public int getSelfSponsorshipAlgoV3Height() {
return this.featureTriggers.get(FeatureTrigger.selfSponsorshipAlgoV3Height.name()).intValue();
}
public long getOnlineAccountMinterLevelValidationHeight() {
return this.featureTriggers.get(FeatureTrigger.onlineAccountMinterLevelValidationHeight.name()).intValue();
}
@@ -591,10 +610,6 @@ public class BlockChain {
return this.featureTriggers.get(FeatureTrigger.enableTransferPrivsTimestamp.name()).longValue();
}
public int getPenaltyFixHeight() {
return this.featureTriggers.get(FeatureTrigger.penaltyFixHeight.name()).intValue();
}
// More complex getters for aspects that change by height or timestamp
public long getRewardAtHeight(int ourHeight) {

View File

@@ -1,187 +0,0 @@
package org.qortal.block;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
import org.qortal.api.model.AccountPenaltyStats;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountData;
import org.qortal.data.account.AccountPenaltyData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.utils.Base58;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
public final class PenaltyFix {
private static final Logger LOGGER = LogManager.getLogger(PenaltyFix.class);
private static final String PENALTY_FIX_SOURCE = "penalty-fix.json";
private static final String PENALTY_FIX_HASH = BlockChain.getInstance().getPenaltyFixHash();
private static final List<AccountPenaltyData> penalties = accountsPenaltyFix();
private PenaltyFix() {
/* Do not instantiate */
}
@SuppressWarnings("unchecked")
private static List<AccountPenaltyData> accountsPenaltyFix() {
Unmarshaller unmarshaller;
try {
// Create JAXB context aware of classes we need to unmarshal
JAXBContext pf = JAXBContextFactory.createContext(new Class[] {
AccountPenaltyData.class
}, null);
// Create unmarshaller
unmarshaller = pf.createUnmarshaller();
// Set the unmarshaller media type to JSON
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
// Tell unmarshaller that there's no JSON root element in the JSON input
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
} catch (JAXBException e) {
String message = "Failed to setup unmarshaller for penalty fix";
LOGGER.error(message, e);
throw new RuntimeException(message, e);
}
ClassLoader classLoader = BlockChain.class.getClassLoader();
InputStream pfIn = classLoader.getResourceAsStream(PENALTY_FIX_SOURCE);
StreamSource pfSource = new StreamSource(pfIn);
try {
// Attempt to unmarshal JSON stream to BlockChain config
return (List<AccountPenaltyData>) unmarshaller.unmarshal(pfSource, AccountPenaltyData.class).getValue();
} catch (UnmarshalException e) {
String message = "Failed to parse penalty fix";
LOGGER.error(message, e);
throw new RuntimeException(message, e);
} catch (JAXBException e) {
String message = "Unexpected JAXB issue while processing penalty fix";
LOGGER.error(message, e);
throw new RuntimeException(message, e);
}
}
public static void processPenaltiesFix(Block block) throws DataException {
// Create reverse penalties
List<AccountPenaltyData> reversePenalties = penalties.stream()
.map(penalty -> new AccountPenaltyData(penalty.getAddress(), 0 +5000000))
.collect(Collectors.toList());
Set<AccountPenaltyData> penaltiesFix = new HashSet<AccountPenaltyData>(reversePenalties);
final String PENALTY_FIX_HASH_VERIFY = getHash(penaltiesFix.stream().map(p -> p.getAddress()).collect(Collectors.toList()));
// Verify if we are on same penalty hash
if (PENALTY_FIX_HASH.equals(PENALTY_FIX_HASH_VERIFY)) {
LOGGER.info("Verify hash passed! Running process penalty fix - this will take a while...");
logPenaltyStats(block.repository);
long startTime = System.currentTimeMillis();
block.repository.getAccountRepository().updateBlocksMintedPenalties(penaltiesFix);
long totalTime = System.currentTimeMillis() - startTime;
LOGGER.info("{} penalty addresses processed. Total time taken: {} seconds", reversePenalties.size(), (int)(totalTime / 1000.0f));
logPenaltyStats(block.repository);
int updatedCount = updateAccountLevels(block.repository, penaltiesFix);
LOGGER.info("Account levels updated for {} penalty addresses", updatedCount);
} else {
LOGGER.info("Verify hash failed! Stopping process penalty fix!");
}
}
public static void orphanPenaltiesFix(Block block) throws DataException {
// Create inverse penalties
List<AccountPenaltyData> inversePenalties = penalties.stream()
.map(penalty -> new AccountPenaltyData(penalty.getAddress(), 0 -5000000))
.collect(Collectors.toList());
Set<AccountPenaltyData> penaltiesFix = new HashSet<AccountPenaltyData>(inversePenalties);
final String PENALTY_FIX_HASH_VERIFY = getHash(penaltiesFix.stream().map(p -> p.getAddress()).collect(Collectors.toList()));
// Verify if we are on same penalty hash
if (PENALTY_FIX_HASH.equals(PENALTY_FIX_HASH_VERIFY)) {
LOGGER.info("Verify hash passed! Running orphan penalty fix - this will take a while...");
logPenaltyStats(block.repository);
long startTime = System.currentTimeMillis();
block.repository.getAccountRepository().updateBlocksMintedPenalties(penaltiesFix);
long totalTime = System.currentTimeMillis() - startTime;
LOGGER.info("{} penalty addresses processed. Total time taken: {} seconds", inversePenalties.size(), (int)(totalTime / 1000.0f));
logPenaltyStats(block.repository);
int updatedCount = updateAccountLevels(block.repository, penaltiesFix);
LOGGER.info("Account levels updated for {} penalty addresses", updatedCount);
} else {
LOGGER.info("Verify hash failed! Stopping orphan penalty fix!");
}
}
private static int updateAccountLevels(Repository repository, Set<AccountPenaltyData> accountPenalties) throws DataException {
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
int updatedCount = 0;
for (AccountPenaltyData penaltyData : accountPenalties) {
AccountData accountData = repository.getAccountRepository().getAccount(penaltyData.getAddress());
final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty();
// Shortcut for penalties
if (effectiveBlocksMinted < 0) {
accountData.setLevel(0);
repository.getAccountRepository().setLevel(accountData);
updatedCount++;
LOGGER.trace(() -> String.format("Block minter %s dropped to level %d", accountData.getAddress(), accountData.getLevel()));
continue;
}
for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) {
if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) {
accountData.setLevel(newLevel);
repository.getAccountRepository().setLevel(accountData);
updatedCount++;
LOGGER.trace(() -> String.format("Block minter %s increased to level %d", accountData.getAddress(), accountData.getLevel()));
break;
}
}
}
return updatedCount;
}
private static void logPenaltyStats(Repository repository) {
try {
LOGGER.info(getPenaltyStats(repository));
} catch (DataException e) {}
}
private static AccountPenaltyStats getPenaltyStats(Repository repository) throws DataException {
List<AccountData> accounts = repository.getAccountRepository().getPenaltyAccounts();
return AccountPenaltyStats.fromAccounts(accounts);
}
public static String getHash(List<String> penaltyAddresses) {
if (penaltyAddresses == null || penaltyAddresses.isEmpty()) {
return null;
}
Collections.sort(penaltyAddresses);
return Base58.encode(Crypto.digest(StringUtils.join(penaltyAddresses).getBytes(StandardCharsets.UTF_8)));
}
}

View File

@@ -0,0 +1,143 @@
package org.qortal.block;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.SelfSponsorshipAlgoV2;
import org.qortal.api.model.AccountPenaltyStats;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountData;
import org.qortal.data.account.AccountPenaltyData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.utils.Base58;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
/**
* Self Sponsorship AlgoV2 Block
* <p>
* Selected block for the initial run on the "self sponsorship detection algorithm"
*/
public final class SelfSponsorshipAlgoV2Block {
private static final Logger LOGGER = LogManager.getLogger(SelfSponsorshipAlgoV2Block.class);
private SelfSponsorshipAlgoV2Block() {
/* Do not instantiate */
}
public static void processAccountPenalties(Block block) throws DataException {
LOGGER.info("Process Self Sponsorship Algo V2 - this will take a while...");
logPenaltyStats(block.repository);
long startTime = System.currentTimeMillis();
Set<AccountPenaltyData> penalties = getAccountPenalties(block.repository, -5000000);
block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties);
long totalTime = System.currentTimeMillis() - startTime;
String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList()));
LOGGER.info("{} penalty addresses processed (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f));
logPenaltyStats(block.repository);
int updatedCount = updateAccountLevels(block.repository, penalties);
LOGGER.info("Account levels updated for {} penalty addresses", updatedCount);
}
public static void orphanAccountPenalties(Block block) throws DataException {
LOGGER.info("Orphan Self Sponsorship Algo V2 - this will take a while...");
logPenaltyStats(block.repository);
long startTime = System.currentTimeMillis();
Set<AccountPenaltyData> penalties = getAccountPenalties(block.repository, 5000000);
block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties);
long totalTime = System.currentTimeMillis() - startTime;
String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList()));
LOGGER.info("{} penalty addresses orphaned (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f));
logPenaltyStats(block.repository);
int updatedCount = updateAccountLevels(block.repository, penalties);
LOGGER.info("Account levels updated for {} penalty addresses", updatedCount);
}
private static Set<AccountPenaltyData> getAccountPenalties(Repository repository, int penalty) throws DataException {
Set<AccountPenaltyData> penalties = new LinkedHashSet<>();
List<AccountData> penalizedAddresses = repository.getAccountRepository().getPenaltyAccounts();
List<String> assetAddresses = repository.getTransactionRepository().getConfirmedTransferAssetCreators();
for (AccountData penalizedAddress : penalizedAddresses) {
//System.out.println(String.format("address: %s", address));
SelfSponsorshipAlgoV2 selfSponsorshipAlgoV2 = new SelfSponsorshipAlgoV2(repository, penalizedAddress.getAddress(), false);
selfSponsorshipAlgoV2.run();
//System.out.println(String.format("Penalty addresses: %d", selfSponsorshipAlgoV2.getPenaltyAddresses().size()));
for (String penaltyAddress : selfSponsorshipAlgoV2.getPenaltyAddresses()) {
penalties.add(new AccountPenaltyData(penaltyAddress, penalty));
}
}
for (String assetAddress : assetAddresses) {
//System.out.println(String.format("address: %s", address));
SelfSponsorshipAlgoV2 selfSponsorshipAlgoV2 = new SelfSponsorshipAlgoV2(repository, assetAddress, true);
selfSponsorshipAlgoV2.run();
//System.out.println(String.format("Penalty addresses: %d", selfSponsorshipAlgoV2.getPenaltyAddresses().size()));
for (String penaltyAddress : selfSponsorshipAlgoV2.getPenaltyAddresses()) {
penalties.add(new AccountPenaltyData(penaltyAddress, penalty));
}
}
return penalties;
}
private static int updateAccountLevels(Repository repository, Set<AccountPenaltyData> accountPenalties) throws DataException {
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
int updatedCount = 0;
for (AccountPenaltyData penaltyData : accountPenalties) {
AccountData accountData = repository.getAccountRepository().getAccount(penaltyData.getAddress());
final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty();
// Shortcut for penalties
if (effectiveBlocksMinted < 0) {
accountData.setLevel(0);
repository.getAccountRepository().setLevel(accountData);
updatedCount++;
LOGGER.trace(() -> String.format("Block minter %s dropped to level %d", accountData.getAddress(), accountData.getLevel()));
continue;
}
for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) {
if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) {
accountData.setLevel(newLevel);
repository.getAccountRepository().setLevel(accountData);
updatedCount++;
LOGGER.trace(() -> String.format("Block minter %s increased to level %d", accountData.getAddress(), accountData.getLevel()));
break;
}
}
}
return updatedCount;
}
private static void logPenaltyStats(Repository repository) {
try {
LOGGER.info(getPenaltyStats(repository));
} catch (DataException e) {}
}
private static AccountPenaltyStats getPenaltyStats(Repository repository) throws DataException {
List<AccountData> accounts = repository.getAccountRepository().getPenaltyAccounts();
return AccountPenaltyStats.fromAccounts(accounts);
}
public static String getHash(List<String> penaltyAddresses) {
if (penaltyAddresses == null || penaltyAddresses.isEmpty()) {
return null;
}
Collections.sort(penaltyAddresses);
return Base58.encode(Crypto.digest(StringUtils.join(penaltyAddresses).getBytes(StandardCharsets.UTF_8)));
}
}

View File

@@ -0,0 +1,136 @@
package org.qortal.block;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.SelfSponsorshipAlgoV3;
import org.qortal.api.model.AccountPenaltyStats;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountData;
import org.qortal.data.account.AccountPenaltyData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.utils.Base58;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Self Sponsorship AlgoV3 Block
* <p>
* Selected block for the initial run on the "self sponsorship detection algorithm"
*/
public final class SelfSponsorshipAlgoV3Block {
private static final Logger LOGGER = LogManager.getLogger(SelfSponsorshipAlgoV3Block.class);
private SelfSponsorshipAlgoV3Block() {
/* Do not instantiate */
}
public static void processAccountPenalties(Block block) throws DataException {
LOGGER.info("Process Self Sponsorship Algo V3 - this will take a while...");
logPenaltyStats(block.repository);
long startTime = System.currentTimeMillis();
Set<AccountPenaltyData> penalties = getAccountPenalties(block.repository, -5000000);
block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties);
long totalTime = System.currentTimeMillis() - startTime;
String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList()));
LOGGER.info("{} penalty addresses processed (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f));
logPenaltyStats(block.repository);
int updatedCount = updateAccountLevels(block.repository, penalties);
LOGGER.info("Account levels updated for {} penalty addresses", updatedCount);
}
public static void orphanAccountPenalties(Block block) throws DataException {
LOGGER.info("Orphan Self Sponsorship Algo V3 - this will take a while...");
logPenaltyStats(block.repository);
long startTime = System.currentTimeMillis();
Set<AccountPenaltyData> penalties = getAccountPenalties(block.repository, 5000000);
block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties);
long totalTime = System.currentTimeMillis() - startTime;
String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList()));
LOGGER.info("{} penalty addresses orphaned (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f));
logPenaltyStats(block.repository);
int updatedCount = updateAccountLevels(block.repository, penalties);
LOGGER.info("Account levels updated for {} penalty addresses", updatedCount);
}
public static Set<AccountPenaltyData> getAccountPenalties(Repository repository, int penalty) throws DataException {
final long snapshotTimestampV1 = BlockChain.getInstance().getSelfSponsorshipAlgoV1SnapshotTimestamp();
final long snapshotTimestampV3 = BlockChain.getInstance().getSelfSponsorshipAlgoV3SnapshotTimestamp();
Set<AccountPenaltyData> penalties = new LinkedHashSet<>();
List<String> addresses = repository.getTransactionRepository().getConfirmedRewardShareCreatorsExcludingSelfShares();
for (String address : addresses) {
//System.out.println(String.format("address: %s", address));
SelfSponsorshipAlgoV3 selfSponsorshipAlgoV3 = new SelfSponsorshipAlgoV3(repository, address, snapshotTimestampV1, snapshotTimestampV3, false);
selfSponsorshipAlgoV3.run();
//System.out.println(String.format("Penalty addresses: %d", selfSponsorshipAlgoV3.getPenaltyAddresses().size()));
for (String penaltyAddress : selfSponsorshipAlgoV3.getPenaltyAddresses()) {
penalties.add(new AccountPenaltyData(penaltyAddress, penalty));
}
}
return penalties;
}
private static int updateAccountLevels(Repository repository, Set<AccountPenaltyData> accountPenalties) throws DataException {
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
int updatedCount = 0;
for (AccountPenaltyData penaltyData : accountPenalties) {
AccountData accountData = repository.getAccountRepository().getAccount(penaltyData.getAddress());
final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty();
// Shortcut for penalties
if (effectiveBlocksMinted < 0) {
accountData.setLevel(0);
repository.getAccountRepository().setLevel(accountData);
updatedCount++;
LOGGER.trace(() -> String.format("Block minter %s dropped to level %d", accountData.getAddress(), accountData.getLevel()));
continue;
}
for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) {
if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) {
accountData.setLevel(newLevel);
repository.getAccountRepository().setLevel(accountData);
updatedCount++;
LOGGER.trace(() -> String.format("Block minter %s increased to level %d", accountData.getAddress(), accountData.getLevel()));
break;
}
}
}
return updatedCount;
}
private static void logPenaltyStats(Repository repository) {
try {
LOGGER.info(getPenaltyStats(repository));
} catch (DataException e) {}
}
private static AccountPenaltyStats getPenaltyStats(Repository repository) throws DataException {
List<AccountData> accounts = repository.getAccountRepository().getPenaltyAccounts();
return AccountPenaltyStats.fromAccounts(accounts);
}
public static String getHash(List<String> penaltyAddresses) {
if (penaltyAddresses == null || penaltyAddresses.isEmpty()) {
return null;
}
Collections.sort(penaltyAddresses);
return Base58.encode(Crypto.digest(StringUtils.join(penaltyAddresses).getBytes(StandardCharsets.UTF_8)));
}
}

View File

@@ -159,8 +159,7 @@ public class BlockMinter extends Thread {
int level = mintingAccount.getEffectiveMintingLevel();
if (level < BlockChain.getInstance().getMinAccountLevelForBlockSubmissions()) {
madi.remove();
continue;
}
}
}
// Needs a mutable copy of the unmodifiableList
@@ -172,7 +171,7 @@ public class BlockMinter extends Thread {
// Disregard peers that don't have a recent block, but only if we're not in recovery mode.
// In that mode, we want to allow minting on top of older blocks, to recover stalled networks.
if (Synchronizer.getInstance().getRecoveryMode() == false)
if (!Synchronizer.getInstance().getRecoveryMode())
peers.removeIf(Controller.hasNoRecentBlock);
// Don't mint if we don't have enough up-to-date peers as where would the transactions/consensus come from?
@@ -197,7 +196,7 @@ public class BlockMinter extends Thread {
// If our latest block isn't recent then we need to synchronize instead of minting, unless we're in recovery mode.
if (!peers.isEmpty() && lastBlockData.getTimestamp() < minLatestBlockTimestamp)
if (Synchronizer.getInstance().getRecoveryMode() == false && recoverInvalidBlock == false)
if (!Synchronizer.getInstance().getRecoveryMode() && !recoverInvalidBlock)
continue;
// There are enough peers with a recent block and our latest block is recent

View File

@@ -17,7 +17,7 @@ public class ChatNotifier {
void notify(ChatTransactionData chatTransactionData);
}
private Map<Session, Listener> listenersBySession = new HashMap<>();
private final Map<Session, Listener> listenersBySession = new HashMap<>();
private ChatNotifier() {
}

View File

@@ -564,6 +564,34 @@ public class Controller extends Thread {
// If GUI is enabled, we're no longer starting up but actually running now
Gui.getInstance().notifyRunning();
// Check every 10 minutes to see if the block minter is running
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (blockMinter.isAlive()) {
LOGGER.debug("Block minter is running? {}", blockMinter.isAlive());
} else if (!blockMinter.isAlive()) {
LOGGER.debug("Block minter is running? {}", blockMinter.isAlive());
blockMinter.shutdown();
try {
// Wait 10 seconds before restart
TimeUnit.SECONDS.sleep(10);
// Start new block minter thread
LOGGER.info("Restarting block minter");
blockMinter.start();
} catch (InterruptedException e) {
// Couldn't start new block minter thread
LOGGER.info("Starting block minter failed {}", e);
throw new RuntimeException(e);
}
}
}
}, 10*60*1000, 10*60*1000);
}
/** Called by AdvancedInstaller's launch EXE in single-instance mode, when an instance is already running. */
@@ -571,7 +599,6 @@ public class Controller extends Thread {
// Return as we don't want to run more than one instance
}
// Main thread
@Override
@@ -775,7 +802,7 @@ public class Controller extends Thread {
public static final Predicate<Peer> hasOldVersion = peer -> {
final String minPeerVersion = Settings.getInstance().getMinPeerVersion();
return peer.isAtLeastVersion(minPeerVersion) == false;
return !peer.isAtLeastVersion(minPeerVersion);
};
public static final Predicate<Peer> hasInvalidSigner = peer -> {
@@ -1921,8 +1948,7 @@ public class Controller extends Thread {
// Disregard peers that don't have a recent block
if (peerChainTipData.getTimestamp() == null || peerChainTipData.getTimestamp() < minLatestBlockTimestamp) {
iterator.remove();
continue;
}
}
}
return peers;
@@ -2002,5 +2028,4 @@ public class Controller extends Thread {
public StatsSnapshot getStatsSnapshot() {
return this.stats;
}
}

View File

@@ -538,7 +538,6 @@ public class OnlineAccountsManager {
if (++i > 1 + 1) {
iterator.remove();
continue;
}
}
} catch (DataException e) {

View File

@@ -2,7 +2,7 @@ package org.qortal.controller;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.ApplyRestart;;
import org.qortal.ApplyRestart;
import org.qortal.globalization.Translator;
import org.qortal.gui.SysTray;
import org.qortal.repository.RepositoryManager;

View File

@@ -90,8 +90,8 @@ 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, CHAIN_TIP_TOO_OLD;
}
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 {
private final BlockData priorChainTip;
@@ -258,7 +258,7 @@ public class Synchronizer extends Thread {
peers.removeIf(Controller.hasNoRecentBlock);
final int peersRemoved = peersBeforeComparison - peers.size();
if (peersRemoved > 0 && peers.size() > 0)
if (peersRemoved > 0 && !peers.isEmpty())
LOGGER.debug(String.format("Ignoring %d peers on inferior chains. Peers remaining: %d", peersRemoved, peers.size()));
if (peers.isEmpty())
@@ -392,7 +392,7 @@ public class Synchronizer extends Thread {
private boolean checkRecoveryModeForPeers(List<Peer> qualifiedPeers) {
List<Peer> handshakedPeers = Network.getInstance().getImmutableHandshakedPeers();
if (handshakedPeers.size() > 0) {
if (!handshakedPeers.isEmpty()) {
// There is at least one handshaked peer
if (qualifiedPeers.isEmpty()) {
// There are no 'qualified' peers - i.e. peers that have a recent block we can sync to
@@ -406,7 +406,7 @@ public class Synchronizer extends Thread {
// If enough time has passed, enter recovery mode, which lifts some restrictions on who we can sync with and when we can mint
long recoveryModeTimeout = Settings.getInstance().getRecoveryModeTimeout();
if (NTP.getTime() - timePeersLastAvailable > recoveryModeTimeout) {
if (recoveryMode == false) {
if (!recoveryMode) {
LOGGER.info(String.format("Peers have been unavailable for %d minutes. Entering recovery mode...", recoveryModeTimeout/60/1000));
recoveryMode = true;
}
@@ -445,7 +445,7 @@ public class Synchronizer extends Thread {
try (final Repository repository = RepositoryManager.getRepository()) {
try {
if (peers.size() == 0)
if (peers.isEmpty())
return SynchronizationResult.NOTHING_TO_DO;
// If our latest block is very old, it's best that we don't try and determine the best peers to sync to.
@@ -663,7 +663,7 @@ public class Synchronizer extends Thread {
}
}
if (useCachedSummaries == false) {
if (!useCachedSummaries) {
if (summariesRequired > 0) {
LOGGER.trace(String.format("Requesting %d block summar%s from peer %s after common block %.8s. Peer height: %d", summariesRequired, (summariesRequired != 1 ? "ies" : "y"), peer, Base58.encode(commonBlockSummary.getSignature()), peerHeight));
@@ -701,7 +701,7 @@ public class Synchronizer extends Thread {
// Reduce minChainLength if needed. If we don't have any blocks, this peer will be excluded from chain weight comparisons later in the process, so we shouldn't update minChainLength
List <BlockSummaryData> peerBlockSummaries = peer.getCommonBlockData().getBlockSummariesAfterCommonBlock();
if (peerBlockSummaries != null && peerBlockSummaries.size() > 0)
if (peerBlockSummaries != null && !peerBlockSummaries.isEmpty())
if (peerBlockSummaries.size() < minChainLength)
minChainLength = peerBlockSummaries.size();
}
@@ -728,7 +728,7 @@ public class Synchronizer extends Thread {
// Calculate our chain weight
BigInteger ourChainWeight = BigInteger.valueOf(0);
if (ourBlockSummaries.size() > 0)
if (!ourBlockSummaries.isEmpty())
ourChainWeight = Block.calcChainWeight(commonBlockSummary.getHeight(), commonBlockSummary.getSignature(), ourBlockSummaries, maxHeightForChainWeightComparisons);
LOGGER.debug(String.format("Our chain weight based on %d blocks is %s", (usingSameLengthChainWeight ? minChainLength : ourBlockSummaries.size()), accurateFormatter.format(ourChainWeight)));
@@ -780,7 +780,7 @@ public class Synchronizer extends Thread {
}
// Now that we have selected the best peers, compare them against each other and remove any with lower weights
if (superiorPeersForComparison.size() > 0) {
if (!superiorPeersForComparison.isEmpty()) {
BigInteger bestChainWeight = null;
for (Peer peer : superiorPeersForComparison) {
// Increase bestChainWeight if needed
@@ -1290,7 +1290,7 @@ public class Synchronizer extends Thread {
cachedCommonBlockData.setBlockSummariesAfterCommonBlock(null);
// If we have already received newer blocks from this peer that what we have already, go ahead and apply them
if (peerBlocks.size() > 0) {
if (!peerBlocks.isEmpty()) {
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
@@ -1352,7 +1352,7 @@ public class Synchronizer extends Thread {
if (retryCount >= maxRetries) {
// If we have already received newer blocks from this peer that what we have already, go ahead and apply them
if (peerBlocks.size() > 0) {
if (!peerBlocks.isEmpty()) {
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
final Block peerLatestBlock = peerBlocks.get(peerBlocks.size() - 1);
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();

View File

@@ -208,8 +208,7 @@ public class ArbitraryDataCleanupManager extends Thread {
Base58.encode(arbitraryTransactionData.getSignature())));
ArbitraryTransactionUtils.convertFileToChunks(arbitraryTransactionData, now, STALE_FILE_TIMEOUT);
continue;
}
}
}
} catch (DataException e) {
@@ -284,8 +283,7 @@ public class ArbitraryDataCleanupManager extends Thread {
}
} catch (DataException e) {
continue;
}
}
}
return pathList;

View File

@@ -605,7 +605,7 @@ public class ArbitraryDataFileListManager {
}
// Add the chunk hashes
if (arbitraryDataFile.getChunkHashes().size() > 0) {
if (!arbitraryDataFile.getChunkHashes().isEmpty()) {
requestedHashes.addAll(arbitraryDataFile.getChunkHashes());
}
// Add complete file if there are no hashes
@@ -641,7 +641,7 @@ public class ArbitraryDataFileListManager {
}
// We should only respond if we have at least one hash
if (hashes.size() > 0) {
if (!hashes.isEmpty()) {
// Firstly we should keep track of the requesting peer, to allow for potential direct connections later
ArbitraryDataFileManager.getInstance().addRecentDataRequest(requestingPeer);

View File

@@ -43,7 +43,7 @@ public class ArbitraryDataFileManager extends Thread {
/**
* Map to keep track of hashes that we might need to relay
*/
public List<ArbitraryRelayInfo> arbitraryRelayMap = Collections.synchronizedList(new ArrayList<>());
public final List<ArbitraryRelayInfo> arbitraryRelayMap = Collections.synchronizedList(new ArrayList<>());
/**
* List to keep track of any arbitrary data file hash responses
@@ -53,7 +53,7 @@ public class ArbitraryDataFileManager extends Thread {
/**
* List to keep track of peers potentially available for direct connections, based on recent requests
*/
private List<ArbitraryDirectConnectionInfo> directConnectionInfo = Collections.synchronizedList(new ArrayList<>());
private final List<ArbitraryDirectConnectionInfo> directConnectionInfo = Collections.synchronizedList(new ArrayList<>());
/**
* Map to keep track of peers requesting QDN data that we hold.
@@ -242,13 +242,14 @@ public class ArbitraryDataFileManager extends Thread {
boolean isRelayRequest = (requestingPeer != null);
if (isRelayRequest) {
if (!fileAlreadyExists) {
// File didn't exist locally before the request, and it's a forwarding request, so delete it
LOGGER.debug("Deleting file {} because it was needed for forwarding only", Base58.encode(hash));
// Keep trying to delete the data until it is deleted, or we reach 10 attempts
// File didn't exist locally before the request, and it's a forwarding request, so delete it if it exists.
// It shouldn't exist on the filesystem yet, but leaving this here just in case.
arbitraryDataFile.delete(10);
}
}
else {
arbitraryDataFile.save();
}
// If this is a metadata file then we need to update the cache
if (arbitraryTransactionData != null && arbitraryTransactionData.getMetadataHash() != null) {

View File

@@ -230,8 +230,7 @@ public class ArbitraryDataManager extends Thread {
// Remove transactions that we already have local data for
if (hasLocalData(arbitraryTransaction)) {
iterator.remove();
continue;
}
}
}
if (signatures.isEmpty()) {
@@ -313,8 +312,7 @@ public class ArbitraryDataManager extends Thread {
// Remove transactions that we already have local data for
if (hasLocalMetadata(arbitraryTransaction)) {
iterator.remove();
continue;
}
}
}
if (signatures.isEmpty()) {

View File

@@ -291,7 +291,6 @@ public class ArbitraryDataStorageManager extends Thread {
arbitraryTransactionDataList.add(arbitraryTransactionData);
} catch (DataException e) {
continue;
}
}
@@ -345,7 +344,6 @@ public class ArbitraryDataStorageManager extends Thread {
}
} catch (Exception e) {
continue;
}
}

View File

@@ -334,11 +334,17 @@ public class ArbitraryMetadataManager {
}
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData;
// Check if the name is blocked
boolean isBlocked = (arbitraryTransactionData == null || ListUtils.isNameBlocked(arbitraryTransactionData.getName()));
// Save if not blocked
ArbitraryDataFile arbitraryMetadataFile = arbitraryMetadataMessage.getArbitraryMetadataFile();
if (!isBlocked && arbitraryMetadataFile != null) {
arbitraryMetadataFile.save();
}
// Forwarding
if (isRelayRequest && Settings.getInstance().isRelayModeEnabled()) {
// Check if the name is blocked
boolean isBlocked = (arbitraryTransactionData == null || ListUtils.isNameBlocked(arbitraryTransactionData.getName()));
if (!isBlocked) {
Peer requestingPeer = request.getB();
if (requestingPeer != null) {

View File

@@ -207,7 +207,7 @@ public class NamesDatabaseIntegrityCheck {
// FUTURE: check database integrity for names that have been updated and then the original name re-registered
else if (Objects.equals(updateNameTransactionData.getName(), registeredName)) {
String newName = updateNameTransactionData.getNewName();
if (newName == null || newName.length() == 0) {
if (newName == null || newName.isEmpty()) {
// If new name is blank (or maybe null, just to be safe), it means that it stayed the same
newName = registeredName;
}

View File

@@ -724,8 +724,7 @@ public class TradeBot implements Listener {
} catch (DataException e) {
LOGGER.info("Unable to determine failed state of AT {}", crossChainTradeData.qortalAtAddress);
continue;
}
}
}
return updatedCrossChainTrades;

View File

@@ -22,8 +22,6 @@ public class Bitcoin extends Bitcoiny {
private static final long MINIMUM_ORDER_AMOUNT = 100000; // 0.001 BTC minimum order, due to high fees
// Temporary values until a dynamic fee system is written.
private static final long OLD_FEE_AMOUNT = 4_000L; // Not 5000 so that existing P2SH-B can output 1000, avoiding dust issue, leaving 4000 for fees.
private static final long NEW_FEE_TIMESTAMP = 1598280000000L; // milliseconds since epoch
private static final long NEW_FEE_AMOUNT = 6_000L;
private static final long NON_MAINNET_FEE = 1000L; // enough for TESTNET3 and should be OK for REGTEST
@@ -46,74 +44,62 @@ public class Bitcoin extends Bitcoiny {
return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources!
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=btc
new Server("104.248.139.211", Server.ConnectionType.SSL, 50002),
new Server("104.198.149.61", Server.ConnectionType.SSL, 50002),
new Server("128.0.190.26", Server.ConnectionType.SSL, 50002),
new Server("142.93.6.38", Server.ConnectionType.SSL, 50002),
new Server("157.245.172.236", Server.ConnectionType.SSL, 50002),
new Server("167.172.226.175", Server.ConnectionType.SSL, 50002),
new Server("167.172.42.31", Server.ConnectionType.SSL, 50002),
new Server("178.62.80.20", Server.ConnectionType.SSL, 50002),
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
new Server("188.165.206.215", Server.ConnectionType.SSL, 50002),
new Server("188.165.211.112", Server.ConnectionType.SSL, 50002),
new Server("2azzarita.hopto.org", Server.ConnectionType.SSL, 50002),
new Server("2electrumx.hopto.me", Server.ConnectionType.SSL, 56022),
new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002),
new Server("65.39.140.37", Server.ConnectionType.SSL, 50002),
new Server("68.183.188.105", Server.ConnectionType.SSL, 50002),
new Server("71.73.14.254", Server.ConnectionType.SSL, 50002),
new Server("94.23.247.135", Server.ConnectionType.SSL, 50002),
new Server("assuredly.not.fyi", Server.ConnectionType.SSL, 50002),
new Server("ax101.blockeng.ch", Server.ConnectionType.SSL, 50002),
new Server("ax102.blockeng.ch", Server.ConnectionType.SSL, 50002),
new Server("260.whyza.net", Server.ConnectionType.SSL, 50002),
new Server("34.136.93.37", Server.ConnectionType.SSL, 50002),
new Server("34.67.22.216", Server.ConnectionType.SSL, 50002),
new Server("34.68.133.78", Server.ConnectionType.SSL, 50002),
new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
new Server("b.1209k.com", Server.ConnectionType.SSL, 50002),
new Server("b6.1209k.com", Server.ConnectionType.SSL, 50002),
new Server("bitcoin.dermichi.com", Server.ConnectionType.SSL, 50002),
new Server("bitcoin.lu.ke", Server.ConnectionType.SSL, 50002),
new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
new Server("blkhub.net", Server.ConnectionType.SSL, 50002),
new Server("btc.electroncash.dk", Server.ConnectionType.SSL, 60002),
new Server("btc.aftrek.org", Server.ConnectionType.SSL, 50002),
new Server("btc.hodler.ninja", Server.ConnectionType.SSL, 50002),
new Server("btc.ocf.sh", Server.ConnectionType.SSL, 50002),
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
new Server("d762li0k0g.d.firewalla.org", Server.ConnectionType.SSL, 50002),
new Server("de.poiuty.com", Server.ConnectionType.SSL, 50002),
new Server("dijon.anties.org", Server.ConnectionType.SSL, 50002),
new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002),
new Server("electrum.bhoovd.com", Server.ConnectionType.SSL, 50002),
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
new Server("electrum.bitcoinlizard.net", Server.ConnectionType.SSL, 50002),
new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 50002),
new Server("electrum.bitrefill.com", Server.ConnectionType.SSL, 50002),
new Server("electrum.brainshome.de", Server.ConnectionType.SSL, 50002),
new Server("electrum.emzy.de", Server.ConnectionType.SSL, 50002),
new Server("electrum.exan.tech", Server.ConnectionType.SSL, 50002),
new Server("electrum.kcicom.net", Server.ConnectionType.SSL, 50002),
new Server("electrum.kendigisland.xyz", Server.ConnectionType.SSL, 50002),
new Server("electrum.mmitech.info", Server.ConnectionType.SSL, 50002),
new Server("electrum.petrkr.net", Server.ConnectionType.SSL, 50002),
new Server("electrum.stippy.com", Server.ConnectionType.SSL, 50002),
new Server("electrum.thomasfischbach.de", Server.ConnectionType.SSL, 50002),
new Server("electrum-btc.leblancnet.us", Server.ConnectionType.SSL, 50002),
new Server("electrum0.snel.it", Server.ConnectionType.SSL, 50002),
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 50002),
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 50002),
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 50002),
new Server("electrumx.alexridevski.net", Server.ConnectionType.SSL, 50002),
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20000),
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20000),
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20000),
new Server("electrumx.blockfinance-eco.li", Server.ConnectionType.SSL, 50002),
new Server("electrumx.indoor.app", Server.ConnectionType.SSL, 50002),
new Server("electrumx.iodata.org", Server.ConnectionType.SSL, 50002),
new Server("electrumx-core.1209k.com", Server.ConnectionType.SSL, 50002),
new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002),
new Server("ex03.axalgo.com", Server.ConnectionType.SSL, 50002),
new Server("ex05.axalgo.com", Server.ConnectionType.SSL, 50002),
new Server("ex07.axalgo.com", Server.ConnectionType.SSL, 50002),
new Server("exs.dyshek.org", Server.ConnectionType.SSL, 50002),
new Server("fortress.qtornado.com", Server.ConnectionType.SSL, 50002),
new Server("fulcrum.grey.pw", Server.ConnectionType.SSL, 50002),
new Server("fulcrum.sethforprivacy.com", Server.ConnectionType.SSL, 51002),
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
new Server("hodl.artyomk13.me", Server.ConnectionType.SSL, 50002),
new Server("hodlers.beer", Server.ConnectionType.SSL, 50002),
new Server("kareoke.qoppa.org", Server.ConnectionType.SSL, 50002),
new Server("kirsche.emzy.de", Server.ConnectionType.SSL, 50002),
new Server("kittyserver.ddnsfree.com", Server.ConnectionType.SSL, 50002),
new Server("lille.anties.org", Server.ConnectionType.SSL, 50002),
new Server("marseille.anties.org", Server.ConnectionType.SSL, 50002),
new Server("node1.btccuracao.com", Server.ConnectionType.SSL, 50002),
new Server("osr1ex1.compumundohipermegared.one", Server.ConnectionType.SSL, 50002),
new Server("smmalis37.ddns.net", Server.ConnectionType.SSL, 50002),
new Server("ulrichard.ch", Server.ConnectionType.SSL, 50002),
new Server("vmd104012.contaboserver.net", Server.ConnectionType.SSL, 50002),
new Server("vmd104014.contaboserver.net", Server.ConnectionType.SSL, 50002),
new Server("paris.anties.org", Server.ConnectionType.SSL, 50002),
new Server("ragtor.duckdns.org", Server.ConnectionType.SSL, 50002),
new Server("stavver.dyshek.org", Server.ConnectionType.SSL, 50002),
new Server("vmd63185.contaboserver.net", Server.ConnectionType.SSL, 50002),
new Server("vmd71287.contaboserver.net", Server.ConnectionType.SSL, 50002),
new Server("vmd84592.contaboserver.net", Server.ConnectionType.SSL, 50002),
new Server("xtrum.com", Server.ConnectionType.SSL, 50002)
);
}
@@ -125,11 +111,7 @@ public class Bitcoin extends Bitcoiny {
@Override
public long getP2shFee(Long timestamp) {
// TODO: This will need to be replaced with something better in the near future!
if (timestamp != null && timestamp < NEW_FEE_TIMESTAMP)
return OLD_FEE_AMOUNT;
return NEW_FEE_AMOUNT;
return this.getFeeCeiling();
}
},
TEST3 {
@@ -141,12 +123,17 @@ public class Bitcoin extends Bitcoiny {
@Override
public Collection<ElectrumX.Server> getServers() {
return Arrays.asList(
new Server("tn.not.fyi", Server.ConnectionType.SSL, 55002),
new Server("electrumx-test.1209k.com", Server.ConnectionType.SSL, 50002),
new Server("testnet.qtornado.com", Server.ConnectionType.SSL, 51002),
new Server("testnet.aranguren.org", Server.ConnectionType.TCP, 51001),
new Server("bitcoin.devmole.eu", Server.ConnectionType.TCP, 5000),
new Server("bitcoin.stagemole.eu", Server.ConnectionType.TCP, 5000),
new Server("blockstream.info", Server.ConnectionType.SSL, 993),
new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 60002),
new Server("electrum1.cipig.net", Server.ConnectionType.TCP, 10068),
new Server("electrum2.cipig.net", Server.ConnectionType.TCP, 10068),
new Server("electrum3.cipig.net", Server.ConnectionType.TCP, 10068),
new Server("testnet.aranguren.org", Server.ConnectionType.SSL, 51002),
new Server("testnet.hsmiths.com", Server.ConnectionType.SSL, 53012)
new Server("testnet.hsmiths.com", Server.ConnectionType.SSL, 53012),
new Server("testnet.qtornado.com", Server.ConnectionType.SSL, 51002),
new Server("v22019051929289916.bestsrv.de", Server.ConnectionType.SSL, 50002)
);
}
@@ -186,6 +173,16 @@ public class Bitcoin extends Bitcoiny {
}
};
private long feeCeiling = NEW_FEE_AMOUNT;
public long getFeeCeiling() {
return feeCeiling;
}
public void setFeeCeiling(long feeCeiling) {
this.feeCeiling = feeCeiling;
}
public abstract NetworkParameters getParams();
public abstract Collection<ElectrumX.Server> getServers();
public abstract String getGenesisHash();
@@ -199,7 +196,7 @@ public class Bitcoin extends Bitcoiny {
// Constructors and instance
private Bitcoin(BitcoinNet bitcoinNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
super(blockchain, bitcoinjContext, currencyCode);
super(blockchain, bitcoinjContext, currencyCode, bitcoinjContext.getFeePerKb());
this.bitcoinNet = bitcoinNet;
LOGGER.info(() -> String.format("Starting Bitcoin support using %s", this.bitcoinNet.name()));
@@ -244,6 +241,16 @@ public class Bitcoin extends Bitcoiny {
return this.bitcoinNet.getP2shFee(timestamp);
}
@Override
public long getFeeCeiling() {
return this.bitcoinNet.getFeeCeiling();
}
@Override
public void setFeeCeiling(long fee) {
this.bitcoinNet.setFeeCeiling( fee );
}
/**
* Returns bitcoinj transaction sending <tt>amount</tt> to <tt>recipient</tt> using 20 sat/byte fee.
*

View File

@@ -53,12 +53,15 @@ public abstract class Bitcoiny implements ForeignBlockchain {
/** Byte offset into raw block headers to block timestamp. */
private static final int TIMESTAMP_OFFSET = 4 + 32 + 32;
protected Coin feePerKb;
// Constructors and instance
protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode) {
protected Bitcoiny(BitcoinyBlockchainProvider blockchainProvider, Context bitcoinjContext, String currencyCode, Coin feePerKb) {
this.blockchainProvider = blockchainProvider;
this.bitcoinjContext = bitcoinjContext;
this.currencyCode = currencyCode;
this.feePerKb = feePerKb;
this.params = this.bitcoinjContext.getParams();
}
@@ -167,7 +170,11 @@ public abstract class Bitcoiny implements ForeignBlockchain {
/** Returns fee per transaction KB. To be overridden for testnet/regtest. */
public Coin getFeePerKb() {
return this.bitcoinjContext.getFeePerKb();
return this.feePerKb;
}
public void setFeePerKb(Coin feePerKb) {
this.feePerKb = feePerKb;
}
/** Returns minimum order size in sats. To be overridden for coins that need to restrict order size. */
@@ -750,13 +757,17 @@ public abstract class Bitcoiny implements ForeignBlockchain {
Address address = Address.fromKey(this.params, keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS), ScriptType.P2PKH);
// if zero transactions, return address
if( 0 == getAddressTransactions(ScriptBuilder.createOutputScript(address).getProgram(), true).size() )
if(getAddressTransactions(ScriptBuilder.createOutputScript(address).getProgram(), true).isEmpty())
return address.toString();
// else try the next receive funds address
} while (true);
}
public abstract long getFeeCeiling();
public abstract void setFeeCeiling(long fee);
// UTXOProvider support
static class WalletAwareUTXOProvider implements UTXOProvider {

View File

@@ -3,12 +3,14 @@ package org.qortal.crosschain;
import cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public abstract class BitcoinyBlockchainProvider {
public static final boolean INCLUDE_UNCONFIRMED = true;
public static final boolean EXCLUDE_UNCONFIRMED = false;
public static final String EMPTY = "";
/** Sets the blockchain using this provider instance */
public abstract void setBlockchain(Bitcoiny blockchain);
@@ -67,4 +69,62 @@ public abstract class BitcoinyBlockchainProvider {
public abstract Set<ChainableServer> getUselessServers();
public abstract ChainableServer getCurrentServer();
/**
* Add Server
*
* Add server to list of candidate servers.
*
* @param server the server
*
* @return true if added, otherwise false
*/
public abstract boolean addServer( ChainableServer server );
/**
* Remove Server
*
* Remove server from list of candidate servers.
*
* @param server the server
*
* @return true if removed, otherwise false
*/
public abstract boolean removeServer( ChainableServer server );
/**
* Set Current Server
*
* Set server to be used for this foreign blockchain.
*
* @param server the server
* @param requestedBy who requested this setting
*
* @return the connection that was made
*
* @throws ForeignBlockchainException
*/
public abstract Optional<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) throws ForeignBlockchainException;
/**
* Get Server Connections
*
* Get the server connections made to this foreign blockchain,
*
* @return the server connections
*/
public abstract List<ChainableServerConnection> getServerConnections();
/**
* Get Server
*
* Get a server for this foreign blockchain.
*
* @param hostName the host URL
* @param type the type of connection (TCP, SSL)
* @param port the port
*
* @return the server
*/
public abstract ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port);
}

View File

@@ -0,0 +1,71 @@
package org.qortal.crosschain;
import java.util.Objects;
public class ChainableServerConnection {
private ChainableServer server;
private String requestedBy;
private boolean open;
private boolean success;
private long currentTimeMillis;
private String notes;
public ChainableServerConnection(ChainableServer server, String requestedBy, boolean open, boolean success, long currentTimeMillis, String notes) {
this.server = server;
this.requestedBy = requestedBy;
this.open = open;
this.success = success;
this.currentTimeMillis = currentTimeMillis;
this.notes = notes;
}
public ChainableServer getServer() {
return server;
}
public String getRequestedBy() {
return requestedBy;
}
public boolean isOpen() {
return open;
}
public boolean isSuccess() {
return success;
}
public long getCurrentTimeMillis() {
return currentTimeMillis;
}
public String getNotes() {
return notes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChainableServerConnection that = (ChainableServerConnection) o;
return currentTimeMillis == that.currentTimeMillis && Objects.equals(server, that.server);
}
@Override
public int hashCode() {
return Objects.hash(server, currentTimeMillis);
}
@Override
public String toString() {
return "ChainableServerConnection{" +
"server=" + server +
", requestedBy='" + requestedBy + '\'' +
", open=" + open +
", success=" + success +
", currentTimeMillis=" + currentTimeMillis +
", notes='" + notes + '\'' +
'}';
}
}

View File

@@ -0,0 +1,45 @@
package org.qortal.crosschain;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class ChainableServerConnectionRecorder {
private List<ChainableServerConnection> connections;
private int limit;
public ChainableServerConnectionRecorder(int limit) {
this.connections = new ArrayList<>(limit);
this.limit = limit;
}
public ChainableServerConnection recordConnection(
ChainableServer server, String requestedBy, boolean open, boolean success, String notes) {
ChainableServerConnection connection
= new ChainableServerConnection(server, requestedBy, open, success, System.currentTimeMillis(), notes);
connections.add(connection);
if( connections.size() > limit) {
ChainableServerConnection firstConnection
= connections.stream().sorted(Comparator.comparing(ChainableServerConnection::getCurrentTimeMillis))
.findFirst().get();
connections.remove(firstConnection);
}
return connection;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public List<ChainableServerConnection> getConnections() {
return this.connections;
}
}

View File

@@ -59,8 +59,7 @@ public class Digibyte extends Bitcoiny {
@Override
public long getP2shFee(Long timestamp) {
// TODO: This will need to be replaced with something better in the near future!
return MAINNET_FEE;
return this.getFeeCeiling();
}
},
TEST3 {
@@ -110,6 +109,16 @@ public class Digibyte extends Bitcoiny {
}
};
private long feeCeiling = MAINNET_FEE;
public long getFeeCeiling() {
return feeCeiling;
}
public void setFeeCeiling(long feeCeiling) {
this.feeCeiling = feeCeiling;
}
public abstract NetworkParameters getParams();
public abstract Collection<Server> getServers();
public abstract String getGenesisHash();
@@ -123,7 +132,7 @@ public class Digibyte extends Bitcoiny {
// Constructors and instance
private Digibyte(DigibyteNet digibyteNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
super(blockchain, bitcoinjContext, currencyCode);
super(blockchain, bitcoinjContext, currencyCode, DEFAULT_FEE_PER_KB);
this.digibyteNet = digibyteNet;
LOGGER.info(() -> String.format("Starting Digibyte support using %s", this.digibyteNet.name()));
@@ -152,11 +161,6 @@ public class Digibyte extends Bitcoiny {
// 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;
@@ -173,4 +177,14 @@ public class Digibyte extends Bitcoiny {
return this.digibyteNet.getP2shFee(timestamp);
}
@Override
public long getFeeCeiling() {
return this.digibyteNet.getFeeCeiling();
}
@Override
public void setFeeCeiling(long fee) {
this.digibyteNet.setFeeCeiling( fee );
}
}

View File

@@ -45,6 +45,7 @@ public class Dogecoin extends Bitcoiny {
return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources!
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=doge
new Server("dogecoin.stackwallet.com", Server.ConnectionType.SSL, 50022),
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 54002),
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20060),
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20060),
@@ -59,8 +60,7 @@ public class Dogecoin extends Bitcoiny {
@Override
public long getP2shFee(Long timestamp) {
// TODO: This will need to be replaced with something better in the near future!
return MAINNET_FEE;
return this.getFeeCeiling();
}
},
TEST3 {
@@ -110,6 +110,16 @@ public class Dogecoin extends Bitcoiny {
}
};
private long feeCeiling = MAINNET_FEE;
public long getFeeCeiling() {
return feeCeiling;
}
public void setFeeCeiling(long feeCeiling) {
this.feeCeiling = feeCeiling;
}
public abstract NetworkParameters getParams();
public abstract Collection<Server> getServers();
public abstract String getGenesisHash();
@@ -123,7 +133,7 @@ public class Dogecoin extends Bitcoiny {
// Constructors and instance
private Dogecoin(DogecoinNet dogecoinNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
super(blockchain, bitcoinjContext, currencyCode);
super(blockchain, bitcoinjContext, currencyCode, DEFAULT_FEE_PER_KB);
this.dogecoinNet = dogecoinNet;
LOGGER.info(() -> String.format("Starting Dogecoin support using %s", this.dogecoinNet.name()));
@@ -152,11 +162,6 @@ public class Dogecoin extends Bitcoiny {
// 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;
@@ -173,4 +178,14 @@ public class Dogecoin extends Bitcoiny {
return this.dogecoinNet.getP2shFee(timestamp);
}
@Override
public long getFeeCeiling() {
return this.dogecoinNet.getFeeCeiling();
}
@Override
public void setFeeCeiling(long fee) {
this.dogecoinNet.setFeeCeiling( fee );
}
}

View File

@@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.qortal.api.resource.CrossChainUtils;
import org.qortal.crypto.Crypto;
import org.qortal.crypto.TrustlessSSLSocketFactory;
import org.qortal.utils.BitTwiddling;
@@ -26,6 +27,7 @@ import java.util.regex.Pattern;
/** ElectrumX network support for querying Bitcoiny-related info like block headers, transaction outputs, etc. */
public class ElectrumX extends BitcoinyBlockchainProvider {
public static final String NULL_RESPONSE_FROM_ELECTRUM_X_SERVER = "Null response from ElectrumX server";
private static final Logger LOGGER = LogManager.getLogger(ElectrumX.class);
private static final Random RANDOM = new Random();
@@ -44,6 +46,10 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
private static final int RESPONSE_TIME_READINGS = 5;
private static final long MAX_AVG_RESPONSE_TIME = 2000L; // ms
public static final String MINIMUM_VERSION_ERROR = "MINIMUM VERSION ERROR";
public static final String EXPECTED_GENESIS_ERROR = "EXPECTED GENESIS ERROR";
private ChainableServerConnectionRecorder recorder = new ChainableServerConnectionRecorder(100);
public static class Server implements ChainableServer {
String hostname;
@@ -136,7 +142,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
private int nextId = 1;
private static final int TX_CACHE_SIZE = 1000;
@SuppressWarnings("serial")
private final Map<String, BitcoinyTransaction> transactionCache = Collections.synchronizedMap(new LinkedHashMap<>(TX_CACHE_SIZE + 1, 0.75F, true) {
// This method is called just after a new entry has been added
@Override
@@ -216,10 +222,10 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
if (!(countObj instanceof Long) || !(hexObj instanceof String))
throw new ForeignBlockchainException.NetworkException("Missing/invalid 'count' or 'hex' entries in JSON from ElectrumX blockchain.block.headers RPC");
Long returnedCount = (Long) countObj;
long returnedCount = (Long) countObj;
String hex = (String) hexObj;
List<byte[]> rawBlockHeaders = new ArrayList<>(returnedCount.intValue());
List<byte[]> rawBlockHeaders = new ArrayList<>((int) returnedCount);
byte[] raw = HashCode.fromString(hex).asBytes();
@@ -421,7 +427,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
Server uselessServer = (Server) e.getServer();
LOGGER.trace(() -> String.format("Server %s doesn't support verbose transactions - barring use of that server", uselessServer));
this.uselessServers.add(uselessServer);
this.closeServer(uselessServer);
this.closeServer(uselessServer, this.getClass().getSimpleName(), CrossChainUtils.getNotes(e));
continue;
}
@@ -495,12 +501,13 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
// Update: it turns out that they were just using a different key - "address" instead of "addresses"
// The code below can remain in place, just in case a peer returns a missing address in the future
if (addresses == null || addresses.isEmpty()) {
final String message = String.format("No output addresses returned for transaction %s", txHash);
if (this.currentServer != null) {
this.uselessServers.add(this.currentServer);
this.closeServer(this.currentServer);
this.closeServer(this.currentServer, this.getClass().getSimpleName(), message);
}
LOGGER.info("No output addresses returned for transaction {}", txHash);
throw new ForeignBlockchainException(String.format("No output addresses returned for transaction %s", txHash));
throw new ForeignBlockchainException(message);
}
outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses));
@@ -585,7 +592,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
Object peers = this.connectedRpc("server.peers.subscribe");
for (Object rawPeer : (JSONArray) peers) {
for (Object rawPeer : (JSONArray) Objects.requireNonNull(peers)) {
JSONArray peer = (JSONArray) rawPeer;
if (peer.size() < 3)
// We're expecting at least 3 fields for each peer entry: IP, hostname, features
@@ -654,8 +661,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
if (!this.remainingServers.isEmpty()) {
long averageResponseTime = this.currentServer.averageResponseTime();
if (averageResponseTime > MAX_AVG_RESPONSE_TIME) {
LOGGER.info("Slow average response time {}ms from {} - trying another server...", averageResponseTime, this.currentServer.getHostName());
this.closeServer();
String message = String.format("Slow average response time %dms from %s - trying another server...", averageResponseTime, this.currentServer.getHostName());
LOGGER.info(message);
this.closeServer(this.getClass().getSimpleName(), message);
break;
}
}
@@ -663,8 +671,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
if (response != null)
return response;
LOGGER.info(NULL_RESPONSE_FROM_ELECTRUM_X_SERVER);
// Didn't work, try another server...
this.closeServer();
this.closeServer(this.getClass().getSimpleName(), NULL_RESPONSE_FROM_ELECTRUM_X_SERVER);
}
// Failed to perform RPC - maybe lack of servers?
@@ -680,56 +689,63 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
while (!this.remainingServers.isEmpty()) {
ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size()));
LOGGER.trace(() -> String.format("Connecting to %s", server));
try {
SocketAddress endpoint = new InetSocketAddress(server.getHostName(), server.getPort());
int timeout = 5000; // ms
this.socket = new Socket();
this.socket.connect(endpoint, timeout);
this.socket.setTcpNoDelay(true);
if (server.getConnectionType() == Server.ConnectionType.SSL) {
SSLSocketFactory factory = TrustlessSSLSocketFactory.getSocketFactory();
this.socket = factory.createSocket(this.socket, server.getHostName(), server.getPort(), true);
}
this.scanner = new Scanner(this.socket.getInputStream());
this.scanner.useDelimiter("\n");
// All connections need to start with a version negotiation
this.connectedRpc("server.version");
// Check connection is suitable by asking for server features, including genesis block hash
JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features");
if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION)
continue;
if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash))
continue;
// Ask for more servers
Set<Server> moreServers = serverPeersSubscribe();
// Discard duplicate servers we already know
moreServers.removeAll(this.servers);
// Add to both lists
this.remainingServers.addAll(moreServers);
this.servers.addAll(moreServers);
LOGGER.debug(() -> String.format("Connected to %s", server));
this.currentServer = server;
return true;
} catch (IOException | ForeignBlockchainException | ClassCastException | NullPointerException e) {
// Didn't work, try another server...
closeServer();
}
Optional<ChainableServerConnection> chainableServerConnection = makeConnection(server, this.getClass().getSimpleName());
if(chainableServerConnection.isPresent() && chainableServerConnection.get().isSuccess() ) return true;
}
return false;
}
private Optional<ChainableServerConnection> makeConnection(ChainableServer server, String requestedBy) {
LOGGER.info(() -> String.format("Connecting to %s", server));
try {
SocketAddress endpoint = new InetSocketAddress(server.getHostName(), server.getPort());
int timeout = 5000; // ms
this.socket = new Socket();
this.socket.connect(endpoint, timeout);
this.socket.setTcpNoDelay(true);
if (server.getConnectionType() == Server.ConnectionType.SSL) {
SSLSocketFactory factory = TrustlessSSLSocketFactory.getSocketFactory();
this.socket = factory.createSocket(this.socket, server.getHostName(), server.getPort(), true);
}
this.scanner = new Scanner(this.socket.getInputStream());
this.scanner.useDelimiter("\n");
// All connections need to start with a version negotiation
this.connectedRpc("server.version");
// Check connection is suitable by asking for server features, including genesis block hash
JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features");
if (featuresJson == null || Double.parseDouble((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION)
return Optional.of( recorder.recordConnection(server, requestedBy, true, false, MINIMUM_VERSION_ERROR) );
if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash))
return Optional.of( recorder.recordConnection(server, requestedBy, true, false, EXPECTED_GENESIS_ERROR) );
// Ask for more servers
Set<Server> moreServers = serverPeersSubscribe();
// Discard duplicate servers we already know
moreServers.removeAll(this.servers);
// Add all servers to both lists
this.remainingServers.addAll(moreServers);
this.servers.addAll(moreServers);
LOGGER.info(() -> String.format("Connected to %s", server));
this.currentServer = server;
return Optional.of( this.recorder.recordConnection( server, requestedBy, true, true, EMPTY) );
} catch (IOException | ForeignBlockchainException | ClassCastException | NullPointerException e) {
// Didn't work, try another server...
return Optional.of( this.recorder.recordConnection( server, requestedBy, true, false, CrossChainUtils.getNotes(e)));
}
}
/**
* Perform RPC using currently connected server.
* <p>
@@ -846,12 +862,19 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
/**
* Closes connection to <tt>server</tt> if it is currently connected server.
*
* @param server
* @param notes
*/
private void closeServer(ChainableServer server) {
private Optional<ChainableServerConnection> closeServer(ChainableServer server, String requestedBy, String notes) {
ChainableServerConnection chainableServerConnection;
synchronized (this.serverLock) {
if (this.currentServer == null || !this.currentServer.equals(server))
return;
return Optional.empty();
chainableServerConnection = this.recorder.recordConnection(server, requestedBy, false, true, notes);
if (this.socket != null && !this.socket.isClosed())
try {
@@ -864,12 +887,14 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
this.scanner = null;
this.currentServer = null;
}
return Optional.of( chainableServerConnection );
}
/** Closes connection to currently connected server (if any). */
private void closeServer() {
private Optional<ChainableServerConnection> closeServer(String requestedBy, String notes) {
synchronized (this.serverLock) {
this.closeServer(this.currentServer);
return this.closeServer(this.currentServer, requestedBy, notes);
}
}
@@ -893,4 +918,32 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
public ChainableServer getCurrentServer() {
return currentServer;
}
@Override
public boolean addServer(ChainableServer server) {
return this.servers.add(server);
}
@Override
public boolean removeServer(ChainableServer server) {
boolean removedServer = this.servers.remove(server);
boolean removedRemaining = this.remainingServers.remove(server);
return removedServer || removedRemaining;
}
@Override
public Optional<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) {
return this.makeConnection(server, requestedBy);
}
@Override
public List<ChainableServerConnection> getServerConnections() {
return this.recorder.getConnections();
}
@Override
public ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port) {
return new ElectrumX.Server(hostName, type, port);
}
}

View File

@@ -45,9 +45,9 @@ public class Litecoin extends Bitcoiny {
return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources!
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 50002),
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443),
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002),
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 50002),
new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
@@ -63,8 +63,7 @@ public class Litecoin extends Bitcoiny {
@Override
public long getP2shFee(Long timestamp) {
// TODO: This will need to be replaced with something better in the near future!
return MAINNET_FEE;
return this.getFeeCeiling();
}
},
TEST3 {
@@ -76,9 +75,7 @@ public class Litecoin extends Bitcoiny {
@Override
public Collection<ElectrumX.Server> getServers() {
return Arrays.asList(
new Server("electrum-ltc.bysh.me", Server.ConnectionType.TCP, 51001),
new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 51002),
new Server("electrum.ltc.xurious.com", Server.ConnectionType.TCP, 51001),
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 51002)
);
}
@@ -119,6 +116,16 @@ public class Litecoin extends Bitcoiny {
}
};
private long feeCeiling = MAINNET_FEE;
public long getFeeCeiling() {
return feeCeiling;
}
public void setFeeCeiling(long feeCeiling) {
this.feeCeiling = feeCeiling;
}
public abstract NetworkParameters getParams();
public abstract Collection<ElectrumX.Server> getServers();
public abstract String getGenesisHash();
@@ -132,7 +139,7 @@ public class Litecoin extends Bitcoiny {
// Constructors and instance
private Litecoin(LitecoinNet litecoinNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
super(blockchain, bitcoinjContext, currencyCode);
super(blockchain, bitcoinjContext, currencyCode, DEFAULT_FEE_PER_KB);
this.litecoinNet = litecoinNet;
LOGGER.info(() -> String.format("Starting Litecoin support using %s", this.litecoinNet.name()));
@@ -161,12 +168,6 @@ public class Litecoin extends Bitcoiny {
// Actual useful methods for use by other classes
/** Default Litecoin fee is lower than Bitcoin: only 10sats/byte. */
@Override
public Coin getFeePerKb() {
return DEFAULT_FEE_PER_KB;
}
@Override
public long getMinimumOrderAmount() {
return MINIMUM_ORDER_AMOUNT;
@@ -183,4 +184,14 @@ public class Litecoin extends Bitcoiny {
return this.litecoinNet.getP2shFee(timestamp);
}
@Override
public long getFeeCeiling() {
return this.litecoinNet.getFeeCeiling();
}
@Override
public void setFeeCeiling(long fee) {
this.litecoinNet.setFeeCeiling( fee );
}
}

View File

@@ -51,7 +51,12 @@ public class PirateChain extends Bitcoiny {
public Collection<Server> getServers() {
return Arrays.asList(
// Servers chosen on NO BASIS WHATSOEVER from various sources!
new Server("lightd.pirate.black", Server.ConnectionType.SSL, 443)
new Server("lightd.pirate.black", Server.ConnectionType.SSL, 443),
new Server("wallet-arrr1.qortal.online", Server.ConnectionType.SSL, 443),
new Server("wallet-arrr2.qortal.online", Server.ConnectionType.SSL, 443),
new Server("wallet-arrr3.qortal.online", Server.ConnectionType.SSL, 443),
new Server("wallet-arrr4.qortal.online", Server.ConnectionType.SSL, 443),
new Server("wallet-arrr5.qortal.online", Server.ConnectionType.SSL, 443)
);
}
@@ -62,8 +67,7 @@ public class PirateChain extends Bitcoiny {
@Override
public long getP2shFee(Long timestamp) {
// TODO: This will need to be replaced with something better in the near future!
return MAINNET_FEE;
return this.getFeeCeiling();
}
},
TEST3 {
@@ -113,6 +117,16 @@ public class PirateChain extends Bitcoiny {
}
};
private long feeCeiling = MAINNET_FEE;
public long getFeeCeiling() {
return feeCeiling;
}
public void setFeeCeiling(long feeCeiling) {
this.feeCeiling = feeCeiling;
}
public abstract NetworkParameters getParams();
public abstract Collection<Server> getServers();
public abstract String getGenesisHash();
@@ -126,7 +140,7 @@ public class PirateChain extends Bitcoiny {
// Constructors and instance
private PirateChain(PirateChainNet pirateChainNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
super(blockchain, bitcoinjContext, currencyCode);
super(blockchain, bitcoinjContext, currencyCode, DEFAULT_FEE_PER_KB);
this.pirateChainNet = pirateChainNet;
LOGGER.info(() -> String.format("Starting Pirate Chain support using %s", this.pirateChainNet.name()));
@@ -155,12 +169,6 @@ public class PirateChain extends Bitcoiny {
// Actual useful methods for use by other classes
/** Default Litecoin fee is lower than Bitcoin: only 10sats/byte. */
@Override
public Coin getFeePerKb() {
return DEFAULT_FEE_PER_KB;
}
@Override
public long getMinimumOrderAmount() {
return MINIMUM_ORDER_AMOUNT;
@@ -177,6 +185,16 @@ public class PirateChain extends Bitcoiny {
return this.pirateChainNet.getP2shFee(timestamp);
}
@Override
public long getFeeCeiling() {
return this.pirateChainNet.getFeeCeiling();
}
@Override
public void setFeeCeiling(long fee) {
this.pirateChainNet.setFeeCeiling( fee );
}
/**
* Returns confirmed balance, based on passed payment script.
* <p>

View File

@@ -14,6 +14,7 @@ import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.qortal.api.resource.CrossChainUtils;
import org.qortal.settings.Settings;
import org.qortal.transform.TransformationException;
@@ -127,6 +128,8 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
}
});
private ChainableServerConnectionRecorder recorder = new ChainableServerConnectionRecorder(100);
// Constructors
public PirateLightClient(String netId, String genesisHash, Collection<Server> initialServerList, Map<Server.ConnectionType, Integer> defaultPorts) {
@@ -443,12 +446,13 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
// Update: it turns out that they were just using a different key - "address" instead of "addresses"
// The code below can remain in place, just in case a peer returns a missing address in the future
if (addresses == null || addresses.isEmpty()) {
final String message = String.format("No output addresses returned for transaction %s", txHash);
if (this.currentServer != null) {
this.uselessServers.add(this.currentServer);
this.closeServer(this.currentServer);
this.closeServer(this.currentServer, message, this.getClass().getSimpleName());
}
LOGGER.info("No output addresses returned for transaction {}", txHash);
throw new ForeignBlockchainException(String.format("No output addresses returned for transaction %s", txHash));
LOGGER.info(message);
throw new ForeignBlockchainException(message);
}
outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses));
@@ -557,6 +561,42 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
@Override
public ChainableServer getCurrentServer() { return this.currentServer; }
@Override
public boolean addServer(ChainableServer server) {
return this.servers.add(server);
}
@Override
public boolean removeServer(ChainableServer server) {
boolean removedServer = this.servers.remove(server);
boolean removedRemaining = this.remainingServers.remove(server);
return removedServer || removedRemaining;
}
@Override
public Optional<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) throws ForeignBlockchainException {
closeServer( requestedBy, "Connecting to different server by request." );
Optional<ChainableServerConnection> connection = makeConnection(server, requestedBy);
if( !connection.isPresent() || !connection.get().isSuccess() ) {
haveConnection();
}
return connection;
}
@Override
public List<ChainableServerConnection> getServerConnections() {
return this.recorder.getConnections();
}
@Override
public ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port) {
return new PirateLightClient.Server(hostName, type, port);
}
// Class-private utility methods
@@ -576,8 +616,9 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
if (!this.remainingServers.isEmpty()) {
long averageResponseTime = this.currentServer.averageResponseTime();
if (averageResponseTime > MAX_AVG_RESPONSE_TIME) {
LOGGER.info("Slow average response time {}ms from {} - trying another server...", averageResponseTime, this.currentServer.getHostName());
this.closeServer();
String message = String.format("Slow average response time %dms from %s - trying another server...", averageResponseTime, this.currentServer.getHostName());
LOGGER.info(message);
this.closeServer(this.getClass().getSimpleName(), message);
continue;
}
}
@@ -601,18 +642,27 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
while (!this.remainingServers.isEmpty()) {
ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size()));
LOGGER.trace(() -> String.format("Connecting to %s", server));
try {
this.channel = ManagedChannelBuilder.forAddress(server.getHostName(), server.getPort()).build();
Optional<ChainableServerConnection> chainableServerConnection = makeConnection(server, this.getClass().getSimpleName());
if( chainableServerConnection.isPresent() && chainableServerConnection.get().isSuccess() ) return true;
}
CompactTxStreamerGrpc.CompactTxStreamerBlockingStub stub = CompactTxStreamerGrpc.newBlockingStub(this.channel);
LightdInfo lightdInfo = stub.getLightdInfo(Empty.newBuilder().build());
return false;
}
if (lightdInfo == null || lightdInfo.getBlockHeight() <= 0)
continue;
private Optional<ChainableServerConnection> makeConnection(ChainableServer server, String requestedBy) {
LOGGER.info(() -> String.format("Connecting to %s", server));
// TODO: find a way to verify that the server is using the expected chain
try {
this.channel = ManagedChannelBuilder.forAddress(server.getHostName(), server.getPort()).build();
CompactTxStreamerGrpc.CompactTxStreamerBlockingStub stub = CompactTxStreamerGrpc.newBlockingStub(this.channel);
LightdInfo lightdInfo = stub.getLightdInfo(Empty.newBuilder().build());
if (lightdInfo == null || lightdInfo.getBlockHeight() <= 0)
return Optional.of( this.recorder.recordConnection(server, requestedBy,true, false, "lightd info issues") );
// TODO: find a way to verify that the server is using the expected chain
// if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION)
// continue;
@@ -620,28 +670,31 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
// if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash))
// continue;
LOGGER.debug(() -> String.format("Connected to %s", server));
this.currentServer = server;
return true;
} catch (Exception e) {
// Didn't work, try another server...
closeServer();
}
LOGGER.info(() -> String.format("Connected to %s", server));
this.currentServer = server;
return Optional.of( this.recorder.recordConnection(server, requestedBy,true, true, EMPTY) );
} catch (Exception e) {
// Didn't work, try another server...
return Optional.of( this.recorder.recordConnection( server, requestedBy, true, false, CrossChainUtils.getNotes(e)));
}
return false;
}
/**
* Closes connection to <tt>server</tt> if it is currently connected server.
*
* @param server
* @param requestedBy
*/
private void closeServer(ChainableServer server) {
private Optional<ChainableServerConnection> closeServer(ChainableServer server, String notes, String requestedBy) {
final ChainableServerConnection connection;
synchronized (this.serverLock) {
if (this.currentServer == null || !this.currentServer.equals(server) || this.channel == null) {
return;
return Optional.empty();
}
connection = this.recorder.recordConnection(server, requestedBy, false, true, notes);
// Close the gRPC managed-channel if not shut down already.
if (!this.channel.isShutdown()) {
try {
@@ -669,12 +722,14 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
this.channel = null;
this.currentServer = null;
}
return Optional.of( connection );
}
/** Closes connection to currently connected server (if any). */
private void closeServer() {
private Optional<ChainableServerConnection> closeServer(String requestedBy, String notes) {
synchronized (this.serverLock) {
this.closeServer(this.currentServer);
return this.closeServer(this.currentServer, notes, requestedBy);
}
}

View File

@@ -61,8 +61,7 @@ public class Ravencoin extends Bitcoiny {
@Override
public long getP2shFee(Long timestamp) {
// TODO: This will need to be replaced with something better in the near future!
return MAINNET_FEE;
return this.getFeeCeiling();
}
},
TEST3 {
@@ -112,6 +111,16 @@ public class Ravencoin extends Bitcoiny {
}
};
private long feeCeiling = MAINNET_FEE;
public long getFeeCeiling() {
return feeCeiling;
}
public void setFeeCeiling(long feeCeiling) {
this.feeCeiling = feeCeiling;
}
public abstract NetworkParameters getParams();
public abstract Collection<Server> getServers();
public abstract String getGenesisHash();
@@ -125,7 +134,7 @@ public class Ravencoin extends Bitcoiny {
// Constructors and instance
private Ravencoin(RavencoinNet ravencoinNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
super(blockchain, bitcoinjContext, currencyCode);
super(blockchain, bitcoinjContext, currencyCode, DEFAULT_FEE_PER_KB);
this.ravencoinNet = ravencoinNet;
LOGGER.info(() -> String.format("Starting Ravencoin support using %s", this.ravencoinNet.name()));
@@ -154,11 +163,6 @@ public class Ravencoin extends Bitcoiny {
// 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;
@@ -175,4 +179,14 @@ public class Ravencoin extends Bitcoiny {
return this.ravencoinNet.getP2shFee(timestamp);
}
@Override
public long getFeeCeiling() {
return this.ravencoinNet.getFeeCeiling();
}
@Override
public void setFeeCeiling(long fee) {
this.ravencoinNet.setFeeCeiling( fee );
}
}

View File

@@ -0,0 +1,82 @@
package org.qortal.crosschain;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import java.util.Objects;
@XmlAccessorType(XmlAccessType.FIELD)
public class ServerConnectionInfo {
private ServerInfo serverInfo;
private String requestedBy;
private boolean open;
private boolean success;
private long timeInMillis;
private String notes;
public ServerConnectionInfo() {
}
public ServerConnectionInfo(ServerInfo serverInfo, String requestedBy, boolean open, boolean success, long timeInMillis, String notes) {
this.serverInfo = serverInfo;
this.requestedBy = requestedBy;
this.open = open;
this.success = success;
this.timeInMillis = timeInMillis;
this.notes = notes;
}
public ServerInfo getServerInfo() {
return serverInfo;
}
public String getRequestedBy() {
return requestedBy;
}
public boolean isOpen() {
return open;
}
public boolean isSuccess() {
return success;
}
public long getTimeInMillis() {
return timeInMillis;
}
public String getNotes() {
return notes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerConnectionInfo that = (ServerConnectionInfo) o;
return timeInMillis == that.timeInMillis && Objects.equals(serverInfo, that.serverInfo);
}
@Override
public int hashCode() {
return Objects.hash(serverInfo, timeInMillis);
}
@Override
public String toString() {
return "ServerConnectionInfo{" +
"serverInfo=" + serverInfo +
", requestedBy='" + requestedBy + '\'' +
", open=" + open +
", success=" + success +
", timeInMillis=" + timeInMillis +
", notes='" + notes + '\'' +
'}';
}
}

View File

@@ -0,0 +1,32 @@
package org.qortal.data.crosschain;
import org.qortal.crosschain.BitcoinyTransaction;
import org.qortal.crosschain.TransactionHash;
import java.util.List;
import java.util.Map;
public class AtomicTransactionData {
public final TransactionHash hash;
public final Integer timestamp;
public final List<BitcoinyTransaction.Input> inputs;
public final Map<List<String>, Long> valueByAddress;
public final long totalAmount;
public final int size;
public AtomicTransactionData(
TransactionHash hash,
Integer timestamp,
List<BitcoinyTransaction.Input> inputs,
Map<List<String>, Long> valueByAddress,
long totalAmount,
int size) {
this.hash = hash;
this.timestamp = timestamp;
this.inputs = inputs;
this.valueByAddress = valueByAddress;
this.totalAmount = totalAmount;
this.size = size;
}
}

View File

@@ -0,0 +1,106 @@
package org.qortal.data.crosschain;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class TransactionSummary {
private String atAddress;
private String p2shValue;
private String p2shAddress;
private String lockingHash;
private Integer lockingTimestamp;
private long lockingTotalAmount;
private long lockingFee;
private int lockingSize;
private String unlockingHash;
private Integer unlockingTimestamp;
private long unlockingTotalAmount;
private long unlockingFee;
private int unlockingSize;
public TransactionSummary(){}
public TransactionSummary(
String atAddress,
String p2shValue,
String p2shAddress,
String lockingHash,
Integer lockingTimestamp,
long lockingTotalAmount,
long lockingFee,
int lockingSize,
String unlockingHash,
Integer unlockingTimestamp,
long unlockingTotalAmount,
long unlockingFee,
int unlockingSize) {
this.atAddress = atAddress;
this.p2shValue = p2shValue;
this.p2shAddress = p2shAddress;
this.lockingHash = lockingHash;
this.lockingTimestamp = lockingTimestamp;
this.lockingTotalAmount = lockingTotalAmount;
this.lockingFee = lockingFee;
this.lockingSize = lockingSize;
this.unlockingHash = unlockingHash;
this.unlockingTimestamp = unlockingTimestamp;
this.unlockingTotalAmount = unlockingTotalAmount;
this.unlockingFee = unlockingFee;
this.unlockingSize = unlockingSize;
}
public String getAtAddress() {
return atAddress;
}
public String getP2shValue() {
return p2shValue;
}
public String getP2shAddress() {
return p2shAddress;
}
public String getLockingHash() {
return lockingHash;
}
public Integer getLockingTimestamp() {
return lockingTimestamp;
}
public long getLockingTotalAmount() {
return lockingTotalAmount;
}
public long getLockingFee() {
return lockingFee;
}
public int getLockingSize() {
return lockingSize;
}
public String getUnlockingHash() {
return unlockingHash;
}
public Integer getUnlockingTimestamp() {
return unlockingTimestamp;
}
public long getUnlockingTotalAmount() {
return unlockingTotalAmount;
}
public long getUnlockingFee() {
return unlockingFee;
}
public int getUnlockingSize() {
return unlockingSize;
}
}

View File

@@ -25,8 +25,8 @@ public class ArbitraryTransactionData extends TransactionData {
// "data" field types
public enum DataType {
RAW_DATA,
DATA_HASH;
}
DATA_HASH
}
// Methods
public enum Method {

View File

@@ -4,7 +4,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.controller.Controller;
import org.qortal.globalization.Translator;
import org.qortal.settings.Settings;
import org.qortal.utils.URLViewer;
import javax.swing.*;
@@ -17,14 +16,11 @@ import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

View File

@@ -70,15 +70,15 @@ public enum Handshake {
peer.setPeersVersion(versionString, version);
// Ensure the peer is running at least the version specified in MIN_PEER_VERSION
if (peer.isAtLeastVersion(MIN_PEER_VERSION) == false) {
if (!peer.isAtLeastVersion(MIN_PEER_VERSION)) {
LOGGER.debug(String.format("Ignoring peer %s because it is on an old version (%s)", peer, versionString));
return null;
}
if (Settings.getInstance().getAllowConnectionsWithOlderPeerVersions() == false) {
if (!Settings.getInstance().getAllowConnectionsWithOlderPeerVersions()) {
// Ensure the peer is running at least the minimum version allowed for connections
final String minPeerVersion = Settings.getInstance().getMinPeerVersion();
if (peer.isAtLeastVersion(minPeerVersion) == false) {
if (!peer.isAtLeastVersion(minPeerVersion)) {
LOGGER.debug(String.format("Ignoring peer %s because it is on an old version (%s)", peer, versionString));
return null;
}

View File

@@ -810,7 +810,7 @@ public class Network {
.filter(peer -> peer.hasReachedMaxConnectionAge())
.collect(Collectors.toList());
if (peersToDisconnect != null && peersToDisconnect.size() > 0) {
if (peersToDisconnect != null && !peersToDisconnect.isEmpty()) {
for (Peer peer : peersToDisconnect) {
LOGGER.debug("Forcing disconnection of peer {} because connection age ({} ms) " +
"has reached the maximum ({} ms)", peer, peer.getConnectionAge(), peer.getMaxConnectionAge());

View File

@@ -859,7 +859,7 @@ public class Peer {
}
}
if (logStats && this.receivedMessageStats.size() > 0) {
if (logStats && !this.receivedMessageStats.isEmpty()) {
StringBuilder statsBuilder = new StringBuilder(1024);
statsBuilder.append("peer ").append(this).append(" message stats:\n=received=");
appendMessageStats(statsBuilder, this.receivedMessageStats);

View File

@@ -205,6 +205,15 @@ public interface TransactionRepository {
*/
public List<String> getConfirmedRewardShareCreatorsExcludingSelfShares() throws DataException;
/**
* Returns list of transfer asset transaction creators.
* This uses confirmed transactions only.
*
* @return
* @throws DataException
*/
public List<String> getConfirmedTransferAssetCreators() throws DataException;
/**
* Returns list of transactions pending approval, with optional txGgroupId filtering.
* <p>

View File

@@ -1024,7 +1024,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
String tag5 = null;
if (tags != null) {
if (tags.size() > 0) tag1 = tags.get(0);
if (!tags.isEmpty()) tag1 = tags.get(0);
if (tags.size() > 1) tag2 = tags.get(1);
if (tags.size() > 2) tag3 = tags.get(2);
if (tags.size() > 3) tag4 = tags.get(3);

View File

@@ -69,10 +69,10 @@ public class HSQLDBChatRepository implements ChatRepository {
bindParams.add(chatReferenceBytes);
}
if (hasChatReference != null && hasChatReference == true) {
if (hasChatReference != null && hasChatReference) {
whereClauses.add("chat_reference IS NOT NULL");
}
else if (hasChatReference != null && hasChatReference == false) {
else if (hasChatReference != null && !hasChatReference) {
whereClauses.add("chat_reference IS NULL");
}

View File

@@ -137,7 +137,7 @@ public class HSQLDBImportExport {
String existingTradePrivateKey = (String) existingTradeBotDataItem.get("tradePrivateKey");
// Check if we already have an entry for this trade
boolean found = allTradeBotData.stream().anyMatch(tradeBotData -> Base58.encode(tradeBotData.getTradePrivateKey()).equals(existingTradePrivateKey));
if (found == false)
if (!found)
// Add the data from the backup file to our "allTradeBotDataJson" array as it's not currently in the db
allTradeBotDataJson.put(existingTradeBotDataItem);
}

View File

@@ -1043,6 +1043,33 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
}
}
public List<String> getConfirmedTransferAssetCreators() throws DataException {
List<String> transferAssetCreators = new ArrayList<>();
String sql = "SELECT account "
+ "FROM TransferAssetTransactions "
+ "JOIN Accounts ON Accounts.public_key = TransferAssetTransactions.sender "
+ "JOIN Transactions ON Transactions.signature = TransferAssetTransactions.signature "
+ "WHERE block_height IS NOT NULL AND TransferAssetTransactions.recipient != Accounts.account "
+ "GROUP BY account "
+ "ORDER BY account";
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
if (resultSet == null)
return transferAssetCreators;
do {
String address = resultSet.getString(1);
transferAssetCreators.add(address);
} while (resultSet.next());
return transferAssetCreators;
} catch (SQLException e) {
throw new DataException("Unable to fetch transfer asset from repository", e);
}
}
@Override
public List<TransactionData> getApprovalPendingTransactions(Integer txGroupId, Integer limit, Integer offset, Boolean reverse) throws DataException {
StringBuilder sql = new StringBuilder(512);

View File

@@ -211,7 +211,7 @@ public class Settings {
public long recoveryModeTimeout = 9999999999999L;
/** Minimum peer version number required in order to sync with them */
private String minPeerVersion = "4.4.2";
private String minPeerVersion = "4.5.1";
/** Whether to allow connections with peers below minPeerVersion
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
* If false, sync will be blocked both ways, and they will not appear in the peers list */

View File

@@ -70,6 +70,10 @@ public class CancelGroupBanTransaction extends Transaction {
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN;
// Can't unban if not group's current owner
if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
Account member = getMember();
// Check ban actually exists

View File

@@ -168,7 +168,7 @@ public class ChatTransaction extends Transaction {
// Check for blocked author by registered name
List<NameData> names = this.repository.getNameRepository().getNamesByOwner(this.chatTransactionData.getSender());
if (names != null && names.size() > 0) {
if (names != null && !names.isEmpty()) {
for (NameData nameData : names) {
if (nameData != null && nameData.getName() != null) {
if (ListUtils.isNameBlocked(nameData.getName())) {

View File

@@ -70,6 +70,10 @@ public class GroupBanTransaction extends Transaction {
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN;
// Can't ban if not group's current owner
if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
Account offender = getOffender();
// Can't ban group owner

View File

@@ -82,6 +82,10 @@ public class GroupKickTransaction extends Transaction {
if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress()))
return ValidationResult.INVALID_GROUP_OWNER;
// Can't kick if not group's current owner
if (!admin.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT) < this.groupKickTransactionData.getFee())
return ValidationResult.NO_BALANCE;

View File

@@ -54,6 +54,10 @@ public class RegisterNameTransaction extends Transaction {
Account registrant = getRegistrant();
String name = this.registerNameTransactionData.getName();
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
final int start = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height() - 1180;
final int end = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height();
// Check name size bounds
int nameLength = Utf8.encodedLength(name);
if (nameLength < Name.MIN_NAME_SIZE || nameLength > Name.MAX_NAME_SIZE)
@@ -76,6 +80,10 @@ public class RegisterNameTransaction extends Transaction {
if (registrant.getConfirmedBalance(Asset.QORT) < this.registerNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
// Check if we are on algo runs
if (blockchainHeight >= start && blockchainHeight <= end)
return ValidationResult.TEMPORARY_DISABLED;
return ValidationResult.OK;
}

View File

@@ -43,7 +43,7 @@ public class RewardShareTransaction extends Transaction {
}
private RewardShareData getExistingRewardShare() throws DataException {
if (this.haveCheckedForExistingRewardShare == false) {
if (!this.haveCheckedForExistingRewardShare) {
this.haveCheckedForExistingRewardShare = true;
// Look up any existing reward-share (using transaction's reward-share public key)
@@ -183,18 +183,28 @@ public class RewardShareTransaction extends Transaction {
@Override
public boolean isConfirmableAtHeight(int height) {
final int min = BlockChain.getInstance().getPenaltyFixHeight() - 50;
final int max = BlockChain.getInstance().getPenaltyFixHeight() + 50;
final int startV2 = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height() - 15;
final int startV3 = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height() - 15;
final int endV2 = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height() + 10;
final int endV3 = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height() + 10;
if (height >= BlockChain.getInstance().getUnconfirmableRewardSharesHeight()) {
// Not confirmable in online accounts or distribution blocks
if (Block.isOnlineAccountsBlock(height) || Block.isBatchRewardDistributionBlock(height)) {
return false;
}
}
// Not confirmable on penalty fix
if (height > min && height < max) {
if (height >= startV2 && height <= endV2) {
// Not confirmable on algo V2 run
return false;
}
if (height >= startV3 && height <= endV3) {
// Not confirmable on algo V3 run
return false;
}
return true;
}

View File

@@ -173,6 +173,7 @@ public abstract class Transaction {
INVALID_OPTION_LENGTH(20),
DUPLICATE_OPTION(21),
POLL_ALREADY_EXISTS(22),
POLL_ALREADY_HAS_VOTES(23),
POLL_DOES_NOT_EXIST(24),
POLL_OPTION_DOES_NOT_EXIST(25),
ALREADY_VOTED_FOR_THAT_OPTION(26),
@@ -247,6 +248,7 @@ public abstract class Transaction {
GROUP_APPROVAL_REQUIRED(98),
ACCOUNT_NOT_TRANSFERABLE(99),
TRANSFER_PRIVS_DISABLED(100),
TEMPORARY_DISABLED(101),
INVALID_BUT_OK(999),
NOT_YET_RELEASED(1000),
NOT_SUPPORTED(1001);

View File

@@ -1,6 +1,7 @@
package org.qortal.transaction;
import org.qortal.account.Account;
import org.qortal.block.BlockChain;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.TransferAssetTransactionData;
@@ -51,6 +52,14 @@ public class TransferAssetTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
final int start = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height();
final int end = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height();
// Check if we are on algo runs
if (blockchainHeight >= start && blockchainHeight <= end)
return ValidationResult.ASSET_NOT_SPENDABLE;
// Wrap asset transfer as a payment and delegate final payment checks to Payment class
return new Payment(this.repository).isValid(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee());
}

View File

@@ -83,6 +83,10 @@ public class UpdateGroupTransaction extends Transaction {
Account owner = getOwner();
// Check creator is group's current owner
if (!owner.getAddress().equals(groupData.getOwner()))
return ValidationResult.INVALID_GROUP_OWNER;
// Check creator has enough funds
if (owner.getConfirmedBalance(Asset.QORT) < this.updateGroupTransactionData.getFee())
return ValidationResult.NO_BALANCE;

View File

@@ -398,7 +398,7 @@ public class ArbitraryTransactionUtils {
public static ArbitraryResourceStatus getStatus(Service service, String name, String identifier, Boolean build, boolean updateCache) {
// If "build" has been specified, build the resource before returning its status
if (build != null && build == true) {
if (build != null && build) {
try {
ArbitraryDataReader reader = new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, service, identifier);
if (!reader.isBuilding()) {

View File

@@ -111,7 +111,7 @@ public class Base58 {
//
// Nothing to do if we have an empty string
//
if (string.length() == 0)
if (string.isEmpty())
return null;
//
// Convert the input string to a byte sequence

View File

@@ -59,7 +59,7 @@ public class BlockArchiveUtils {
if (firstBlock == null || firstBlock.getBlockData().getHeight() != startHeight) {
throw new IllegalStateException("Non matching first block when importing from archive");
}
if (blockInfoList.size() > 0) {
if (!blockInfoList.isEmpty()) {
BlockTransformation lastBlock = blockInfoList.get(blockInfoList.size() - 1);
if (lastBlock == null || lastBlock.getBlockData().getHeight() != endHeight) {
throw new IllegalStateException("Non matching last block when importing from archive");

View File

@@ -141,7 +141,7 @@ public abstract class Unicode {
while ((line = bufferedReader.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || line.length() == 0)
if (line.startsWith("#") || line.isEmpty())
continue;
String[] charCodes = line.split(",");

View File

@@ -30,12 +30,13 @@
"onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 1659801600000,
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
"selfSponsorshipAlgoV2SnapshotTimestamp": 1706745600000,
"selfSponsorshipAlgoV2SnapshotTimestamp": 1708360200000,
"selfSponsorshipAlgoV3SnapshotTimestamp": 1708432200000,
"referenceTimestampBlock": 1670684455220,
"mempowTransactionUpdatesTimestamp": 1693558800000,
"blockRewardBatchStartHeight": 1508000,
"blockRewardBatchSize": 1000,
"blockRewardBatchAccountsBlockCount": 25,
"penaltyFixHash": "DG23E4Y63wC8FquG9FKAnfKwatvdJV3Z3iYnozMHn2A3",
"rewardsByHeight": [
{ "height": 1, "reward": 5.00 },
{ "height": 259201, "reward": 4.75 },
@@ -95,15 +96,14 @@
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
"onlineAccountMinterLevelValidationHeight": 1092000,
"selfSponsorshipAlgoV1Height": 1092400,
"selfSponsorshipAlgoV2Height": 9999999,
"selfSponsorshipAlgoV2Height": 1611200,
"selfSponsorshipAlgoV3Height": 1612200,
"feeValidationFixTimestamp": 1671918000000,
"chatReferenceTimestamp": 1674316800000,
"arbitraryOptionalFeeTimestamp": 1680278400000,
"unconfirmableRewardSharesHeight": 1575500,
"disableTransferPrivsTimestamp": 1706745000000,
"enableTransferPrivsTimestamp": 1709251200000,
"penaltyFixHeight": 1589200
"enableTransferPrivsTimestamp": 1709251200000
},
"checkpoints": [
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }

View File

@@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = Transaktion unbekannt
TX_GROUP_ID_MISMATCH = die Gruppen-ID der Transaktion stimmt nicht überein
TRANSFER_PRIVS_DISABLED = Übertragungsberechtigungen deaktiviert
TEMPORARY_DISABLED = Namensregistrierung vorübergehend deaktiviert

Some files were not shown because too many files have changed in this diff Show More