diff --git a/src/main/java/org/qortal/api/model/AccountPenaltyStats.java b/src/main/java/org/qortal/api/model/AccountPenaltyStats.java new file mode 100644 index 00000000..68c3a6ed --- /dev/null +++ b/src/main/java/org/qortal/api/model/AccountPenaltyStats.java @@ -0,0 +1,56 @@ +package org.qortal.api.model; + +import org.qortal.block.SelfSponsorshipAlgoV1Block; +import org.qortal.data.account.AccountData; +import org.qortal.data.naming.NameData; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import java.util.ArrayList; +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class AccountPenaltyStats { + + public int totalPenalties; + public int maxPenalty; + public int minPenalty; + public String penaltyHash; + + protected AccountPenaltyStats() { + } + + public AccountPenaltyStats(int totalPenalties, int maxPenalty, int minPenalty, String penaltyHash) { + this.totalPenalties = totalPenalties; + this.maxPenalty = maxPenalty; + this.minPenalty = minPenalty; + this.penaltyHash = penaltyHash; + } + + public static AccountPenaltyStats fromAccounts(List accounts) { + int totalPenalties = 0; + Integer maxPenalty = null; + Integer minPenalty = null; + + List addresses = new ArrayList<>(); + for (AccountData accountData : accounts) { + int penalty = accountData.getBlocksMintedPenalty(); + addresses.add(accountData.getAddress()); + totalPenalties++; + + // Penalties are expressed as a negative number, so the min and the max are reversed here + if (maxPenalty == null || penalty < maxPenalty) maxPenalty = penalty; + if (minPenalty == null || penalty > minPenalty) minPenalty = penalty; + } + + String penaltyHash = SelfSponsorshipAlgoV1Block.getHash(addresses); + return new AccountPenaltyStats(totalPenalties, maxPenalty, minPenalty, penaltyHash); + } + + + @Override + public String toString() { + return String.format("totalPenalties: %d, maxPenalty: %d, minPenalty: %d, penaltyHash: %s", totalPenalties, maxPenalty, minPenalty, penaltyHash == null ? "null" : penaltyHash); + } +} diff --git a/src/main/java/org/qortal/api/resource/AddressesResource.java b/src/main/java/org/qortal/api/resource/AddressesResource.java index 468b90a8..79cb6e05 100644 --- a/src/main/java/org/qortal/api/resource/AddressesResource.java +++ b/src/main/java/org/qortal/api/resource/AddressesResource.java @@ -14,6 +14,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; @@ -27,6 +28,7 @@ import org.qortal.api.ApiErrors; import org.qortal.api.ApiException; import org.qortal.api.ApiExceptionFactory; import org.qortal.api.Security; +import org.qortal.api.model.AccountPenaltyStats; import org.qortal.api.model.ApiOnlineAccount; import org.qortal.api.model.RewardShareKeyRequest; import org.qortal.asset.Asset; @@ -34,6 +36,7 @@ import org.qortal.controller.LiteNode; import org.qortal.controller.OnlineAccountsManager; import org.qortal.crypto.Crypto; import org.qortal.data.account.AccountData; +import org.qortal.data.account.AccountPenaltyData; import org.qortal.data.account.RewardShareData; import org.qortal.data.network.OnlineAccountData; import org.qortal.data.network.OnlineAccountLevel; @@ -471,6 +474,54 @@ public class AddressesResource { } } + @GET + @Path("/penalties") + @Operation( + summary = "Get addresses with penalties", + description = "Returns a list of accounts with a blocksMintedPenalty", + responses = { + @ApiResponse( + description = "accounts with penalties", + content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = AccountPenaltyData.class))) + ) + } + ) + @ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) + public List getAccountsWithPenalties() { + try (final Repository repository = RepositoryManager.getRepository()) { + + List accounts = repository.getAccountRepository().getPenaltyAccounts(); + List penalties = accounts.stream().map(a -> new AccountPenaltyData(a.getAddress(), a.getBlocksMintedPenalty())).collect(Collectors.toList()); + + return penalties; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + + @GET + @Path("/penalties/stats") + @Operation( + summary = "Get stats about current penalties", + responses = { + @ApiResponse( + description = "aggregated stats about accounts with penalties", + content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = AccountPenaltyStats.class))) + ) + } + ) + @ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) + public AccountPenaltyStats getPenaltyStats() { + try (final Repository repository = RepositoryManager.getRepository()) { + + List accounts = repository.getAccountRepository().getPenaltyAccounts(); + return AccountPenaltyStats.fromAccounts(accounts); + + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @POST @Path("/publicize") @Operation( diff --git a/src/main/java/org/qortal/data/account/AccountPenaltyData.java b/src/main/java/org/qortal/data/account/AccountPenaltyData.java new file mode 100644 index 00000000..61947a5f --- /dev/null +++ b/src/main/java/org/qortal/data/account/AccountPenaltyData.java @@ -0,0 +1,52 @@ +package org.qortal.data.account; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; + +// All properties to be converted to JSON via JAXB +@XmlAccessorType(XmlAccessType.FIELD) +public class AccountPenaltyData { + + // Properties + private String address; + private int blocksMintedPenalty; + + // Constructors + + // necessary for JAXB + protected AccountPenaltyData() { + } + + public AccountPenaltyData(String address, int blocksMintedPenalty) { + this.address = address; + this.blocksMintedPenalty = blocksMintedPenalty; + } + + // Getters/Setters + + public String getAddress() { + return this.address; + } + + public int getBlocksMintedPenalty() { + return this.blocksMintedPenalty; + } + + public String toString() { + return String.format("%s has penalty %d", this.address, this.blocksMintedPenalty); + } + + @Override + public boolean equals(Object b) { + if (!(b instanceof AccountPenaltyData)) + return false; + + return this.getAddress().equals(((AccountPenaltyData) b).getAddress()); + } + + @Override + public int hashCode() { + return address.hashCode(); + } + +}