forked from Qortal/qortal
Compare commits
11 Commits
AT-sleep-u
...
lists
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd7adc997b | ||
|
|
9fdc901b7a | ||
|
|
b29ae67501 | ||
|
|
24f1fb566d | ||
|
|
a253294890 | ||
|
|
ec008b4a16 | ||
|
|
f09677d376 | ||
|
|
f669e3f6c4 | ||
|
|
711ad638b8 | ||
|
|
4956c3328c | ||
|
|
96a82381d1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
/db*
|
||||
/lists/
|
||||
/bin/
|
||||
/target/
|
||||
/qortal-backup/
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<ROW Property="Manufacturer" Value="Qortal"/>
|
||||
<ROW Property="MsiLogging" MultiBuildValue="DefaultBuild:vp"/>
|
||||
<ROW Property="NTP_GOOD" Value="false"/>
|
||||
<ROW Property="ProductCode" Value="1033:{E67F58ED-2236-43A9-8895-B9AB96C60EB9} 1049:{E612D7B8-EB9F-481C-81FF-7530E0801F95} 2052:{8EC6F665-1D21-4DB8-8C55-05C2550FF1B3} 2057:{6CE725B6-BBDD-459D-8016-6D1D2FC1F4EC} " Type="16"/>
|
||||
<ROW Property="ProductCode" Value="1033:{482E9390-1005-42FD-9F3F-E160E0E6FB19} 1049:{8FE09AC2-814B-42FC-9FCE-53D45A396529} 2052:{4FABD326-8345-438B-82B8-66C2DC3676E6} 2057:{7ECFFF43-DEC7-4B7F-BC88-260A10AF132A} " Type="16"/>
|
||||
<ROW Property="ProductLanguage" Value="2057"/>
|
||||
<ROW Property="ProductName" Value="Qortal"/>
|
||||
<ROW Property="ProductVersion" Value="1.5.6" Type="32"/>
|
||||
<ROW Property="ProductVersion" Value="1.6.0" Type="32"/>
|
||||
<ROW Property="RECONFIG_NTP" Value="true"/>
|
||||
<ROW Property="REMOVE_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
<ROW Property="REPAIR_BLOCKCHAIN" Value="YES" Type="4"/>
|
||||
@@ -212,7 +212,7 @@
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_71" ComponentId="{12A3ADBE-BB7A-496C-8869-410681E6232F}" Directory_="jdk.zipfs_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_71" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_8" ComponentId="{D53AD95E-CF96-4999-80FC-5812277A7456}" Directory_="java.naming_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_8" Type="0"/>
|
||||
<ROW Component="ADDITIONAL_LICENSE_INFO_9" ComponentId="{6B7EA9B0-5D17-47A8-B78C-FACE86D15E01}" Directory_="java.net.http_Dir" Attributes="0" KeyPath="ADDITIONAL_LICENSE_INFO_9" Type="0"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{9DA2985C-778C-4D85-A44E-5B00D935EED2}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_CustomARPName" ComponentId="{7941AD6C-7C09-48E7-93ED-0340E0F52EC0}" Directory_="APPDIR" Attributes="260" KeyPath="DisplayName" Options="1"/>
|
||||
<ROW Component="AI_ExePath" ComponentId="{3644948D-AE0B-41BB-9FAF-A79E70490A08}" Directory_="APPDIR" Attributes="260" KeyPath="AI_ExePath"/>
|
||||
<ROW Component="APPDIR" ComponentId="{680DFDDE-3FB4-47A5-8FF5-934F576C6F91}" Directory_="APPDIR" Attributes="0"/>
|
||||
<ROW Component="AccessBridgeCallbacks.h" ComponentId="{288055D1-1062-47A3-AA44-5601B4E38AED}" Directory_="bridge_Dir" Attributes="0" KeyPath="AccessBridgeCallbacks.h" Type="0"/>
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>1.5.6</version>
|
||||
<version>1.6.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
||||
18
src/main/java/org/qortal/api/model/AddressListRequest.java
Normal file
18
src/main/java/org/qortal/api/model/AddressListRequest.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.util.List;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AddressListRequest {
|
||||
|
||||
@Schema(description = "A list of addresses")
|
||||
public List<String> addresses;
|
||||
|
||||
public AddressListRequest() {
|
||||
}
|
||||
|
||||
}
|
||||
271
src/main/java/org/qortal/api/resource/ListsResource.java
Normal file
271
src/main/java/org/qortal/api/resource/ListsResource.java
Normal file
@@ -0,0 +1,271 @@
|
||||
package org.qortal.api.resource;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import org.qortal.api.*;
|
||||
import org.qortal.api.model.AddressListRequest;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.list.ResourceListManager;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
|
||||
@Path("/lists")
|
||||
@Tag(name = "Lists")
|
||||
public class ListsResource {
|
||||
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
|
||||
@POST
|
||||
@Path("/blacklist/address/{address}")
|
||||
@Operation(
|
||||
summary = "Add a QORT address to the local blacklist",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "Returns true on success, or an exception on failure",
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
|
||||
public String addAddressToBlacklist(@PathParam("address") String address) {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||
// Not found?
|
||||
if (accountData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
|
||||
|
||||
// Valid address, so go ahead and blacklist it
|
||||
boolean success = ResourceListManager.getInstance().addAddressToBlacklist(address, true);
|
||||
|
||||
return success ? "true" : "false";
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/blacklist/addresses")
|
||||
@Operation(
|
||||
summary = "Add one or more QORT addresses to the local blacklist",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(
|
||||
implementation = AddressListRequest.class
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "Returns true if all addresses were processed, false if any couldn't be " +
|
||||
"processed, or an exception on failure. If false or an exception is returned, " +
|
||||
"the list will not be updated, and the request will need to be re-issued.",
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
|
||||
public String addAddressesToBlacklist(AddressListRequest addressListRequest) {
|
||||
if (addressListRequest == null || addressListRequest.addresses == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
int errorCount = 0;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
for (String address : addressListRequest.addresses) {
|
||||
|
||||
if (!Crypto.isValidAddress(address)) {
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||
// Not found?
|
||||
if (accountData == null) {
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Valid address, so go ahead and blacklist it
|
||||
boolean success = ResourceListManager.getInstance().addAddressToBlacklist(address, false);
|
||||
if (success) {
|
||||
successCount++;
|
||||
}
|
||||
else {
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
|
||||
if (successCount > 0 && errorCount == 0) {
|
||||
// All were successful, so save the blacklist
|
||||
ResourceListManager.getInstance().saveBlacklist();
|
||||
return "true";
|
||||
}
|
||||
else {
|
||||
// Something went wrong, so revert
|
||||
ResourceListManager.getInstance().revertBlacklist();
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@DELETE
|
||||
@Path("/blacklist/address/{address}")
|
||||
@Operation(
|
||||
summary = "Remove a QORT address from the local blacklist",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "Returns true on success, or an exception on failure",
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
|
||||
public String removeAddressFromBlacklist(@PathParam("address") String address) {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||
// Not found?
|
||||
if (accountData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
|
||||
|
||||
// Valid address, so go ahead and blacklist it
|
||||
boolean success = ResourceListManager.getInstance().removeAddressFromBlacklist(address, true);
|
||||
|
||||
return success ? "true" : "false";
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/blacklist/addresses")
|
||||
@Operation(
|
||||
summary = "Remove one or more QORT addresses from the local blacklist",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(
|
||||
implementation = AddressListRequest.class
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "Returns true if all addresses were processed, false if any couldn't be " +
|
||||
"processed, or an exception on failure. If false or an exception is returned, " +
|
||||
"the list will not be updated, and the request will need to be re-issued.",
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
|
||||
public String removeAddressesFromBlacklist(AddressListRequest addressListRequest) {
|
||||
if (addressListRequest == null || addressListRequest.addresses == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
int errorCount = 0;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
for (String address : addressListRequest.addresses) {
|
||||
|
||||
if (!Crypto.isValidAddress(address)) {
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||
// Not found?
|
||||
if (accountData == null) {
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Valid address, so go ahead and blacklist it
|
||||
// Don't save as we will do this at the end of the process
|
||||
boolean success = ResourceListManager.getInstance().removeAddressFromBlacklist(address, false);
|
||||
if (success) {
|
||||
successCount++;
|
||||
}
|
||||
else {
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
|
||||
if (successCount > 0 && errorCount == 0) {
|
||||
// All were successful, so save the blacklist
|
||||
ResourceListManager.getInstance().saveBlacklist();
|
||||
return "true";
|
||||
}
|
||||
else {
|
||||
// Something went wrong, so revert
|
||||
ResourceListManager.getInstance().revertBlacklist();
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/blacklist/address/{address}")
|
||||
@Operation(
|
||||
summary = "Check if an address is present in the local blacklist",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "Returns true or false if the list was queried, or an exception on failure",
|
||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
|
||||
public String checkAddressInBlacklist(@PathParam("address") String address) {
|
||||
if (!Crypto.isValidAddress(address))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||
// Not found?
|
||||
if (accountData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
|
||||
|
||||
// Valid address, so go ahead and blacklist it
|
||||
boolean blacklisted = ResourceListManager.getInstance().isAddressInBlacklist(address);
|
||||
|
||||
return blacklisted ? "true" : "false";
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -131,8 +131,10 @@ public class AT {
|
||||
|
||||
// Nothing happened?
|
||||
if (state.getSteps() == 0 && Arrays.equals(stateHash, latestAtStateData.getStateHash()))
|
||||
// this.atStateData will be null
|
||||
return Collections.emptyList();
|
||||
// We currently want to execute frozen ATs, to maintain backwards support.
|
||||
if (state.isFrozen() == false)
|
||||
// this.atStateData will be null
|
||||
return Collections.emptyList();
|
||||
|
||||
long atFees = api.calcFinalFees(state);
|
||||
Long sleepUntilMessageTimestamp = this.atData.getSleepUntilMessageTimestamp();
|
||||
|
||||
@@ -406,14 +406,24 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
protected SimpleTransaction convertToSimpleTransaction(BitcoinyTransaction t, Set<String> keySet) {
|
||||
long amount = 0;
|
||||
long total = 0L;
|
||||
long totalInputAmount = 0L;
|
||||
long totalOutputAmount = 0L;
|
||||
List<SimpleTransaction.Input> inputs = new ArrayList<>();
|
||||
List<SimpleTransaction.Output> outputs = new ArrayList<>();
|
||||
|
||||
for (BitcoinyTransaction.Input input : t.inputs) {
|
||||
try {
|
||||
BitcoinyTransaction t2 = getTransaction(input.outputTxHash);
|
||||
List<String> senders = t2.outputs.get(input.outputVout).addresses;
|
||||
long inputAmount = t2.outputs.get(input.outputVout).value;
|
||||
totalInputAmount += inputAmount;
|
||||
for (String sender : senders) {
|
||||
boolean addressInWallet = false;
|
||||
if (keySet.contains(sender)) {
|
||||
total += t2.outputs.get(input.outputVout).value;
|
||||
total += inputAmount;
|
||||
addressInWallet = true;
|
||||
}
|
||||
inputs.add(new SimpleTransaction.Input(sender, inputAmount, addressInWallet));
|
||||
}
|
||||
} catch (ForeignBlockchainException e) {
|
||||
LOGGER.trace("Failed to retrieve transaction information {}", input.outputTxHash);
|
||||
@@ -422,17 +432,22 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
if (t.outputs != null && !t.outputs.isEmpty()) {
|
||||
for (BitcoinyTransaction.Output output : t.outputs) {
|
||||
for (String address : output.addresses) {
|
||||
boolean addressInWallet = false;
|
||||
if (keySet.contains(address)) {
|
||||
if (total > 0L) {
|
||||
amount -= (total - output.value);
|
||||
} else {
|
||||
amount += output.value;
|
||||
}
|
||||
addressInWallet = true;
|
||||
}
|
||||
outputs.add(new SimpleTransaction.Output(address, output.value, addressInWallet));
|
||||
}
|
||||
totalOutputAmount += output.value;
|
||||
}
|
||||
}
|
||||
return new SimpleTransaction(t.txHash, t.timestamp, amount);
|
||||
long fee = totalInputAmount - totalOutputAmount;
|
||||
return new SimpleTransaction(t.txHash, t.timestamp, amount, fee, inputs, outputs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,20 +2,85 @@ package org.qortal.crosschain;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.util.List;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class SimpleTransaction {
|
||||
private String txHash;
|
||||
private Integer timestamp;
|
||||
private long totalAmount;
|
||||
private long feeAmount;
|
||||
private List<Input> inputs;
|
||||
private List<Output> outputs;
|
||||
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class Input {
|
||||
private String address;
|
||||
private long amount;
|
||||
private boolean addressInWallet;
|
||||
|
||||
public Input() {
|
||||
}
|
||||
|
||||
public Input(String address, long amount, boolean addressInWallet) {
|
||||
this.address = address;
|
||||
this.amount = amount;
|
||||
this.addressInWallet = addressInWallet;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public boolean getAddressInWallet() {
|
||||
return addressInWallet;
|
||||
}
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class Output {
|
||||
private String address;
|
||||
private long amount;
|
||||
private boolean addressInWallet;
|
||||
|
||||
public Output() {
|
||||
}
|
||||
|
||||
public Output(String address, long amount, boolean addressInWallet) {
|
||||
this.address = address;
|
||||
this.amount = amount;
|
||||
this.addressInWallet = addressInWallet;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public boolean getAddressInWallet() {
|
||||
return addressInWallet;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SimpleTransaction() {
|
||||
}
|
||||
|
||||
public SimpleTransaction(String txHash, Integer timestamp, long totalAmount) {
|
||||
public SimpleTransaction(String txHash, Integer timestamp, long totalAmount, long feeAmount, List<Input> inputs, List<Output> outputs) {
|
||||
this.txHash = txHash;
|
||||
this.timestamp = timestamp;
|
||||
this.totalAmount = totalAmount;
|
||||
this.feeAmount = feeAmount;
|
||||
this.inputs = inputs;
|
||||
this.outputs = outputs;
|
||||
}
|
||||
|
||||
public String getTxHash() {
|
||||
@@ -29,4 +94,16 @@ public class SimpleTransaction {
|
||||
public long getTotalAmount() {
|
||||
return totalAmount;
|
||||
}
|
||||
}
|
||||
|
||||
public long getFeeAmount() {
|
||||
return feeAmount;
|
||||
}
|
||||
|
||||
public List<Input> getInputs() {
|
||||
return this.inputs;
|
||||
}
|
||||
|
||||
public List<Output> getOutputs() {
|
||||
return this.outputs;
|
||||
}
|
||||
}
|
||||
|
||||
137
src/main/java/org/qortal/list/ResourceList.java
Normal file
137
src/main/java/org/qortal/list/ResourceList.java
Normal file
@@ -0,0 +1,137 @@
|
||||
package org.qortal.list;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.json.JSONArray;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ResourceList {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ResourceList.class);
|
||||
|
||||
private String category;
|
||||
private String resourceName;
|
||||
private List<String> list;
|
||||
|
||||
/**
|
||||
* ResourceList
|
||||
* Creates or updates a list for the purpose of tracking resources on the Qortal network
|
||||
* This can be used for local blocking, or even for curating and sharing content lists
|
||||
* Lists are backed off to JSON files (in the lists folder) to ease sharing between nodes and users
|
||||
*
|
||||
* @param category - for instance "blacklist", "whitelist", or "userlist"
|
||||
* @param resourceName - for instance "address", "poll", or "group"
|
||||
* @throws IOException
|
||||
*/
|
||||
public ResourceList(String category, String resourceName) throws IOException {
|
||||
this.category = category;
|
||||
this.resourceName = resourceName;
|
||||
this.load();
|
||||
}
|
||||
|
||||
|
||||
/* Filesystem */
|
||||
|
||||
private Path getFilePath() {
|
||||
String pathString = String.format("%s%s%s_%s.json", Settings.getInstance().getListsPath(),
|
||||
File.separator, this.resourceName, this.category);
|
||||
Path outputFilePath = Paths.get(pathString);
|
||||
try {
|
||||
Files.createDirectories(outputFilePath.getParent());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to create lists directory");
|
||||
}
|
||||
return outputFilePath;
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
if (this.resourceName == null) {
|
||||
throw new IllegalStateException("Can't save list with missing resource name");
|
||||
}
|
||||
if (this.category == null) {
|
||||
throw new IllegalStateException("Can't save list with missing category");
|
||||
}
|
||||
String jsonString = ResourceList.listToJSONString(this.list);
|
||||
|
||||
Path filePath = this.getFilePath();
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(filePath.toString()));
|
||||
writer.write(jsonString);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private boolean load() throws IOException {
|
||||
Path path = this.getFilePath();
|
||||
File resourceListFile = new File(path.toString());
|
||||
if (!resourceListFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
String jsonString = new String(Files.readAllBytes(path));
|
||||
this.list = ResourceList.listFromJSONString(jsonString);
|
||||
} catch (IOException e) {
|
||||
throw new IOException(String.format("Couldn't read contents from file %s", path.toString()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean revert() {
|
||||
try {
|
||||
return this.load();
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Unable to revert {} {}", this.resourceName, this.category);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* List management */
|
||||
|
||||
public void add(String resource) {
|
||||
if (!this.contains(resource)) {
|
||||
this.list.add(resource);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(String resource) {
|
||||
this.list.remove(resource);
|
||||
}
|
||||
|
||||
public boolean contains(String resource) {
|
||||
return this.list.contains(resource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Utils */
|
||||
|
||||
public static String listToJSONString(List<String> list) {
|
||||
JSONArray items = new JSONArray();
|
||||
for (String item : list) {
|
||||
items.put(item);
|
||||
}
|
||||
return items.toString(4);
|
||||
}
|
||||
|
||||
private static List<String> listFromJSONString(String jsonString) {
|
||||
JSONArray jsonList = new JSONArray(jsonString);
|
||||
List<String> resourceList = new ArrayList<>();
|
||||
for (int i=0; i<jsonList.length(); i++) {
|
||||
String item = (String)jsonList.get(i);
|
||||
resourceList.add(item);
|
||||
}
|
||||
return resourceList;
|
||||
}
|
||||
|
||||
}
|
||||
87
src/main/java/org/qortal/list/ResourceListManager.java
Normal file
87
src/main/java/org/qortal/list/ResourceListManager.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package org.qortal.list;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ResourceListManager {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ResourceListManager.class);
|
||||
|
||||
private static ResourceListManager instance;
|
||||
private ResourceList addressBlacklist;
|
||||
|
||||
public ResourceListManager() {
|
||||
try {
|
||||
this.addressBlacklist = new ResourceList("blacklist", "address");
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error while loading address blacklist. Blocking is currently unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized ResourceListManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ResourceListManager();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean addAddressToBlacklist(String address, boolean save) {
|
||||
try {
|
||||
this.addressBlacklist.add(address);
|
||||
if (save) {
|
||||
this.addressBlacklist.save();
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (IllegalStateException | IOException e) {
|
||||
LOGGER.info("Unable to add address to blacklist", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeAddressFromBlacklist(String address, boolean save) {
|
||||
try {
|
||||
this.addressBlacklist.remove(address);
|
||||
|
||||
if (save) {
|
||||
this.addressBlacklist.save();
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (IllegalStateException | IOException e) {
|
||||
LOGGER.info("Unable to remove address from blacklist", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAddressInBlacklist(String address) {
|
||||
if (this.addressBlacklist == null) {
|
||||
return false;
|
||||
}
|
||||
return this.addressBlacklist.contains(address);
|
||||
}
|
||||
|
||||
public void saveBlacklist() {
|
||||
if (this.addressBlacklist == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.addressBlacklist.save();
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Unable to save blacklist - reverting back to last saved state");
|
||||
this.addressBlacklist.revert();
|
||||
}
|
||||
}
|
||||
|
||||
public void revertBlacklist() {
|
||||
if (this.addressBlacklist == null) {
|
||||
return;
|
||||
}
|
||||
this.addressBlacklist.revert();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -161,6 +161,9 @@ public class Settings {
|
||||
"https://raw.githubusercontent.com@151.101.16.133/Qortal/qortal/%s/qortal.update"
|
||||
};
|
||||
|
||||
// Lists
|
||||
private String listsPath = "lists";
|
||||
|
||||
/** Array of NTP server hostnames. */
|
||||
private String[] ntpServers = new String[] {
|
||||
"pool.ntp.org",
|
||||
@@ -474,6 +477,10 @@ public class Settings {
|
||||
return this.autoUpdateRepos;
|
||||
}
|
||||
|
||||
public String getListsPath() {
|
||||
return this.listsPath;
|
||||
}
|
||||
|
||||
public String[] getNtpServers() {
|
||||
return this.ntpServers;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.data.transaction.ChatTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.list.ResourceListManager;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.GroupRepository;
|
||||
import org.qortal.repository.Repository;
|
||||
@@ -138,6 +139,12 @@ public class ChatTransaction extends Transaction {
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
|
||||
// Check for blacklisted author by address
|
||||
ResourceListManager listManager = ResourceListManager.getInstance();
|
||||
if (listManager.isAddressInBlacklist(this.chatTransactionData.getSender())) {
|
||||
return ValidationResult.ADDRESS_IN_BLACKLIST;
|
||||
}
|
||||
|
||||
// If we exist in the repository then we've been imported as unconfirmed,
|
||||
// but we don't want to make it into a block, so return fake non-OK result.
|
||||
if (this.repository.getTransactionRepository().exists(this.chatTransactionData.getSignature()))
|
||||
|
||||
@@ -247,6 +247,7 @@ public abstract class Transaction {
|
||||
INVALID_GROUP_BLOCK_DELAY(93),
|
||||
INCORRECT_NONCE(94),
|
||||
INVALID_TIMESTAMP_SIGNATURE(95),
|
||||
ADDRESS_IN_BLACKLIST(96),
|
||||
INVALID_BUT_OK(999),
|
||||
NOT_YET_RELEASED(1000);
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ public class DogecoinTests extends Common {
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
@@ -64,10 +64,11 @@ public class DogecoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
String recipient = "DP1iFao33xdEPa5vaArpj7sykfzKNeiJeX";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = dogecoin.buildSpend(xprv58, recipient, amount);
|
||||
@@ -81,7 +82,7 @@ public class DogecoinTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
Long balance = dogecoin.getWalletBalance(xprv58);
|
||||
|
||||
@@ -102,7 +103,7 @@ public class DogecoinTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String address = dogecoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user