mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-21 18:37:50 +00:00
Compare commits
No commits in common. "master" and "v4.6.6" have entirely different histories.
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>4.7.1</version>
|
<version>4.6.6</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
@ -1,173 +0,0 @@
|
|||||||
package org.hsqldb.jdbc;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.hsqldb.jdbc.pool.JDBCPooledConnection;
|
|
||||||
import org.qortal.data.system.DbConnectionInfo;
|
|
||||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
|
||||||
|
|
||||||
import javax.sql.ConnectionEvent;
|
|
||||||
import javax.sql.PooledConnection;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class HSQLDBPoolMonitored
|
|
||||||
*
|
|
||||||
* This class uses the same logic as HSQLDBPool. The only difference is it monitors the state of every connection
|
|
||||||
* to the database. This is used for debugging purposes only.
|
|
||||||
*/
|
|
||||||
public class HSQLDBPoolMonitored extends HSQLDBPool {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepositoryFactory.class);
|
|
||||||
|
|
||||||
private static final String EMPTY = "Empty";
|
|
||||||
private static final String AVAILABLE = "Available";
|
|
||||||
private static final String ALLOCATED = "Allocated";
|
|
||||||
|
|
||||||
private ConcurrentHashMap<Integer, DbConnectionInfo> infoByIndex;
|
|
||||||
|
|
||||||
public HSQLDBPoolMonitored(int poolSize) {
|
|
||||||
super(poolSize);
|
|
||||||
|
|
||||||
this.infoByIndex = new ConcurrentHashMap<>(poolSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to retrieve a new connection using the properties that have already been
|
|
||||||
* set.
|
|
||||||
*
|
|
||||||
* @return a connection to the data source, or null if no spare connections in pool
|
|
||||||
* @exception SQLException if a database access error occurs
|
|
||||||
*/
|
|
||||||
public Connection tryConnection() throws SQLException {
|
|
||||||
for (int i = 0; i < states.length(); i++) {
|
|
||||||
if (states.compareAndSet(i, RefState.available, RefState.allocated)) {
|
|
||||||
JDBCPooledConnection pooledConnection = connections[i];
|
|
||||||
|
|
||||||
if (pooledConnection == null)
|
|
||||||
// Probably shutdown situation
|
|
||||||
return null;
|
|
||||||
|
|
||||||
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
|
|
||||||
|
|
||||||
return pooledConnection.getConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (states.compareAndSet(i, RefState.empty, RefState.allocated)) {
|
|
||||||
try {
|
|
||||||
JDBCPooledConnection pooledConnection = (JDBCPooledConnection) source.getPooledConnection();
|
|
||||||
|
|
||||||
if (pooledConnection == null)
|
|
||||||
// Probably shutdown situation
|
|
||||||
return null;
|
|
||||||
|
|
||||||
pooledConnection.addConnectionEventListener(this);
|
|
||||||
pooledConnection.addStatementEventListener(this);
|
|
||||||
connections[i] = pooledConnection;
|
|
||||||
|
|
||||||
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
|
|
||||||
|
|
||||||
return pooledConnection.getConnection();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
states.set(i, RefState.empty);
|
|
||||||
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), EMPTY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Connection getConnection() throws SQLException {
|
|
||||||
int var1 = 300;
|
|
||||||
if (this.source.loginTimeout != 0) {
|
|
||||||
var1 = this.source.loginTimeout * 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.closed) {
|
|
||||||
throw new SQLException("connection pool is closed");
|
|
||||||
} else {
|
|
||||||
for(int var2 = 0; var2 < var1; ++var2) {
|
|
||||||
for(int var3 = 0; var3 < this.states.length(); ++var3) {
|
|
||||||
if (this.states.compareAndSet(var3, 1, 2)) {
|
|
||||||
infoByIndex.put(var3, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
|
|
||||||
return this.connections[var3].getConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.states.compareAndSet(var3, 0, 2)) {
|
|
||||||
try {
|
|
||||||
JDBCPooledConnection var4 = (JDBCPooledConnection)this.source.getPooledConnection();
|
|
||||||
var4.addConnectionEventListener(this);
|
|
||||||
var4.addStatementEventListener(this);
|
|
||||||
this.connections[var3] = var4;
|
|
||||||
|
|
||||||
infoByIndex.put(var3, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), ALLOCATED));
|
|
||||||
|
|
||||||
return this.connections[var3].getConnection();
|
|
||||||
} catch (SQLException var6) {
|
|
||||||
this.states.set(var3, 0);
|
|
||||||
infoByIndex.put(var3, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), EMPTY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(100L);
|
|
||||||
} catch (InterruptedException var5) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw JDBCUtil.invalidArgument();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionClosed(ConnectionEvent event) {
|
|
||||||
PooledConnection connection = (PooledConnection) event.getSource();
|
|
||||||
|
|
||||||
for (int i = 0; i < connections.length; i++) {
|
|
||||||
if (connections[i] == connection) {
|
|
||||||
states.set(i, RefState.available);
|
|
||||||
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), AVAILABLE));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectionErrorOccurred(ConnectionEvent event) {
|
|
||||||
PooledConnection connection = (PooledConnection) event.getSource();
|
|
||||||
|
|
||||||
for (int i = 0; i < connections.length; i++) {
|
|
||||||
if (connections[i] == connection) {
|
|
||||||
states.set(i, RefState.allocated);
|
|
||||||
connections[i] = null;
|
|
||||||
states.set(i, RefState.empty);
|
|
||||||
infoByIndex.put(i, new DbConnectionInfo(System.currentTimeMillis(), Thread.currentThread().getName(), EMPTY));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DbConnectionInfo> getDbConnectionsStates() {
|
|
||||||
|
|
||||||
return infoByIndex.values().stream()
|
|
||||||
.sorted(Comparator.comparingLong(DbConnectionInfo::getUpdated))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private int findConnectionIndex(ConnectionEvent connectionEvent) {
|
|
||||||
PooledConnection pooledConnection = (PooledConnection) connectionEvent.getSource();
|
|
||||||
|
|
||||||
for(int i = 0; i < this.connections.length; ++i) {
|
|
||||||
if (this.connections[i] == pooledConnection) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,7 +14,6 @@ import org.qortal.repository.NameRepository;
|
|||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
import org.qortal.utils.Groups;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
@ -217,18 +216,9 @@ public class Account {
|
|||||||
String myAddress = accountData.getAddress();
|
String myAddress = accountData.getAddress();
|
||||||
|
|
||||||
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
int levelToMint = BlockChain.getInstance().getMinAccountLevelToMint();
|
||||||
int levelToMint;
|
|
||||||
|
|
||||||
if( blockchainHeight >= BlockChain.getInstance().getIgnoreLevelForRewardShareHeight() ) {
|
|
||||||
levelToMint = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
levelToMint = BlockChain.getInstance().getMinAccountLevelToMint();
|
|
||||||
}
|
|
||||||
|
|
||||||
int level = accountData.getLevel();
|
int level = accountData.getLevel();
|
||||||
List<Integer> groupIdsToMint = Groups.getGroupIdsToMint( BlockChain.getInstance(), blockchainHeight );
|
int groupIdToMint = BlockChain.getInstance().getMintingGroupId();
|
||||||
int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight();
|
int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight();
|
||||||
int groupCheckHeight = BlockChain.getInstance().getGroupMemberCheckHeight();
|
int groupCheckHeight = BlockChain.getInstance().getGroupMemberCheckHeight();
|
||||||
int removeNameCheckHeight = BlockChain.getInstance().getRemoveOnlyMintWithNameHeight();
|
int removeNameCheckHeight = BlockChain.getInstance().getRemoveOnlyMintWithNameHeight();
|
||||||
@ -262,9 +252,9 @@ public class Account {
|
|||||||
if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight) {
|
if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight) {
|
||||||
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
||||||
if (Account.isFounder(accountData.getFlags())) {
|
if (Account.isFounder(accountData.getFlags())) {
|
||||||
return accountData.getBlocksMintedPenalty() == 0 && !myName.isEmpty() && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress));
|
return accountData.getBlocksMintedPenalty() == 0 && !myName.isEmpty() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||||
} else {
|
} else {
|
||||||
return level >= levelToMint && !myName.isEmpty() && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress));
|
return level >= levelToMint && !myName.isEmpty() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,9 +263,9 @@ public class Account {
|
|||||||
// Account's address is a member of the minter group
|
// Account's address is a member of the minter group
|
||||||
if (blockchainHeight >= removeNameCheckHeight) {
|
if (blockchainHeight >= removeNameCheckHeight) {
|
||||||
if (Account.isFounder(accountData.getFlags())) {
|
if (Account.isFounder(accountData.getFlags())) {
|
||||||
return accountData.getBlocksMintedPenalty() == 0 && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress));
|
return accountData.getBlocksMintedPenalty() == 0 && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||||
} else {
|
} else {
|
||||||
return level >= levelToMint && (isGroupValidated || Groups.memberExistsInAnyGroup(groupRepository, groupIdsToMint, myAddress));
|
return level >= levelToMint && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,9 +306,6 @@ public class Account {
|
|||||||
if (Account.isFounder(accountData.getFlags()) && accountData.getBlocksMintedPenalty() == 0)
|
if (Account.isFounder(accountData.getFlags()) && accountData.getBlocksMintedPenalty() == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getIgnoreLevelForRewardShareHeight() )
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,6 @@ public class ApiService {
|
|||||||
|
|
||||||
context.addServlet(AdminStatusWebSocket.class, "/websockets/admin/status");
|
context.addServlet(AdminStatusWebSocket.class, "/websockets/admin/status");
|
||||||
context.addServlet(BlocksWebSocket.class, "/websockets/blocks");
|
context.addServlet(BlocksWebSocket.class, "/websockets/blocks");
|
||||||
context.addServlet(DataMonitorSocket.class, "/websockets/datamonitor");
|
|
||||||
context.addServlet(ActiveChatsWebSocket.class, "/websockets/chat/active/*");
|
context.addServlet(ActiveChatsWebSocket.class, "/websockets/chat/active/*");
|
||||||
context.addServlet(ChatMessagesWebSocket.class, "/websockets/chat/messages");
|
context.addServlet(ChatMessagesWebSocket.class, "/websockets/chat/messages");
|
||||||
context.addServlet(TradeOffersWebSocket.class, "/websockets/crosschain/tradeoffers");
|
context.addServlet(TradeOffersWebSocket.class, "/websockets/crosschain/tradeoffers");
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
package org.qortal.api.model;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class CrossChainTradeLedgerEntry {
|
|
||||||
|
|
||||||
private String market;
|
|
||||||
|
|
||||||
private String currency;
|
|
||||||
|
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
|
||||||
private long quantity;
|
|
||||||
|
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
|
||||||
private long feeAmount;
|
|
||||||
|
|
||||||
private String feeCurrency;
|
|
||||||
|
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
|
||||||
private long totalPrice;
|
|
||||||
|
|
||||||
private long tradeTimestamp;
|
|
||||||
|
|
||||||
protected CrossChainTradeLedgerEntry() {
|
|
||||||
/* For JAXB */
|
|
||||||
}
|
|
||||||
|
|
||||||
public CrossChainTradeLedgerEntry(String market, String currency, long quantity, long feeAmount, String feeCurrency, long totalPrice, long tradeTimestamp) {
|
|
||||||
this.market = market;
|
|
||||||
this.currency = currency;
|
|
||||||
this.quantity = quantity;
|
|
||||||
this.feeAmount = feeAmount;
|
|
||||||
this.feeCurrency = feeCurrency;
|
|
||||||
this.totalPrice = totalPrice;
|
|
||||||
this.tradeTimestamp = tradeTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarket() {
|
|
||||||
return market;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrency() {
|
|
||||||
return currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getQuantity() {
|
|
||||||
return quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getFeeAmount() {
|
|
||||||
return feeAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFeeCurrency() {
|
|
||||||
return feeCurrency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTotalPrice() {
|
|
||||||
return totalPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTradeTimestamp() {
|
|
||||||
return tradeTimestamp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package org.qortal.api.model;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class DatasetStatus {
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private long count;
|
|
||||||
|
|
||||||
public DatasetStatus() {}
|
|
||||||
|
|
||||||
public DatasetStatus(String name, long count) {
|
|
||||||
this.name = name;
|
|
||||||
this.count = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCount() {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
DatasetStatus that = (DatasetStatus) o;
|
|
||||||
return count == that.count && Objects.equals(name, that.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(name, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "DatasetStatus{" +
|
|
||||||
"name='" + name + '\'' +
|
|
||||||
", count=" + count +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,13 +33,9 @@ import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
|
|||||||
import org.qortal.controller.arbitrary.ArbitraryMetadataManager;
|
import org.qortal.controller.arbitrary.ArbitraryMetadataManager;
|
||||||
import org.qortal.data.account.AccountData;
|
import org.qortal.data.account.AccountData;
|
||||||
import org.qortal.data.arbitrary.ArbitraryCategoryInfo;
|
import org.qortal.data.arbitrary.ArbitraryCategoryInfo;
|
||||||
import org.qortal.data.arbitrary.ArbitraryDataIndexDetail;
|
|
||||||
import org.qortal.data.arbitrary.ArbitraryDataIndexScoreKey;
|
|
||||||
import org.qortal.data.arbitrary.ArbitraryDataIndexScorecard;
|
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
|
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
|
||||||
import org.qortal.data.arbitrary.IndexCache;
|
|
||||||
import org.qortal.data.naming.NameData;
|
import org.qortal.data.naming.NameData;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
@ -73,11 +69,8 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Path("/arbitrary")
|
@Path("/arbitrary")
|
||||||
@Tag(name = "Arbitrary")
|
@Tag(name = "Arbitrary")
|
||||||
@ -179,7 +172,6 @@ public class ArbitraryResource {
|
|||||||
@Parameter(description = "Name (searches name field only)") @QueryParam("name") List<String> names,
|
@Parameter(description = "Name (searches name field only)") @QueryParam("name") List<String> names,
|
||||||
@Parameter(description = "Title (searches title metadata field only)") @QueryParam("title") String title,
|
@Parameter(description = "Title (searches title metadata field only)") @QueryParam("title") String title,
|
||||||
@Parameter(description = "Description (searches description metadata field only)") @QueryParam("description") String description,
|
@Parameter(description = "Description (searches description metadata field only)") @QueryParam("description") String description,
|
||||||
@Parameter(description = "Keyword (searches description metadata field by keywords)") @QueryParam("keywords") List<String> keywords,
|
|
||||||
@Parameter(description = "Prefix only (if true, only the beginning of fields are matched)") @QueryParam("prefix") Boolean prefixOnly,
|
@Parameter(description = "Prefix only (if true, only the beginning of fields are matched)") @QueryParam("prefix") Boolean prefixOnly,
|
||||||
@Parameter(description = "Exact match names only (if true, partial name matches are excluded)") @QueryParam("exactmatchnames") Boolean exactMatchNamesOnly,
|
@Parameter(description = "Exact match names only (if true, partial name matches are excluded)") @QueryParam("exactmatchnames") Boolean exactMatchNamesOnly,
|
||||||
@Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource,
|
@Parameter(description = "Default resources (without identifiers) only") @QueryParam("default") Boolean defaultResource,
|
||||||
@ -220,7 +212,7 @@ public class ArbitraryResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<ArbitraryResourceData> resources = repository.getArbitraryRepository()
|
List<ArbitraryResourceData> resources = repository.getArbitraryRepository()
|
||||||
.searchArbitraryResources(service, query, identifier, names, title, description, keywords, usePrefixOnly,
|
.searchArbitraryResources(service, query, identifier, names, title, description, usePrefixOnly,
|
||||||
exactMatchNames, defaultRes, mode, minLevel, followedOnly, excludeBlocked, includeMetadata, includeStatus,
|
exactMatchNames, defaultRes, mode, minLevel, followedOnly, excludeBlocked, includeMetadata, includeStatus,
|
||||||
before, after, limit, offset, reverse);
|
before, after, limit, offset, reverse);
|
||||||
|
|
||||||
@ -1193,90 +1185,6 @@ public class ArbitraryResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/indices")
|
|
||||||
@Operation(
|
|
||||||
summary = "Find matching arbitrary resource indices",
|
|
||||||
description = "",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "indices",
|
|
||||||
content = @Content(
|
|
||||||
array = @ArraySchema(
|
|
||||||
schema = @Schema(
|
|
||||||
implementation = ArbitraryDataIndexScorecard.class
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public List<ArbitraryDataIndexScorecard> searchIndices(@QueryParam("terms") String[] terms) {
|
|
||||||
|
|
||||||
List<ArbitraryDataIndexDetail> indices = new ArrayList<>();
|
|
||||||
|
|
||||||
// get index details for each term
|
|
||||||
for( String term : terms ) {
|
|
||||||
List<ArbitraryDataIndexDetail> details = IndexCache.getInstance().getIndicesByTerm().get(term);
|
|
||||||
|
|
||||||
if( details != null ) {
|
|
||||||
indices.addAll(details);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sum up the scores for each index with identical attributes
|
|
||||||
Map<ArbitraryDataIndexScoreKey, Double> scoreForKey
|
|
||||||
= indices.stream()
|
|
||||||
.collect(
|
|
||||||
Collectors.groupingBy(
|
|
||||||
index -> new ArbitraryDataIndexScoreKey(index.name, index.category, index.link),
|
|
||||||
Collectors.summingDouble(detail -> 1.0 / detail.rank)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// create scorecards for each index group and put them in descending order by score
|
|
||||||
List<ArbitraryDataIndexScorecard> scorecards
|
|
||||||
= scoreForKey.entrySet().stream().map(
|
|
||||||
entry
|
|
||||||
->
|
|
||||||
new ArbitraryDataIndexScorecard(
|
|
||||||
entry.getValue(),
|
|
||||||
entry.getKey().name,
|
|
||||||
entry.getKey().category,
|
|
||||||
entry.getKey().link)
|
|
||||||
)
|
|
||||||
.sorted(Comparator.comparingDouble(ArbitraryDataIndexScorecard::getScore).reversed())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return scorecards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/indices/{name}/{idPrefix}")
|
|
||||||
@Operation(
|
|
||||||
summary = "Find matching arbitrary resource indices for a registered name and identifier prefix",
|
|
||||||
description = "",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "indices",
|
|
||||||
content = @Content(
|
|
||||||
array = @ArraySchema(
|
|
||||||
schema = @Schema(
|
|
||||||
implementation = ArbitraryDataIndexDetail.class
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public List<ArbitraryDataIndexDetail> searchIndicesByName(@PathParam("name") String name, @PathParam("idPrefix") String idPrefix) {
|
|
||||||
|
|
||||||
return
|
|
||||||
IndexCache.getInstance().getIndicesByIssuer()
|
|
||||||
.getOrDefault(name, new ArrayList<>(0)).stream()
|
|
||||||
.filter( indexDetail -> indexDetail.indexIdentifer.startsWith(idPrefix))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shared methods
|
// Shared methods
|
||||||
|
|
||||||
|
@ -16,13 +16,9 @@ import org.qortal.api.model.AggregatedOrder;
|
|||||||
import org.qortal.api.model.TradeWithOrderInfo;
|
import org.qortal.api.model.TradeWithOrderInfo;
|
||||||
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.controller.hsqldb.HSQLDBBalanceRecorder;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
import org.qortal.data.account.AccountData;
|
import org.qortal.data.account.AccountData;
|
||||||
import org.qortal.data.account.AddressAmountData;
|
|
||||||
import org.qortal.data.account.BlockHeightRange;
|
|
||||||
import org.qortal.data.account.BlockHeightRangeAddressAmounts;
|
|
||||||
import org.qortal.data.asset.AssetData;
|
import org.qortal.data.asset.AssetData;
|
||||||
import org.qortal.data.asset.OrderData;
|
import org.qortal.data.asset.OrderData;
|
||||||
import org.qortal.data.asset.RecentTradeData;
|
import org.qortal.data.asset.RecentTradeData;
|
||||||
@ -37,7 +33,6 @@ import org.qortal.transaction.Transaction;
|
|||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
import org.qortal.transform.TransformationException;
|
import org.qortal.transform.TransformationException;
|
||||||
import org.qortal.transform.transaction.*;
|
import org.qortal.transform.transaction.*;
|
||||||
import org.qortal.utils.BalanceRecorderUtils;
|
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -47,7 +42,6 @@ import javax.ws.rs.core.MediaType;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Path("/assets")
|
@Path("/assets")
|
||||||
@ -185,122 +179,6 @@ public class AssetsResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/balancedynamicranges")
|
|
||||||
@Operation(
|
|
||||||
summary = "Get balance dynamic ranges listed.",
|
|
||||||
description = ".",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
content = @Content(
|
|
||||||
array = @ArraySchema(
|
|
||||||
schema = @Schema(
|
|
||||||
implementation = BlockHeightRange.class
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public List<BlockHeightRange> getBalanceDynamicRanges(
|
|
||||||
@Parameter(ref = "offset") @QueryParam("offset") Integer offset,
|
|
||||||
@Parameter(ref = "limit") @QueryParam("limit") Integer limit,
|
|
||||||
@Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) {
|
|
||||||
|
|
||||||
Optional<HSQLDBBalanceRecorder> recorder = HSQLDBBalanceRecorder.getInstance();
|
|
||||||
|
|
||||||
if( recorder.isPresent()) {
|
|
||||||
return recorder.get().getRanges(offset, limit, reverse);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/balancedynamicrange/{height}")
|
|
||||||
@Operation(
|
|
||||||
summary = "Get balance dynamic range for a given height.",
|
|
||||||
description = ".",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
content = @Content(
|
|
||||||
schema = @Schema(
|
|
||||||
implementation = BlockHeightRange.class
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({
|
|
||||||
ApiError.INVALID_CRITERIA, ApiError.INVALID_DATA
|
|
||||||
})
|
|
||||||
public BlockHeightRange getBalanceDynamicRange(@PathParam("height") int height) {
|
|
||||||
|
|
||||||
Optional<HSQLDBBalanceRecorder> recorder = HSQLDBBalanceRecorder.getInstance();
|
|
||||||
|
|
||||||
if( recorder.isPresent()) {
|
|
||||||
Optional<BlockHeightRange> range = recorder.get().getRange(height);
|
|
||||||
|
|
||||||
if( range.isPresent() ) {
|
|
||||||
return range.get();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/balancedynamicamounts/{begin}/{end}")
|
|
||||||
@Operation(
|
|
||||||
summary = "Get balance dynamic ranges address amounts listed.",
|
|
||||||
description = ".",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
content = @Content(
|
|
||||||
array = @ArraySchema(
|
|
||||||
schema = @Schema(
|
|
||||||
implementation = AddressAmountData.class
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({
|
|
||||||
ApiError.INVALID_CRITERIA, ApiError.INVALID_DATA
|
|
||||||
})
|
|
||||||
public List<AddressAmountData> getBalanceDynamicAddressAmounts(
|
|
||||||
@PathParam("begin") int begin,
|
|
||||||
@PathParam("end") int end,
|
|
||||||
@Parameter(ref = "offset") @QueryParam("offset") Integer offset,
|
|
||||||
@Parameter(ref = "limit") @QueryParam("limit") Integer limit) {
|
|
||||||
|
|
||||||
Optional<HSQLDBBalanceRecorder> recorder = HSQLDBBalanceRecorder.getInstance();
|
|
||||||
|
|
||||||
if( recorder.isPresent()) {
|
|
||||||
Optional<BlockHeightRangeAddressAmounts> addressAmounts = recorder.get().getAddressAmounts(new BlockHeightRange(begin, end, false));
|
|
||||||
|
|
||||||
if( addressAmounts.isPresent() ) {
|
|
||||||
return addressAmounts.get().getAmounts().stream()
|
|
||||||
.sorted(BalanceRecorderUtils.ADDRESS_AMOUNT_DATA_COMPARATOR.reversed())
|
|
||||||
.skip(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/openorders/{assetid}/{otherassetid}")
|
@Path("/openorders/{assetid}/{otherassetid}")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -19,8 +19,6 @@ import org.qortal.crypto.Crypto;
|
|||||||
import org.qortal.data.account.AccountData;
|
import org.qortal.data.account.AccountData;
|
||||||
import org.qortal.data.block.BlockData;
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.block.BlockSummaryData;
|
import org.qortal.data.block.BlockSummaryData;
|
||||||
import org.qortal.data.block.DecodedOnlineAccountData;
|
|
||||||
import org.qortal.data.network.OnlineAccountData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.repository.BlockArchiveReader;
|
import org.qortal.repository.BlockArchiveReader;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
@ -29,7 +27,6 @@ import org.qortal.repository.RepositoryManager;
|
|||||||
import org.qortal.transform.TransformationException;
|
import org.qortal.transform.TransformationException;
|
||||||
import org.qortal.transform.block.BlockTransformer;
|
import org.qortal.transform.block.BlockTransformer;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
import org.qortal.utils.Blocks;
|
|
||||||
import org.qortal.utils.Triple;
|
import org.qortal.utils.Triple;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -48,7 +45,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Path("/blocks")
|
@Path("/blocks")
|
||||||
@Tag(name = "Blocks")
|
@Tag(name = "Blocks")
|
||||||
@ -893,50 +889,4 @@ public class BlocksResource {
|
|||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/onlineaccounts/{height}")
|
|
||||||
@Operation(
|
|
||||||
summary = "Get online accounts for block",
|
|
||||||
description = "Returns the online accounts who submitted signatures for this block",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "online accounts",
|
|
||||||
content = @Content(
|
|
||||||
array = @ArraySchema(
|
|
||||||
schema = @Schema(
|
|
||||||
implementation = DecodedOnlineAccountData.class
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({
|
|
||||||
ApiError.BLOCK_UNKNOWN, ApiError.REPOSITORY_ISSUE
|
|
||||||
})
|
|
||||||
public Set<DecodedOnlineAccountData> getOnlineAccounts(@PathParam("height") int height) {
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
// get block from database
|
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
|
||||||
|
|
||||||
// if block data is not in the database, then try the archive
|
|
||||||
if (blockData == null) {
|
|
||||||
blockData = repository.getBlockArchiveRepository().fromHeight(height);
|
|
||||||
|
|
||||||
// if the block is not in the database or the archive, then the block is unknown
|
|
||||||
if( blockData == null ) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<DecodedOnlineAccountData> onlineAccounts = Blocks.getDecodedOnlineAccountsForBlock(repository, blockData);
|
|
||||||
|
|
||||||
return onlineAccounts;
|
|
||||||
} catch (DataException e) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -10,13 +10,11 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.glassfish.jersey.media.multipart.ContentDisposition;
|
|
||||||
import org.qortal.api.ApiError;
|
import org.qortal.api.ApiError;
|
||||||
import org.qortal.api.ApiErrors;
|
import org.qortal.api.ApiErrors;
|
||||||
import org.qortal.api.ApiExceptionFactory;
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
import org.qortal.api.Security;
|
import org.qortal.api.Security;
|
||||||
import org.qortal.api.model.CrossChainCancelRequest;
|
import org.qortal.api.model.CrossChainCancelRequest;
|
||||||
import org.qortal.api.model.CrossChainTradeLedgerEntry;
|
|
||||||
import org.qortal.api.model.CrossChainTradeSummary;
|
import org.qortal.api.model.CrossChainTradeSummary;
|
||||||
import org.qortal.controller.tradebot.TradeBot;
|
import org.qortal.controller.tradebot.TradeBot;
|
||||||
import org.qortal.crosschain.ACCT;
|
import org.qortal.crosschain.ACCT;
|
||||||
@ -46,20 +44,14 @@ import org.qortal.utils.Base58;
|
|||||||
import org.qortal.utils.ByteArray;
|
import org.qortal.utils.ByteArray;
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Path("/crosschain")
|
@Path("/crosschain")
|
||||||
@Tag(name = "Cross-Chain")
|
@Tag(name = "Cross-Chain")
|
||||||
public class CrossChainResource {
|
public class CrossChainResource {
|
||||||
@ -67,13 +59,6 @@ public class CrossChainResource {
|
|||||||
@Context
|
@Context
|
||||||
HttpServletRequest request;
|
HttpServletRequest request;
|
||||||
|
|
||||||
@Context
|
|
||||||
HttpServletResponse response;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
ServletContext context;
|
|
||||||
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/tradeoffers")
|
@Path("/tradeoffers")
|
||||||
@Operation(
|
@Operation(
|
||||||
@ -270,12 +255,6 @@ public class CrossChainResource {
|
|||||||
description = "Only return trades that completed on/after this timestamp (milliseconds since epoch)",
|
description = "Only return trades that completed on/after this timestamp (milliseconds since epoch)",
|
||||||
example = "1597310000000"
|
example = "1597310000000"
|
||||||
) @QueryParam("minimumTimestamp") Long minimumTimestamp,
|
) @QueryParam("minimumTimestamp") Long minimumTimestamp,
|
||||||
@Parameter(
|
|
||||||
description = "Optionally filter by buyer Qortal public key"
|
|
||||||
) @QueryParam("buyerPublicKey") String buyerPublicKey58,
|
|
||||||
@Parameter(
|
|
||||||
description = "Optionally filter by seller Qortal public key"
|
|
||||||
) @QueryParam("sellerPublicKey") String sellerPublicKey58,
|
|
||||||
@Parameter( ref = "limit") @QueryParam("limit") Integer limit,
|
@Parameter( ref = "limit") @QueryParam("limit") Integer limit,
|
||||||
@Parameter( ref = "offset" ) @QueryParam("offset") Integer offset,
|
@Parameter( ref = "offset" ) @QueryParam("offset") Integer offset,
|
||||||
@Parameter( ref = "reverse" ) @QueryParam("reverse") Boolean reverse) {
|
@Parameter( ref = "reverse" ) @QueryParam("reverse") Boolean reverse) {
|
||||||
@ -287,10 +266,6 @@ public class CrossChainResource {
|
|||||||
if (minimumTimestamp != null && minimumTimestamp <= 0)
|
if (minimumTimestamp != null && minimumTimestamp <= 0)
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
// Decode public keys
|
|
||||||
byte[] buyerPublicKey = decodePublicKey(buyerPublicKey58);
|
|
||||||
byte[] sellerPublicKey = decodePublicKey(sellerPublicKey58);
|
|
||||||
|
|
||||||
final Boolean isFinished = Boolean.TRUE;
|
final Boolean isFinished = Boolean.TRUE;
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -321,7 +296,7 @@ public class CrossChainResource {
|
|||||||
byte[] codeHash = acctInfo.getKey().value;
|
byte[] codeHash = acctInfo.getKey().value;
|
||||||
ACCT acct = acctInfo.getValue().get();
|
ACCT acct = acctInfo.getValue().get();
|
||||||
|
|
||||||
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(codeHash, buyerPublicKey, sellerPublicKey,
|
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(codeHash,
|
||||||
isFinished, acct.getModeByteOffset(), (long) AcctMode.REDEEMED.value, minimumFinalHeight,
|
isFinished, acct.getModeByteOffset(), (long) AcctMode.REDEEMED.value, minimumFinalHeight,
|
||||||
limit, offset, reverse);
|
limit, offset, reverse);
|
||||||
|
|
||||||
@ -360,120 +335,6 @@ public class CrossChainResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode Public Key
|
|
||||||
*
|
|
||||||
* @param publicKey58 the public key in a string
|
|
||||||
*
|
|
||||||
* @return the public key in bytes
|
|
||||||
*/
|
|
||||||
private byte[] decodePublicKey(String publicKey58) {
|
|
||||||
|
|
||||||
if( publicKey58 == null ) return null;
|
|
||||||
if( publicKey58.isEmpty() ) return new byte[0];
|
|
||||||
|
|
||||||
byte[] publicKey;
|
|
||||||
try {
|
|
||||||
publicKey = Base58.decode(publicKey58);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Correct size for public key?
|
|
||||||
if (publicKey.length != Transformer.PUBLIC_KEY_LENGTH)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
|
||||||
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/ledger/{publicKey}")
|
|
||||||
@Operation(
|
|
||||||
summary = "Accounting entries for all trades.",
|
|
||||||
description = "Returns accounting entries for all completed cross-chain trades",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
content = @Content(
|
|
||||||
schema = @Schema(
|
|
||||||
type = "string",
|
|
||||||
format = "byte"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
|
||||||
public HttpServletResponse getLedgerEntries(
|
|
||||||
@PathParam("publicKey") String publicKey58,
|
|
||||||
@Parameter(
|
|
||||||
description = "Only return trades that completed on/after this timestamp (milliseconds since epoch)",
|
|
||||||
example = "1597310000000"
|
|
||||||
) @QueryParam("minimumTimestamp") Long minimumTimestamp) {
|
|
||||||
|
|
||||||
byte[] publicKey = decodePublicKey(publicKey58);
|
|
||||||
|
|
||||||
// minimumTimestamp (if given) needs to be positive
|
|
||||||
if (minimumTimestamp != null && minimumTimestamp <= 0)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
Integer minimumFinalHeight = null;
|
|
||||||
|
|
||||||
if (minimumTimestamp != null) {
|
|
||||||
minimumFinalHeight = repository.getBlockRepository().getHeightFromTimestamp(minimumTimestamp);
|
|
||||||
// If not found in the block repository it will return either 0 or 1
|
|
||||||
if (minimumFinalHeight == 0 || minimumFinalHeight == 1) {
|
|
||||||
// Try the archive
|
|
||||||
minimumFinalHeight = repository.getBlockArchiveRepository().getHeightFromTimestamp(minimumTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minimumFinalHeight == 0)
|
|
||||||
// We don't have any blocks since minimumTimestamp, let alone trades, so nothing to return
|
|
||||||
return response;
|
|
||||||
|
|
||||||
// height returned from repository is for block BEFORE timestamp
|
|
||||||
// but we want trades AFTER timestamp so bump height accordingly
|
|
||||||
minimumFinalHeight++;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<CrossChainTradeLedgerEntry> crossChainTradeLedgerEntries = new ArrayList<>();
|
|
||||||
|
|
||||||
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getAcctMap();
|
|
||||||
|
|
||||||
// collect ledger entries for each ACCT
|
|
||||||
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
|
||||||
byte[] codeHash = acctInfo.getKey().value;
|
|
||||||
ACCT acct = acctInfo.getValue().get();
|
|
||||||
|
|
||||||
// collect buys and sells
|
|
||||||
CrossChainUtils.collectLedgerEntries(publicKey, repository, minimumFinalHeight, crossChainTradeLedgerEntries, codeHash, acct, true);
|
|
||||||
CrossChainUtils.collectLedgerEntries(publicKey, repository, minimumFinalHeight, crossChainTradeLedgerEntries, codeHash, acct, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
crossChainTradeLedgerEntries.sort((a, b) -> Longs.compare(a.getTradeTimestamp(), b.getTradeTimestamp()));
|
|
||||||
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
response.setContentType("text/csv");
|
|
||||||
response.setHeader(
|
|
||||||
HttpHeaders.CONTENT_DISPOSITION,
|
|
||||||
ContentDisposition
|
|
||||||
.type("attachment")
|
|
||||||
.fileName(CrossChainUtils.createLedgerFileName(Crypto.toAddress(publicKey)))
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
CrossChainUtils.writeToLedger( response.getWriter(), crossChainTradeLedgerEntries);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (DataException e) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/price/{blockchain}")
|
@Path("/price/{blockchain}")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -10,36 +10,21 @@ import org.bitcoinj.script.ScriptBuilder;
|
|||||||
|
|
||||||
import org.bouncycastle.util.Strings;
|
import org.bouncycastle.util.Strings;
|
||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
import org.qortal.api.model.CrossChainTradeLedgerEntry;
|
|
||||||
import org.qortal.api.model.crosschain.BitcoinyTBDRequest;
|
import org.qortal.api.model.crosschain.BitcoinyTBDRequest;
|
||||||
import org.qortal.crosschain.*;
|
import org.qortal.crosschain.*;
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.*;
|
import org.qortal.data.crosschain.*;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
import org.qortal.utils.BitTwiddling;
|
import org.qortal.utils.BitTwiddling;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
public class CrossChainUtils {
|
public class CrossChainUtils {
|
||||||
public static final String QORT_CURRENCY_CODE = "QORT";
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(CrossChainUtils.class);
|
private static final Logger LOGGER = LogManager.getLogger(CrossChainUtils.class);
|
||||||
public static final String CORE_API_CALL = "Core API Call";
|
public static final String CORE_API_CALL = "Core API Call";
|
||||||
public static final String QORTAL_EXCHANGE_LABEL = "Qortal";
|
|
||||||
|
|
||||||
public static ServerConfigurationInfo buildServerConfigurationInfo(Bitcoiny blockchain) {
|
public static ServerConfigurationInfo buildServerConfigurationInfo(Bitcoiny blockchain) {
|
||||||
|
|
||||||
@ -647,128 +632,4 @@ public class CrossChainUtils {
|
|||||||
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
byte[] lockTimeABytes = BitTwiddling.toBEByteArray((long) lockTimeA);
|
||||||
return Bytes.concat(partnerBitcoinPKH, hashOfSecretA, lockTimeABytes);
|
return Bytes.concat(partnerBitcoinPKH, hashOfSecretA, lockTimeABytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write To Ledger
|
|
||||||
*
|
|
||||||
* @param writer the writer to the ledger
|
|
||||||
* @param entries the entries to write to the ledger
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public static void writeToLedger(Writer writer, List<CrossChainTradeLedgerEntry> entries) throws IOException {
|
|
||||||
|
|
||||||
BufferedWriter bufferedWriter = new BufferedWriter(writer);
|
|
||||||
|
|
||||||
StringJoiner header = new StringJoiner(",");
|
|
||||||
header.add("Market");
|
|
||||||
header.add("Currency");
|
|
||||||
header.add("Quantity");
|
|
||||||
header.add("Commission Paid");
|
|
||||||
header.add("Commission Currency");
|
|
||||||
header.add("Total Price");
|
|
||||||
header.add("Date Time");
|
|
||||||
header.add("Exchange");
|
|
||||||
|
|
||||||
bufferedWriter.append(header.toString());
|
|
||||||
|
|
||||||
DateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd HH:mm");
|
|
||||||
dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
||||||
|
|
||||||
for( CrossChainTradeLedgerEntry entry : entries ) {
|
|
||||||
StringJoiner joiner = new StringJoiner(",");
|
|
||||||
|
|
||||||
joiner.add(entry.getMarket());
|
|
||||||
joiner.add(entry.getCurrency());
|
|
||||||
joiner.add(String.valueOf(Amounts.prettyAmount(entry.getQuantity())));
|
|
||||||
joiner.add(String.valueOf(Amounts.prettyAmount(entry.getFeeAmount())));
|
|
||||||
joiner.add(entry.getFeeCurrency());
|
|
||||||
joiner.add(String.valueOf(Amounts.prettyAmount(entry.getTotalPrice())));
|
|
||||||
joiner.add(dateFormatter.format(new Date(entry.getTradeTimestamp())));
|
|
||||||
joiner.add(QORTAL_EXCHANGE_LABEL);
|
|
||||||
|
|
||||||
bufferedWriter.newLine();
|
|
||||||
bufferedWriter.append(joiner.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferedWriter.newLine();
|
|
||||||
bufferedWriter.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create Ledger File Name
|
|
||||||
*
|
|
||||||
* Create a file name the includes timestamp and address.
|
|
||||||
*
|
|
||||||
* @param address the address
|
|
||||||
*
|
|
||||||
* @return the file name created
|
|
||||||
*/
|
|
||||||
public static String createLedgerFileName(String address) {
|
|
||||||
DateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
|
|
||||||
String fileName = "ledger-" + address + "-" + dateFormatter.format(new Date());
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect Ledger Entries
|
|
||||||
*
|
|
||||||
* @param publicKey the public key for the ledger entries, buy and sell
|
|
||||||
* @param repository the data repository
|
|
||||||
* @param minimumFinalHeight the minimum block height for entries to be collected
|
|
||||||
* @param entries the ledger entries to add to
|
|
||||||
* @param codeHash code hash for the entry blockchain
|
|
||||||
* @param acct the ACCT for the entry blockchain
|
|
||||||
* @param isBuy true collecting entries for a buy, otherwise false
|
|
||||||
*
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static void collectLedgerEntries(
|
|
||||||
byte[] publicKey,
|
|
||||||
Repository repository,
|
|
||||||
Integer minimumFinalHeight,
|
|
||||||
List<CrossChainTradeLedgerEntry> entries,
|
|
||||||
byte[] codeHash,
|
|
||||||
ACCT acct,
|
|
||||||
boolean isBuy) throws DataException {
|
|
||||||
|
|
||||||
// get all the final AT states for the code hash (foreign coin)
|
|
||||||
List<ATStateData> atStates
|
|
||||||
= repository.getATRepository().getMatchingFinalATStates(
|
|
||||||
codeHash,
|
|
||||||
isBuy ? publicKey : null,
|
|
||||||
!isBuy ? publicKey : null,
|
|
||||||
Boolean.TRUE, acct.getModeByteOffset(),
|
|
||||||
(long) AcctMode.REDEEMED.value,
|
|
||||||
minimumFinalHeight,
|
|
||||||
null, null, false
|
|
||||||
);
|
|
||||||
|
|
||||||
String foreignBlockchainCurrencyCode = acct.getBlockchain().getCurrencyCode();
|
|
||||||
|
|
||||||
// for each trade, build ledger entry, collect ledger entry
|
|
||||||
for (ATStateData atState : atStates) {
|
|
||||||
CrossChainTradeData crossChainTradeData = acct.populateTradeData(repository, atState);
|
|
||||||
|
|
||||||
// We also need block timestamp for use as trade timestamp
|
|
||||||
long localTimestamp = repository.getBlockRepository().getTimestampFromHeight(atState.getHeight());
|
|
||||||
|
|
||||||
if (localTimestamp == 0) {
|
|
||||||
// Try the archive
|
|
||||||
localTimestamp = repository.getBlockArchiveRepository().getTimestampFromHeight(atState.getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
CrossChainTradeLedgerEntry ledgerEntry
|
|
||||||
= new CrossChainTradeLedgerEntry(
|
|
||||||
isBuy ? QORT_CURRENCY_CODE : foreignBlockchainCurrencyCode,
|
|
||||||
isBuy ? foreignBlockchainCurrencyCode : QORT_CURRENCY_CODE,
|
|
||||||
isBuy ? crossChainTradeData.qortAmount : crossChainTradeData.expectedForeignAmount,
|
|
||||||
0,
|
|
||||||
foreignBlockchainCurrencyCode,
|
|
||||||
isBuy ? crossChainTradeData.expectedForeignAmount : crossChainTradeData.qortAmount,
|
|
||||||
localTimestamp);
|
|
||||||
|
|
||||||
entries.add(ledgerEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -32,7 +32,6 @@ import org.qortal.controller.Synchronizer.SynchronizationResult;
|
|||||||
import org.qortal.controller.repository.BlockArchiveRebuilder;
|
import org.qortal.controller.repository.BlockArchiveRebuilder;
|
||||||
import org.qortal.data.account.MintingAccountData;
|
import org.qortal.data.account.MintingAccountData;
|
||||||
import org.qortal.data.account.RewardShareData;
|
import org.qortal.data.account.RewardShareData;
|
||||||
import org.qortal.data.system.DbConnectionInfo;
|
|
||||||
import org.qortal.network.Network;
|
import org.qortal.network.Network;
|
||||||
import org.qortal.network.Peer;
|
import org.qortal.network.Peer;
|
||||||
import org.qortal.network.PeerAddress;
|
import org.qortal.network.PeerAddress;
|
||||||
@ -41,7 +40,6 @@ import org.qortal.repository.DataException;
|
|||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.data.system.SystemInfo;
|
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
@ -54,7 +52,6 @@ import java.net.InetSocketAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
@ -1067,50 +1064,4 @@ public class AdminResource {
|
|||||||
return "true";
|
return "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/systeminfo")
|
|
||||||
@Operation(
|
|
||||||
summary = "System Information",
|
|
||||||
description = "System memory usage and available processors.",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "memory usage and available processors",
|
|
||||||
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = SystemInfo.class))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
|
||||||
public SystemInfo getSystemInformation() {
|
|
||||||
|
|
||||||
SystemInfo info
|
|
||||||
= new SystemInfo(
|
|
||||||
Runtime.getRuntime().freeMemory(),
|
|
||||||
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(),
|
|
||||||
Runtime.getRuntime().totalMemory(),
|
|
||||||
Runtime.getRuntime().maxMemory(),
|
|
||||||
Runtime.getRuntime().availableProcessors());
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/dbstates")
|
|
||||||
@Operation(
|
|
||||||
summary = "Get DB States",
|
|
||||||
description = "Get DB States",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = DbConnectionInfo.class)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public List<DbConnectionInfo> getDbConnectionsStates() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
return Controller.REPOSITORY_FACTORY.getDbConnectionsStates();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,102 +0,0 @@
|
|||||||
package org.qortal.api.websocket;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketException;
|
|
||||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
|
||||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
|
||||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
|
||||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
|
||||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
|
||||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
|
||||||
import org.qortal.api.ApiError;
|
|
||||||
import org.qortal.controller.Controller;
|
|
||||||
import org.qortal.data.arbitrary.DataMonitorInfo;
|
|
||||||
import org.qortal.event.DataMonitorEvent;
|
|
||||||
import org.qortal.event.Event;
|
|
||||||
import org.qortal.event.EventBus;
|
|
||||||
import org.qortal.event.Listener;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.repository.Repository;
|
|
||||||
import org.qortal.repository.RepositoryManager;
|
|
||||||
import org.qortal.utils.Base58;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@WebSocket
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class DataMonitorSocket extends ApiWebSocket implements Listener {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(DataMonitorSocket.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configure(WebSocketServletFactory factory) {
|
|
||||||
LOGGER.info("configure");
|
|
||||||
|
|
||||||
factory.register(DataMonitorSocket.class);
|
|
||||||
|
|
||||||
EventBus.INSTANCE.addListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void listen(Event event) {
|
|
||||||
if (!(event instanceof DataMonitorEvent))
|
|
||||||
return;
|
|
||||||
|
|
||||||
DataMonitorEvent dataMonitorEvent = (DataMonitorEvent) event;
|
|
||||||
|
|
||||||
for (Session session : getSessions())
|
|
||||||
sendDataEventSummary(session, buildInfo(dataMonitorEvent));
|
|
||||||
}
|
|
||||||
|
|
||||||
private DataMonitorInfo buildInfo(DataMonitorEvent dataMonitorEvent) {
|
|
||||||
|
|
||||||
return new DataMonitorInfo(
|
|
||||||
dataMonitorEvent.getTimestamp(),
|
|
||||||
dataMonitorEvent.getIdentifier(),
|
|
||||||
dataMonitorEvent.getName(),
|
|
||||||
dataMonitorEvent.getService(),
|
|
||||||
dataMonitorEvent.getDescription(),
|
|
||||||
dataMonitorEvent.getTransactionTimestamp(),
|
|
||||||
dataMonitorEvent.getLatestPutTimestamp()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnWebSocketConnect
|
|
||||||
@Override
|
|
||||||
public void onWebSocketConnect(Session session) {
|
|
||||||
super.onWebSocketConnect(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnWebSocketClose
|
|
||||||
@Override
|
|
||||||
public void onWebSocketClose(Session session, int statusCode, String reason) {
|
|
||||||
super.onWebSocketClose(session, statusCode, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnWebSocketError
|
|
||||||
public void onWebSocketError(Session session, Throwable throwable) {
|
|
||||||
/* We ignore errors for now, but method here to silence log spam */
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnWebSocketMessage
|
|
||||||
public void onWebSocketMessage(Session session, String message) {
|
|
||||||
LOGGER.info("onWebSocketMessage: message = " + message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendDataEventSummary(Session session, DataMonitorInfo dataMonitorInfo) {
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
|
|
||||||
try {
|
|
||||||
marshall(stringWriter, dataMonitorInfo);
|
|
||||||
|
|
||||||
session.getRemote().sendStringByFuture(stringWriter.toString());
|
|
||||||
} catch (IOException | WebSocketException e) {
|
|
||||||
// No output this time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -98,7 +98,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
byte[] codeHash = acctInfo.getKey().value;
|
byte[] codeHash = acctInfo.getKey().value;
|
||||||
ACCT acct = acctInfo.getValue().get();
|
ACCT acct = acctInfo.getValue().get();
|
||||||
|
|
||||||
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(codeHash, null, null,
|
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(codeHash,
|
||||||
isFinished, dataByteOffset, expectedValue, minimumFinalHeight,
|
isFinished, dataByteOffset, expectedValue, minimumFinalHeight,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
@ -259,7 +259,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
ACCT acct = acctInfo.getValue().get();
|
ACCT acct = acctInfo.getValue().get();
|
||||||
|
|
||||||
Integer dataByteOffset = acct.getModeByteOffset();
|
Integer dataByteOffset = acct.getModeByteOffset();
|
||||||
List<ATStateData> initialAtStates = repository.getATRepository().getMatchingFinalATStates(codeHash, null, null,
|
List<ATStateData> initialAtStates = repository.getATRepository().getMatchingFinalATStates(codeHash,
|
||||||
isFinished, dataByteOffset, expectedValue, minimumFinalHeight,
|
isFinished, dataByteOffset, expectedValue, minimumFinalHeight,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
@ -298,7 +298,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
|||||||
byte[] codeHash = acctInfo.getKey().value;
|
byte[] codeHash = acctInfo.getKey().value;
|
||||||
ACCT acct = acctInfo.getValue().get();
|
ACCT acct = acctInfo.getValue().get();
|
||||||
|
|
||||||
List<ATStateData> historicAtStates = repository.getATRepository().getMatchingFinalATStates(codeHash, null, null,
|
List<ATStateData> historicAtStates = repository.getATRepository().getMatchingFinalATStates(codeHash,
|
||||||
isFinished, dataByteOffset, expectedValue, minimumFinalHeight,
|
isFinished, dataByteOffset, expectedValue, minimumFinalHeight,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
|
@ -439,15 +439,7 @@ public class ArbitraryDataReader {
|
|||||||
// Ensure the complete hash matches the joined chunks
|
// Ensure the complete hash matches the joined chunks
|
||||||
if (!Arrays.equals(arbitraryDataFile.digest(), transactionData.getData())) {
|
if (!Arrays.equals(arbitraryDataFile.digest(), transactionData.getData())) {
|
||||||
// Delete the invalid file
|
// Delete the invalid file
|
||||||
LOGGER.info("Deleting invalid file: path = " + arbitraryDataFile.getFilePath());
|
arbitraryDataFile.delete();
|
||||||
|
|
||||||
if( arbitraryDataFile.delete() ) {
|
|
||||||
LOGGER.info("Deleted invalid file successfully: path = " + arbitraryDataFile.getFilePath());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOGGER.warn("Could not delete invalid file: path = " + arbitraryDataFile.getFilePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new DataException("Unable to validate complete file hash");
|
throw new DataException("Unable to validate complete file hash");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,8 @@ import org.qortal.data.at.ATStateData;
|
|||||||
import org.qortal.data.block.BlockData;
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.block.BlockSummaryData;
|
import org.qortal.data.block.BlockSummaryData;
|
||||||
import org.qortal.data.block.BlockTransactionData;
|
import org.qortal.data.block.BlockTransactionData;
|
||||||
import org.qortal.data.group.GroupAdminData;
|
|
||||||
import org.qortal.data.network.OnlineAccountData;
|
import org.qortal.data.network.OnlineAccountData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.group.Group;
|
|
||||||
import org.qortal.repository.*;
|
import org.qortal.repository.*;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.transaction.AtTransaction;
|
import org.qortal.transaction.AtTransaction;
|
||||||
@ -39,7 +37,6 @@ import org.qortal.transform.block.BlockTransformer;
|
|||||||
import org.qortal.transform.transaction.TransactionTransformer;
|
import org.qortal.transform.transaction.TransactionTransformer;
|
||||||
import org.qortal.utils.Amounts;
|
import org.qortal.utils.Amounts;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
import org.qortal.utils.Groups;
|
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -151,7 +148,7 @@ public class Block {
|
|||||||
|
|
||||||
final BlockChain blockChain = BlockChain.getInstance();
|
final BlockChain blockChain = BlockChain.getInstance();
|
||||||
|
|
||||||
ExpandedAccount(Repository repository, RewardShareData rewardShareData, int blockHeight) throws DataException {
|
ExpandedAccount(Repository repository, RewardShareData rewardShareData) throws DataException {
|
||||||
this.rewardShareData = rewardShareData;
|
this.rewardShareData = rewardShareData;
|
||||||
this.sharePercent = this.rewardShareData.getSharePercent();
|
this.sharePercent = this.rewardShareData.getSharePercent();
|
||||||
|
|
||||||
@ -160,12 +157,7 @@ public class Block {
|
|||||||
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
|
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
|
||||||
|
|
||||||
this.isRecipientAlsoMinter = this.rewardShareData.getRecipient().equals(this.mintingAccount.getAddress());
|
this.isRecipientAlsoMinter = this.rewardShareData.getRecipient().equals(this.mintingAccount.getAddress());
|
||||||
this.isMinterMember
|
this.isMinterMember = repository.getGroupRepository().memberExists(BlockChain.getInstance().getMintingGroupId(), this.mintingAccount.getAddress());
|
||||||
= Groups.memberExistsInAnyGroup(
|
|
||||||
repository.getGroupRepository(),
|
|
||||||
Groups.getGroupIdsToMint(BlockChain.getInstance(), blockHeight),
|
|
||||||
this.mintingAccount.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.isRecipientAlsoMinter) {
|
if (this.isRecipientAlsoMinter) {
|
||||||
// Self-share: minter is also recipient
|
// Self-share: minter is also recipient
|
||||||
@ -178,19 +170,6 @@ public class Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Effective Minting Level
|
|
||||||
*
|
|
||||||
* @return the effective minting level, if a data exception is thrown, it catches the exception and returns a zero
|
|
||||||
*/
|
|
||||||
public int getEffectiveMintingLevel() {
|
|
||||||
try {
|
|
||||||
return this.mintingAccount.getEffectiveMintingLevel();
|
|
||||||
} catch (DataException e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Account getMintingAccount() {
|
public Account getMintingAccount() {
|
||||||
return this.mintingAccount;
|
return this.mintingAccount;
|
||||||
}
|
}
|
||||||
@ -207,7 +186,7 @@ public class Block {
|
|||||||
* @return account-level share "bin" from blockchain config, or null if founder / none found
|
* @return account-level share "bin" from blockchain config, or null if founder / none found
|
||||||
*/
|
*/
|
||||||
public AccountLevelShareBin getShareBin(int blockHeight) {
|
public AccountLevelShareBin getShareBin(int blockHeight) {
|
||||||
if (this.isMinterFounder && blockHeight < BlockChain.getInstance().getAdminsReplaceFoundersHeight())
|
if (this.isMinterFounder)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
final int accountLevel = this.mintingAccountData.getLevel();
|
final int accountLevel = this.mintingAccountData.getLevel();
|
||||||
@ -424,9 +403,7 @@ public class Block {
|
|||||||
onlineAccounts.removeIf(a -> a.getNonce() == null || a.getNonce() < 0);
|
onlineAccounts.removeIf(a -> a.getNonce() == null || a.getNonce() < 0);
|
||||||
|
|
||||||
// After feature trigger, remove any online accounts that are level 0
|
// After feature trigger, remove any online accounts that are level 0
|
||||||
// but only if they are before the ignore level feature trigger
|
if (height >= BlockChain.getInstance().getOnlineAccountMinterLevelValidationHeight()) {
|
||||||
if (height < BlockChain.getInstance().getIgnoreLevelForRewardShareHeight() &&
|
|
||||||
height >= BlockChain.getInstance().getOnlineAccountMinterLevelValidationHeight()) {
|
|
||||||
onlineAccounts.removeIf(a -> {
|
onlineAccounts.removeIf(a -> {
|
||||||
try {
|
try {
|
||||||
return Account.getRewardShareEffectiveMintingLevel(repository, a.getPublicKey()) == 0;
|
return Account.getRewardShareEffectiveMintingLevel(repository, a.getPublicKey()) == 0;
|
||||||
@ -441,9 +418,9 @@ public class Block {
|
|||||||
if (height >= BlockChain.getInstance().getGroupMemberCheckHeight()) {
|
if (height >= BlockChain.getInstance().getGroupMemberCheckHeight()) {
|
||||||
onlineAccounts.removeIf(a -> {
|
onlineAccounts.removeIf(a -> {
|
||||||
try {
|
try {
|
||||||
List<Integer> groupIdsToMint = Groups.getGroupIdsToMint(BlockChain.getInstance(), height);
|
int groupId = BlockChain.getInstance().getMintingGroupId();
|
||||||
String address = Account.getRewardShareMintingAddress(repository, a.getPublicKey());
|
String address = Account.getRewardShareMintingAddress(repository, a.getPublicKey());
|
||||||
boolean isMinterGroupMember = Groups.memberExistsInAnyGroup(repository.getGroupRepository(), groupIdsToMint, address);
|
boolean isMinterGroupMember = repository.getGroupRepository().memberExists(groupId, address);
|
||||||
return !isMinterGroupMember;
|
return !isMinterGroupMember;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
// Something went wrong, so remove the account
|
// Something went wrong, so remove the account
|
||||||
@ -759,7 +736,15 @@ public class Block {
|
|||||||
List<ExpandedAccount> expandedAccounts = new ArrayList<>();
|
List<ExpandedAccount> expandedAccounts = new ArrayList<>();
|
||||||
|
|
||||||
for (RewardShareData rewardShare : this.cachedOnlineRewardShares) {
|
for (RewardShareData rewardShare : this.cachedOnlineRewardShares) {
|
||||||
expandedAccounts.add(new ExpandedAccount(repository, rewardShare, this.blockData.getHeight()));
|
int groupId = BlockChain.getInstance().getMintingGroupId();
|
||||||
|
String address = rewardShare.getMinter();
|
||||||
|
boolean isMinterGroupMember = repository.getGroupRepository().memberExists(groupId, address);
|
||||||
|
|
||||||
|
if (this.getBlockData().getHeight() < BlockChain.getInstance().getFixBatchRewardHeight())
|
||||||
|
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
|
||||||
|
|
||||||
|
if (this.getBlockData().getHeight() >= BlockChain.getInstance().getFixBatchRewardHeight() && isMinterGroupMember)
|
||||||
|
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cachedExpandedAccounts = expandedAccounts;
|
this.cachedExpandedAccounts = expandedAccounts;
|
||||||
@ -1169,32 +1154,23 @@ public class Block {
|
|||||||
if (onlineRewardShares == null)
|
if (onlineRewardShares == null)
|
||||||
return ValidationResult.ONLINE_ACCOUNT_UNKNOWN;
|
return ValidationResult.ONLINE_ACCOUNT_UNKNOWN;
|
||||||
|
|
||||||
// After feature trigger, require all online account minters to be greater than level 0,
|
// After feature trigger, require all online account minters to be greater than level 0
|
||||||
// but only if it is before the feature trigger where we ignore level again
|
if (this.getBlockData().getHeight() >= BlockChain.getInstance().getOnlineAccountMinterLevelValidationHeight()) {
|
||||||
if (this.blockData.getHeight() < BlockChain.getInstance().getIgnoreLevelForRewardShareHeight() &&
|
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||||
this.getBlockData().getHeight() >= BlockChain.getInstance().getOnlineAccountMinterLevelValidationHeight()) {
|
|
||||||
List<ExpandedAccount> expandedAccounts
|
|
||||||
= this.getExpandedAccounts().stream()
|
|
||||||
.filter(expandedAccount -> expandedAccount.isMinterMember)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
for (ExpandedAccount account : expandedAccounts) {
|
for (ExpandedAccount account : expandedAccounts) {
|
||||||
|
int groupId = BlockChain.getInstance().getMintingGroupId();
|
||||||
|
String address = account.getMintingAccount().getAddress();
|
||||||
|
boolean isMinterGroupMember = repository.getGroupRepository().memberExists(groupId, address);
|
||||||
|
|
||||||
if (account.getMintingAccount().getEffectiveMintingLevel() == 0)
|
if (account.getMintingAccount().getEffectiveMintingLevel() == 0)
|
||||||
return ValidationResult.ONLINE_ACCOUNTS_INVALID;
|
return ValidationResult.ONLINE_ACCOUNTS_INVALID;
|
||||||
|
|
||||||
if (this.getBlockData().getHeight() >= BlockChain.getInstance().getFixBatchRewardHeight()) {
|
if (this.getBlockData().getHeight() >= BlockChain.getInstance().getFixBatchRewardHeight()) {
|
||||||
if (!account.isMinterMember)
|
if (!isMinterGroupMember)
|
||||||
return ValidationResult.ONLINE_ACCOUNTS_INVALID;
|
return ValidationResult.ONLINE_ACCOUNTS_INVALID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (this.blockData.getHeight() >= BlockChain.getInstance().getIgnoreLevelForRewardShareHeight()){
|
|
||||||
Optional<ExpandedAccount> anyInvalidAccount
|
|
||||||
= this.getExpandedAccounts().stream()
|
|
||||||
.filter(account -> !account.isMinterMember)
|
|
||||||
.findAny();
|
|
||||||
if( anyInvalidAccount.isPresent() ) return ValidationResult.ONLINE_ACCOUNTS_INVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If block is past a certain age then we simply assume the signatures were correct
|
// If block is past a certain age then we simply assume the signatures were correct
|
||||||
long signatureRequirementThreshold = NTP.getTime() - BlockChain.getInstance().getOnlineAccountSignaturesMinLifetime();
|
long signatureRequirementThreshold = NTP.getTime() - BlockChain.getInstance().getOnlineAccountSignaturesMinLifetime();
|
||||||
@ -1683,17 +1659,7 @@ public class Block {
|
|||||||
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||||
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
|
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
|
||||||
|
|
||||||
final List<ExpandedAccount> expandedAccounts;
|
final List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||||
|
|
||||||
if (this.getBlockData().getHeight() < BlockChain.getInstance().getFixBatchRewardHeight()) {
|
|
||||||
expandedAccounts = this.getExpandedAccounts().stream().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expandedAccounts
|
|
||||||
= this.getExpandedAccounts().stream()
|
|
||||||
.filter(expandedAccount -> expandedAccount.isMinterMember)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<AccountData> allUniqueExpandedAccounts = new HashSet<>();
|
Set<AccountData> allUniqueExpandedAccounts = new HashSet<>();
|
||||||
for (ExpandedAccount expandedAccount : expandedAccounts) {
|
for (ExpandedAccount expandedAccount : expandedAccounts) {
|
||||||
@ -2093,17 +2059,7 @@ public class Block {
|
|||||||
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
|
||||||
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
|
final int maximumLevel = cumulativeBlocksByLevel.size() - 1;
|
||||||
|
|
||||||
final List<ExpandedAccount> expandedAccounts;
|
final List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||||
|
|
||||||
if (this.getBlockData().getHeight() < BlockChain.getInstance().getFixBatchRewardHeight()) {
|
|
||||||
expandedAccounts = this.getExpandedAccounts().stream().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expandedAccounts
|
|
||||||
= this.getExpandedAccounts().stream()
|
|
||||||
.filter(expandedAccount -> expandedAccount.isMinterMember)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<AccountData> allUniqueExpandedAccounts = new HashSet<>();
|
Set<AccountData> allUniqueExpandedAccounts = new HashSet<>();
|
||||||
for (ExpandedAccount expandedAccount : expandedAccounts) {
|
for (ExpandedAccount expandedAccount : expandedAccounts) {
|
||||||
@ -2307,17 +2263,7 @@ public class Block {
|
|||||||
List<BlockRewardCandidate> rewardCandidates = new ArrayList<>();
|
List<BlockRewardCandidate> rewardCandidates = new ArrayList<>();
|
||||||
|
|
||||||
// All online accounts
|
// All online accounts
|
||||||
final List<ExpandedAccount> expandedAccounts;
|
final List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||||
|
|
||||||
if (this.getBlockData().getHeight() < BlockChain.getInstance().getFixBatchRewardHeight()) {
|
|
||||||
expandedAccounts = this.getExpandedAccounts().stream().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expandedAccounts
|
|
||||||
= this.getExpandedAccounts().stream()
|
|
||||||
.filter(expandedAccount -> expandedAccount.isMinterMember)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Distribution rules:
|
* Distribution rules:
|
||||||
@ -2442,7 +2388,7 @@ public class Block {
|
|||||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(this.blockData.getHeight());
|
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(this.blockData.getHeight());
|
||||||
|
|
||||||
// Perform account-level-based reward scaling if appropriate
|
// Perform account-level-based reward scaling if appropriate
|
||||||
if (!haveFounders && this.blockData.getHeight() < BlockChain.getInstance().getAdminsReplaceFoundersHeight() ) {
|
if (!haveFounders) {
|
||||||
// Recalculate distribution ratios based on candidates
|
// Recalculate distribution ratios based on candidates
|
||||||
|
|
||||||
// Nothing shared? This shouldn't happen
|
// Nothing shared? This shouldn't happen
|
||||||
@ -2478,103 +2424,18 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add founders as reward candidate if appropriate
|
// Add founders as reward candidate if appropriate
|
||||||
if (haveFounders && this.blockData.getHeight() < BlockChain.getInstance().getAdminsReplaceFoundersHeight()) {
|
if (haveFounders) {
|
||||||
// Yes: add to reward candidates list
|
// Yes: add to reward candidates list
|
||||||
BlockRewardDistributor founderDistributor = (distributionAmount, balanceChanges) -> distributeBlockRewardShare(distributionAmount, onlineFounderAccounts, balanceChanges);
|
BlockRewardDistributor founderDistributor = (distributionAmount, balanceChanges) -> distributeBlockRewardShare(distributionAmount, onlineFounderAccounts, balanceChanges);
|
||||||
|
|
||||||
final long foundersShare = 1_00000000 - totalShares;
|
final long foundersShare = 1_00000000 - totalShares;
|
||||||
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate("Founders", foundersShare, founderDistributor);
|
BlockRewardCandidate rewardCandidate = new BlockRewardCandidate("Founders", foundersShare, founderDistributor);
|
||||||
rewardCandidates.add(rewardCandidate);
|
rewardCandidates.add(rewardCandidate);
|
||||||
LOGGER.info("logging foundersShare prior to reward modifications {}",foundersShare);
|
|
||||||
}
|
|
||||||
else if (this.blockData.getHeight() >= BlockChain.getInstance().getAdminsReplaceFoundersHeight()) {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
GroupRepository groupRepository = repository.getGroupRepository();
|
|
||||||
|
|
||||||
List<Integer> mintingGroupIds = Groups.getGroupIdsToMint(BlockChain.getInstance(), this.blockData.getHeight());
|
|
||||||
|
|
||||||
// all minter admins
|
|
||||||
List<String> minterAdmins = Groups.getAllAdmins(groupRepository, mintingGroupIds);
|
|
||||||
|
|
||||||
// all minter admins that are online
|
|
||||||
List<ExpandedAccount> onlineMinterAdminAccounts
|
|
||||||
= expandedAccounts.stream()
|
|
||||||
.filter(expandedAccount -> minterAdmins.contains(expandedAccount.getMintingAccount().getAddress()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
long minterAdminShare;
|
|
||||||
|
|
||||||
if( onlineMinterAdminAccounts.isEmpty() ) {
|
|
||||||
minterAdminShare = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
BlockRewardDistributor minterAdminDistributor
|
|
||||||
= (distributionAmount, balanceChanges)
|
|
||||||
->
|
|
||||||
distributeBlockRewardShare(distributionAmount, onlineMinterAdminAccounts, balanceChanges);
|
|
||||||
|
|
||||||
long adminShare = 1_00000000 - totalShares;
|
|
||||||
LOGGER.info("initial total Shares: {}", totalShares);
|
|
||||||
LOGGER.info("logging adminShare after hardfork, this is the primary reward that will be split {}", adminShare);
|
|
||||||
|
|
||||||
minterAdminShare = adminShare / 2;
|
|
||||||
BlockRewardCandidate minterAdminRewardCandidate
|
|
||||||
= new BlockRewardCandidate("Minter Admins", minterAdminShare, minterAdminDistributor);
|
|
||||||
rewardCandidates.add(minterAdminRewardCandidate);
|
|
||||||
|
|
||||||
totalShares += minterAdminShare;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("MINTER ADMIN SHARE: {}",minterAdminShare);
|
|
||||||
|
|
||||||
// all dev admins
|
|
||||||
List<String> devAdminAddresses
|
|
||||||
= groupRepository.getGroupAdmins(1).stream()
|
|
||||||
.map(GroupAdminData::getAdmin)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
LOGGER.info("Removing NULL Account Address, Dev Admin Count = {}", devAdminAddresses.size());
|
|
||||||
devAdminAddresses.removeIf( address -> Group.NULL_OWNER_ADDRESS.equals(address) );
|
|
||||||
LOGGER.info("Removed NULL Account Address, Dev Admin Count = {}", devAdminAddresses.size());
|
|
||||||
|
|
||||||
BlockRewardDistributor devAdminDistributor
|
|
||||||
= (distributionAmount, balanceChanges) -> distributeToAccounts(distributionAmount, devAdminAddresses, balanceChanges);
|
|
||||||
|
|
||||||
long devAdminShare = 1_00000000 - totalShares;
|
|
||||||
LOGGER.info("DEV ADMIN SHARE: {}",devAdminShare);
|
|
||||||
BlockRewardCandidate devAdminRewardCandidate
|
|
||||||
= new BlockRewardCandidate("Dev Admins", devAdminShare,devAdminDistributor);
|
|
||||||
rewardCandidates.add(devAdminRewardCandidate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rewardCandidates;
|
return rewardCandidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Distribute To Accounts
|
|
||||||
*
|
|
||||||
* Merges distribute shares to a map of distribution shares.
|
|
||||||
*
|
|
||||||
* @param distributionAmount the amount to distribute
|
|
||||||
* @param accountAddressess the addresses to distribute to
|
|
||||||
* @param balanceChanges the map of distribution shares, this gets appended to
|
|
||||||
*
|
|
||||||
* @return the total amount mapped to addresses for distribution
|
|
||||||
*/
|
|
||||||
public static long distributeToAccounts(long distributionAmount, List<String> accountAddressess, Map<String, Long> balanceChanges) {
|
|
||||||
|
|
||||||
if( accountAddressess.isEmpty() ) return 0;
|
|
||||||
|
|
||||||
long distibutionShare = distributionAmount / accountAddressess.size();
|
|
||||||
|
|
||||||
for(String accountAddress : accountAddressess ) {
|
|
||||||
balanceChanges.merge(accountAddress, distibutionShare, Long::sum);
|
|
||||||
}
|
|
||||||
|
|
||||||
return distibutionShare * accountAddressess.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long distributeBlockRewardShare(long distributionAmount, List<ExpandedAccount> accounts, Map<String, Long> balanceChanges) {
|
private static long distributeBlockRewardShare(long distributionAmount, List<ExpandedAccount> accounts, Map<String, Long> balanceChanges) {
|
||||||
// Collate all expanded accounts by minting account
|
// Collate all expanded accounts by minting account
|
||||||
Map<String, List<ExpandedAccount>> accountsByMinter = new HashMap<>();
|
Map<String, List<ExpandedAccount>> accountsByMinter = new HashMap<>();
|
||||||
|
@ -88,11 +88,7 @@ public class BlockChain {
|
|||||||
onlyMintWithNameHeight,
|
onlyMintWithNameHeight,
|
||||||
removeOnlyMintWithNameHeight,
|
removeOnlyMintWithNameHeight,
|
||||||
groupMemberCheckHeight,
|
groupMemberCheckHeight,
|
||||||
fixBatchRewardHeight,
|
fixBatchRewardHeight
|
||||||
adminsReplaceFoundersHeight,
|
|
||||||
nullGroupMembershipHeight,
|
|
||||||
ignoreLevelForRewardShareHeight,
|
|
||||||
adminQueryFixHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom transaction fees
|
// Custom transaction fees
|
||||||
@ -212,13 +208,7 @@ public class BlockChain {
|
|||||||
private int minAccountLevelToRewardShare;
|
private int minAccountLevelToRewardShare;
|
||||||
private int maxRewardSharesPerFounderMintingAccount;
|
private int maxRewardSharesPerFounderMintingAccount;
|
||||||
private int founderEffectiveMintingLevel;
|
private int founderEffectiveMintingLevel;
|
||||||
|
private int mintingGroupId;
|
||||||
public static class IdsForHeight {
|
|
||||||
public int height;
|
|
||||||
public List<Integer> ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<IdsForHeight> mintingGroupIds;
|
|
||||||
|
|
||||||
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
||||||
private long onlineAccountSignaturesMinLifetime;
|
private long onlineAccountSignaturesMinLifetime;
|
||||||
@ -550,8 +540,8 @@ public class BlockChain {
|
|||||||
return this.onlineAccountSignaturesMaxLifetime;
|
return this.onlineAccountSignaturesMaxLifetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IdsForHeight> getMintingGroupIds() {
|
public int getMintingGroupId() {
|
||||||
return mintingGroupIds;
|
return this.mintingGroupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CiyamAtSettings getCiyamAtSettings() {
|
public CiyamAtSettings getCiyamAtSettings() {
|
||||||
@ -672,22 +662,6 @@ public class BlockChain {
|
|||||||
return this.featureTriggers.get(FeatureTrigger.fixBatchRewardHeight.name()).intValue();
|
return this.featureTriggers.get(FeatureTrigger.fixBatchRewardHeight.name()).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAdminsReplaceFoundersHeight() {
|
|
||||||
return this.featureTriggers.get(FeatureTrigger.adminsReplaceFoundersHeight.name()).intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNullGroupMembershipHeight() {
|
|
||||||
return this.featureTriggers.get(FeatureTrigger.nullGroupMembershipHeight.name()).intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIgnoreLevelForRewardShareHeight() {
|
|
||||||
return this.featureTriggers.get(FeatureTrigger.ignoreLevelForRewardShareHeight.name()).intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAdminQueryFixHeight() {
|
|
||||||
return this.featureTriggers.get(FeatureTrigger.adminQueryFixHeight.name()).intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// More complex getters for aspects that change by height or timestamp
|
// More complex getters for aspects that change by height or timestamp
|
||||||
|
|
||||||
public long getRewardAtHeight(int ourHeight) {
|
public long getRewardAtHeight(int ourHeight) {
|
||||||
|
@ -423,12 +423,6 @@ public class Controller extends Thread {
|
|||||||
LOGGER.info("Db Cache Disabled");
|
LOGGER.info("Db Cache Disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("Arbitrary Indexing Starting ...");
|
|
||||||
ArbitraryIndexUtils.startCaching(
|
|
||||||
Settings.getInstance().getArbitraryIndexingPriority(),
|
|
||||||
Settings.getInstance().getArbitraryIndexingFrequency()
|
|
||||||
);
|
|
||||||
|
|
||||||
if( Settings.getInstance().isBalanceRecorderEnabled() ) {
|
if( Settings.getInstance().isBalanceRecorderEnabled() ) {
|
||||||
Optional<HSQLDBBalanceRecorder> recorder = HSQLDBBalanceRecorder.getInstance();
|
Optional<HSQLDBBalanceRecorder> recorder = HSQLDBBalanceRecorder.getInstance();
|
||||||
|
|
||||||
@ -547,16 +541,6 @@ public class Controller extends Thread {
|
|||||||
ArbitraryDataStorageManager.getInstance().start();
|
ArbitraryDataStorageManager.getInstance().start();
|
||||||
ArbitraryDataRenderManager.getInstance().start();
|
ArbitraryDataRenderManager.getInstance().start();
|
||||||
|
|
||||||
// start rebuild arbitrary resource cache timer task
|
|
||||||
if( Settings.getInstance().isRebuildArbitraryResourceCacheTaskEnabled() ) {
|
|
||||||
new Timer().schedule(
|
|
||||||
new RebuildArbitraryResourceCacheTask(),
|
|
||||||
Settings.getInstance().getRebuildArbitraryResourceCacheTaskDelay() * RebuildArbitraryResourceCacheTask.MILLIS_IN_MINUTE,
|
|
||||||
Settings.getInstance().getRebuildArbitraryResourceCacheTaskPeriod() * RebuildArbitraryResourceCacheTask.MILLIS_IN_HOUR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
LOGGER.info("Starting online accounts manager");
|
LOGGER.info("Starting online accounts manager");
|
||||||
OnlineAccountsManager.getInstance().start();
|
OnlineAccountsManager.getInstance().start();
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ import org.qortal.repository.Repository;
|
|||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
import org.qortal.utils.Groups;
|
|
||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
import org.qortal.utils.NamedThreadFactory;
|
import org.qortal.utils.NamedThreadFactory;
|
||||||
|
|
||||||
@ -226,14 +225,11 @@ public class OnlineAccountsManager {
|
|||||||
Set<OnlineAccountData> onlineAccountsToAdd = new HashSet<>();
|
Set<OnlineAccountData> onlineAccountsToAdd = new HashSet<>();
|
||||||
Set<OnlineAccountData> onlineAccountsToRemove = new HashSet<>();
|
Set<OnlineAccountData> onlineAccountsToRemove = new HashSet<>();
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
int blockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
List<String> mintingGroupMemberAddresses
|
List<String> mintingGroupMemberAddresses
|
||||||
= Groups.getAllMembers(
|
= repository.getGroupRepository()
|
||||||
repository.getGroupRepository(),
|
.getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream()
|
||||||
Groups.getGroupIdsToMint(BlockChain.getInstance(), blockHeight)
|
.map(GroupMemberData::getMember)
|
||||||
);
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) {
|
for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) {
|
||||||
if (isStopping)
|
if (isStopping)
|
||||||
|
@ -2,30 +2,22 @@ package org.qortal.controller.arbitrary;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.qortal.api.resource.TransactionsResource;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||||
import org.qortal.event.DataMonitorEvent;
|
|
||||||
import org.qortal.event.EventBus;
|
|
||||||
import org.qortal.gui.SplashFrame;
|
import org.qortal.gui.SplashFrame;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.transaction.ArbitraryTransaction;
|
import org.qortal.transaction.ArbitraryTransaction;
|
||||||
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class ArbitraryDataCacheManager extends Thread {
|
public class ArbitraryDataCacheManager extends Thread {
|
||||||
|
|
||||||
@ -37,11 +29,6 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
/** Queue of arbitrary transactions that require cache updates */
|
/** Queue of arbitrary transactions that require cache updates */
|
||||||
private final List<ArbitraryTransactionData> updateQueue = Collections.synchronizedList(new ArrayList<>());
|
private final List<ArbitraryTransactionData> updateQueue = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
private static final NumberFormat FORMATTER = NumberFormat.getNumberInstance();
|
|
||||||
|
|
||||||
static {
|
|
||||||
FORMATTER.setGroupingUsed(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized ArbitraryDataCacheManager getInstance() {
|
public static synchronized ArbitraryDataCacheManager getInstance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
@ -58,22 +45,17 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
while (!Controller.isStopping()) {
|
while (!Controller.isStopping()) {
|
||||||
try {
|
Thread.sleep(500L);
|
||||||
Thread.sleep(500L);
|
|
||||||
|
|
||||||
// Process queue
|
// Process queue
|
||||||
processResourceQueue();
|
processResourceQueue();
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
Thread.sleep(600_000L); // wait 10 minutes to continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
// Clear queue before terminating thread
|
// Fall through to exit thread
|
||||||
processResourceQueue();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear queue before terminating thread
|
||||||
|
processResourceQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
@ -103,25 +85,14 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
// Update arbitrary resource caches
|
// Update arbitrary resource caches
|
||||||
try {
|
try {
|
||||||
ArbitraryTransaction arbitraryTransaction = new ArbitraryTransaction(repository, transactionData);
|
ArbitraryTransaction arbitraryTransaction = new ArbitraryTransaction(repository, transactionData);
|
||||||
arbitraryTransaction.updateArbitraryResourceCacheIncludingMetadata(repository, new HashSet<>(0), new HashMap<>(0));
|
arbitraryTransaction.updateArbitraryResourceCache(repository);
|
||||||
|
arbitraryTransaction.updateArbitraryMetadataCache(repository);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
// Update status as separate commit, as this is more prone to failure
|
// Update status as separate commit, as this is more prone to failure
|
||||||
arbitraryTransaction.updateArbitraryResourceStatus(repository);
|
arbitraryTransaction.updateArbitraryResourceStatus(repository);
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
transactionData.getIdentifier(),
|
|
||||||
transactionData.getName(),
|
|
||||||
transactionData.getService().name(),
|
|
||||||
"updated resource cache and status, queue",
|
|
||||||
transactionData.getTimestamp(),
|
|
||||||
transactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
LOGGER.debug(() -> String.format("Finished processing transaction %.8s in arbitrary resource queue...", Base58.encode(transactionData.getSignature())));
|
LOGGER.debug(() -> String.format("Finished processing transaction %.8s in arbitrary resource queue...", Base58.encode(transactionData.getSignature())));
|
||||||
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
@ -132,9 +103,6 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
LOGGER.error("Repository issue while processing arbitrary resource cache updates", e);
|
LOGGER.error("Repository issue while processing arbitrary resource cache updates", e);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToUpdateQueue(ArbitraryTransactionData transactionData) {
|
public void addToUpdateQueue(ArbitraryTransactionData transactionData) {
|
||||||
@ -180,66 +148,34 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
LOGGER.info("Building arbitrary resources cache...");
|
LOGGER.info("Building arbitrary resources cache...");
|
||||||
SplashFrame.getInstance().updateStatus("Building QDN cache - please wait...");
|
SplashFrame.getInstance().updateStatus("Building QDN cache - please wait...");
|
||||||
|
|
||||||
final int batchSize = Settings.getInstance().getBuildArbitraryResourcesBatchSize();
|
final int batchSize = 100;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
List<ArbitraryTransactionData> allArbitraryTransactionsInDescendingOrder
|
|
||||||
= repository.getArbitraryRepository().getLatestArbitraryTransactions();
|
|
||||||
|
|
||||||
LOGGER.info("arbitrary transactions: count = " + allArbitraryTransactionsInDescendingOrder.size());
|
|
||||||
|
|
||||||
List<ArbitraryResourceData> resources = repository.getArbitraryRepository().getArbitraryResources(null, null, true);
|
|
||||||
|
|
||||||
Map<ArbitraryTransactionDataHashWrapper, ArbitraryResourceData> resourceByWrapper = new HashMap<>(resources.size());
|
|
||||||
for( ArbitraryResourceData resource : resources ) {
|
|
||||||
resourceByWrapper.put(
|
|
||||||
new ArbitraryTransactionDataHashWrapper(resource.service.value, resource.name, resource.identifier),
|
|
||||||
resource
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("arbitrary resources: count = " + resourceByWrapper.size());
|
|
||||||
|
|
||||||
Set<ArbitraryTransactionDataHashWrapper> latestTransactionsWrapped = new HashSet<>(allArbitraryTransactionsInDescendingOrder.size());
|
|
||||||
|
|
||||||
// Loop through all ARBITRARY transactions, and determine latest state
|
// Loop through all ARBITRARY transactions, and determine latest state
|
||||||
while (!Controller.isStopping()) {
|
while (!Controller.isStopping()) {
|
||||||
LOGGER.info(
|
LOGGER.info("Fetching arbitrary transactions {} - {}", offset, offset+batchSize-1);
|
||||||
"Fetching arbitrary transactions {} - {} / {} Total",
|
|
||||||
FORMATTER.format(offset),
|
|
||||||
FORMATTER.format(offset+batchSize-1),
|
|
||||||
FORMATTER.format(allArbitraryTransactionsInDescendingOrder.size())
|
|
||||||
);
|
|
||||||
|
|
||||||
List<ArbitraryTransactionData> transactionsToProcess
|
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, List.of(Transaction.TransactionType.ARBITRARY), null, null, null, TransactionsResource.ConfirmationStatus.BOTH, batchSize, offset, false);
|
||||||
= allArbitraryTransactionsInDescendingOrder.stream()
|
if (signatures.isEmpty()) {
|
||||||
.skip(offset)
|
|
||||||
.limit(batchSize)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (transactionsToProcess.isEmpty()) {
|
|
||||||
// Complete
|
// Complete
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Expand signatures to transactions
|
||||||
for( ArbitraryTransactionData transactionData : transactionsToProcess) {
|
for (byte[] signature : signatures) {
|
||||||
if (transactionData.getService() == null) {
|
ArbitraryTransactionData transactionData = (ArbitraryTransactionData) repository
|
||||||
// Unsupported service - ignore this resource
|
.getTransactionRepository().fromSignature(signature);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
latestTransactionsWrapped.add(new ArbitraryTransactionDataHashWrapper(transactionData));
|
if (transactionData.getService() == null) {
|
||||||
|
// Unsupported service - ignore this resource
|
||||||
// Update arbitrary resource caches
|
continue;
|
||||||
ArbitraryTransaction arbitraryTransaction = new ArbitraryTransaction(repository, transactionData);
|
|
||||||
arbitraryTransaction.updateArbitraryResourceCacheIncludingMetadata(repository, latestTransactionsWrapped, resourceByWrapper);
|
|
||||||
}
|
}
|
||||||
repository.saveChanges();
|
|
||||||
} catch (DataException e) {
|
|
||||||
repository.discardChanges();
|
|
||||||
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
// Update arbitrary resource caches
|
||||||
|
ArbitraryTransaction arbitraryTransaction = new ArbitraryTransaction(repository, transactionData);
|
||||||
|
arbitraryTransaction.updateArbitraryResourceCache(repository);
|
||||||
|
arbitraryTransaction.updateArbitraryMetadataCache(repository);
|
||||||
|
repository.saveChanges();
|
||||||
}
|
}
|
||||||
offset += batchSize;
|
offset += batchSize;
|
||||||
}
|
}
|
||||||
@ -257,11 +193,6 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
throw new DataException("Build of arbitrary resources cache failed.");
|
throw new DataException("Build of arbitrary resources cache failed.");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean refreshArbitraryStatuses(Repository repository) throws DataException {
|
private boolean refreshArbitraryStatuses(Repository repository) throws DataException {
|
||||||
@ -269,48 +200,27 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
LOGGER.info("Refreshing arbitrary resource statuses for locally hosted transactions...");
|
LOGGER.info("Refreshing arbitrary resource statuses for locally hosted transactions...");
|
||||||
SplashFrame.getInstance().updateStatus("Refreshing statuses - please wait...");
|
SplashFrame.getInstance().updateStatus("Refreshing statuses - please wait...");
|
||||||
|
|
||||||
final int batchSize = Settings.getInstance().getBuildArbitraryResourcesBatchSize();
|
final int batchSize = 100;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
List<ArbitraryTransactionData> allHostedTransactions
|
|
||||||
= ArbitraryDataStorageManager.getInstance()
|
|
||||||
.listAllHostedTransactions(repository, null, null);
|
|
||||||
|
|
||||||
// Loop through all ARBITRARY transactions, and determine latest state
|
// Loop through all ARBITRARY transactions, and determine latest state
|
||||||
while (!Controller.isStopping()) {
|
while (!Controller.isStopping()) {
|
||||||
LOGGER.info(
|
LOGGER.info("Fetching hosted transactions {} - {}", offset, offset+batchSize-1);
|
||||||
"Fetching hosted transactions {} - {} / {} Total",
|
|
||||||
FORMATTER.format(offset),
|
|
||||||
FORMATTER.format(offset+batchSize-1),
|
|
||||||
FORMATTER.format(allHostedTransactions.size())
|
|
||||||
);
|
|
||||||
|
|
||||||
List<ArbitraryTransactionData> hostedTransactions
|
|
||||||
= allHostedTransactions.stream()
|
|
||||||
.skip(offset)
|
|
||||||
.limit(batchSize)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
|
List<ArbitraryTransactionData> hostedTransactions = ArbitraryDataStorageManager.getInstance().listAllHostedTransactions(repository, batchSize, offset);
|
||||||
if (hostedTransactions.isEmpty()) {
|
if (hostedTransactions.isEmpty()) {
|
||||||
// Complete
|
// Complete
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Loop through hosted transactions
|
||||||
// Loop through hosted transactions
|
for (ArbitraryTransactionData transactionData : hostedTransactions) {
|
||||||
for (ArbitraryTransactionData transactionData : hostedTransactions) {
|
|
||||||
|
|
||||||
// Determine status and update cache
|
// Determine status and update cache
|
||||||
ArbitraryTransaction arbitraryTransaction = new ArbitraryTransaction(repository, transactionData);
|
ArbitraryTransaction arbitraryTransaction = new ArbitraryTransaction(repository, transactionData);
|
||||||
arbitraryTransaction.updateArbitraryResourceStatus(repository);
|
arbitraryTransaction.updateArbitraryResourceStatus(repository);
|
||||||
}
|
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
} catch (DataException e) {
|
|
||||||
repository.discardChanges();
|
|
||||||
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += batchSize;
|
offset += batchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,11 +234,6 @@ public class ArbitraryDataCacheManager extends Thread {
|
|||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
throw new DataException("Refresh of arbitrary resource statuses failed.");
|
throw new DataException("Refresh of arbitrary resource statuses failed.");
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,9 @@ package org.qortal.controller.arbitrary;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.event.DataMonitorEvent;
|
|
||||||
import org.qortal.event.EventBus;
|
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
@ -22,12 +21,8 @@ import java.nio.file.Paths;
|
|||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.qortal.controller.arbitrary.ArbitraryDataStorageManager.DELETION_THRESHOLD;
|
import static org.qortal.controller.arbitrary.ArbitraryDataStorageManager.DELETION_THRESHOLD;
|
||||||
|
|
||||||
@ -82,19 +77,6 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
final int limit = 100;
|
final int limit = 100;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
List<ArbitraryTransactionData> allArbitraryTransactionsInDescendingOrder;
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
allArbitraryTransactionsInDescendingOrder
|
|
||||||
= repository.getArbitraryRepository()
|
|
||||||
.getLatestArbitraryTransactions();
|
|
||||||
} catch( Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
allArbitraryTransactionsInDescendingOrder = new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<ArbitraryTransactionData> processedTransactions = new HashSet<>();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (!isStopping) {
|
while (!isStopping) {
|
||||||
Thread.sleep(30000);
|
Thread.sleep(30000);
|
||||||
@ -125,31 +107,27 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
|
|
||||||
// Any arbitrary transactions we want to fetch data for?
|
// Any arbitrary transactions we want to fetch data for?
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<ArbitraryTransactionData> transactions = allArbitraryTransactionsInDescendingOrder.stream().skip(offset).limit(limit).collect(Collectors.toList());
|
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, ARBITRARY_TX_TYPE, null, null, null, ConfirmationStatus.BOTH, limit, offset, true);
|
||||||
|
// LOGGER.info("Found {} arbitrary transactions at offset: {}, limit: {}", signatures.size(), offset, limit);
|
||||||
if (isStopping) {
|
if (isStopping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactions == null || transactions.isEmpty()) {
|
if (signatures == null || signatures.isEmpty()) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
allArbitraryTransactionsInDescendingOrder
|
continue;
|
||||||
= repository.getArbitraryRepository()
|
|
||||||
.getLatestArbitraryTransactions();
|
|
||||||
transactions = allArbitraryTransactionsInDescendingOrder.stream().limit(limit).collect(Collectors.toList());
|
|
||||||
processedTransactions.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += limit;
|
offset += limit;
|
||||||
now = NTP.getTime();
|
now = NTP.getTime();
|
||||||
|
|
||||||
// Loop through the signatures in this batch
|
// Loop through the signatures in this batch
|
||||||
for (int i=0; i<transactions.size(); i++) {
|
for (int i=0; i<signatures.size(); i++) {
|
||||||
if (isStopping) {
|
if (isStopping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArbitraryTransactionData arbitraryTransactionData = transactions.get(i);
|
byte[] signature = signatures.get(i);
|
||||||
if (arbitraryTransactionData == null) {
|
if (signature == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +136,9 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
Thread.sleep(5000);
|
Thread.sleep(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arbitraryTransactionData.getService() == null) {
|
// Fetch the transaction data
|
||||||
|
ArbitraryTransactionData arbitraryTransactionData = ArbitraryTransactionUtils.fetchTransactionData(repository, signature);
|
||||||
|
if (arbitraryTransactionData == null || arbitraryTransactionData.getService() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,8 +147,6 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean mostRecentTransaction = processedTransactions.add(arbitraryTransactionData);
|
|
||||||
|
|
||||||
// Check if we have the complete file
|
// Check if we have the complete file
|
||||||
boolean completeFileExists = ArbitraryTransactionUtils.completeFileExists(arbitraryTransactionData);
|
boolean completeFileExists = ArbitraryTransactionUtils.completeFileExists(arbitraryTransactionData);
|
||||||
|
|
||||||
@ -189,54 +167,20 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
LOGGER.info("Deleting transaction {} because we can't host its data",
|
LOGGER.info("Deleting transaction {} because we can't host its data",
|
||||||
Base58.encode(arbitraryTransactionData.getSignature()));
|
Base58.encode(arbitraryTransactionData.getSignature()));
|
||||||
ArbitraryTransactionUtils.deleteCompleteFileAndChunks(arbitraryTransactionData);
|
ArbitraryTransactionUtils.deleteCompleteFileAndChunks(arbitraryTransactionData);
|
||||||
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"can't store data, deleting",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we have had a more recent PUT
|
// Check to see if we have had a more recent PUT
|
||||||
if (!mostRecentTransaction) {
|
boolean hasMoreRecentPutTransaction = ArbitraryTransactionUtils.hasMoreRecentPutTransaction(repository, arbitraryTransactionData);
|
||||||
|
if (hasMoreRecentPutTransaction) {
|
||||||
// There is a more recent PUT transaction than the one we are currently processing.
|
// There is a more recent PUT transaction than the one we are currently processing.
|
||||||
// When a PUT is issued, it replaces any layers that would have been there before.
|
// When a PUT is issued, it replaces any layers that would have been there before.
|
||||||
// Therefore any data relating to this older transaction is no longer needed.
|
// Therefore any data relating to this older transaction is no longer needed.
|
||||||
LOGGER.info(String.format("Newer PUT found for %s %s since transaction %s. " +
|
LOGGER.info(String.format("Newer PUT found for %s %s since transaction %s. " +
|
||||||
"Deleting all files associated with the earlier transaction.", arbitraryTransactionData.getService(),
|
"Deleting all files associated with the earlier transaction.", arbitraryTransactionData.getService(),
|
||||||
arbitraryTransactionData.getName(), Base58.encode(arbitraryTransactionData.getSignature())));
|
arbitraryTransactionData.getName(), Base58.encode(signature)));
|
||||||
|
|
||||||
ArbitraryTransactionUtils.deleteCompleteFileAndChunks(arbitraryTransactionData);
|
ArbitraryTransactionUtils.deleteCompleteFileAndChunks(arbitraryTransactionData);
|
||||||
|
|
||||||
Optional<ArbitraryTransactionData> moreRecentPutTransaction
|
|
||||||
= processedTransactions.stream()
|
|
||||||
.filter(data -> data.equals(arbitraryTransactionData))
|
|
||||||
.findAny();
|
|
||||||
|
|
||||||
if( moreRecentPutTransaction.isPresent() ) {
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"deleting data due to replacement",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
moreRecentPutTransaction.get().getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOGGER.warn("Something went wrong with the most recent put transaction determination!");
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,21 +199,7 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
LOGGER.debug(String.format("Transaction %s has complete file and all chunks",
|
LOGGER.debug(String.format("Transaction %s has complete file and all chunks",
|
||||||
Base58.encode(arbitraryTransactionData.getSignature())));
|
Base58.encode(arbitraryTransactionData.getSignature())));
|
||||||
|
|
||||||
boolean wasDeleted = ArbitraryTransactionUtils.deleteCompleteFile(arbitraryTransactionData, now, STALE_FILE_TIMEOUT);
|
ArbitraryTransactionUtils.deleteCompleteFile(arbitraryTransactionData, now, STALE_FILE_TIMEOUT);
|
||||||
|
|
||||||
if( wasDeleted ) {
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"deleting file, retaining chunks",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +237,17 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
this.storageLimitReached(repository);
|
this.storageLimitReached(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete random data associated with name if we're over our storage limit for this name
|
||||||
|
// Use the DELETION_THRESHOLD, for the same reasons as above
|
||||||
|
for (String followedName : ListUtils.followedNames()) {
|
||||||
|
if (isStopping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!storageManager.isStorageSpaceAvailableForName(repository, followedName, DELETION_THRESHOLD)) {
|
||||||
|
this.storageLimitReachedForName(repository, followedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
LOGGER.error("Repository issue when cleaning up arbitrary transaction data", e);
|
LOGGER.error("Repository issue when cleaning up arbitrary transaction data", e);
|
||||||
}
|
}
|
||||||
@ -385,6 +326,25 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
// FUTURE: consider reducing the expiry time of the reader cache
|
// FUTURE: consider reducing the expiry time of the reader cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void storageLimitReachedForName(Repository repository, String name) throws InterruptedException {
|
||||||
|
// We think that the storage limit has been reached for supplied name - but we should double check
|
||||||
|
if (ArbitraryDataStorageManager.getInstance().isStorageSpaceAvailableForName(repository, name, DELETION_THRESHOLD)) {
|
||||||
|
// We have space available for this name, so don't delete anything
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a batch of random chunks associated with this name
|
||||||
|
// This reduces the chance of too many nodes deleting the same chunk
|
||||||
|
// when they reach their storage limit
|
||||||
|
Path dataPath = Paths.get(Settings.getInstance().getDataPath());
|
||||||
|
for (int i=0; i<CHUNK_DELETION_BATCH_SIZE; i++) {
|
||||||
|
if (isStopping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.deleteRandomFile(repository, dataPath.toFile(), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iteratively walk through given directory and delete a single random file
|
* Iteratively walk through given directory and delete a single random file
|
||||||
*
|
*
|
||||||
@ -463,7 +423,6 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("Deleting random file {} because we have reached max storage capacity...", randomItem.toString());
|
LOGGER.info("Deleting random file {} because we have reached max storage capacity...", randomItem.toString());
|
||||||
fireRandomItemDeletionNotification(randomItem, repository, "Deleting random file, because we have reached max storage capacity");
|
|
||||||
boolean success = randomItem.delete();
|
boolean success = randomItem.delete();
|
||||||
if (success) {
|
if (success) {
|
||||||
try {
|
try {
|
||||||
@ -478,35 +437,6 @@ public class ArbitraryDataCleanupManager extends Thread {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireRandomItemDeletionNotification(File randomItem, Repository repository, String reason) {
|
|
||||||
try {
|
|
||||||
Path parentFileNamePath = randomItem.toPath().toAbsolutePath().getParent().getFileName();
|
|
||||||
if (parentFileNamePath != null) {
|
|
||||||
String signature58 = parentFileNamePath.toString();
|
|
||||||
byte[] signature = Base58.decode(signature58);
|
|
||||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
|
||||||
if (transactionData != null && transactionData.getType() == Transaction.TransactionType.ARBITRARY) {
|
|
||||||
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData;
|
|
||||||
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
reason,
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanupTempDirectory(String folder, long now, long minAge) {
|
private void cleanupTempDirectory(String folder, long now, long minAge) {
|
||||||
String baseDir = Settings.getInstance().getTempDataPath();
|
String baseDir = Settings.getInstance().getTempDataPath();
|
||||||
Path tempDir = Paths.get(baseDir, folder);
|
Path tempDir = Paths.get(baseDir, folder);
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package org.qortal.controller.arbitrary;
|
|
||||||
|
|
||||||
public class ArbitraryDataExamination {
|
|
||||||
|
|
||||||
private boolean pass;
|
|
||||||
|
|
||||||
private String notes;
|
|
||||||
|
|
||||||
public ArbitraryDataExamination(boolean pass, String notes) {
|
|
||||||
this.pass = pass;
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPass() {
|
|
||||||
return pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotes() {
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,8 +5,6 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.data.arbitrary.ArbitraryFileListResponseInfo;
|
import org.qortal.data.arbitrary.ArbitraryFileListResponseInfo;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||||
import org.qortal.event.DataMonitorEvent;
|
|
||||||
import org.qortal.event.EventBus;
|
|
||||||
import org.qortal.network.Peer;
|
import org.qortal.network.Peer;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
@ -10,8 +10,6 @@ import org.qortal.arbitrary.misc.Service;
|
|||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.event.DataMonitorEvent;
|
|
||||||
import org.qortal.event.EventBus;
|
|
||||||
import org.qortal.network.Network;
|
import org.qortal.network.Network;
|
||||||
import org.qortal.network.Peer;
|
import org.qortal.network.Peer;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
@ -30,7 +28,6 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class ArbitraryDataManager extends Thread {
|
public class ArbitraryDataManager extends Thread {
|
||||||
|
|
||||||
@ -198,35 +195,13 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
final int limit = 100;
|
final int limit = 100;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
List<ArbitraryTransactionData> allArbitraryTransactionsInDescendingOrder;
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
if( name == null ) {
|
|
||||||
allArbitraryTransactionsInDescendingOrder
|
|
||||||
= repository.getArbitraryRepository()
|
|
||||||
.getLatestArbitraryTransactions();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
allArbitraryTransactionsInDescendingOrder
|
|
||||||
= repository.getArbitraryRepository()
|
|
||||||
.getLatestArbitraryTransactionsByName(name);
|
|
||||||
}
|
|
||||||
} catch( Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
allArbitraryTransactionsInDescendingOrder = new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect processed transactions in a set to ensure outdated data transactions do not get fetched
|
|
||||||
Set<ArbitraryTransactionDataHashWrapper> processedTransactions = new HashSet<>();
|
|
||||||
|
|
||||||
while (!isStopping) {
|
while (!isStopping) {
|
||||||
Thread.sleep(1000L);
|
Thread.sleep(1000L);
|
||||||
|
|
||||||
// Any arbitrary transactions we want to fetch data for?
|
// Any arbitrary transactions we want to fetch data for?
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<byte[]> signatures = processTransactionsForSignatures(limit, offset, allArbitraryTransactionsInDescendingOrder, processedTransactions);
|
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, ARBITRARY_TX_TYPE, null, name, null, ConfirmationStatus.BOTH, limit, offset, true);
|
||||||
|
// LOGGER.trace("Found {} arbitrary transactions at offset: {}, limit: {}", signatures.size(), offset, limit);
|
||||||
if (signatures == null || signatures.isEmpty()) {
|
if (signatures == null || signatures.isEmpty()) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
break;
|
break;
|
||||||
@ -248,38 +223,14 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) arbitraryTransaction.getTransactionData();
|
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) arbitraryTransaction.getTransactionData();
|
||||||
|
|
||||||
// Skip transactions that we don't need to proactively store data for
|
// Skip transactions that we don't need to proactively store data for
|
||||||
ArbitraryDataExamination arbitraryDataExamination = storageManager.shouldPreFetchData(repository, arbitraryTransactionData);
|
if (!storageManager.shouldPreFetchData(repository, arbitraryTransactionData)) {
|
||||||
if (!arbitraryDataExamination.isPass()) {
|
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
arbitraryDataExamination.getNotes(),
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove transactions that we already have local data for
|
// Remove transactions that we already have local data for
|
||||||
if (hasLocalData(arbitraryTransaction)) {
|
if (hasLocalData(arbitraryTransaction)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"already have local data, skipping",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,21 +248,8 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
|
|
||||||
// Check to see if we have had a more recent PUT
|
// Check to see if we have had a more recent PUT
|
||||||
ArbitraryTransactionData arbitraryTransactionData = ArbitraryTransactionUtils.fetchTransactionData(repository, signature);
|
ArbitraryTransactionData arbitraryTransactionData = ArbitraryTransactionUtils.fetchTransactionData(repository, signature);
|
||||||
|
boolean hasMoreRecentPutTransaction = ArbitraryTransactionUtils.hasMoreRecentPutTransaction(repository, arbitraryTransactionData);
|
||||||
Optional<ArbitraryTransactionData> moreRecentPutTransaction = ArbitraryTransactionUtils.hasMoreRecentPutTransaction(repository, arbitraryTransactionData);
|
if (hasMoreRecentPutTransaction) {
|
||||||
|
|
||||||
if (moreRecentPutTransaction.isPresent()) {
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"not fetching old data",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
moreRecentPutTransaction.get().getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// There is a more recent PUT transaction than the one we are currently processing.
|
// There is a more recent PUT transaction than the one we are currently processing.
|
||||||
// When a PUT is issued, it replaces any layers that would have been there before.
|
// When a PUT is issued, it replaces any layers that would have been there before.
|
||||||
// Therefore any data relating to this older transaction is no longer needed and we
|
// Therefore any data relating to this older transaction is no longer needed and we
|
||||||
@ -319,34 +257,10 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"fetching data",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ask our connected peers if they have files for this signature
|
// Ask our connected peers if they have files for this signature
|
||||||
// This process automatically then fetches the files themselves if a peer is found
|
// This process automatically then fetches the files themselves if a peer is found
|
||||||
fetchData(arbitraryTransactionData);
|
fetchData(arbitraryTransactionData);
|
||||||
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"fetched data",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
LOGGER.error("Repository issue when fetching arbitrary transaction data", e);
|
LOGGER.error("Repository issue when fetching arbitrary transaction data", e);
|
||||||
}
|
}
|
||||||
@ -360,20 +274,6 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
final int limit = 100;
|
final int limit = 100;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
List<ArbitraryTransactionData> allArbitraryTransactionsInDescendingOrder;
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
allArbitraryTransactionsInDescendingOrder
|
|
||||||
= repository.getArbitraryRepository()
|
|
||||||
.getLatestArbitraryTransactions();
|
|
||||||
} catch( Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
allArbitraryTransactionsInDescendingOrder = new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect processed transactions in a set to ensure outdated data transactions do not get fetched
|
|
||||||
Set<ArbitraryTransactionDataHashWrapper> processedTransactions = new HashSet<>();
|
|
||||||
|
|
||||||
while (!isStopping) {
|
while (!isStopping) {
|
||||||
final int minSeconds = 3;
|
final int minSeconds = 3;
|
||||||
final int maxSeconds = 10;
|
final int maxSeconds = 10;
|
||||||
@ -382,8 +282,8 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
|
|
||||||
// Any arbitrary transactions we want to fetch data for?
|
// Any arbitrary transactions we want to fetch data for?
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<byte[]> signatures = processTransactionsForSignatures(limit, offset, allArbitraryTransactionsInDescendingOrder, processedTransactions);
|
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, ARBITRARY_TX_TYPE, null, null, null, ConfirmationStatus.BOTH, limit, offset, true);
|
||||||
|
// LOGGER.trace("Found {} arbitrary transactions at offset: {}, limit: {}", signatures.size(), offset, limit);
|
||||||
if (signatures == null || signatures.isEmpty()) {
|
if (signatures == null || signatures.isEmpty()) {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
break;
|
break;
|
||||||
@ -428,74 +328,26 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No longer need to see if we have had a more recent PUT since we compared the transactions to process
|
// Check to see if we have had a more recent PUT
|
||||||
// to the transactions previously processed, so we can fetch the transactiondata, notify the event bus,
|
|
||||||
// fetch the metadata and notify the event bus again
|
|
||||||
ArbitraryTransactionData arbitraryTransactionData = ArbitraryTransactionUtils.fetchTransactionData(repository, signature);
|
ArbitraryTransactionData arbitraryTransactionData = ArbitraryTransactionUtils.fetchTransactionData(repository, signature);
|
||||||
|
boolean hasMoreRecentPutTransaction = ArbitraryTransactionUtils.hasMoreRecentPutTransaction(repository, arbitraryTransactionData);
|
||||||
|
if (hasMoreRecentPutTransaction) {
|
||||||
|
// There is a more recent PUT transaction than the one we are currently processing.
|
||||||
|
// When a PUT is issued, it replaces any layers that would have been there before.
|
||||||
|
// Therefore any data relating to this older transaction is no longer needed and we
|
||||||
|
// shouldn't fetch it from the network.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Ask our connected peers if they have metadata for this signature
|
// Ask our connected peers if they have metadata for this signature
|
||||||
fetchMetadata(arbitraryTransactionData);
|
fetchMetadata(arbitraryTransactionData);
|
||||||
|
|
||||||
EventBus.INSTANCE.notify(
|
|
||||||
new DataMonitorEvent(
|
|
||||||
System.currentTimeMillis(),
|
|
||||||
arbitraryTransactionData.getIdentifier(),
|
|
||||||
arbitraryTransactionData.getName(),
|
|
||||||
arbitraryTransactionData.getService().name(),
|
|
||||||
"fetched metadata",
|
|
||||||
arbitraryTransactionData.getTimestamp(),
|
|
||||||
arbitraryTransactionData.getTimestamp()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
LOGGER.error("Repository issue when fetching arbitrary transaction data", e);
|
LOGGER.error("Repository issue when fetching arbitrary transaction data", e);
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<byte[]> processTransactionsForSignatures(
|
|
||||||
int limit,
|
|
||||||
int offset,
|
|
||||||
List<ArbitraryTransactionData> transactionsInDescendingOrder,
|
|
||||||
Set<ArbitraryTransactionDataHashWrapper> processedTransactions) {
|
|
||||||
// these transactions are in descending order, latest transactions come first
|
|
||||||
List<ArbitraryTransactionData> transactions
|
|
||||||
= transactionsInDescendingOrder.stream()
|
|
||||||
.skip(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
// wrap the transactions, so they can be used for hashing and comparing
|
|
||||||
// Class ArbitraryTransactionDataHashWrapper supports hashCode() and equals(...) for this purpose
|
|
||||||
List<ArbitraryTransactionDataHashWrapper> wrappedTransactions
|
|
||||||
= transactions.stream()
|
|
||||||
.map(transaction -> new ArbitraryTransactionDataHashWrapper(transaction))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
// create a set of wrappers and populate it first to last, so that all outdated transactions get rejected
|
|
||||||
Set<ArbitraryTransactionDataHashWrapper> transactionsToProcess = new HashSet<>(wrappedTransactions.size());
|
|
||||||
for(ArbitraryTransactionDataHashWrapper wrappedTransaction : wrappedTransactions) {
|
|
||||||
transactionsToProcess.add(wrappedTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the matches for previously processed transactions,
|
|
||||||
// because these transactions have had updates that have already been processed
|
|
||||||
transactionsToProcess.removeAll(processedTransactions);
|
|
||||||
|
|
||||||
// add to processed transactions to compare and remove matches from future processing iterations
|
|
||||||
processedTransactions.addAll(transactionsToProcess);
|
|
||||||
|
|
||||||
List<byte[]> signatures
|
|
||||||
= transactionsToProcess.stream()
|
|
||||||
.map(transactionToProcess -> transactionToProcess.getData()
|
|
||||||
.getSignature())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return signatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArbitraryTransaction fetchTransaction(final Repository repository, byte[] signature) {
|
private ArbitraryTransaction fetchTransaction(final Repository repository, byte[] signature) {
|
||||||
try {
|
try {
|
||||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
||||||
|
@ -155,24 +155,31 @@ public class ArbitraryDataStorageManager extends Thread {
|
|||||||
* @param arbitraryTransactionData - the transaction
|
* @param arbitraryTransactionData - the transaction
|
||||||
* @return boolean - whether to prefetch or not
|
* @return boolean - whether to prefetch or not
|
||||||
*/
|
*/
|
||||||
public ArbitraryDataExamination shouldPreFetchData(Repository repository, ArbitraryTransactionData arbitraryTransactionData) {
|
public boolean shouldPreFetchData(Repository repository, ArbitraryTransactionData arbitraryTransactionData) {
|
||||||
String name = arbitraryTransactionData.getName();
|
String name = arbitraryTransactionData.getName();
|
||||||
|
|
||||||
// Only fetch data associated with hashes, as we already have RAW_DATA
|
// Only fetch data associated with hashes, as we already have RAW_DATA
|
||||||
if (arbitraryTransactionData.getDataType() != ArbitraryTransactionData.DataType.DATA_HASH) {
|
if (arbitraryTransactionData.getDataType() != ArbitraryTransactionData.DataType.DATA_HASH) {
|
||||||
return new ArbitraryDataExamination(false, "Only fetch data associated with hashes");
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't fetch anything more if we're (nearly) out of space
|
// Don't fetch anything more if we're (nearly) out of space
|
||||||
// Make sure to keep STORAGE_FULL_THRESHOLD considerably less than 1, to
|
// Make sure to keep STORAGE_FULL_THRESHOLD considerably less than 1, to
|
||||||
// avoid a fetch/delete loop
|
// avoid a fetch/delete loop
|
||||||
if (!this.isStorageSpaceAvailable(STORAGE_FULL_THRESHOLD)) {
|
if (!this.isStorageSpaceAvailable(STORAGE_FULL_THRESHOLD)) {
|
||||||
return new ArbitraryDataExamination(false,"Don't fetch anything more if we're (nearly) out of space");
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't fetch anything if we're (nearly) out of space for this name
|
||||||
|
// Again, make sure to keep STORAGE_FULL_THRESHOLD considerably less than 1, to
|
||||||
|
// avoid a fetch/delete loop
|
||||||
|
if (!this.isStorageSpaceAvailableForName(repository, arbitraryTransactionData.getName(), STORAGE_FULL_THRESHOLD)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't store data unless it's an allowed type (public/private)
|
// Don't store data unless it's an allowed type (public/private)
|
||||||
if (!this.isDataTypeAllowed(arbitraryTransactionData)) {
|
if (!this.isDataTypeAllowed(arbitraryTransactionData)) {
|
||||||
return new ArbitraryDataExamination(false, "Don't store data unless it's an allowed type (public/private)");
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle transactions without names differently
|
// Handle transactions without names differently
|
||||||
@ -182,21 +189,21 @@ public class ArbitraryDataStorageManager extends Thread {
|
|||||||
|
|
||||||
// Never fetch data from blocked names, even if they are followed
|
// Never fetch data from blocked names, even if they are followed
|
||||||
if (ListUtils.isNameBlocked(name)) {
|
if (ListUtils.isNameBlocked(name)) {
|
||||||
return new ArbitraryDataExamination(false, "blocked name");
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (Settings.getInstance().getStoragePolicy()) {
|
switch (Settings.getInstance().getStoragePolicy()) {
|
||||||
case FOLLOWED:
|
case FOLLOWED:
|
||||||
case FOLLOWED_OR_VIEWED:
|
case FOLLOWED_OR_VIEWED:
|
||||||
return new ArbitraryDataExamination(ListUtils.isFollowingName(name), Settings.getInstance().getStoragePolicy().name());
|
return ListUtils.isFollowingName(name);
|
||||||
|
|
||||||
case ALL:
|
case ALL:
|
||||||
return new ArbitraryDataExamination(true, Settings.getInstance().getStoragePolicy().name());
|
return true;
|
||||||
|
|
||||||
case NONE:
|
case NONE:
|
||||||
case VIEWED:
|
case VIEWED:
|
||||||
default:
|
default:
|
||||||
return new ArbitraryDataExamination(false, Settings.getInstance().getStoragePolicy().name());
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,17 +214,17 @@ public class ArbitraryDataStorageManager extends Thread {
|
|||||||
*
|
*
|
||||||
* @return boolean - whether the storage policy allows for unnamed data
|
* @return boolean - whether the storage policy allows for unnamed data
|
||||||
*/
|
*/
|
||||||
private ArbitraryDataExamination shouldPreFetchDataWithoutName() {
|
private boolean shouldPreFetchDataWithoutName() {
|
||||||
switch (Settings.getInstance().getStoragePolicy()) {
|
switch (Settings.getInstance().getStoragePolicy()) {
|
||||||
case ALL:
|
case ALL:
|
||||||
return new ArbitraryDataExamination(true, "Fetching all data");
|
return true;
|
||||||
|
|
||||||
case NONE:
|
case NONE:
|
||||||
case VIEWED:
|
case VIEWED:
|
||||||
case FOLLOWED:
|
case FOLLOWED:
|
||||||
case FOLLOWED_OR_VIEWED:
|
case FOLLOWED_OR_VIEWED:
|
||||||
default:
|
default:
|
||||||
return new ArbitraryDataExamination(false, Settings.getInstance().getStoragePolicy().name());
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,6 +484,51 @@ public class ArbitraryDataStorageManager extends Thread {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isStorageSpaceAvailableForName(Repository repository, String name, double threshold) {
|
||||||
|
if (!this.isStorageSpaceAvailable(threshold)) {
|
||||||
|
// No storage space available at all, so no need to check this name
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.getInstance().getStoragePolicy() == StoragePolicy.ALL) {
|
||||||
|
// Using storage policy ALL, so don't limit anything per name
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
// This transaction doesn't have a name, so fall back to total space limitations
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int followedNamesCount = ListUtils.followedNamesCount();
|
||||||
|
if (followedNamesCount == 0) {
|
||||||
|
// Not following any names, so we have space
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
long totalSizeForName = 0;
|
||||||
|
long maxStoragePerName = this.storageCapacityPerName(threshold);
|
||||||
|
|
||||||
|
// Fetch all hosted transactions
|
||||||
|
List<ArbitraryTransactionData> hostedTransactions = this.listAllHostedTransactions(repository, null, null);
|
||||||
|
for (ArbitraryTransactionData transactionData : hostedTransactions) {
|
||||||
|
String transactionName = transactionData.getName();
|
||||||
|
if (!Objects.equals(name, transactionName)) {
|
||||||
|
// Transaction relates to a different name
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSizeForName += transactionData.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have we reached the limit for this name?
|
||||||
|
if (totalSizeForName > maxStoragePerName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public long storageCapacityPerName(double threshold) {
|
public long storageCapacityPerName(double threshold) {
|
||||||
int followedNamesCount = ListUtils.followedNamesCount();
|
int followedNamesCount = ListUtils.followedNamesCount();
|
||||||
if (followedNamesCount == 0) {
|
if (followedNamesCount == 0) {
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package org.qortal.controller.arbitrary;
|
|
||||||
|
|
||||||
import org.qortal.arbitrary.misc.Service;
|
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class ArbitraryTransactionDataHashWrapper {
|
|
||||||
|
|
||||||
private ArbitraryTransactionData data;
|
|
||||||
|
|
||||||
private int service;
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private String identifier;
|
|
||||||
|
|
||||||
public ArbitraryTransactionDataHashWrapper(ArbitraryTransactionData data) {
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
this.service = data.getService().value;
|
|
||||||
this.name = data.getName();
|
|
||||||
this.identifier = data.getIdentifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArbitraryTransactionDataHashWrapper(int service, String name, String identifier) {
|
|
||||||
this.service = service;
|
|
||||||
this.name = name;
|
|
||||||
this.identifier = identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArbitraryTransactionData getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
ArbitraryTransactionDataHashWrapper that = (ArbitraryTransactionDataHashWrapper) o;
|
|
||||||
return service == that.service && name.equals(that.name) && Objects.equals(identifier, that.identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(service, name, identifier);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package org.qortal.controller.arbitrary;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.repository.Repository;
|
|
||||||
import org.qortal.repository.RepositoryManager;
|
|
||||||
|
|
||||||
import java.util.TimerTask;
|
|
||||||
|
|
||||||
public class RebuildArbitraryResourceCacheTask extends TimerTask {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(RebuildArbitraryResourceCacheTask.class);
|
|
||||||
|
|
||||||
public static final long MILLIS_IN_HOUR = 60 * 60 * 1000;
|
|
||||||
|
|
||||||
public static final long MILLIS_IN_MINUTE = 60 * 1000;
|
|
||||||
|
|
||||||
private static final String REBUILD_ARBITRARY_RESOURCE_CACHE_TASK = "Rebuild Arbitrary Resource Cache Task";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
Thread.currentThread().setName(REBUILD_ARBITRARY_RESOURCE_CACHE_TASK);
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
ArbitraryDataCacheManager.getInstance().buildArbitraryResourcesCache(repository, true);
|
|
||||||
}
|
|
||||||
catch( DataException e ) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,19 +2,15 @@ package org.qortal.controller.hsqldb;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.logging.log4j.util.PropertySource;
|
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
import org.qortal.data.account.BlockHeightRange;
|
|
||||||
import org.qortal.data.account.BlockHeightRangeAddressAmounts;
|
|
||||||
import org.qortal.repository.hsqldb.HSQLDBCacheUtils;
|
import org.qortal.repository.hsqldb.HSQLDBCacheUtils;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.utils.BalanceRecorderUtils;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class HSQLDBBalanceRecorder extends Thread{
|
public class HSQLDBBalanceRecorder extends Thread{
|
||||||
@ -27,8 +23,6 @@ public class HSQLDBBalanceRecorder extends Thread{
|
|||||||
|
|
||||||
private ConcurrentHashMap<String, List<AccountBalanceData>> balancesByAddress = new ConcurrentHashMap<>();
|
private ConcurrentHashMap<String, List<AccountBalanceData>> balancesByAddress = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> balanceDynamics = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
private int priorityRequested;
|
private int priorityRequested;
|
||||||
private int frequency;
|
private int frequency;
|
||||||
private int capacity;
|
private int capacity;
|
||||||
@ -67,52 +61,36 @@ public class HSQLDBBalanceRecorder extends Thread{
|
|||||||
|
|
||||||
Thread.currentThread().setName("Balance Recorder");
|
Thread.currentThread().setName("Balance Recorder");
|
||||||
|
|
||||||
HSQLDBCacheUtils.startRecordingBalances(this.balancesByHeight, this.balanceDynamics, this.priorityRequested, this.frequency, this.capacity);
|
HSQLDBCacheUtils.startRecordingBalances(this.balancesByHeight, this.balancesByAddress, this.priorityRequested, this.frequency, this.capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BlockHeightRangeAddressAmounts> getLatestDynamics(int limit, long offset) {
|
public List<AccountBalanceData> getLatestRecordings(int limit, long offset) {
|
||||||
|
ArrayList<AccountBalanceData> data;
|
||||||
|
|
||||||
List<BlockHeightRangeAddressAmounts> latest = this.balanceDynamics.stream()
|
Optional<Integer> lastHeight = getLastHeight();
|
||||||
.sorted(BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR.reversed())
|
|
||||||
.skip(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return latest;
|
if(lastHeight.isPresent() ) {
|
||||||
}
|
List<AccountBalanceData> latest = this.balancesByHeight.get(lastHeight.get());
|
||||||
|
|
||||||
public List<BlockHeightRange> getRanges(Integer offset, Integer limit, Boolean reverse) {
|
if( latest != null ) {
|
||||||
|
data = new ArrayList<>(latest.size());
|
||||||
if( reverse ) {
|
data.addAll(
|
||||||
return this.balanceDynamics.stream()
|
latest.stream()
|
||||||
.map(BlockHeightRangeAddressAmounts::getRange)
|
.sorted(Comparator.comparingDouble(AccountBalanceData::getBalance).reversed())
|
||||||
.sorted(BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_COMPARATOR.reversed())
|
.skip(offset)
|
||||||
.skip(offset)
|
.limit(limit)
|
||||||
.limit(limit)
|
.collect(Collectors.toList())
|
||||||
.collect(Collectors.toList());
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data = new ArrayList<>(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return this.balanceDynamics.stream()
|
data = new ArrayList<>(0);
|
||||||
.map(BlockHeightRangeAddressAmounts::getRange)
|
|
||||||
.sorted(BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_COMPARATOR)
|
|
||||||
.skip(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<BlockHeightRangeAddressAmounts> getAddressAmounts(BlockHeightRange range) {
|
return data;
|
||||||
|
|
||||||
return this.balanceDynamics.stream()
|
|
||||||
.filter( dynamic -> dynamic.getRange().equals(range))
|
|
||||||
.findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<BlockHeightRange> getRange( int height ) {
|
|
||||||
return this.balanceDynamics.stream()
|
|
||||||
.map(BlockHeightRangeAddressAmounts::getRange)
|
|
||||||
.filter( range -> range.getBegin() < height && range.getEnd() >= height )
|
|
||||||
.findAny();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Integer> getLastHeight() {
|
private Optional<Integer> getLastHeight() {
|
||||||
|
@ -83,7 +83,6 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
|||||||
return this.bitcoinjContext;
|
return this.bitcoinjContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCurrencyCode() {
|
public String getCurrencyCode() {
|
||||||
return this.currencyCode;
|
return this.currencyCode;
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package org.qortal.crosschain;
|
|||||||
|
|
||||||
public interface ForeignBlockchain {
|
public interface ForeignBlockchain {
|
||||||
|
|
||||||
public String getCurrencyCode();
|
|
||||||
|
|
||||||
public boolean isValidAddress(String address);
|
public boolean isValidAddress(String address);
|
||||||
|
|
||||||
public boolean isValidWalletKey(String walletKey);
|
public boolean isValidWalletKey(String walletKey);
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
package org.qortal.data.account;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class AddressAmountData {
|
|
||||||
|
|
||||||
private String address;
|
|
||||||
|
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
|
||||||
private long amount;
|
|
||||||
|
|
||||||
public AddressAmountData() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressAmountData(String address, long amount) {
|
|
||||||
|
|
||||||
this.address = address;
|
|
||||||
this.amount = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
AddressAmountData that = (AddressAmountData) o;
|
|
||||||
return amount == that.amount && Objects.equals(address, that.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(address, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "AddressAmountData{" +
|
|
||||||
"address='" + address + '\'' +
|
|
||||||
", amount=" + amount +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package org.qortal.data.account;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class BlockHeightRange {
|
|
||||||
|
|
||||||
private int begin;
|
|
||||||
|
|
||||||
private int end;
|
|
||||||
|
|
||||||
private boolean isRewardDistribution;
|
|
||||||
|
|
||||||
public BlockHeightRange() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockHeightRange(int begin, int end, boolean isRewardDistribution) {
|
|
||||||
this.begin = begin;
|
|
||||||
this.end = end;
|
|
||||||
this.isRewardDistribution = isRewardDistribution;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBegin() {
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEnd() {
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRewardDistribution() {
|
|
||||||
return isRewardDistribution;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
BlockHeightRange that = (BlockHeightRange) o;
|
|
||||||
return begin == that.begin && end == that.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(begin, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "BlockHeightRange{" +
|
|
||||||
"begin=" + begin +
|
|
||||||
", end=" + end +
|
|
||||||
", isRewardDistribution=" + isRewardDistribution +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package org.qortal.data.account;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class BlockHeightRangeAddressAmounts {
|
|
||||||
|
|
||||||
private BlockHeightRange range;
|
|
||||||
|
|
||||||
private List<AddressAmountData> amounts;
|
|
||||||
|
|
||||||
public BlockHeightRangeAddressAmounts() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockHeightRangeAddressAmounts(BlockHeightRange range, List<AddressAmountData> amounts) {
|
|
||||||
this.range = range;
|
|
||||||
this.amounts = amounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockHeightRange getRange() {
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AddressAmountData> getAmounts() {
|
|
||||||
return amounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
BlockHeightRangeAddressAmounts that = (BlockHeightRangeAddressAmounts) o;
|
|
||||||
return Objects.equals(range, that.range) && Objects.equals(amounts, that.amounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(range, amounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "BlockHeightRangeAddressAmounts{" +
|
|
||||||
"range=" + range +
|
|
||||||
", amounts=" + amounts +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package org.qortal.data.arbitrary;
|
|
||||||
|
|
||||||
import org.qortal.arbitrary.misc.Service;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class ArbitraryDataIndex {
|
|
||||||
|
|
||||||
public String t;
|
|
||||||
public String n;
|
|
||||||
public int c;
|
|
||||||
public String l;
|
|
||||||
|
|
||||||
public ArbitraryDataIndex() {}
|
|
||||||
|
|
||||||
public ArbitraryDataIndex(String t, String n, int c, String l) {
|
|
||||||
this.t = t;
|
|
||||||
this.n = n;
|
|
||||||
this.c = c;
|
|
||||||
this.l = l;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ArbitraryDataIndex{" +
|
|
||||||
"t='" + t + '\'' +
|
|
||||||
", n='" + n + '\'' +
|
|
||||||
", c=" + c +
|
|
||||||
", l='" + l + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package org.qortal.data.arbitrary;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class ArbitraryDataIndexDetail {
|
|
||||||
|
|
||||||
public String issuer;
|
|
||||||
public int rank;
|
|
||||||
public String term;
|
|
||||||
public String name;
|
|
||||||
public int category;
|
|
||||||
public String link;
|
|
||||||
public String indexIdentifer;
|
|
||||||
|
|
||||||
public ArbitraryDataIndexDetail() {}
|
|
||||||
|
|
||||||
public ArbitraryDataIndexDetail(String issuer, int rank, ArbitraryDataIndex index, String indexIdentifer) {
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.rank = rank;
|
|
||||||
this.term = index.t;
|
|
||||||
this.name = index.n;
|
|
||||||
this.category = index.c;
|
|
||||||
this.link = index.l;
|
|
||||||
this.indexIdentifer = indexIdentifer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ArbitraryDataIndexDetail{" +
|
|
||||||
"issuer='" + issuer + '\'' +
|
|
||||||
", rank=" + rank +
|
|
||||||
", term='" + term + '\'' +
|
|
||||||
", name='" + name + '\'' +
|
|
||||||
", category=" + category +
|
|
||||||
", link='" + link + '\'' +
|
|
||||||
", indexIdentifer='" + indexIdentifer + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package org.qortal.data.arbitrary;
|
|
||||||
|
|
||||||
import org.qortal.arbitrary.misc.Service;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class ArbitraryDataIndexScoreKey {
|
|
||||||
|
|
||||||
public String name;
|
|
||||||
public int category;
|
|
||||||
public String link;
|
|
||||||
|
|
||||||
public ArbitraryDataIndexScoreKey() {}
|
|
||||||
|
|
||||||
public ArbitraryDataIndexScoreKey(String name, int category, String link) {
|
|
||||||
this.name = name;
|
|
||||||
this.category = category;
|
|
||||||
this.link = link;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
ArbitraryDataIndexScoreKey that = (ArbitraryDataIndexScoreKey) o;
|
|
||||||
return category == that.category && Objects.equals(name, that.name) && Objects.equals(link, that.link);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(name, category, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package org.qortal.data.arbitrary;
|
|
||||||
|
|
||||||
import org.qortal.arbitrary.misc.Service;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class ArbitraryDataIndexScorecard {
|
|
||||||
|
|
||||||
public double score;
|
|
||||||
public String name;
|
|
||||||
public int category;
|
|
||||||
public String link;
|
|
||||||
|
|
||||||
public ArbitraryDataIndexScorecard() {}
|
|
||||||
|
|
||||||
public ArbitraryDataIndexScorecard(double score, String name, int category, String link) {
|
|
||||||
this.score = score;
|
|
||||||
this.name = name;
|
|
||||||
this.category = category;
|
|
||||||
this.link = link;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getScore() {
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ArbitraryDataIndexScorecard{" +
|
|
||||||
"score=" + score +
|
|
||||||
", name='" + name + '\'' +
|
|
||||||
", category=" + category +
|
|
||||||
", link='" + link + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package org.qortal.data.arbitrary;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class DataMonitorInfo {
|
|
||||||
private long timestamp;
|
|
||||||
private String identifier;
|
|
||||||
private String name;
|
|
||||||
private String service;
|
|
||||||
private String description;
|
|
||||||
private long transactionTimestamp;
|
|
||||||
private long latestPutTimestamp;
|
|
||||||
|
|
||||||
public DataMonitorInfo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataMonitorInfo(long timestamp, String identifier, String name, String service, String description, long transactionTimestamp, long latestPutTimestamp) {
|
|
||||||
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.identifier = identifier;
|
|
||||||
this.name = name;
|
|
||||||
this.service = service;
|
|
||||||
this.description = description;
|
|
||||||
this.transactionTimestamp = transactionTimestamp;
|
|
||||||
this.latestPutTimestamp = latestPutTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdentifier() {
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getService() {
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTransactionTimestamp() {
|
|
||||||
return transactionTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLatestPutTimestamp() {
|
|
||||||
return latestPutTimestamp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package org.qortal.data.arbitrary;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
public class IndexCache {
|
|
||||||
|
|
||||||
public static final IndexCache SINGLETON = new IndexCache();
|
|
||||||
private ConcurrentHashMap<String, List<ArbitraryDataIndexDetail>> indicesByTerm = new ConcurrentHashMap<>();
|
|
||||||
private ConcurrentHashMap<String, List<ArbitraryDataIndexDetail>> indicesByIssuer = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public static IndexCache getInstance() {
|
|
||||||
return SINGLETON;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConcurrentHashMap<String, List<ArbitraryDataIndexDetail>> getIndicesByTerm() {
|
|
||||||
return indicesByTerm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConcurrentHashMap<String, List<ArbitraryDataIndexDetail>> getIndicesByIssuer() {
|
|
||||||
return indicesByIssuer;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package org.qortal.data.block;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAX-RS
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class DecodedOnlineAccountData {
|
|
||||||
|
|
||||||
private long onlineTimestamp;
|
|
||||||
private String minter;
|
|
||||||
private String recipient;
|
|
||||||
private int sharePercent;
|
|
||||||
private boolean minterGroupMember;
|
|
||||||
private String name;
|
|
||||||
private int level;
|
|
||||||
|
|
||||||
public DecodedOnlineAccountData() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public DecodedOnlineAccountData(long onlineTimestamp, String minter, String recipient, int sharePercent, boolean minterGroupMember, String name, int level) {
|
|
||||||
this.onlineTimestamp = onlineTimestamp;
|
|
||||||
this.minter = minter;
|
|
||||||
this.recipient = recipient;
|
|
||||||
this.sharePercent = sharePercent;
|
|
||||||
this.minterGroupMember = minterGroupMember;
|
|
||||||
this.name = name;
|
|
||||||
this.level = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getOnlineTimestamp() {
|
|
||||||
return onlineTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMinter() {
|
|
||||||
return minter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRecipient() {
|
|
||||||
return recipient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSharePercent() {
|
|
||||||
return sharePercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMinterGroupMember() {
|
|
||||||
return minterGroupMember;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLevel() {
|
|
||||||
return level;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
DecodedOnlineAccountData that = (DecodedOnlineAccountData) o;
|
|
||||||
return onlineTimestamp == that.onlineTimestamp && sharePercent == that.sharePercent && minterGroupMember == that.minterGroupMember && level == that.level && Objects.equals(minter, that.minter) && Objects.equals(recipient, that.recipient) && Objects.equals(name, that.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(onlineTimestamp, minter, recipient, sharePercent, minterGroupMember, name, level);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "DecodedOnlineAccountData{" +
|
|
||||||
"onlineTimestamp=" + onlineTimestamp +
|
|
||||||
", minter='" + minter + '\'' +
|
|
||||||
", recipient='" + recipient + '\'' +
|
|
||||||
", sharePercent=" + sharePercent +
|
|
||||||
", minterGroupMember=" + minterGroupMember +
|
|
||||||
", name='" + name + '\'' +
|
|
||||||
", level=" + level +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package org.qortal.data.system;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class DbConnectionInfo {
|
|
||||||
|
|
||||||
private long updated;
|
|
||||||
|
|
||||||
private String owner;
|
|
||||||
|
|
||||||
private String state;
|
|
||||||
|
|
||||||
public DbConnectionInfo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbConnectionInfo(long timeOpened, String owner, String state) {
|
|
||||||
this.updated = timeOpened;
|
|
||||||
this.owner = owner;
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getUpdated() {
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOwner() {
|
|
||||||
return owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package org.qortal.data.system;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class SystemInfo {
|
|
||||||
|
|
||||||
private long freeMemory;
|
|
||||||
|
|
||||||
private long memoryInUse;
|
|
||||||
|
|
||||||
private long totalMemory;
|
|
||||||
|
|
||||||
private long maxMemory;
|
|
||||||
|
|
||||||
private int availableProcessors;
|
|
||||||
|
|
||||||
public SystemInfo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public SystemInfo(long freeMemory, long memoryInUse, long totalMemory, long maxMemory, int availableProcessors) {
|
|
||||||
this.freeMemory = freeMemory;
|
|
||||||
this.memoryInUse = memoryInUse;
|
|
||||||
this.totalMemory = totalMemory;
|
|
||||||
this.maxMemory = maxMemory;
|
|
||||||
this.availableProcessors = availableProcessors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getFreeMemory() {
|
|
||||||
return freeMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMemoryInUse() {
|
|
||||||
return memoryInUse;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTotalMemory() {
|
|
||||||
return totalMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMaxMemory() {
|
|
||||||
return maxMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAvailableProcessors() {
|
|
||||||
return availableProcessors;
|
|
||||||
}
|
|
||||||
}
|
|
@ -200,26 +200,4 @@ public class ArbitraryTransactionData extends TransactionData {
|
|||||||
return this.payments;
|
return this.payments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ArbitraryTransactionData{" +
|
|
||||||
"version=" + version +
|
|
||||||
", service=" + service +
|
|
||||||
", nonce=" + nonce +
|
|
||||||
", size=" + size +
|
|
||||||
", name='" + name + '\'' +
|
|
||||||
", identifier='" + identifier + '\'' +
|
|
||||||
", method=" + method +
|
|
||||||
", compression=" + compression +
|
|
||||||
", dataType=" + dataType +
|
|
||||||
", type=" + type +
|
|
||||||
", timestamp=" + timestamp +
|
|
||||||
", fee=" + fee +
|
|
||||||
", txGroupId=" + txGroupId +
|
|
||||||
", blockHeight=" + blockHeight +
|
|
||||||
", blockSequence=" + blockSequence +
|
|
||||||
", approvalStatus=" + approvalStatus +
|
|
||||||
", approvalHeight=" + approvalHeight +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package org.qortal.event;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
public class DataMonitorEvent implements Event{
|
|
||||||
private long timestamp;
|
|
||||||
private String identifier;
|
|
||||||
private String name;
|
|
||||||
private String service;
|
|
||||||
private String description;
|
|
||||||
private long transactionTimestamp;
|
|
||||||
private long latestPutTimestamp;
|
|
||||||
|
|
||||||
public DataMonitorEvent() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public DataMonitorEvent(long timestamp, String identifier, String name, String service, String description, long transactionTimestamp, long latestPutTimestamp) {
|
|
||||||
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.identifier = identifier;
|
|
||||||
this.name = name;
|
|
||||||
this.service = service;
|
|
||||||
this.description = description;
|
|
||||||
this.transactionTimestamp = transactionTimestamp;
|
|
||||||
this.latestPutTimestamp = latestPutTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdentifier() {
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getService() {
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTransactionTimestamp() {
|
|
||||||
return transactionTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLatestPutTimestamp() {
|
|
||||||
return latestPutTimestamp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ package org.qortal.group;
|
|||||||
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.account.PublicKeyAccount;
|
import org.qortal.account.PublicKeyAccount;
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.group.*;
|
import org.qortal.data.group.*;
|
||||||
@ -151,12 +150,7 @@ public class Group {
|
|||||||
// Adminship
|
// Adminship
|
||||||
|
|
||||||
private GroupAdminData getAdmin(String admin) throws DataException {
|
private GroupAdminData getAdmin(String admin) throws DataException {
|
||||||
if( repository.getBlockRepository().getBlockchainHeight() < BlockChain.getInstance().getAdminQueryFixHeight()) {
|
return groupRepository.getAdmin(this.groupData.getGroupId(), admin);
|
||||||
return groupRepository.getAdminFaulty(this.groupData.getGroupId(), admin);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return groupRepository.getAdmin(this.groupData.getGroupId(), admin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean adminExists(String admin) throws DataException {
|
private boolean adminExists(String admin) throws DataException {
|
||||||
@ -674,8 +668,8 @@ public class Group {
|
|||||||
public void uninvite(GroupInviteTransactionData groupInviteTransactionData) throws DataException {
|
public void uninvite(GroupInviteTransactionData groupInviteTransactionData) throws DataException {
|
||||||
String invitee = groupInviteTransactionData.getInvitee();
|
String invitee = groupInviteTransactionData.getInvitee();
|
||||||
|
|
||||||
// If member exists and the join request is present then they were added when invite matched join request
|
// If member exists then they were added when invite matched join request
|
||||||
if (this.memberExists(invitee) && groupInviteTransactionData.getJoinReference() != null) {
|
if (this.memberExists(invitee)) {
|
||||||
// Rebuild join request using cached reference to transaction that created join request.
|
// Rebuild join request using cached reference to transaction that created join request.
|
||||||
this.rebuildJoinRequest(invitee, groupInviteTransactionData.getJoinReference());
|
this.rebuildJoinRequest(invitee, groupInviteTransactionData.getJoinReference());
|
||||||
|
|
||||||
|
@ -76,9 +76,9 @@ public interface ATRepository {
|
|||||||
* Although <tt>expectedValue</tt>, if provided, is natively an unsigned long,
|
* Although <tt>expectedValue</tt>, if provided, is natively an unsigned long,
|
||||||
* the data segment comparison is done via unsigned hex string.
|
* the data segment comparison is done via unsigned hex string.
|
||||||
*/
|
*/
|
||||||
public List<ATStateData> getMatchingFinalATStates(byte[] codeHash, byte[] buyerPublicKey, byte[] sellerPublicKey, Boolean isFinished,
|
public List<ATStateData> getMatchingFinalATStates(byte[] codeHash, Boolean isFinished,
|
||||||
Integer dataByteOffset, Long expectedValue, Integer minimumFinalHeight,
|
Integer dataByteOffset, Long expectedValue, Integer minimumFinalHeight,
|
||||||
Integer limit, Integer offset, Boolean reverse) throws DataException;
|
Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns final ATStateData for ATs matching codeHash (required)
|
* Returns final ATStateData for ATs matching codeHash (required)
|
||||||
|
@ -27,10 +27,6 @@ public interface ArbitraryRepository {
|
|||||||
|
|
||||||
public List<ArbitraryTransactionData> getArbitraryTransactions(String name, Service service, String identifier, long since) throws DataException;
|
public List<ArbitraryTransactionData> getArbitraryTransactions(String name, Service service, String identifier, long since) throws DataException;
|
||||||
|
|
||||||
List<ArbitraryTransactionData> getLatestArbitraryTransactions() throws DataException;
|
|
||||||
|
|
||||||
List<ArbitraryTransactionData> getLatestArbitraryTransactionsByName(String name) throws DataException;
|
|
||||||
|
|
||||||
public ArbitraryTransactionData getInitialTransaction(String name, Service service, Method method, String identifier) throws DataException;
|
public ArbitraryTransactionData getInitialTransaction(String name, Service service, Method method, String identifier) throws DataException;
|
||||||
|
|
||||||
public ArbitraryTransactionData getLatestTransaction(String name, Service service, Method method, String identifier) throws DataException;
|
public ArbitraryTransactionData getLatestTransaction(String name, Service service, Method method, String identifier) throws DataException;
|
||||||
@ -46,7 +42,7 @@ public interface ArbitraryRepository {
|
|||||||
|
|
||||||
public List<ArbitraryResourceData> getArbitraryResources(Service service, String identifier, List<String> names, boolean defaultResource, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
public List<ArbitraryResourceData> getArbitraryResources(Service service, String identifier, List<String> names, boolean defaultResource, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public List<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, List<String> keywords, boolean prefixOnly, List<String> namesFilter, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
public List<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, boolean prefixOnly, List<String> namesFilter, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked, Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
List<ArbitraryResourceData> searchArbitraryResourcesSimple(
|
List<ArbitraryResourceData> searchArbitraryResourcesSimple(
|
||||||
Service service,
|
Service service,
|
||||||
|
@ -48,8 +48,6 @@ public interface GroupRepository {
|
|||||||
|
|
||||||
// Group Admins
|
// Group Admins
|
||||||
|
|
||||||
public GroupAdminData getAdminFaulty(int groupId, String address) throws DataException;
|
|
||||||
|
|
||||||
public GroupAdminData getAdmin(int groupId, String address) throws DataException;
|
public GroupAdminData getAdmin(int groupId, String address) throws DataException;
|
||||||
|
|
||||||
public boolean adminExists(int groupId, String address) throws DataException;
|
public boolean adminExists(int groupId, String address) throws DataException;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package org.qortal.repository.hsqldb;
|
package org.qortal.repository.hsqldb;
|
||||||
|
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.crypto.Crypto;
|
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.at.ATStateData;
|
import org.qortal.data.at.ATStateData;
|
||||||
import org.qortal.repository.ATRepository;
|
import org.qortal.repository.ATRepository;
|
||||||
@ -18,8 +16,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.qortal.data.account.AccountData;
|
|
||||||
|
|
||||||
public class HSQLDBATRepository implements ATRepository {
|
public class HSQLDBATRepository implements ATRepository {
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(HSQLDBATRepository.class);
|
private static final Logger LOGGER = LogManager.getLogger(HSQLDBATRepository.class);
|
||||||
@ -404,9 +400,9 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ATStateData> getMatchingFinalATStates(byte[] codeHash, byte[] buyerPublicKey, byte[] sellerPublicKey, Boolean isFinished,
|
public List<ATStateData> getMatchingFinalATStates(byte[] codeHash, Boolean isFinished,
|
||||||
Integer dataByteOffset, Long expectedValue, Integer minimumFinalHeight,
|
Integer dataByteOffset, Long expectedValue, Integer minimumFinalHeight,
|
||||||
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
StringBuilder sql = new StringBuilder(1024);
|
StringBuilder sql = new StringBuilder(1024);
|
||||||
List<Object> bindParams = new ArrayList<>();
|
List<Object> bindParams = new ArrayList<>();
|
||||||
|
|
||||||
@ -425,14 +421,10 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
|
|
||||||
// Order by AT_address and height to use compound primary key as index
|
// Order by AT_address and height to use compound primary key as index
|
||||||
// Both must be the same direction (DESC) also
|
// Both must be the same direction (DESC) also
|
||||||
sql.append("ORDER BY ATStates.height DESC LIMIT 1) AS FinalATStates ");
|
sql.append("ORDER BY ATStates.AT_address DESC, ATStates.height DESC "
|
||||||
|
+ "LIMIT 1 "
|
||||||
// Optional JOIN with ATTRANSACTIONS for buyerAddress
|
+ ") AS FinalATStates "
|
||||||
if (buyerPublicKey != null && buyerPublicKey.length > 0) {
|
+ "WHERE code_hash = ? ");
|
||||||
sql.append("JOIN ATTRANSACTIONS tx ON tx.at_address = ATs.AT_address ");
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.append("WHERE ATs.code_hash = ? ");
|
|
||||||
bindParams.add(codeHash);
|
bindParams.add(codeHash);
|
||||||
|
|
||||||
if (isFinished != null) {
|
if (isFinished != null) {
|
||||||
@ -451,20 +443,6 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
bindParams.add(rawExpectedValue);
|
bindParams.add(rawExpectedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buyerPublicKey != null && buyerPublicKey.length > 0 ) {
|
|
||||||
// the buyer must be the recipient of the transaction and not the creator of the AT
|
|
||||||
sql.append("AND tx.recipient = ? AND ATs.creator != ? ");
|
|
||||||
|
|
||||||
bindParams.add(Crypto.toAddress(buyerPublicKey));
|
|
||||||
bindParams.add(buyerPublicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (sellerPublicKey != null && sellerPublicKey.length > 0) {
|
|
||||||
sql.append("AND ATs.creator = ? ");
|
|
||||||
bindParams.add(sellerPublicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.append(" ORDER BY FinalATStates.height ");
|
sql.append(" ORDER BY FinalATStates.height ");
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql.append("DESC");
|
sql.append("DESC");
|
||||||
@ -505,7 +483,7 @@ public class HSQLDBATRepository implements ATRepository {
|
|||||||
Integer dataByteOffset, Long expectedValue,
|
Integer dataByteOffset, Long expectedValue,
|
||||||
int minimumCount, int maximumCount, long minimumPeriod) throws DataException {
|
int minimumCount, int maximumCount, long minimumPeriod) throws DataException {
|
||||||
// We need most recent entry first so we can use its timestamp to slice further results
|
// We need most recent entry first so we can use its timestamp to slice further results
|
||||||
List<ATStateData> mostRecentStates = this.getMatchingFinalATStates(codeHash, null, null, isFinished,
|
List<ATStateData> mostRecentStates = this.getMatchingFinalATStates(codeHash, isFinished,
|
||||||
dataByteOffset, expectedValue, null,
|
dataByteOffset, expectedValue, null,
|
||||||
1, 0, true);
|
1, 0, true);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import org.qortal.arbitrary.ArbitraryDataFile;
|
|||||||
import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata;
|
import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata;
|
||||||
import org.qortal.arbitrary.misc.Category;
|
import org.qortal.arbitrary.misc.Category;
|
||||||
import org.qortal.arbitrary.misc.Service;
|
import org.qortal.arbitrary.misc.Service;
|
||||||
|
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceCache;
|
import org.qortal.data.arbitrary.ArbitraryResourceCache;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
||||||
@ -28,7 +29,6 @@ import org.qortal.utils.ListUtils;
|
|||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -227,144 +227,6 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ArbitraryTransactionData> getLatestArbitraryTransactions() throws DataException {
|
|
||||||
String sql = "SELECT type, reference, signature, creator, created_when, fee, " +
|
|
||||||
"tx_group_id, block_height, approval_status, approval_height, " +
|
|
||||||
"version, nonce, service, size, is_data_raw, data, metadata_hash, " +
|
|
||||||
"name, identifier, update_method, secret, compression FROM ArbitraryTransactions " +
|
|
||||||
"JOIN Transactions USING (signature) " +
|
|
||||||
"WHERE name IS NOT NULL " +
|
|
||||||
"ORDER BY created_when DESC";
|
|
||||||
List<ArbitraryTransactionData> arbitraryTransactionData = new ArrayList<>();
|
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
|
||||||
if (resultSet == null)
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
|
|
||||||
do {
|
|
||||||
byte[] reference = resultSet.getBytes(2);
|
|
||||||
byte[] signature = resultSet.getBytes(3);
|
|
||||||
byte[] creatorPublicKey = resultSet.getBytes(4);
|
|
||||||
long timestamp = resultSet.getLong(5);
|
|
||||||
|
|
||||||
Long fee = resultSet.getLong(6);
|
|
||||||
if (fee == 0 && resultSet.wasNull())
|
|
||||||
fee = null;
|
|
||||||
|
|
||||||
int txGroupId = resultSet.getInt(7);
|
|
||||||
|
|
||||||
Integer blockHeight = resultSet.getInt(8);
|
|
||||||
if (blockHeight == 0 && resultSet.wasNull())
|
|
||||||
blockHeight = null;
|
|
||||||
|
|
||||||
ApprovalStatus approvalStatus = ApprovalStatus.valueOf(resultSet.getInt(9));
|
|
||||||
Integer approvalHeight = resultSet.getInt(10);
|
|
||||||
if (approvalHeight == 0 && resultSet.wasNull())
|
|
||||||
approvalHeight = null;
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, approvalStatus, blockHeight, approvalHeight, signature);
|
|
||||||
|
|
||||||
int version = resultSet.getInt(11);
|
|
||||||
int nonce = resultSet.getInt(12);
|
|
||||||
int serviceInt = resultSet.getInt(13);
|
|
||||||
int size = resultSet.getInt(14);
|
|
||||||
boolean isDataRaw = resultSet.getBoolean(15); // NOT NULL, so no null to false
|
|
||||||
DataType dataType = isDataRaw ? DataType.RAW_DATA : DataType.DATA_HASH;
|
|
||||||
byte[] data = resultSet.getBytes(16);
|
|
||||||
byte[] metadataHash = resultSet.getBytes(17);
|
|
||||||
String nameResult = resultSet.getString(18);
|
|
||||||
String identifierResult = resultSet.getString(19);
|
|
||||||
Method method = Method.valueOf(resultSet.getInt(20));
|
|
||||||
byte[] secret = resultSet.getBytes(21);
|
|
||||||
Compression compression = Compression.valueOf(resultSet.getInt(22));
|
|
||||||
// FUTURE: get payments from signature if needed. Avoiding for now to reduce database calls.
|
|
||||||
|
|
||||||
ArbitraryTransactionData transactionData = new ArbitraryTransactionData(baseTransactionData,
|
|
||||||
version, serviceInt, nonce, size, nameResult, identifierResult, method, secret,
|
|
||||||
compression, data, dataType, metadataHash, null);
|
|
||||||
|
|
||||||
arbitraryTransactionData.add(transactionData);
|
|
||||||
} while (resultSet.next());
|
|
||||||
|
|
||||||
return arbitraryTransactionData;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new DataException("Unable to fetch arbitrary transactions from repository", e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ArbitraryTransactionData> getLatestArbitraryTransactionsByName( String name ) throws DataException {
|
|
||||||
String sql = "SELECT type, reference, signature, creator, created_when, fee, " +
|
|
||||||
"tx_group_id, block_height, approval_status, approval_height, " +
|
|
||||||
"version, nonce, service, size, is_data_raw, data, metadata_hash, " +
|
|
||||||
"name, identifier, update_method, secret, compression FROM ArbitraryTransactions " +
|
|
||||||
"JOIN Transactions USING (signature) " +
|
|
||||||
"WHERE name = ? " +
|
|
||||||
"ORDER BY created_when DESC";
|
|
||||||
List<ArbitraryTransactionData> arbitraryTransactionData = new ArrayList<>();
|
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, name)) {
|
|
||||||
if (resultSet == null)
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
|
|
||||||
do {
|
|
||||||
byte[] reference = resultSet.getBytes(2);
|
|
||||||
byte[] signature = resultSet.getBytes(3);
|
|
||||||
byte[] creatorPublicKey = resultSet.getBytes(4);
|
|
||||||
long timestamp = resultSet.getLong(5);
|
|
||||||
|
|
||||||
Long fee = resultSet.getLong(6);
|
|
||||||
if (fee == 0 && resultSet.wasNull())
|
|
||||||
fee = null;
|
|
||||||
|
|
||||||
int txGroupId = resultSet.getInt(7);
|
|
||||||
|
|
||||||
Integer blockHeight = resultSet.getInt(8);
|
|
||||||
if (blockHeight == 0 && resultSet.wasNull())
|
|
||||||
blockHeight = null;
|
|
||||||
|
|
||||||
ApprovalStatus approvalStatus = ApprovalStatus.valueOf(resultSet.getInt(9));
|
|
||||||
Integer approvalHeight = resultSet.getInt(10);
|
|
||||||
if (approvalHeight == 0 && resultSet.wasNull())
|
|
||||||
approvalHeight = null;
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, approvalStatus, blockHeight, approvalHeight, signature);
|
|
||||||
|
|
||||||
int version = resultSet.getInt(11);
|
|
||||||
int nonce = resultSet.getInt(12);
|
|
||||||
int serviceInt = resultSet.getInt(13);
|
|
||||||
int size = resultSet.getInt(14);
|
|
||||||
boolean isDataRaw = resultSet.getBoolean(15); // NOT NULL, so no null to false
|
|
||||||
DataType dataType = isDataRaw ? DataType.RAW_DATA : DataType.DATA_HASH;
|
|
||||||
byte[] data = resultSet.getBytes(16);
|
|
||||||
byte[] metadataHash = resultSet.getBytes(17);
|
|
||||||
String nameResult = resultSet.getString(18);
|
|
||||||
String identifierResult = resultSet.getString(19);
|
|
||||||
Method method = Method.valueOf(resultSet.getInt(20));
|
|
||||||
byte[] secret = resultSet.getBytes(21);
|
|
||||||
Compression compression = Compression.valueOf(resultSet.getInt(22));
|
|
||||||
// FUTURE: get payments from signature if needed. Avoiding for now to reduce database calls.
|
|
||||||
|
|
||||||
ArbitraryTransactionData transactionData = new ArbitraryTransactionData(baseTransactionData,
|
|
||||||
version, serviceInt, nonce, size, nameResult, identifierResult, method, secret,
|
|
||||||
compression, data, dataType, metadataHash, null);
|
|
||||||
|
|
||||||
arbitraryTransactionData.add(transactionData);
|
|
||||||
} while (resultSet.next());
|
|
||||||
|
|
||||||
return arbitraryTransactionData;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new DataException("Unable to fetch arbitrary transactions from repository", e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArbitraryTransactionData getSingleTransaction(String name, Service service, Method method, String identifier, boolean firstNotLast) throws DataException {
|
private ArbitraryTransactionData getSingleTransaction(String name, Service service, Method method, String identifier, boolean firstNotLast) throws DataException {
|
||||||
if (name == null || service == null) {
|
if (name == null || service == null) {
|
||||||
// Required fields
|
// Required fields
|
||||||
@ -862,11 +724,12 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, List<String> keywords, boolean prefixOnly,
|
public List<ArbitraryResourceData> searchArbitraryResources(Service service, String query, String identifier, List<String> names, String title, String description, boolean prefixOnly,
|
||||||
List<String> exactMatchNames, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked,
|
List<String> exactMatchNames, boolean defaultResource, SearchMode mode, Integer minLevel, Boolean followedOnly, Boolean excludeBlocked,
|
||||||
Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
Boolean includeMetadata, Boolean includeStatus, Long before, Long after, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
|
||||||
if(Settings.getInstance().isDbCacheEnabled()) {
|
if(Settings.getInstance().isDbCacheEnabled()) {
|
||||||
|
|
||||||
List<ArbitraryResourceData> list
|
List<ArbitraryResourceData> list
|
||||||
= HSQLDBCacheUtils.callCache(
|
= HSQLDBCacheUtils.callCache(
|
||||||
ArbitraryResourceCache.getInstance(),
|
ArbitraryResourceCache.getInstance(),
|
||||||
@ -888,7 +751,6 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
Optional.ofNullable(description),
|
Optional.ofNullable(description),
|
||||||
prefixOnly,
|
prefixOnly,
|
||||||
Optional.ofNullable(exactMatchNames),
|
Optional.ofNullable(exactMatchNames),
|
||||||
Optional.ofNullable(keywords),
|
|
||||||
defaultResource,
|
defaultResource,
|
||||||
Optional.ofNullable(minLevel),
|
Optional.ofNullable(minLevel),
|
||||||
Optional.ofNullable(() -> ListUtils.followedNames()),
|
Optional.ofNullable(() -> ListUtils.followedNames()),
|
||||||
@ -909,7 +771,6 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StringBuilder sql = new StringBuilder(512);
|
StringBuilder sql = new StringBuilder(512);
|
||||||
List<Object> bindParams = new ArrayList<>();
|
List<Object> bindParams = new ArrayList<>();
|
||||||
|
|
||||||
@ -996,26 +857,6 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
bindParams.add(queryWildcard);
|
bindParams.add(queryWildcard);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keywords != null && !keywords.isEmpty()) {
|
|
||||||
List<String> searchKeywords = new ArrayList<>(keywords);
|
|
||||||
|
|
||||||
List<String> conditions = new ArrayList<>();
|
|
||||||
List<String> bindValues = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < searchKeywords.size(); i++) {
|
|
||||||
conditions.add("LOWER(description) LIKE ?");
|
|
||||||
bindValues.add("%" + searchKeywords.get(i).trim().toLowerCase() + "%");
|
|
||||||
}
|
|
||||||
|
|
||||||
String finalCondition = String.join(" OR ", conditions);
|
|
||||||
sql.append(" AND (").append(finalCondition).append(")");
|
|
||||||
|
|
||||||
bindParams.addAll(bindValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Handle name searches
|
// Handle name searches
|
||||||
if (names != null && !names.isEmpty()) {
|
if (names != null && !names.isEmpty()) {
|
||||||
sql.append(" AND (");
|
sql.append(" AND (");
|
||||||
|
@ -3,24 +3,15 @@ package org.qortal.repository.hsqldb;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qortal.api.SearchMode;
|
import org.qortal.api.SearchMode;
|
||||||
import org.qortal.api.resource.TransactionsResource;
|
|
||||||
import org.qortal.arbitrary.misc.Category;
|
import org.qortal.arbitrary.misc.Category;
|
||||||
import org.qortal.arbitrary.misc.Service;
|
import org.qortal.arbitrary.misc.Service;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
import org.qortal.data.account.AddressAmountData;
|
|
||||||
import org.qortal.data.account.BlockHeightRange;
|
|
||||||
import org.qortal.data.account.BlockHeightRangeAddressAmounts;
|
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceCache;
|
import org.qortal.data.arbitrary.ArbitraryResourceCache;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
|
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
|
||||||
import org.qortal.repository.RepositoryManager;
|
|
||||||
import org.qortal.settings.Settings;
|
|
||||||
import org.qortal.utils.BalanceRecorderUtils;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
@ -37,7 +28,6 @@ import java.util.Optional;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@ -167,7 +157,6 @@ public class HSQLDBCacheUtils {
|
|||||||
Optional<String> description,
|
Optional<String> description,
|
||||||
boolean prefixOnly,
|
boolean prefixOnly,
|
||||||
Optional<List<String>> exactMatchNames,
|
Optional<List<String>> exactMatchNames,
|
||||||
Optional<List<String>> keywords,
|
|
||||||
boolean defaultResource,
|
boolean defaultResource,
|
||||||
Optional<Integer> minLevel,
|
Optional<Integer> minLevel,
|
||||||
Optional<Supplier<List<String>>> includeOnly,
|
Optional<Supplier<List<String>>> includeOnly,
|
||||||
@ -181,18 +170,7 @@ public class HSQLDBCacheUtils {
|
|||||||
Optional<Boolean> reverse) {
|
Optional<Boolean> reverse) {
|
||||||
|
|
||||||
// retain only candidates with names
|
// retain only candidates with names
|
||||||
Stream<ArbitraryResourceData> stream = candidates.stream().filter(candidate -> candidate.name != null );
|
Stream<ArbitraryResourceData> stream = candidates.stream().filter(candidate -> candidate.name != null);
|
||||||
|
|
||||||
if(after.isPresent()) {
|
|
||||||
stream = stream.filter( candidate -> candidate.created > after.get().longValue() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if(before.isPresent()) {
|
|
||||||
stream = stream.filter( candidate -> candidate.created < before.get().longValue() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if(exclude.isPresent())
|
|
||||||
stream = stream.filter( candidate -> !exclude.get().get().contains( candidate.name ));
|
|
||||||
|
|
||||||
// filter by service
|
// filter by service
|
||||||
if( service.isPresent() )
|
if( service.isPresent() )
|
||||||
@ -216,36 +194,6 @@ public class HSQLDBCacheUtils {
|
|||||||
stream = filterTerm(title, data -> data.metadata != null ? data.metadata.getTitle() : null, prefixOnly, stream);
|
stream = filterTerm(title, data -> data.metadata != null ? data.metadata.getTitle() : null, prefixOnly, stream);
|
||||||
stream = filterTerm(description, data -> data.metadata != null ? data.metadata.getDescription() : null, prefixOnly, stream);
|
stream = filterTerm(description, data -> data.metadata != null ? data.metadata.getDescription() : null, prefixOnly, stream);
|
||||||
|
|
||||||
// New: Filter by keywords if provided
|
|
||||||
if (keywords.isPresent() && !keywords.get().isEmpty()) {
|
|
||||||
List<String> searchKeywords = keywords.get().stream()
|
|
||||||
.map(String::toLowerCase)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
stream = stream.filter(candidate -> {
|
|
||||||
|
|
||||||
if (candidate.metadata != null && candidate.metadata.getDescription() != null) {
|
|
||||||
String descriptionLower = candidate.metadata.getDescription().toLowerCase();
|
|
||||||
return searchKeywords.stream().anyMatch(descriptionLower::contains);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keywords.isPresent() && !keywords.get().isEmpty()) {
|
|
||||||
List<String> searchKeywords = keywords.get().stream()
|
|
||||||
.map(String::toLowerCase)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
stream = stream.filter(candidate -> {
|
|
||||||
if (candidate.metadata != null && candidate.metadata.getDescription() != null) {
|
|
||||||
String descriptionLower = candidate.metadata.getDescription().toLowerCase();
|
|
||||||
return searchKeywords.stream().anyMatch(descriptionLower::contains);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// if exact names is set, retain resources with exact names
|
// if exact names is set, retain resources with exact names
|
||||||
if( exactMatchNames.isPresent() && !exactMatchNames.get().isEmpty()) {
|
if( exactMatchNames.isPresent() && !exactMatchNames.get().isEmpty()) {
|
||||||
|
|
||||||
@ -301,58 +249,15 @@ public class HSQLDBCacheUtils {
|
|||||||
// truncate to limit
|
// truncate to limit
|
||||||
if( limit.isPresent() && limit.get() > 0 ) stream = stream.limit(limit.get());
|
if( limit.isPresent() && limit.get() > 0 ) stream = stream.limit(limit.get());
|
||||||
|
|
||||||
List<ArbitraryResourceData> listCopy1 = stream.collect(Collectors.toList());
|
// include metadata
|
||||||
|
if( includeMetadata.isEmpty() || !includeMetadata.get() )
|
||||||
|
stream = stream.peek( candidate -> candidate.metadata = null );
|
||||||
|
|
||||||
List<ArbitraryResourceData> listCopy2 = new ArrayList<>(listCopy1.size());
|
// include status
|
||||||
|
if( includeStatus.isEmpty() || !includeStatus.get() )
|
||||||
|
stream = stream.peek( candidate -> candidate.status = null);
|
||||||
|
|
||||||
// remove metadata from the first copy
|
return stream.collect(Collectors.toList());
|
||||||
if( includeMetadata.isEmpty() || !includeMetadata.get() ) {
|
|
||||||
for( ArbitraryResourceData data : listCopy1 ) {
|
|
||||||
ArbitraryResourceData copy = new ArbitraryResourceData();
|
|
||||||
copy.name = data.name;
|
|
||||||
copy.service = data.service;
|
|
||||||
copy.identifier = data.identifier;
|
|
||||||
copy.status = data.status;
|
|
||||||
copy.metadata = null;
|
|
||||||
|
|
||||||
copy.size = data.size;
|
|
||||||
copy.created = data.created;
|
|
||||||
copy.updated = data.updated;
|
|
||||||
|
|
||||||
listCopy2.add(copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// put the list copy 1 into the second copy
|
|
||||||
else {
|
|
||||||
listCopy2.addAll(listCopy1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove status from final copy
|
|
||||||
if( includeStatus.isEmpty() || !includeStatus.get() ) {
|
|
||||||
|
|
||||||
List<ArbitraryResourceData> finalCopy = new ArrayList<>(listCopy2.size());
|
|
||||||
|
|
||||||
for( ArbitraryResourceData data : listCopy2 ) {
|
|
||||||
ArbitraryResourceData copy = new ArbitraryResourceData();
|
|
||||||
copy.name = data.name;
|
|
||||||
copy.service = data.service;
|
|
||||||
copy.identifier = data.identifier;
|
|
||||||
copy.status = null;
|
|
||||||
copy.metadata = data.metadata;
|
|
||||||
|
|
||||||
copy.size = data.size;
|
|
||||||
copy.created = data.created;
|
|
||||||
copy.updated = data.updated;
|
|
||||||
|
|
||||||
finalCopy.add(copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalCopy;
|
|
||||||
}
|
|
||||||
// keep status included by returning the second copy
|
|
||||||
else {
|
|
||||||
return listCopy2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -484,15 +389,14 @@ public class HSQLDBCacheUtils {
|
|||||||
/**
|
/**
|
||||||
* Start Recording Balances
|
* Start Recording Balances
|
||||||
*
|
*
|
||||||
* @param balancesByHeight height -> account balances
|
* @param queue the queue to add to, remove oldest data if necssary
|
||||||
* @param balanceDynamics every balance dynamic
|
* @param repository the db repsoitory
|
||||||
* @param priorityRequested the requested thread priority
|
* @param priorityRequested the requested thread priority
|
||||||
* @param frequency the recording frequencies, in minutes
|
* @param frequency the recording frequencies, in minutes
|
||||||
* @param capacity the maximum size of balanceDynamics
|
|
||||||
*/
|
*/
|
||||||
public static void startRecordingBalances(
|
public static void startRecordingBalances(
|
||||||
final ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight,
|
final ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight,
|
||||||
CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> balanceDynamics,
|
final ConcurrentHashMap<String, List<AccountBalanceData>> balancesByAddress,
|
||||||
int priorityRequested,
|
int priorityRequested,
|
||||||
int frequency,
|
int frequency,
|
||||||
int capacity) {
|
int capacity) {
|
||||||
@ -505,38 +409,55 @@ public class HSQLDBCacheUtils {
|
|||||||
|
|
||||||
Thread.currentThread().setName(BALANCE_RECORDER_TIMER_TASK);
|
Thread.currentThread().setName(BALANCE_RECORDER_TIMER_TASK);
|
||||||
|
|
||||||
int currentHeight = recordCurrentBalances(balancesByHeight);
|
try (final HSQLDBRepository repository = (HSQLDBRepository) Controller.REPOSITORY_FACTORY.getRepository()) {
|
||||||
|
while (balancesByHeight.size() > capacity + 1) {
|
||||||
|
Optional<Integer> firstHeight = balancesByHeight.keySet().stream().sorted().findFirst();
|
||||||
|
|
||||||
LOGGER.debug("recorded balances: height = " + currentHeight);
|
if (firstHeight.isPresent()) balancesByHeight.remove(firstHeight.get());
|
||||||
|
}
|
||||||
|
|
||||||
// remove invalidated recordings, recording after current height
|
// get current balances
|
||||||
BalanceRecorderUtils.removeRecordingsAboveHeight(currentHeight, balancesByHeight);
|
List<AccountBalanceData> accountBalances = getAccountBalances(repository);
|
||||||
|
|
||||||
// remove invalidated dynamics, on or after current height
|
// get anyone of the balances
|
||||||
BalanceRecorderUtils.removeDynamicsOnOrAboveHeight(currentHeight, balanceDynamics);
|
Optional<AccountBalanceData> data = accountBalances.stream().findAny();
|
||||||
|
|
||||||
// if there are 2 or more recordings, then produce balance dynamics for the first 2 recordings
|
// if there are any balances, then record them
|
||||||
if( balancesByHeight.size() > 1 ) {
|
if (data.isPresent()) {
|
||||||
|
// map all new balances to the current height
|
||||||
|
balancesByHeight.put(data.get().getHeight(), accountBalances);
|
||||||
|
|
||||||
Optional<Integer> priorHeight = BalanceRecorderUtils.getPriorHeight(currentHeight, balancesByHeight);
|
// for each new balance, map to address
|
||||||
|
for (AccountBalanceData accountBalance : accountBalances) {
|
||||||
|
|
||||||
// if there is a prior height
|
// get recorded balances for this address
|
||||||
if(priorHeight.isPresent()) {
|
List<AccountBalanceData> establishedBalances
|
||||||
|
= balancesByAddress.getOrDefault(accountBalance.getAddress(), new ArrayList<>(0));
|
||||||
|
|
||||||
boolean isRewardDistribution = BalanceRecorderUtils.isRewardDistributionRange(priorHeight.get(), currentHeight);
|
// start a new list of recordings for this address, add the new balance and add the established
|
||||||
|
// balances
|
||||||
|
List<AccountBalanceData> balances = new ArrayList<>(establishedBalances.size() + 1);
|
||||||
|
balances.add(accountBalance);
|
||||||
|
balances.addAll(establishedBalances);
|
||||||
|
|
||||||
// if this range has a reward recording block or if other blocks are enabled for recording
|
// reset tha balances for this address
|
||||||
if( isRewardDistribution || !Settings.getInstance().isRewardRecordingOnly() ) {
|
balancesByAddress.put(accountBalance.getAddress(), balances);
|
||||||
produceBalanceDynamics(currentHeight, priorHeight, isRewardDistribution, balancesByHeight, balanceDynamics, capacity);
|
|
||||||
|
// TODO: reduce account balances to capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduce height balances to capacity
|
||||||
|
while( balancesByHeight.size() > capacity ) {
|
||||||
|
Optional<Integer> lowestHeight
|
||||||
|
= balancesByHeight.entrySet().stream()
|
||||||
|
.min(Comparator.comparingInt(Map.Entry::getKey))
|
||||||
|
.map(Map.Entry::getKey);
|
||||||
|
|
||||||
|
if (lowestHeight.isPresent()) balancesByHeight.entrySet().remove(lowestHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
} catch (DataException e) {
|
||||||
LOGGER.warn("Expecting prior height and nothing was discovered, current height = " + currentHeight);
|
LOGGER.error(e.getMessage(), e);
|
||||||
}
|
|
||||||
}
|
|
||||||
// else this should be the first recording
|
|
||||||
else {
|
|
||||||
LOGGER.info("first balance recording completed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -545,98 +466,6 @@ public class HSQLDBCacheUtils {
|
|||||||
timer.scheduleAtFixedRate(task, 300_000, frequency * 60_000);
|
timer.scheduleAtFixedRate(task, 300_000, frequency * 60_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void produceBalanceDynamics(int currentHeight, Optional<Integer> priorHeight, boolean isRewardDistribution, ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight, CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> balanceDynamics, int capacity) {
|
|
||||||
BlockHeightRange blockHeightRange = new BlockHeightRange(priorHeight.get(), currentHeight, isRewardDistribution);
|
|
||||||
|
|
||||||
LOGGER.debug("building dynamics for block heights: range = " + blockHeightRange);
|
|
||||||
|
|
||||||
List<AccountBalanceData> currentBalances = balancesByHeight.get(currentHeight);
|
|
||||||
|
|
||||||
ArrayList<TransactionData> transactions = getTransactionDataForBlocks(blockHeightRange);
|
|
||||||
|
|
||||||
LOGGER.info("transactions counted for balance adjustments: count = " + transactions.size());
|
|
||||||
List<AddressAmountData> currentDynamics
|
|
||||||
= BalanceRecorderUtils.buildBalanceDynamics(
|
|
||||||
currentBalances,
|
|
||||||
balancesByHeight.get(priorHeight.get()),
|
|
||||||
Settings.getInstance().getMinimumBalanceRecording(),
|
|
||||||
transactions);
|
|
||||||
|
|
||||||
LOGGER.debug("dynamics built: count = " + currentDynamics.size());
|
|
||||||
|
|
||||||
if(LOGGER.isDebugEnabled())
|
|
||||||
currentDynamics.stream()
|
|
||||||
.sorted(Comparator.comparingLong(AddressAmountData::getAmount).reversed())
|
|
||||||
.limit(Settings.getInstance().getTopBalanceLoggingLimit())
|
|
||||||
.forEach(top5Dynamic -> LOGGER.debug("Top Dynamics = " + top5Dynamic));
|
|
||||||
|
|
||||||
BlockHeightRangeAddressAmounts amounts
|
|
||||||
= new BlockHeightRangeAddressAmounts( blockHeightRange, currentDynamics );
|
|
||||||
|
|
||||||
balanceDynamics.add(amounts);
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeRecordingsBelowHeight(currentHeight - Settings.getInstance().getBalanceRecorderRollbackAllowance(), balancesByHeight);
|
|
||||||
|
|
||||||
while(balanceDynamics.size() > capacity) {
|
|
||||||
BlockHeightRangeAddressAmounts oldestDynamics = BalanceRecorderUtils.removeOldestDynamics(balanceDynamics);
|
|
||||||
|
|
||||||
LOGGER.debug("removing oldest dynamics: range " + oldestDynamics.getRange());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ArrayList<TransactionData> getTransactionDataForBlocks(BlockHeightRange blockHeightRange) {
|
|
||||||
ArrayList<TransactionData> transactions;
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
List<byte[]> signatures
|
|
||||||
= repository.getTransactionRepository().getSignaturesMatchingCriteria(
|
|
||||||
blockHeightRange.getBegin() + 1, blockHeightRange.getEnd() - blockHeightRange.getBegin(),
|
|
||||||
null, null,null, null, null,
|
|
||||||
TransactionsResource.ConfirmationStatus.CONFIRMED,
|
|
||||||
null, null, null);
|
|
||||||
|
|
||||||
transactions = new ArrayList<>(signatures.size());
|
|
||||||
for (byte[] signature : signatures) {
|
|
||||||
transactions.add(repository.getTransactionRepository().fromSignature(signature));
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.debug(String.format("Found %s transactions for " + blockHeightRange, transactions.size()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
transactions = new ArrayList<>(0);
|
|
||||||
LOGGER.warn("Problems getting transactions for balance recording: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int recordCurrentBalances(ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight) {
|
|
||||||
int currentHeight;
|
|
||||||
|
|
||||||
try (final HSQLDBRepository repository = (HSQLDBRepository) Controller.REPOSITORY_FACTORY.getRepository()) {
|
|
||||||
|
|
||||||
// get current balances
|
|
||||||
List<AccountBalanceData> accountBalances = getAccountBalances(repository);
|
|
||||||
|
|
||||||
// get anyone of the balances
|
|
||||||
Optional<AccountBalanceData> data = accountBalances.stream().findAny();
|
|
||||||
|
|
||||||
// if there are any balances, then record them
|
|
||||||
if (data.isPresent()) {
|
|
||||||
// map all new balances to the current height
|
|
||||||
balancesByHeight.put(data.get().getHeight(), accountBalances);
|
|
||||||
|
|
||||||
currentHeight = data.get().getHeight();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
currentHeight = Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
} catch (DataException e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
currentHeight = Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build Timer
|
* Build Timer
|
||||||
*
|
*
|
||||||
|
@ -454,41 +454,41 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
|
|
||||||
case 12:
|
case 12:
|
||||||
// Groups
|
// Groups
|
||||||
// NOTE: We need to set Groups to `GROUPS` here to avoid SQL Standard Keywords in HSQLDB v2.7.4
|
// NOTE: We need to set Groups to `Groups` here to avoid SQL Standard Keywords in HSQLDB v2.7.4
|
||||||
stmt.execute("CREATE TABLE `GROUPS` (group_id GroupID, owner QortalAddress NOT NULL, group_name GroupName NOT NULL, "
|
stmt.execute("CREATE TABLE `Groups` (group_id GroupID, owner QortalAddress NOT NULL, group_name GroupName NOT NULL, "
|
||||||
+ "created_when EpochMillis NOT NULL, updated_when EpochMillis, is_open BOOLEAN NOT NULL, "
|
+ "created_when EpochMillis NOT NULL, updated_when EpochMillis, is_open BOOLEAN NOT NULL, "
|
||||||
+ "approval_threshold TINYINT NOT NULL, min_block_delay INTEGER NOT NULL, max_block_delay INTEGER NOT NULL, "
|
+ "approval_threshold TINYINT NOT NULL, min_block_delay INTEGER NOT NULL, max_block_delay INTEGER NOT NULL, "
|
||||||
+ "reference Signature, creation_group_id GroupID, reduced_group_name GroupName NOT NULL, "
|
+ "reference Signature, creation_group_id GroupID, reduced_group_name GroupName NOT NULL, "
|
||||||
+ "description GenericDescription NOT NULL, PRIMARY KEY (group_id))");
|
+ "description GenericDescription NOT NULL, PRIMARY KEY (group_id))");
|
||||||
// For finding groups by name
|
// For finding groups by name
|
||||||
stmt.execute("CREATE INDEX GroupNameIndex on `GROUPS` (group_name)");
|
stmt.execute("CREATE INDEX GroupNameIndex on `Groups` (group_name)");
|
||||||
// For finding groups by reduced name
|
// For finding groups by reduced name
|
||||||
stmt.execute("CREATE INDEX GroupReducedNameIndex on `GROUPS` (reduced_group_name)");
|
stmt.execute("CREATE INDEX GroupReducedNameIndex on `Groups` (reduced_group_name)");
|
||||||
// For finding groups by owner
|
// For finding groups by owner
|
||||||
stmt.execute("CREATE INDEX GroupOwnerIndex ON `GROUPS` (owner)");
|
stmt.execute("CREATE INDEX GroupOwnerIndex ON `Groups` (owner)");
|
||||||
|
|
||||||
// We need a corresponding trigger to make sure new group_id values are assigned sequentially starting from 1
|
// We need a corresponding trigger to make sure new group_id values are assigned sequentially starting from 1
|
||||||
stmt.execute("CREATE TRIGGER Group_ID_Trigger BEFORE INSERT ON `GROUPS` "
|
stmt.execute("CREATE TRIGGER Group_ID_Trigger BEFORE INSERT ON `Groups` "
|
||||||
+ "REFERENCING NEW ROW AS new_row FOR EACH ROW WHEN (new_row.group_id IS NULL) "
|
+ "REFERENCING NEW ROW AS new_row FOR EACH ROW WHEN (new_row.group_id IS NULL) "
|
||||||
+ "SET new_row.group_id = (SELECT IFNULL(MAX(group_id) + 1, 1) FROM `GROUPS`)");
|
+ "SET new_row.group_id = (SELECT IFNULL(MAX(group_id) + 1, 1) FROM `Groups`)");
|
||||||
|
|
||||||
// Admins
|
// Admins
|
||||||
stmt.execute("CREATE TABLE GroupAdmins (group_id GroupID, admin QortalAddress, reference Signature NOT NULL, "
|
stmt.execute("CREATE TABLE GroupAdmins (group_id GroupID, admin QortalAddress, reference Signature NOT NULL, "
|
||||||
+ "PRIMARY KEY (group_id, admin), FOREIGN KEY (group_id) REFERENCES `GROUPS` (group_id) ON DELETE CASCADE)");
|
+ "PRIMARY KEY (group_id, admin), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||||
// For finding groups by admin address
|
// For finding groups by admin address
|
||||||
stmt.execute("CREATE INDEX GroupAdminIndex ON GroupAdmins (admin)");
|
stmt.execute("CREATE INDEX GroupAdminIndex ON GroupAdmins (admin)");
|
||||||
|
|
||||||
// Members
|
// Members
|
||||||
stmt.execute("CREATE TABLE GroupMembers (group_id GroupID, address QortalAddress, "
|
stmt.execute("CREATE TABLE GroupMembers (group_id GroupID, address QortalAddress, "
|
||||||
+ "joined_when EpochMillis NOT NULL, reference Signature NOT NULL, "
|
+ "joined_when EpochMillis NOT NULL, reference Signature NOT NULL, "
|
||||||
+ "PRIMARY KEY (group_id, address), FOREIGN KEY (group_id) REFERENCES `GROUPS` (group_id) ON DELETE CASCADE)");
|
+ "PRIMARY KEY (group_id, address), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||||
// For finding groups by member address
|
// For finding groups by member address
|
||||||
stmt.execute("CREATE INDEX GroupMemberIndex ON GroupMembers (address)");
|
stmt.execute("CREATE INDEX GroupMemberIndex ON GroupMembers (address)");
|
||||||
|
|
||||||
// Invites
|
// Invites
|
||||||
stmt.execute("CREATE TABLE GroupInvites (group_id GroupID, inviter QortalAddress, invitee QortalAddress, "
|
stmt.execute("CREATE TABLE GroupInvites (group_id GroupID, inviter QortalAddress, invitee QortalAddress, "
|
||||||
+ "expires_when EpochMillis, reference Signature, "
|
+ "expires_when EpochMillis, reference Signature, "
|
||||||
+ "PRIMARY KEY (group_id, invitee), FOREIGN KEY (group_id) REFERENCES `GROUPS` (group_id) ON DELETE CASCADE)");
|
+ "PRIMARY KEY (group_id, invitee), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||||
// For finding invites sent by inviter
|
// For finding invites sent by inviter
|
||||||
stmt.execute("CREATE INDEX GroupInviteInviterIndex ON GroupInvites (inviter)");
|
stmt.execute("CREATE INDEX GroupInviteInviterIndex ON GroupInvites (inviter)");
|
||||||
// For finding invites by group
|
// For finding invites by group
|
||||||
@ -504,7 +504,7 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
// NULL expires_when means does not expire!
|
// NULL expires_when means does not expire!
|
||||||
stmt.execute("CREATE TABLE GroupBans (group_id GroupID, offender QortalAddress, admin QortalAddress NOT NULL, "
|
stmt.execute("CREATE TABLE GroupBans (group_id GroupID, offender QortalAddress, admin QortalAddress NOT NULL, "
|
||||||
+ "banned_when EpochMillis NOT NULL, reason GenericDescription NOT NULL, expires_when EpochMillis, reference Signature NOT NULL, "
|
+ "banned_when EpochMillis NOT NULL, reason GenericDescription NOT NULL, expires_when EpochMillis, reference Signature NOT NULL, "
|
||||||
+ "PRIMARY KEY (group_id, offender), FOREIGN KEY (group_id) REFERENCES `GROUPS` (group_id) ON DELETE CASCADE)");
|
+ "PRIMARY KEY (group_id, offender), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||||
// For expiry maintenance
|
// For expiry maintenance
|
||||||
stmt.execute("CREATE INDEX GroupBanExpiryIndex ON GroupBans (expires_when)");
|
stmt.execute("CREATE INDEX GroupBanExpiryIndex ON GroupBans (expires_when)");
|
||||||
break;
|
break;
|
||||||
|
@ -350,24 +350,9 @@ public class HSQLDBGroupRepository implements GroupRepository {
|
|||||||
|
|
||||||
// Group Admins
|
// Group Admins
|
||||||
|
|
||||||
@Override
|
|
||||||
public GroupAdminData getAdminFaulty(int groupId, String address) throws DataException {
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT admin, reference FROM GroupAdmins WHERE group_id = ?", groupId)) {
|
|
||||||
if (resultSet == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
String admin = resultSet.getString(1);
|
|
||||||
byte[] reference = resultSet.getBytes(2);
|
|
||||||
|
|
||||||
return new GroupAdminData(groupId, admin, reference);
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new DataException("Unable to fetch group admin from repository", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupAdminData getAdmin(int groupId, String address) throws DataException {
|
public GroupAdminData getAdmin(int groupId, String address) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT admin, reference FROM GroupAdmins WHERE group_id = ? AND admin = ?", groupId, address)) {
|
try (ResultSet resultSet = this.repository.checkedExecute("SELECT admin, reference FROM GroupAdmins WHERE group_id = ?", groupId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -5,8 +5,6 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.hsqldb.HsqlException;
|
import org.hsqldb.HsqlException;
|
||||||
import org.hsqldb.error.ErrorCode;
|
import org.hsqldb.error.ErrorCode;
|
||||||
import org.hsqldb.jdbc.HSQLDBPool;
|
import org.hsqldb.jdbc.HSQLDBPool;
|
||||||
import org.hsqldb.jdbc.HSQLDBPoolMonitored;
|
|
||||||
import org.qortal.data.system.DbConnectionInfo;
|
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryFactory;
|
import org.qortal.repository.RepositoryFactory;
|
||||||
@ -16,8 +14,6 @@ import java.sql.Connection;
|
|||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
||||||
@ -61,13 +57,7 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
|||||||
HSQLDBRepository.attemptRecovery(connectionUrl, "backup");
|
HSQLDBRepository.attemptRecovery(connectionUrl, "backup");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Settings.getInstance().isConnectionPoolMonitorEnabled()) {
|
this.connectionPool = new HSQLDBPool(Settings.getInstance().getRepositoryConnectionPoolSize());
|
||||||
this.connectionPool = new HSQLDBPoolMonitored(Settings.getInstance().getRepositoryConnectionPoolSize());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.connectionPool = new HSQLDBPool(Settings.getInstance().getRepositoryConnectionPoolSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connectionPool.setUrl(this.connectionUrl);
|
this.connectionPool.setUrl(this.connectionUrl);
|
||||||
|
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
@ -163,19 +153,4 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
|
|||||||
return HSQLDBRepository.isDeadlockException(e);
|
return HSQLDBRepository.isDeadlockException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Connection States
|
|
||||||
*
|
|
||||||
* Get the database connection states, if database connection pool monitoring is enabled.
|
|
||||||
*
|
|
||||||
* @return the connection states if enabled, otherwise an empty list
|
|
||||||
*/
|
|
||||||
public List<DbConnectionInfo> getDbConnectionsStates() {
|
|
||||||
if( Settings.getInstance().isConnectionPoolMonitorEnabled() ) {
|
|
||||||
return ((HSQLDBPoolMonitored) this.connectionPool).getDbConnectionsStates();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -386,7 +386,7 @@ public class Settings {
|
|||||||
/**
|
/**
|
||||||
* DB Cache Enabled?
|
* DB Cache Enabled?
|
||||||
*/
|
*/
|
||||||
private boolean dbCacheEnabled = true;
|
private boolean dbCacheEnabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DB Cache Thread Priority
|
* DB Cache Thread Priority
|
||||||
@ -444,107 +444,14 @@ public class Settings {
|
|||||||
*/
|
*/
|
||||||
private long archivingPause = 3000;
|
private long archivingPause = 3000;
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable Balance Recorder?
|
|
||||||
*
|
|
||||||
* True for balance recording, otherwise false.
|
|
||||||
*/
|
|
||||||
private boolean balanceRecorderEnabled = false;
|
private boolean balanceRecorderEnabled = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Balance Recorder Priority
|
|
||||||
*
|
|
||||||
* The thread priority (1 is lowest, 10 is highest) of the balance recorder thread, if enabled.
|
|
||||||
*/
|
|
||||||
private int balanceRecorderPriority = 1;
|
private int balanceRecorderPriority = 1;
|
||||||
|
|
||||||
/**
|
private int balanceRecorderFrequency = 2*60*1000;
|
||||||
* Balance Recorder Frequency
|
|
||||||
*
|
|
||||||
* How often the balances will be recorded, if enabled, measured in minutes.
|
|
||||||
*/
|
|
||||||
private int balanceRecorderFrequency = 20;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Balance Recorder Capacity
|
|
||||||
*
|
|
||||||
* The number of balance recorder ranges will be held in memory.
|
|
||||||
*/
|
|
||||||
private int balanceRecorderCapacity = 1000;
|
private int balanceRecorderCapacity = 1000;
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum Balance Recording
|
|
||||||
*
|
|
||||||
* The minimum recored balance change in Qortoshis (1/100000000 QORT)
|
|
||||||
*/
|
|
||||||
private long minimumBalanceRecording = 100000000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Top Balance Logging Limit
|
|
||||||
*
|
|
||||||
* When logging the number limit of top balance changes to show in the logs for any given block range.
|
|
||||||
*/
|
|
||||||
private long topBalanceLoggingLimit = 100;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Balance Recorder Rollback Allowance
|
|
||||||
*
|
|
||||||
* If the balance recorder is enabled, it must protect its prior balances by this number of blocks in case of
|
|
||||||
* a blockchain rollback and reorganization.
|
|
||||||
*/
|
|
||||||
private int balanceRecorderRollbackAllowance = 100;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is Reward Recording Only
|
|
||||||
*
|
|
||||||
* Set true to only retain the recordings that cover reward distributions, otherwise set false.
|
|
||||||
*/
|
|
||||||
private boolean rewardRecordingOnly = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is The Connection Monitored?
|
|
||||||
*
|
|
||||||
* Is the database connection pooled monitored?
|
|
||||||
*/
|
|
||||||
private boolean connectionPoolMonitorEnabled = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Buiild Arbitrary Resources Batch Size
|
|
||||||
*
|
|
||||||
* The number resources to batch per iteration when rebuilding.
|
|
||||||
*/
|
|
||||||
private int buildArbitraryResourcesBatchSize = 200;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Arbitrary Indexing Priority
|
|
||||||
*
|
|
||||||
* The thread priority when indexing arbirary resources.
|
|
||||||
*/
|
|
||||||
private int arbitraryIndexingPriority = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Arbitrary Indexing Frequency (In Minutes)
|
|
||||||
*
|
|
||||||
* The frequency at which the arbitrary indices are cached.
|
|
||||||
*/
|
|
||||||
private int arbitraryIndexingFrequency = 10;
|
|
||||||
|
|
||||||
private boolean rebuildArbitraryResourceCacheTaskEnabled = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuild Arbitrary Resource Cache Task Delay (In Minutes)
|
|
||||||
*
|
|
||||||
* Waiting period before the first rebuild task is started.
|
|
||||||
*/
|
|
||||||
private int rebuildArbitraryResourceCacheTaskDelay = 300;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuild Arbitrary Resource Cache Task Period (In Hours)
|
|
||||||
*
|
|
||||||
* The frequency the arbitrary resource cache is rebuilt.
|
|
||||||
*/
|
|
||||||
private int rebuildArbitraryResourceCacheTaskPeriod = 24;
|
|
||||||
|
|
||||||
// Domain mapping
|
// Domain mapping
|
||||||
public static class ThreadLimit {
|
public static class ThreadLimit {
|
||||||
private String messageType;
|
private String messageType;
|
||||||
@ -1350,48 +1257,4 @@ public class Settings {
|
|||||||
public boolean isBalanceRecorderEnabled() {
|
public boolean isBalanceRecorderEnabled() {
|
||||||
return balanceRecorderEnabled;
|
return balanceRecorderEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMinimumBalanceRecording() {
|
|
||||||
return minimumBalanceRecording;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTopBalanceLoggingLimit() {
|
|
||||||
return topBalanceLoggingLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBalanceRecorderRollbackAllowance() {
|
|
||||||
return balanceRecorderRollbackAllowance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRewardRecordingOnly() {
|
|
||||||
return rewardRecordingOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConnectionPoolMonitorEnabled() {
|
|
||||||
return connectionPoolMonitorEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBuildArbitraryResourcesBatchSize() {
|
|
||||||
return buildArbitraryResourcesBatchSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getArbitraryIndexingPriority() {
|
|
||||||
return arbitraryIndexingPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getArbitraryIndexingFrequency() {
|
|
||||||
return arbitraryIndexingFrequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRebuildArbitraryResourceCacheTaskEnabled() {
|
|
||||||
return rebuildArbitraryResourceCacheTaskEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRebuildArbitraryResourceCacheTaskDelay() {
|
|
||||||
return rebuildArbitraryResourceCacheTaskDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRebuildArbitraryResourceCacheTaskPeriod() {
|
|
||||||
return rebuildArbitraryResourceCacheTaskPeriod;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import org.qortal.arbitrary.metadata.ArbitraryDataTransactionMetadata;
|
|||||||
import org.qortal.arbitrary.misc.Service;
|
import org.qortal.arbitrary.misc.Service;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryTransactionDataHashWrapper;
|
|
||||||
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.crypto.MemoryPoW;
|
import org.qortal.crypto.MemoryPoW;
|
||||||
@ -32,12 +31,8 @@ import org.qortal.utils.ArbitraryTransactionUtils;
|
|||||||
import org.qortal.utils.NTP;
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ArbitraryTransaction extends Transaction {
|
public class ArbitraryTransaction extends Transaction {
|
||||||
@ -308,13 +303,8 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
// Add/update arbitrary resource caches, but don't update the status as this involves time-consuming
|
// Add/update arbitrary resource caches, but don't update the status as this involves time-consuming
|
||||||
// disk reads, and is more prone to failure. The status will be updated on metadata retrieval, or when
|
// disk reads, and is more prone to failure. The status will be updated on metadata retrieval, or when
|
||||||
// accessing the resource.
|
// accessing the resource.
|
||||||
// Also, must add this transaction as a latest transaction, since the it has not been saved to the
|
this.updateArbitraryResourceCache(repository);
|
||||||
// repository yet.
|
this.updateArbitraryMetadataCache(repository);
|
||||||
this.updateArbitraryResourceCacheIncludingMetadata(
|
|
||||||
repository,
|
|
||||||
Set.of(new ArbitraryTransactionDataHashWrapper(arbitraryTransactionData)),
|
|
||||||
new HashMap<>(0)
|
|
||||||
);
|
|
||||||
|
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
|
||||||
@ -370,10 +360,7 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
*
|
*
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public void updateArbitraryResourceCacheIncludingMetadata(
|
public void updateArbitraryResourceCache(Repository repository) throws DataException {
|
||||||
Repository repository,
|
|
||||||
Set<ArbitraryTransactionDataHashWrapper> latestTransactionWrappers,
|
|
||||||
Map<ArbitraryTransactionDataHashWrapper, ArbitraryResourceData> resourceByWrapper) throws DataException {
|
|
||||||
// Don't cache resources without a name (such as auto updates)
|
// Don't cache resources without a name (such as auto updates)
|
||||||
if (arbitraryTransactionData.getName() == null) {
|
if (arbitraryTransactionData.getName() == null) {
|
||||||
return;
|
return;
|
||||||
@ -398,33 +385,17 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
arbitraryResourceData.name = name;
|
arbitraryResourceData.name = name;
|
||||||
arbitraryResourceData.identifier = identifier;
|
arbitraryResourceData.identifier = identifier;
|
||||||
|
|
||||||
final ArbitraryTransactionDataHashWrapper wrapper = new ArbitraryTransactionDataHashWrapper(arbitraryTransactionData);
|
// Get the latest transaction
|
||||||
|
ArbitraryTransactionData latestTransactionData = repository.getArbitraryRepository().getLatestTransaction(arbitraryTransactionData.getName(), arbitraryTransactionData.getService(), null, arbitraryTransactionData.getIdentifier());
|
||||||
|
if (latestTransactionData == null) {
|
||||||
|
// We don't have a latest transaction, so delete from cache
|
||||||
|
repository.getArbitraryRepository().delete(arbitraryResourceData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ArbitraryTransactionData latestTransactionData;
|
// Get existing cached entry if it exists
|
||||||
if( latestTransactionWrappers.contains(wrapper)) {
|
ArbitraryResourceData existingArbitraryResourceData = repository.getArbitraryRepository()
|
||||||
latestTransactionData
|
.getArbitraryResource(service, name, identifier);
|
||||||
= latestTransactionWrappers.stream()
|
|
||||||
.filter( latestWrapper -> latestWrapper.equals(wrapper))
|
|
||||||
.findAny().get()
|
|
||||||
.getData();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Get the latest transaction
|
|
||||||
latestTransactionData = repository.getArbitraryRepository().getLatestTransaction(arbitraryTransactionData.getName(), arbitraryTransactionData.getService(), null, arbitraryTransactionData.getIdentifier());
|
|
||||||
if (latestTransactionData == null) {
|
|
||||||
LOGGER.info("We don't have a latest transaction, so delete from cache: arbitraryResourceData = " + arbitraryResourceData);
|
|
||||||
// We don't have a latest transaction, so delete from cache
|
|
||||||
repository.getArbitraryRepository().delete(arbitraryResourceData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArbitraryResourceData existingArbitraryResourceData = resourceByWrapper.get(wrapper);
|
|
||||||
|
|
||||||
if( existingArbitraryResourceData == null ) {
|
|
||||||
// Get existing cached entry if it exists
|
|
||||||
existingArbitraryResourceData = repository.getArbitraryRepository()
|
|
||||||
.getArbitraryResource(service, name, identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing cached data
|
// Check for existing cached data
|
||||||
if (existingArbitraryResourceData == null) {
|
if (existingArbitraryResourceData == null) {
|
||||||
@ -433,7 +404,6 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
arbitraryResourceData.updated = null;
|
arbitraryResourceData.updated = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resourceByWrapper.put(wrapper, existingArbitraryResourceData);
|
|
||||||
// An entry already exists - update created time from current transaction if this is older
|
// An entry already exists - update created time from current transaction if this is older
|
||||||
arbitraryResourceData.created = Math.min(existingArbitraryResourceData.created, arbitraryTransactionData.getTimestamp());
|
arbitraryResourceData.created = Math.min(existingArbitraryResourceData.created, arbitraryTransactionData.getTimestamp());
|
||||||
|
|
||||||
@ -451,34 +421,6 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
|
|
||||||
// Save
|
// Save
|
||||||
repository.getArbitraryRepository().save(arbitraryResourceData);
|
repository.getArbitraryRepository().save(arbitraryResourceData);
|
||||||
|
|
||||||
// Update metadata for latest transaction if it is local
|
|
||||||
if (latestTransactionData.getMetadataHash() != null) {
|
|
||||||
ArbitraryDataFile metadataFile = ArbitraryDataFile.fromHash(latestTransactionData.getMetadataHash(), latestTransactionData.getSignature());
|
|
||||||
if (metadataFile.exists()) {
|
|
||||||
ArbitraryDataTransactionMetadata transactionMetadata = new ArbitraryDataTransactionMetadata(metadataFile.getFilePath());
|
|
||||||
try {
|
|
||||||
transactionMetadata.read();
|
|
||||||
|
|
||||||
ArbitraryResourceMetadata metadata = new ArbitraryResourceMetadata();
|
|
||||||
metadata.setArbitraryResourceData(arbitraryResourceData);
|
|
||||||
metadata.setTitle(transactionMetadata.getTitle());
|
|
||||||
metadata.setDescription(transactionMetadata.getDescription());
|
|
||||||
metadata.setCategory(transactionMetadata.getCategory());
|
|
||||||
metadata.setTags(transactionMetadata.getTags());
|
|
||||||
repository.getArbitraryRepository().save(metadata);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Ignore, as we can add it again later
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We don't have a local copy of this metadata file, so delete it from the cache
|
|
||||||
// It will be re-added if the file later arrives via the network
|
|
||||||
ArbitraryResourceMetadata metadata = new ArbitraryResourceMetadata();
|
|
||||||
metadata.setArbitraryResourceData(arbitraryResourceData);
|
|
||||||
repository.getArbitraryRepository().delete(metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateArbitraryResourceStatus(Repository repository) throws DataException {
|
public void updateArbitraryResourceStatus(Repository repository) throws DataException {
|
||||||
@ -513,4 +455,60 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
repository.getArbitraryRepository().setStatus(arbitraryResourceData, status);
|
repository.getArbitraryRepository().setStatus(arbitraryResourceData, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateArbitraryMetadataCache(Repository repository) throws DataException {
|
||||||
|
// Get the latest transaction
|
||||||
|
ArbitraryTransactionData latestTransactionData = repository.getArbitraryRepository().getLatestTransaction(arbitraryTransactionData.getName(), arbitraryTransactionData.getService(), null, arbitraryTransactionData.getIdentifier());
|
||||||
|
if (latestTransactionData == null) {
|
||||||
|
// We don't have a latest transaction, so give up
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Service service = latestTransactionData.getService();
|
||||||
|
String name = latestTransactionData.getName();
|
||||||
|
String identifier = latestTransactionData.getIdentifier();
|
||||||
|
|
||||||
|
if (service == null) {
|
||||||
|
// Unsupported service - ignore this resource
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the cache we store null identifiers as "default", as it is part of the primary key
|
||||||
|
if (identifier == null) {
|
||||||
|
identifier = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
ArbitraryResourceData arbitraryResourceData = new ArbitraryResourceData();
|
||||||
|
arbitraryResourceData.service = service;
|
||||||
|
arbitraryResourceData.name = name;
|
||||||
|
arbitraryResourceData.identifier = identifier;
|
||||||
|
|
||||||
|
// Update metadata for latest transaction if it is local
|
||||||
|
if (latestTransactionData.getMetadataHash() != null) {
|
||||||
|
ArbitraryDataFile metadataFile = ArbitraryDataFile.fromHash(latestTransactionData.getMetadataHash(), latestTransactionData.getSignature());
|
||||||
|
if (metadataFile.exists()) {
|
||||||
|
ArbitraryDataTransactionMetadata transactionMetadata = new ArbitraryDataTransactionMetadata(metadataFile.getFilePath());
|
||||||
|
try {
|
||||||
|
transactionMetadata.read();
|
||||||
|
|
||||||
|
ArbitraryResourceMetadata metadata = new ArbitraryResourceMetadata();
|
||||||
|
metadata.setArbitraryResourceData(arbitraryResourceData);
|
||||||
|
metadata.setTitle(transactionMetadata.getTitle());
|
||||||
|
metadata.setDescription(transactionMetadata.getDescription());
|
||||||
|
metadata.setCategory(transactionMetadata.getCategory());
|
||||||
|
metadata.setTags(transactionMetadata.getTags());
|
||||||
|
repository.getArbitraryRepository().save(metadata);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore, as we can add it again later
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We don't have a local copy of this metadata file, so delete it from the cache
|
||||||
|
// It will be re-added if the file later arrives via the network
|
||||||
|
ArbitraryResourceMetadata metadata = new ArbitraryResourceMetadata();
|
||||||
|
metadata.setArbitraryResourceData(arbitraryResourceData);
|
||||||
|
repository.getArbitraryRepository().delete(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package org.qortal.transaction;
|
|||||||
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.group.GroupData;
|
import org.qortal.data.group.GroupData;
|
||||||
import org.qortal.data.transaction.CancelGroupBanTransactionData;
|
import org.qortal.data.transaction.CancelGroupBanTransactionData;
|
||||||
@ -13,7 +12,6 @@ import org.qortal.repository.Repository;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class CancelGroupBanTransaction extends Transaction {
|
public class CancelGroupBanTransaction extends Transaction {
|
||||||
|
|
||||||
@ -72,26 +70,9 @@ public class CancelGroupBanTransaction extends Transaction {
|
|||||||
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
|
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
|
||||||
return ValidationResult.NOT_GROUP_ADMIN;
|
return ValidationResult.NOT_GROUP_ADMIN;
|
||||||
|
|
||||||
if( this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getInstance().getNullGroupMembershipHeight() ) {
|
// Can't unban if not group's current owner
|
||||||
// Can't cancel ban if not group's current owner
|
if (!admin.getAddress().equals(groupData.getOwner()))
|
||||||
if (!admin.getAddress().equals(groupData.getOwner()))
|
return ValidationResult.INVALID_GROUP_OWNER;
|
||||||
return ValidationResult.INVALID_GROUP_OWNER;
|
|
||||||
}
|
|
||||||
// if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() )
|
|
||||||
else {
|
|
||||||
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
|
|
||||||
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
|
|
||||||
|
|
||||||
// if null ownership group, then check for admin approval
|
|
||||||
if(groupOwnedByNullAccount ) {
|
|
||||||
// Require approval if transaction relates to a group owned by the null account
|
|
||||||
if (!this.needsGroupApproval())
|
|
||||||
return ValidationResult.GROUP_APPROVAL_REQUIRED;
|
|
||||||
}
|
|
||||||
// Can't cancel ban if not group's current owner
|
|
||||||
else if (!admin.getAddress().equals(groupData.getOwner()))
|
|
||||||
return ValidationResult.INVALID_GROUP_OWNER;
|
|
||||||
}
|
|
||||||
|
|
||||||
Account member = getMember();
|
Account member = getMember();
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package org.qortal.transaction;
|
|||||||
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.group.GroupData;
|
import org.qortal.data.group.GroupData;
|
||||||
import org.qortal.data.transaction.CancelGroupInviteTransactionData;
|
import org.qortal.data.transaction.CancelGroupInviteTransactionData;
|
||||||
@ -13,7 +12,6 @@ import org.qortal.repository.Repository;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class CancelGroupInviteTransaction extends Transaction {
|
public class CancelGroupInviteTransaction extends Transaction {
|
||||||
|
|
||||||
@ -82,16 +80,6 @@ public class CancelGroupInviteTransaction extends Transaction {
|
|||||||
if (admin.getConfirmedBalance(Asset.QORT) < this.cancelGroupInviteTransactionData.getFee())
|
if (admin.getConfirmedBalance(Asset.QORT) < this.cancelGroupInviteTransactionData.getFee())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
// if null ownership group, then check for admin approval
|
|
||||||
if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() ) {
|
|
||||||
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
|
|
||||||
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
|
|
||||||
|
|
||||||
// Require approval if transaction relates to a group owned by the null account
|
|
||||||
if (groupOwnedByNullAccount && !this.needsGroupApproval())
|
|
||||||
return ValidationResult.GROUP_APPROVAL_REQUIRED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package org.qortal.transaction;
|
|||||||
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.group.GroupData;
|
import org.qortal.data.group.GroupData;
|
||||||
import org.qortal.data.transaction.GroupBanTransactionData;
|
import org.qortal.data.transaction.GroupBanTransactionData;
|
||||||
@ -13,7 +12,6 @@ import org.qortal.repository.Repository;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class GroupBanTransaction extends Transaction {
|
public class GroupBanTransaction extends Transaction {
|
||||||
|
|
||||||
@ -72,25 +70,9 @@ public class GroupBanTransaction extends Transaction {
|
|||||||
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
|
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
|
||||||
return ValidationResult.NOT_GROUP_ADMIN;
|
return ValidationResult.NOT_GROUP_ADMIN;
|
||||||
|
|
||||||
if( this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getInstance().getNullGroupMembershipHeight() ) {
|
// Can't ban if not group's current owner
|
||||||
// Can't ban if not group's current owner
|
if (!admin.getAddress().equals(groupData.getOwner()))
|
||||||
if (!admin.getAddress().equals(groupData.getOwner()))
|
return ValidationResult.INVALID_GROUP_OWNER;
|
||||||
return ValidationResult.INVALID_GROUP_OWNER;
|
|
||||||
}
|
|
||||||
// if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() )
|
|
||||||
else {
|
|
||||||
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
|
|
||||||
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
|
|
||||||
|
|
||||||
// if null ownership group, then check for admin approval
|
|
||||||
if(groupOwnedByNullAccount ) {
|
|
||||||
// Require approval if transaction relates to a group owned by the null account
|
|
||||||
if (!this.needsGroupApproval())
|
|
||||||
return ValidationResult.GROUP_APPROVAL_REQUIRED;
|
|
||||||
}
|
|
||||||
else if (!admin.getAddress().equals(groupData.getOwner()))
|
|
||||||
return ValidationResult.INVALID_GROUP_OWNER;
|
|
||||||
}
|
|
||||||
|
|
||||||
Account offender = getOffender();
|
Account offender = getOffender();
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package org.qortal.transaction;
|
|||||||
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.transaction.GroupInviteTransactionData;
|
import org.qortal.data.transaction.GroupInviteTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
@ -12,7 +11,6 @@ import org.qortal.repository.Repository;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class GroupInviteTransaction extends Transaction {
|
public class GroupInviteTransaction extends Transaction {
|
||||||
|
|
||||||
@ -87,16 +85,6 @@ public class GroupInviteTransaction extends Transaction {
|
|||||||
if (admin.getConfirmedBalance(Asset.QORT) < this.groupInviteTransactionData.getFee())
|
if (admin.getConfirmedBalance(Asset.QORT) < this.groupInviteTransactionData.getFee())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
// if null ownership group, then check for admin approval
|
|
||||||
if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() ) {
|
|
||||||
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
|
|
||||||
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
|
|
||||||
|
|
||||||
// Require approval if transaction relates to a group owned by the null account
|
|
||||||
if (groupOwnedByNullAccount && !this.needsGroupApproval())
|
|
||||||
return ValidationResult.GROUP_APPROVAL_REQUIRED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package org.qortal.transaction;
|
|||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.account.PublicKeyAccount;
|
import org.qortal.account.PublicKeyAccount;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.group.GroupData;
|
import org.qortal.data.group.GroupData;
|
||||||
import org.qortal.data.transaction.GroupKickTransactionData;
|
import org.qortal.data.transaction.GroupKickTransactionData;
|
||||||
@ -15,7 +14,6 @@ import org.qortal.repository.Repository;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class GroupKickTransaction extends Transaction {
|
public class GroupKickTransaction extends Transaction {
|
||||||
|
|
||||||
@ -84,26 +82,9 @@ public class GroupKickTransaction extends Transaction {
|
|||||||
if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress()))
|
if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress()))
|
||||||
return ValidationResult.INVALID_GROUP_OWNER;
|
return ValidationResult.INVALID_GROUP_OWNER;
|
||||||
|
|
||||||
if( this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getInstance().getNullGroupMembershipHeight() ) {
|
// Can't kick if not group's current owner
|
||||||
// Can't kick if not group's current owner
|
if (!admin.getAddress().equals(groupData.getOwner()))
|
||||||
if (!admin.getAddress().equals(groupData.getOwner()))
|
return ValidationResult.INVALID_GROUP_OWNER;
|
||||||
return ValidationResult.INVALID_GROUP_OWNER;
|
|
||||||
}
|
|
||||||
// if( this.repository.getBlockRepository().getBlockchainHeight() >= BlockChain.getInstance().getNullGroupMembershipHeight() )
|
|
||||||
else {
|
|
||||||
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
|
|
||||||
boolean groupOwnedByNullAccount = Objects.equals(groupOwner, Group.NULL_OWNER_ADDRESS);
|
|
||||||
|
|
||||||
// if null ownership group, then check for admin approval
|
|
||||||
if(groupOwnedByNullAccount ) {
|
|
||||||
// Require approval if transaction relates to a group owned by the null account
|
|
||||||
if (!this.needsGroupApproval())
|
|
||||||
return ValidationResult.GROUP_APPROVAL_REQUIRED;
|
|
||||||
}
|
|
||||||
// Can't kick if not group's current owner
|
|
||||||
else if (!admin.getAddress().equals(groupData.getOwner()))
|
|
||||||
return ValidationResult.INVALID_GROUP_OWNER;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check creator has enough funds
|
// Check creator has enough funds
|
||||||
if (admin.getConfirmedBalance(Asset.QORT) < this.groupKickTransactionData.getFee())
|
if (admin.getConfirmedBalance(Asset.QORT) < this.groupKickTransactionData.getFee())
|
||||||
|
@ -65,11 +65,11 @@ public abstract class Transaction {
|
|||||||
UPDATE_GROUP(23, true),
|
UPDATE_GROUP(23, true),
|
||||||
ADD_GROUP_ADMIN(24, true),
|
ADD_GROUP_ADMIN(24, true),
|
||||||
REMOVE_GROUP_ADMIN(25, true),
|
REMOVE_GROUP_ADMIN(25, true),
|
||||||
GROUP_BAN(26, true),
|
GROUP_BAN(26, false),
|
||||||
CANCEL_GROUP_BAN(27, true),
|
CANCEL_GROUP_BAN(27, false),
|
||||||
GROUP_KICK(28, true),
|
GROUP_KICK(28, false),
|
||||||
GROUP_INVITE(29, true),
|
GROUP_INVITE(29, false),
|
||||||
CANCEL_GROUP_INVITE(30, true),
|
CANCEL_GROUP_INVITE(30, false),
|
||||||
JOIN_GROUP(31, false),
|
JOIN_GROUP(31, false),
|
||||||
LEAVE_GROUP(32, false),
|
LEAVE_GROUP(32, false),
|
||||||
GROUP_APPROVAL(33, false),
|
GROUP_APPROVAL(33, false),
|
||||||
|
@ -1,250 +0,0 @@
|
|||||||
package org.qortal.utils;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
|
||||||
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.qortal.api.SearchMode;
|
|
||||||
import org.qortal.arbitrary.ArbitraryDataFile;
|
|
||||||
import org.qortal.arbitrary.ArbitraryDataReader;
|
|
||||||
import org.qortal.arbitrary.exception.MissingDataException;
|
|
||||||
import org.qortal.arbitrary.misc.Service;
|
|
||||||
import org.qortal.controller.Controller;
|
|
||||||
import org.qortal.data.arbitrary.ArbitraryDataIndex;
|
|
||||||
import org.qortal.data.arbitrary.ArbitraryDataIndexDetail;
|
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceData;
|
|
||||||
import org.qortal.data.arbitrary.IndexCache;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.repository.Repository;
|
|
||||||
import org.qortal.repository.RepositoryManager;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class ArbitraryIndexUtils {
|
|
||||||
|
|
||||||
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(ArbitraryIndexUtils.class);
|
|
||||||
|
|
||||||
public static final String INDEX_CACHE_TIMER = "Arbitrary Index Cache Timer";
|
|
||||||
public static final String INDEX_CACHE_TIMER_TASK = "Arbitrary Index Cache Timer Task";
|
|
||||||
|
|
||||||
public static void startCaching(int priorityRequested, int frequency) {
|
|
||||||
|
|
||||||
Timer timer = buildTimer(INDEX_CACHE_TIMER, priorityRequested);
|
|
||||||
|
|
||||||
TimerTask task = new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
Thread.currentThread().setName(INDEX_CACHE_TIMER_TASK);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fillCache(IndexCache.getInstance());
|
|
||||||
} catch (IOException | DataException e) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// delay 1 second
|
|
||||||
timer.scheduleAtFixedRate(task, 1_000, frequency * 60_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void fillCache(IndexCache instance) throws DataException, IOException {
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
List<ArbitraryResourceData> indexResources
|
|
||||||
= repository.getArbitraryRepository().searchArbitraryResources(
|
|
||||||
Service.JSON,
|
|
||||||
null,
|
|
||||||
"idx-",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
SearchMode.ALL,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
true);
|
|
||||||
|
|
||||||
List<ArbitraryDataIndexDetail> indexDetails = new ArrayList<>();
|
|
||||||
|
|
||||||
LOGGER.debug("processing index resource data: count = " + indexResources.size());
|
|
||||||
|
|
||||||
// process all index resources
|
|
||||||
for( ArbitraryResourceData indexResource : indexResources ) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
LOGGER.debug("processing index resource: name = " + indexResource.name + ", identifier = " + indexResource.identifier);
|
|
||||||
String json = ArbitraryIndexUtils.getJson(indexResource.name, indexResource.identifier);
|
|
||||||
|
|
||||||
// map the JSON string to a list of Java objects
|
|
||||||
List<ArbitraryDataIndex> indices = OBJECT_MAPPER.readValue(json, new TypeReference<List<ArbitraryDataIndex>>() {});
|
|
||||||
|
|
||||||
LOGGER.debug("processed indices = " + indices);
|
|
||||||
|
|
||||||
// rank and create index detail for each index in this index resource
|
|
||||||
for( int rank = 1; rank <= indices.size(); rank++ ) {
|
|
||||||
|
|
||||||
indexDetails.add( new ArbitraryDataIndexDetail(indexResource.name, rank, indices.get(rank - 1), indexResource.identifier ));
|
|
||||||
}
|
|
||||||
} catch (InvalidFormatException e) {
|
|
||||||
LOGGER.debug("invalid format, skipping: " + indexResource);
|
|
||||||
} catch (UnrecognizedPropertyException e) {
|
|
||||||
LOGGER.debug("unrecognized property, skipping " + indexResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.debug("processing indices by term ...");
|
|
||||||
Map<String, List<ArbitraryDataIndexDetail>> indicesByTerm
|
|
||||||
= indexDetails.stream().collect(
|
|
||||||
Collectors.toMap(
|
|
||||||
detail -> detail.term, // map by term
|
|
||||||
detail -> List.of(detail), // create list for term
|
|
||||||
(list1, list2) // merge lists for same term
|
|
||||||
-> Stream.of(list1, list2)
|
|
||||||
.flatMap(List::stream)
|
|
||||||
.collect(Collectors.toList())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
LOGGER.info("processed indices by term: count = " + indicesByTerm.size());
|
|
||||||
|
|
||||||
// lock, clear old, load new
|
|
||||||
synchronized( IndexCache.getInstance().getIndicesByTerm() ) {
|
|
||||||
IndexCache.getInstance().getIndicesByTerm().clear();
|
|
||||||
IndexCache.getInstance().getIndicesByTerm().putAll(indicesByTerm);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("loaded indices by term");
|
|
||||||
|
|
||||||
LOGGER.debug("processing indices by issuer ...");
|
|
||||||
Map<String, List<ArbitraryDataIndexDetail>> indicesByIssuer
|
|
||||||
= indexDetails.stream().collect(
|
|
||||||
Collectors.toMap(
|
|
||||||
detail -> detail.issuer, // map by issuer
|
|
||||||
detail -> List.of(detail), // create list for issuer
|
|
||||||
(list1, list2) // merge lists for same issuer
|
|
||||||
-> Stream.of(list1, list2)
|
|
||||||
.flatMap(List::stream)
|
|
||||||
.collect(Collectors.toList())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
LOGGER.info("processed indices by issuer: count = " + indicesByIssuer.size());
|
|
||||||
|
|
||||||
// lock, clear old, load new
|
|
||||||
synchronized( IndexCache.getInstance().getIndicesByIssuer() ) {
|
|
||||||
IndexCache.getInstance().getIndicesByIssuer().clear();
|
|
||||||
IndexCache.getInstance().getIndicesByIssuer().putAll(indicesByIssuer);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.info("loaded indices by issuer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Timer buildTimer( final String name, int priorityRequested) {
|
|
||||||
// ensure priority is in between 1-10
|
|
||||||
final int priority = Math.max(0, Math.min(10, priorityRequested));
|
|
||||||
|
|
||||||
// Create a custom Timer with updated priority threads
|
|
||||||
Timer timer = new Timer(true) { // 'true' to make the Timer daemon
|
|
||||||
@Override
|
|
||||||
public void schedule(TimerTask task, long delay) {
|
|
||||||
Thread thread = new Thread(task, name) {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
this.setPriority(priority);
|
|
||||||
super.run();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
thread.setPriority(priority);
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return timer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String getJsonWithExceptionHandling( String name, String identifier ) {
|
|
||||||
try {
|
|
||||||
return getJson(name, identifier);
|
|
||||||
}
|
|
||||||
catch( Exception e ) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
return e.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getJson(String name, String identifier) throws IOException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
ArbitraryDataReader arbitraryDataReader
|
|
||||||
= new ArbitraryDataReader(name, ArbitraryDataFile.ResourceIdType.NAME, Service.JSON, identifier);
|
|
||||||
|
|
||||||
int attempts = 0;
|
|
||||||
Integer maxAttempts = 5;
|
|
||||||
|
|
||||||
while (!Controller.isStopping()) {
|
|
||||||
attempts++;
|
|
||||||
if (!arbitraryDataReader.isBuilding()) {
|
|
||||||
try {
|
|
||||||
arbitraryDataReader.loadSynchronously(false);
|
|
||||||
break;
|
|
||||||
} catch (MissingDataException e) {
|
|
||||||
if (attempts > maxAttempts) {
|
|
||||||
// Give up after 5 attempts
|
|
||||||
throw new IOException("Data unavailable. Please try again later.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Thread.sleep(3000L);
|
|
||||||
}
|
|
||||||
|
|
||||||
java.nio.file.Path outputPath = arbitraryDataReader.getFilePath();
|
|
||||||
if (outputPath == null) {
|
|
||||||
// Assume the resource doesn't exist
|
|
||||||
throw new IOException( "File not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// No file path supplied - so check if this is a single file resource
|
|
||||||
String[] files = ArrayUtils.removeElement(outputPath.toFile().list(), ".qortal");
|
|
||||||
String filepath = files[0];
|
|
||||||
|
|
||||||
java.nio.file.Path path = Paths.get(outputPath.toString(), filepath);
|
|
||||||
if (!Files.exists(path)) {
|
|
||||||
String message = String.format("No file exists at filepath: %s", filepath);
|
|
||||||
throw new IOException( message );
|
|
||||||
}
|
|
||||||
|
|
||||||
String data = Files.readString(path);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IOException(String.format("Unable to load %s %s: %s", Service.JSON, name, e.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,7 +24,6 @@ import java.nio.file.attribute.BasicFileAttributes;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||||
@ -73,23 +72,23 @@ public class ArbitraryTransactionUtils {
|
|||||||
return latestPut;
|
return latestPut;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<ArbitraryTransactionData> hasMoreRecentPutTransaction(Repository repository, ArbitraryTransactionData arbitraryTransactionData) {
|
public static boolean hasMoreRecentPutTransaction(Repository repository, ArbitraryTransactionData arbitraryTransactionData) {
|
||||||
byte[] signature = arbitraryTransactionData.getSignature();
|
byte[] signature = arbitraryTransactionData.getSignature();
|
||||||
if (signature == null) {
|
if (signature == null) {
|
||||||
// We can't make a sensible decision without a signature
|
// We can't make a sensible decision without a signature
|
||||||
// so it's best to assume there is nothing newer
|
// so it's best to assume there is nothing newer
|
||||||
return Optional.empty();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArbitraryTransactionData latestPut = ArbitraryTransactionUtils.fetchLatestPut(repository, arbitraryTransactionData);
|
ArbitraryTransactionData latestPut = ArbitraryTransactionUtils.fetchLatestPut(repository, arbitraryTransactionData);
|
||||||
if (latestPut == null) {
|
if (latestPut == null) {
|
||||||
return Optional.empty();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the latest PUT transaction has a newer timestamp, it will override the existing transaction
|
// If the latest PUT transaction has a newer timestamp, it will override the existing transaction
|
||||||
// Any data relating to the older transaction is no longer needed
|
// Any data relating to the older transaction is no longer needed
|
||||||
boolean hasNewerPut = (latestPut.getTimestamp() > arbitraryTransactionData.getTimestamp());
|
boolean hasNewerPut = (latestPut.getTimestamp() > arbitraryTransactionData.getTimestamp());
|
||||||
return hasNewerPut ? Optional.of(latestPut) : Optional.empty();
|
return hasNewerPut;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean completeFileExists(ArbitraryTransactionData transactionData) throws DataException {
|
public static boolean completeFileExists(ArbitraryTransactionData transactionData) throws DataException {
|
||||||
@ -209,15 +208,7 @@ public class ArbitraryTransactionUtils {
|
|||||||
return ArbitraryTransactionUtils.isFileRecent(filePath, now, cleanupAfter);
|
return ArbitraryTransactionUtils.isFileRecent(filePath, now, cleanupAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static void deleteCompleteFile(ArbitraryTransactionData arbitraryTransactionData, long now, long cleanupAfter) throws DataException {
|
||||||
*
|
|
||||||
* @param arbitraryTransactionData
|
|
||||||
* @param now
|
|
||||||
* @param cleanupAfter
|
|
||||||
* @return true if file is deleted, otherwise return false
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static boolean deleteCompleteFile(ArbitraryTransactionData arbitraryTransactionData, long now, long cleanupAfter) throws DataException {
|
|
||||||
byte[] completeHash = arbitraryTransactionData.getData();
|
byte[] completeHash = arbitraryTransactionData.getData();
|
||||||
byte[] signature = arbitraryTransactionData.getSignature();
|
byte[] signature = arbitraryTransactionData.getSignature();
|
||||||
|
|
||||||
@ -228,11 +219,6 @@ public class ArbitraryTransactionUtils {
|
|||||||
"if needed", Base58.encode(completeHash));
|
"if needed", Base58.encode(completeHash));
|
||||||
|
|
||||||
arbitraryDataFile.delete();
|
arbitraryDataFile.delete();
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,319 +0,0 @@
|
|||||||
package org.qortal.utils;
|
|
||||||
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crypto.Crypto;
|
|
||||||
import org.qortal.data.PaymentData;
|
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
|
||||||
import org.qortal.data.account.AddressAmountData;
|
|
||||||
import org.qortal.data.account.BlockHeightRange;
|
|
||||||
import org.qortal.data.account.BlockHeightRangeAddressAmounts;
|
|
||||||
import org.qortal.data.transaction.ATTransactionData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.BuyNameTransactionData;
|
|
||||||
import org.qortal.data.transaction.CreateAssetOrderTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MultiPaymentTransactionData;
|
|
||||||
import org.qortal.data.transaction.PaymentTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.data.transaction.TransferAssetTransactionData;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class BalanceRecorderUtils {
|
|
||||||
|
|
||||||
public static final Predicate<AddressAmountData> ADDRESS_AMOUNT_DATA_NOT_ZERO = addressAmount -> addressAmount.getAmount() != 0;
|
|
||||||
public static final Comparator<BlockHeightRangeAddressAmounts> BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR = new Comparator<BlockHeightRangeAddressAmounts>() {
|
|
||||||
@Override
|
|
||||||
public int compare(BlockHeightRangeAddressAmounts amounts1, BlockHeightRangeAddressAmounts amounts2) {
|
|
||||||
return amounts1.getRange().getEnd() - amounts2.getRange().getEnd();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final Comparator<AddressAmountData> ADDRESS_AMOUNT_DATA_COMPARATOR = new Comparator<AddressAmountData>() {
|
|
||||||
@Override
|
|
||||||
public int compare(AddressAmountData addressAmountData, AddressAmountData t1) {
|
|
||||||
if( addressAmountData.getAmount() > t1.getAmount() ) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if( addressAmountData.getAmount() < t1.getAmount() ) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final Comparator<BlockHeightRange> BLOCK_HEIGHT_RANGE_COMPARATOR = new Comparator<BlockHeightRange>() {
|
|
||||||
@Override
|
|
||||||
public int compare(BlockHeightRange range1, BlockHeightRange range2) {
|
|
||||||
return range1.getEnd() - range2.getEnd();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build Balance Dynmaics For Account
|
|
||||||
*
|
|
||||||
* @param priorBalances the balances prior to the current height, assuming only one balance per address
|
|
||||||
* @param accountBalance the current balance
|
|
||||||
*
|
|
||||||
* @return the difference between the current balance and the prior balance for the current balance address
|
|
||||||
*/
|
|
||||||
public static AddressAmountData buildBalanceDynamicsForAccount(List<AccountBalanceData> priorBalances, AccountBalanceData accountBalance) {
|
|
||||||
Optional<AccountBalanceData> matchingAccountPriorBalance
|
|
||||||
= priorBalances.stream()
|
|
||||||
.filter(priorBalance -> accountBalance.getAddress().equals(priorBalance.getAddress()))
|
|
||||||
.findFirst();
|
|
||||||
if(matchingAccountPriorBalance.isPresent()) {
|
|
||||||
return new AddressAmountData(accountBalance.getAddress(), accountBalance.getBalance() - matchingAccountPriorBalance.get().getBalance());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new AddressAmountData(accountBalance.getAddress(), accountBalance.getBalance());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<AddressAmountData> buildBalanceDynamics(
|
|
||||||
final List<AccountBalanceData> balances,
|
|
||||||
final List<AccountBalanceData> priorBalances,
|
|
||||||
long minimum,
|
|
||||||
List<TransactionData> transactions) {
|
|
||||||
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>(transactions.size());
|
|
||||||
|
|
||||||
for( TransactionData transactionData : transactions ) {
|
|
||||||
|
|
||||||
mapBalanceModificationsForTransaction(amountsByAddress, transactionData);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AddressAmountData> addressAmounts
|
|
||||||
= balances.stream()
|
|
||||||
.map(balance -> buildBalanceDynamicsForAccount(priorBalances, balance))
|
|
||||||
.map( data -> adjustAddressAmount(amountsByAddress.getOrDefault(data.getAddress(), 0L), data))
|
|
||||||
.filter(ADDRESS_AMOUNT_DATA_NOT_ZERO)
|
|
||||||
.filter(data -> data.getAmount() >= minimum)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
return addressAmounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AddressAmountData adjustAddressAmount(long adjustment, AddressAmountData data) {
|
|
||||||
|
|
||||||
return new AddressAmountData(data.getAddress(), data.getAmount() - adjustment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void mapBalanceModificationsForTransaction(Map<String, Long> amountsByAddress, TransactionData transactionData) {
|
|
||||||
String creatorAddress;
|
|
||||||
|
|
||||||
// AT Transaction
|
|
||||||
if( transactionData instanceof ATTransactionData) {
|
|
||||||
creatorAddress = mapBalanceModificationsForAtTransaction(amountsByAddress, (ATTransactionData) transactionData);
|
|
||||||
}
|
|
||||||
// Buy Name Transaction
|
|
||||||
else if( transactionData instanceof BuyNameTransactionData) {
|
|
||||||
creatorAddress = mapBalanceModificationsForBuyNameTransaction(amountsByAddress, (BuyNameTransactionData) transactionData);
|
|
||||||
}
|
|
||||||
// Create Asset Order Transaction
|
|
||||||
else if( transactionData instanceof CreateAssetOrderTransactionData) {
|
|
||||||
//TODO I'm not sure how to handle this one. This hasn't been used at this point in the blockchain.
|
|
||||||
|
|
||||||
creatorAddress = Crypto.toAddress(transactionData.getCreatorPublicKey());
|
|
||||||
}
|
|
||||||
// Deploy AT Transaction
|
|
||||||
else if( transactionData instanceof DeployAtTransactionData ) {
|
|
||||||
creatorAddress = mapBalanceModificationsForDeployAtTransaction(amountsByAddress, (DeployAtTransactionData) transactionData);
|
|
||||||
}
|
|
||||||
// Multi Payment Transaction
|
|
||||||
else if( transactionData instanceof MultiPaymentTransactionData) {
|
|
||||||
creatorAddress = mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress, (MultiPaymentTransactionData) transactionData);
|
|
||||||
}
|
|
||||||
// Payment Transaction
|
|
||||||
else if( transactionData instanceof PaymentTransactionData ) {
|
|
||||||
creatorAddress = mapBalanceModicationsForPaymentTransaction(amountsByAddress, (PaymentTransactionData) transactionData);
|
|
||||||
}
|
|
||||||
// Transfer Asset Transaction
|
|
||||||
else if( transactionData instanceof TransferAssetTransactionData) {
|
|
||||||
creatorAddress = mapBalanceModificationsForTransferAssetTransaction(amountsByAddress, (TransferAssetTransactionData) transactionData);
|
|
||||||
}
|
|
||||||
// Other Transactions
|
|
||||||
else {
|
|
||||||
creatorAddress = Crypto.toAddress(transactionData.getCreatorPublicKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
// all transactions modify the balance for fees
|
|
||||||
mapBalanceModifications(amountsByAddress, transactionData.getFee(), creatorAddress, Optional.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String mapBalanceModificationsForTransferAssetTransaction(Map<String, Long> amountsByAddress, TransferAssetTransactionData transferAssetData) {
|
|
||||||
String creatorAddress = Crypto.toAddress(transferAssetData.getSenderPublicKey());
|
|
||||||
|
|
||||||
if( transferAssetData.getAssetId() == 0) {
|
|
||||||
mapBalanceModifications(
|
|
||||||
amountsByAddress,
|
|
||||||
transferAssetData.getAmount(),
|
|
||||||
creatorAddress,
|
|
||||||
Optional.of(transferAssetData.getRecipient())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return creatorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String mapBalanceModicationsForPaymentTransaction(Map<String, Long> amountsByAddress, PaymentTransactionData paymentData) {
|
|
||||||
String creatorAddress = Crypto.toAddress(paymentData.getCreatorPublicKey());
|
|
||||||
|
|
||||||
mapBalanceModifications(amountsByAddress,
|
|
||||||
paymentData.getAmount(),
|
|
||||||
creatorAddress,
|
|
||||||
Optional.of(paymentData.getRecipient())
|
|
||||||
);
|
|
||||||
return creatorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String mapBalanceModificationsForMultiPaymentTransaction(Map<String, Long> amountsByAddress, MultiPaymentTransactionData multiPaymentData) {
|
|
||||||
String creatorAddress = Crypto.toAddress(multiPaymentData.getCreatorPublicKey());
|
|
||||||
|
|
||||||
for(PaymentData payment : multiPaymentData.getPayments() ) {
|
|
||||||
mapBalanceModificationsForTransaction(
|
|
||||||
amountsByAddress,
|
|
||||||
getPaymentTransactionData(multiPaymentData, payment)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return creatorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String mapBalanceModificationsForDeployAtTransaction(Map<String, Long> amountsByAddress, DeployAtTransactionData transactionData) {
|
|
||||||
String creatorAddress;
|
|
||||||
DeployAtTransactionData deployAtData = transactionData;
|
|
||||||
|
|
||||||
creatorAddress = Crypto.toAddress(deployAtData.getCreatorPublicKey());
|
|
||||||
|
|
||||||
if( deployAtData.getAssetId() == 0 ) {
|
|
||||||
mapBalanceModifications(
|
|
||||||
amountsByAddress,
|
|
||||||
deployAtData.getAmount(),
|
|
||||||
creatorAddress,
|
|
||||||
Optional.of(deployAtData.getAtAddress())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return creatorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String mapBalanceModificationsForBuyNameTransaction(Map<String, Long> amountsByAddress, BuyNameTransactionData transactionData) {
|
|
||||||
String creatorAddress;
|
|
||||||
BuyNameTransactionData buyNameData = transactionData;
|
|
||||||
|
|
||||||
creatorAddress = Crypto.toAddress(buyNameData.getCreatorPublicKey());
|
|
||||||
|
|
||||||
mapBalanceModifications(
|
|
||||||
amountsByAddress,
|
|
||||||
buyNameData.getAmount(),
|
|
||||||
creatorAddress,
|
|
||||||
Optional.of(buyNameData.getSeller())
|
|
||||||
);
|
|
||||||
return creatorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String mapBalanceModificationsForAtTransaction(Map<String, Long> amountsByAddress, ATTransactionData transactionData) {
|
|
||||||
String creatorAddress;
|
|
||||||
ATTransactionData atData = transactionData;
|
|
||||||
creatorAddress = atData.getATAddress();
|
|
||||||
|
|
||||||
if( atData.getAssetId() != null && atData.getAssetId() == 0) {
|
|
||||||
mapBalanceModifications(
|
|
||||||
amountsByAddress,
|
|
||||||
atData.getAmount(),
|
|
||||||
creatorAddress,
|
|
||||||
Optional.of(atData.getRecipient())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return creatorAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PaymentTransactionData getPaymentTransactionData(MultiPaymentTransactionData multiPaymentData, PaymentData payment) {
|
|
||||||
return new PaymentTransactionData(
|
|
||||||
new BaseTransactionData(
|
|
||||||
multiPaymentData.getTimestamp(),
|
|
||||||
multiPaymentData.getTxGroupId(),
|
|
||||||
multiPaymentData.getReference(),
|
|
||||||
multiPaymentData.getCreatorPublicKey(),
|
|
||||||
0L,
|
|
||||||
multiPaymentData.getSignature()
|
|
||||||
),
|
|
||||||
payment.getRecipient(),
|
|
||||||
payment.getAmount()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void mapBalanceModifications(Map<String, Long> amountsByAddress, Long amount, String sender, Optional<String> recipient) {
|
|
||||||
amountsByAddress.put(
|
|
||||||
sender,
|
|
||||||
amountsByAddress.getOrDefault(sender, 0L) - amount
|
|
||||||
);
|
|
||||||
|
|
||||||
if( recipient.isPresent() )
|
|
||||||
amountsByAddress.put(
|
|
||||||
recipient.get(),
|
|
||||||
amountsByAddress.getOrDefault(recipient.get(), 0L) + amount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeRecordingsAboveHeight(int currentHeight, ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight) {
|
|
||||||
balancesByHeight.entrySet().stream()
|
|
||||||
.filter(heightWithBalances -> heightWithBalances.getKey() > currentHeight)
|
|
||||||
.forEach(heightWithBalances -> balancesByHeight.remove(heightWithBalances.getKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeRecordingsBelowHeight(int currentHeight, ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight) {
|
|
||||||
balancesByHeight.entrySet().stream()
|
|
||||||
.filter(heightWithBalances -> heightWithBalances.getKey() < currentHeight)
|
|
||||||
.forEach(heightWithBalances -> balancesByHeight.remove(heightWithBalances.getKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void removeDynamicsOnOrAboveHeight(int currentHeight, CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> balanceDynamics) {
|
|
||||||
balanceDynamics.stream()
|
|
||||||
.filter(addressAmounts -> addressAmounts.getRange().getEnd() >= currentHeight)
|
|
||||||
.forEach(addressAmounts -> balanceDynamics.remove(addressAmounts));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BlockHeightRangeAddressAmounts removeOldestDynamics(CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> balanceDynamics) {
|
|
||||||
BlockHeightRangeAddressAmounts oldestDynamics
|
|
||||||
= balanceDynamics.stream().sorted(BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR).findFirst().get();
|
|
||||||
|
|
||||||
balanceDynamics.remove(oldestDynamics);
|
|
||||||
return oldestDynamics;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Integer> getPriorHeight(int currentHeight, ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight) {
|
|
||||||
Optional<Integer> priorHeight
|
|
||||||
= balancesByHeight.keySet().stream()
|
|
||||||
.filter(height -> height < currentHeight)
|
|
||||||
.sorted(Comparator.reverseOrder()).findFirst();
|
|
||||||
return priorHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is Reward Distribution Range?
|
|
||||||
*
|
|
||||||
* @param start start height, exclusive
|
|
||||||
* @param end end height, inclusive
|
|
||||||
*
|
|
||||||
* @return true there is a reward distribution block within this block range
|
|
||||||
*/
|
|
||||||
public static boolean isRewardDistributionRange(int start, int end) {
|
|
||||||
|
|
||||||
// iterate through the block height until a reward distribution block or the end of the range
|
|
||||||
for( int i = start + 1; i <= end; i++) {
|
|
||||||
if( Block.isRewardDistributionBlock(i) ) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no reward distribution blocks found within range
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
package org.qortal.utils;
|
|
||||||
|
|
||||||
import io.druid.extendedset.intset.ConciseSet;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.data.account.AddressLevelPairing;
|
|
||||||
import org.qortal.data.account.RewardShareData;
|
|
||||||
import org.qortal.data.block.BlockData;
|
|
||||||
import org.qortal.data.block.DecodedOnlineAccountData;
|
|
||||||
import org.qortal.data.group.GroupMemberData;
|
|
||||||
import org.qortal.data.naming.NameData;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.repository.Repository;
|
|
||||||
import org.qortal.transform.block.BlockTransformer;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Blocks
|
|
||||||
*
|
|
||||||
* Methods for block related logic.
|
|
||||||
*/
|
|
||||||
public class Blocks {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Blocks.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Decode Online Accounts For Block
|
|
||||||
*
|
|
||||||
* @param repository the data repository
|
|
||||||
* @param blockData the block data
|
|
||||||
*
|
|
||||||
* @return the online accounts set to the block
|
|
||||||
*
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static Set<DecodedOnlineAccountData> getDecodedOnlineAccountsForBlock(Repository repository, BlockData blockData) throws DataException {
|
|
||||||
try {
|
|
||||||
// get all online account indices from block
|
|
||||||
ConciseSet onlineAccountIndices = BlockTransformer.decodeOnlineAccounts(blockData.getEncodedOnlineAccounts());
|
|
||||||
|
|
||||||
// get online reward shares from the online accounts on the block
|
|
||||||
List<RewardShareData> onlineRewardShares = repository.getAccountRepository().getRewardSharesByIndexes(onlineAccountIndices.toArray());
|
|
||||||
|
|
||||||
// online timestamp for block
|
|
||||||
long onlineTimestamp = blockData.getOnlineAccountsTimestamp();
|
|
||||||
Set<DecodedOnlineAccountData> onlineAccounts = new HashSet<>();
|
|
||||||
|
|
||||||
// all minting group member addresses
|
|
||||||
List<String> mintingGroupAddresses
|
|
||||||
= Groups.getAllMembers(
|
|
||||||
repository.getGroupRepository(),
|
|
||||||
Groups.getGroupIdsToMint(BlockChain.getInstance(), blockData.getHeight())
|
|
||||||
);
|
|
||||||
|
|
||||||
// all names, indexed by address
|
|
||||||
Map<String, String> nameByAddress
|
|
||||||
= repository.getNameRepository()
|
|
||||||
.getAllNames().stream()
|
|
||||||
.collect(Collectors.toMap(NameData::getOwner, NameData::getName));
|
|
||||||
|
|
||||||
// all accounts at level 1 or higher, indexed by address
|
|
||||||
Map<String, Integer> levelByAddress
|
|
||||||
= repository.getAccountRepository().getAddressLevelPairings(1).stream()
|
|
||||||
.collect(Collectors.toMap(AddressLevelPairing::getAddress, AddressLevelPairing::getLevel));
|
|
||||||
|
|
||||||
// for each reward share where the minter is online,
|
|
||||||
// construct the data object and add it to the return list
|
|
||||||
for (RewardShareData onlineRewardShare : onlineRewardShares) {
|
|
||||||
String minter = onlineRewardShare.getMinter();
|
|
||||||
DecodedOnlineAccountData onlineAccountData
|
|
||||||
= new DecodedOnlineAccountData(
|
|
||||||
onlineTimestamp,
|
|
||||||
minter,
|
|
||||||
onlineRewardShare.getRecipient(),
|
|
||||||
onlineRewardShare.getSharePercent(),
|
|
||||||
mintingGroupAddresses.contains(minter),
|
|
||||||
nameByAddress.get(minter),
|
|
||||||
levelByAddress.get(minter)
|
|
||||||
);
|
|
||||||
|
|
||||||
onlineAccounts.add(onlineAccountData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return onlineAccounts;
|
|
||||||
} catch (DataException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e ) {
|
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
|
|
||||||
return new HashSet<>(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
package org.qortal.utils;
|
|
||||||
|
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.data.group.GroupAdminData;
|
|
||||||
import org.qortal.data.group.GroupMemberData;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.repository.GroupRepository;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Groups
|
|
||||||
*
|
|
||||||
* A utility class for group related functionality.
|
|
||||||
*/
|
|
||||||
public class Groups {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the member exist in any of these groups?
|
|
||||||
*
|
|
||||||
* @param groupRepository the group data repository
|
|
||||||
* @param groupsIds the group Ids to look for the address
|
|
||||||
* @param address the address
|
|
||||||
*
|
|
||||||
* @return true if the address is in any of the groups listed otherwise false
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static boolean memberExistsInAnyGroup(GroupRepository groupRepository, List<Integer> groupsIds, String address) throws DataException {
|
|
||||||
|
|
||||||
// if any of the listed groups have the address as a member, then return true
|
|
||||||
for( Integer groupIdToMint : groupsIds) {
|
|
||||||
if( groupRepository.memberExists(groupIdToMint, address) ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if none of the listed groups have the address as a member, then return false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get All Members
|
|
||||||
*
|
|
||||||
* Get all the group members from a list of groups.
|
|
||||||
*
|
|
||||||
* @param groupRepository the group data repository
|
|
||||||
* @param groupIds the list of group Ids to look at
|
|
||||||
*
|
|
||||||
* @return the list of all members belonging to any of the groups, no duplicates
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static List<String> getAllMembers( GroupRepository groupRepository, List<Integer> groupIds ) throws DataException {
|
|
||||||
// collect all the members in a set, the set keeps out duplicates
|
|
||||||
Set<String> allMembers = new HashSet<>();
|
|
||||||
|
|
||||||
// add all members from each group to the all members set
|
|
||||||
for( int groupId : groupIds ) {
|
|
||||||
allMembers.addAll( groupRepository.getGroupMembers(groupId).stream().map(GroupMemberData::getMember).collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>(allMembers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get All Admins
|
|
||||||
*
|
|
||||||
* Get all the admins from a list of groups.
|
|
||||||
*
|
|
||||||
* @param groupRepository the group data repository
|
|
||||||
* @param groupIds the list of group Ids to look at
|
|
||||||
*
|
|
||||||
* @return the list of all admins to any of the groups, no duplicates
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static List<String> getAllAdmins( GroupRepository groupRepository, List<Integer> groupIds ) throws DataException {
|
|
||||||
// collect all the admins in a set, the set keeps out duplicates
|
|
||||||
Set<String> allAdmins = new HashSet<>();
|
|
||||||
|
|
||||||
// collect admins for each group
|
|
||||||
for( int groupId : groupIds ) {
|
|
||||||
allAdmins.addAll( groupRepository.getGroupAdmins(groupId).stream().map(GroupAdminData::getAdmin).collect(Collectors.toList()) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>(allAdmins);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Group Ids To Mint
|
|
||||||
*
|
|
||||||
* @param blockchain the blockchain
|
|
||||||
* @param blockchainHeight the block height to mint
|
|
||||||
*
|
|
||||||
* @return the group Ids for the minting groups at the height given
|
|
||||||
*/
|
|
||||||
public static List<Integer> getGroupIdsToMint(BlockChain blockchain, int blockchainHeight) {
|
|
||||||
|
|
||||||
// sort heights lowest to highest
|
|
||||||
Comparator<BlockChain.IdsForHeight> compareByHeight = Comparator.comparingInt(entry -> entry.height);
|
|
||||||
|
|
||||||
// sort heights highest to lowest
|
|
||||||
Comparator<BlockChain.IdsForHeight> compareByHeightReversed = compareByHeight.reversed();
|
|
||||||
|
|
||||||
// get highest height that is less than the blockchain height
|
|
||||||
Optional<BlockChain.IdsForHeight> ids = blockchain.getMintingGroupIds().stream()
|
|
||||||
.filter(entry -> entry.height < blockchainHeight)
|
|
||||||
.sorted(compareByHeightReversed)
|
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
if( ids.isPresent()) {
|
|
||||||
return ids.get().ids;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new ArrayList<>(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -38,9 +38,7 @@
|
|||||||
"blockRewardBatchStartHeight": 1508000,
|
"blockRewardBatchStartHeight": 1508000,
|
||||||
"blockRewardBatchSize": 1000,
|
"blockRewardBatchSize": 1000,
|
||||||
"blockRewardBatchAccountsBlockCount": 25,
|
"blockRewardBatchAccountsBlockCount": 25,
|
||||||
"mintingGroupIds": [
|
"mintingGroupId": 694,
|
||||||
{ "height": 0, "ids": [ 694 ]}
|
|
||||||
],
|
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 5.00 },
|
{ "height": 1, "reward": 5.00 },
|
||||||
{ "height": 259201, "reward": 4.75 },
|
{ "height": 259201, "reward": 4.75 },
|
||||||
@ -115,11 +113,7 @@
|
|||||||
"onlyMintWithNameHeight": 1900300,
|
"onlyMintWithNameHeight": 1900300,
|
||||||
"removeOnlyMintWithNameHeight": 1935500,
|
"removeOnlyMintWithNameHeight": 1935500,
|
||||||
"groupMemberCheckHeight": 1902700,
|
"groupMemberCheckHeight": 1902700,
|
||||||
"fixBatchRewardHeight": 1945900,
|
"fixBatchRewardHeight": 1945900
|
||||||
"adminsReplaceFoundersHeight": 2012800,
|
|
||||||
"nullGroupMembershipHeight": 2012800,
|
|
||||||
"ignoreLevelForRewardShareHeight": 2012800,
|
|
||||||
"adminQueryFixHeight": 2012800
|
|
||||||
},
|
},
|
||||||
"checkpoints": [
|
"checkpoints": [
|
||||||
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
|
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
|
||||||
|
@ -20,21 +20,17 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
top: 50%;
|
top: 45%;
|
||||||
-ms-transform: translateY(-50%);
|
-ms-transform: translateY(-50%);
|
||||||
transform: translate(-50% , -50%);
|
transform: translateY(-50%);
|
||||||
left: 50%;
|
|
||||||
}
|
}
|
||||||
#panel {
|
#panel {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: white;
|
background: white;
|
||||||
word-wrap: break-word;
|
|
||||||
width: 350px;
|
width: 350px;
|
||||||
max-width: 100%;
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
#status {
|
#status {
|
||||||
color: #03a9f4;
|
color: #03a9f4;
|
||||||
|
@ -84,7 +84,6 @@ isDOMContentLoaded: isDOMContentLoaded ? true : false
|
|||||||
|
|
||||||
function handleQDNResourceDisplayed(pathurl, isDOMContentLoaded) {
|
function handleQDNResourceDisplayed(pathurl, isDOMContentLoaded) {
|
||||||
// make sure that an empty string the root path
|
// make sure that an empty string the root path
|
||||||
if(pathurl?.startsWith('/render/hash/')) return;
|
|
||||||
const path = pathurl || '/'
|
const path = pathurl || '/'
|
||||||
if (!isManualNavigation) {
|
if (!isManualNavigation) {
|
||||||
isManualNavigation = true
|
isManualNavigation = true
|
||||||
@ -285,9 +284,11 @@ window.addEventListener("message", async (event) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Core received action: " + JSON.stringify(event.data.action));
|
||||||
|
|
||||||
let url;
|
let url;
|
||||||
let data = event.data;
|
let data = event.data;
|
||||||
let identifier;
|
|
||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
case "GET_ACCOUNT_DATA":
|
case "GET_ACCOUNT_DATA":
|
||||||
return httpGetAsyncWithEvent(event, "/addresses/" + data.address);
|
return httpGetAsyncWithEvent(event, "/addresses/" + data.address);
|
||||||
@ -382,7 +383,6 @@ window.addEventListener("message", async (event) => {
|
|||||||
if (data.identifier != null) url = url.concat("&identifier=" + data.identifier);
|
if (data.identifier != null) url = url.concat("&identifier=" + data.identifier);
|
||||||
if (data.name != null) url = url.concat("&name=" + data.name);
|
if (data.name != null) url = url.concat("&name=" + data.name);
|
||||||
if (data.names != null) data.names.forEach((x, i) => url = url.concat("&name=" + x));
|
if (data.names != null) data.names.forEach((x, i) => url = url.concat("&name=" + x));
|
||||||
if (data.keywords != null) data.keywords.forEach((x, i) => url = url.concat("&keywords=" + x));
|
|
||||||
if (data.title != null) url = url.concat("&title=" + data.title);
|
if (data.title != null) url = url.concat("&title=" + data.title);
|
||||||
if (data.description != null) url = url.concat("&description=" + data.description);
|
if (data.description != null) url = url.concat("&description=" + data.description);
|
||||||
if (data.prefix != null) url = url.concat("&prefix=" + new Boolean(data.prefix).toString());
|
if (data.prefix != null) url = url.concat("&prefix=" + new Boolean(data.prefix).toString());
|
||||||
@ -419,7 +419,7 @@ window.addEventListener("message", async (event) => {
|
|||||||
return httpGetAsyncWithEvent(event, url);
|
return httpGetAsyncWithEvent(event, url);
|
||||||
|
|
||||||
case "GET_QDN_RESOURCE_PROPERTIES":
|
case "GET_QDN_RESOURCE_PROPERTIES":
|
||||||
identifier = (data.identifier != null) ? data.identifier : "default";
|
let identifier = (data.identifier != null) ? data.identifier : "default";
|
||||||
url = "/arbitrary/resource/properties/" + data.service + "/" + data.name + "/" + identifier;
|
url = "/arbitrary/resource/properties/" + data.service + "/" + data.name + "/" + identifier;
|
||||||
return httpGetAsyncWithEvent(event, url);
|
return httpGetAsyncWithEvent(event, url);
|
||||||
|
|
||||||
@ -456,7 +456,7 @@ window.addEventListener("message", async (event) => {
|
|||||||
return httpGetAsyncWithEvent(event, url);
|
return httpGetAsyncWithEvent(event, url);
|
||||||
|
|
||||||
case "GET_AT":
|
case "GET_AT":
|
||||||
url = "/at/" + data.atAddress;
|
url = "/at" + data.atAddress;
|
||||||
return httpGetAsyncWithEvent(event, url);
|
return httpGetAsyncWithEvent(event, url);
|
||||||
|
|
||||||
case "GET_AT_DATA":
|
case "GET_AT_DATA":
|
||||||
@ -473,7 +473,7 @@ window.addEventListener("message", async (event) => {
|
|||||||
|
|
||||||
case "FETCH_BLOCK":
|
case "FETCH_BLOCK":
|
||||||
if (data.signature != null) {
|
if (data.signature != null) {
|
||||||
url = "/blocks/signature/" + data.signature;
|
url = "/blocks/" + data.signature;
|
||||||
} else if (data.height != null) {
|
} else if (data.height != null) {
|
||||||
url = "/blocks/byheight/" + data.height;
|
url = "/blocks/byheight/" + data.height;
|
||||||
}
|
}
|
||||||
@ -694,7 +694,6 @@ const qortalRequestWithTimeout = (request, timeout) =>
|
|||||||
* Send current page details to UI
|
* Send current page details to UI
|
||||||
*/
|
*/
|
||||||
document.addEventListener('DOMContentLoaded', (event) => {
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
|
||||||
resetVariables()
|
resetVariables()
|
||||||
qortalRequest({
|
qortalRequest({
|
||||||
action: "QDN_RESOURCE_DISPLAYED",
|
action: "QDN_RESOURCE_DISPLAYED",
|
||||||
@ -713,7 +712,6 @@ resetVariables()
|
|||||||
* Handle app navigation
|
* Handle app navigation
|
||||||
*/
|
*/
|
||||||
navigation.addEventListener('navigate', (event) => {
|
navigation.addEventListener('navigate', (event) => {
|
||||||
|
|
||||||
const url = new URL(event.destination.url);
|
const url = new URL(event.destination.url);
|
||||||
|
|
||||||
let fullpath = url.pathname + url.hash;
|
let fullpath = url.pathname + url.hash;
|
||||||
|
@ -405,7 +405,7 @@ public class RepositoryTests extends Common {
|
|||||||
Integer offset = null;
|
Integer offset = null;
|
||||||
Boolean reverse = null;
|
Boolean reverse = null;
|
||||||
|
|
||||||
hsqldb.getATRepository().getMatchingFinalATStates(codeHash,null, null, isFinished, dataByteOffset, expectedValue, minimumFinalHeight, limit, offset, reverse);
|
hsqldb.getATRepository().getMatchingFinalATStates(codeHash, isFinished, dataByteOffset, expectedValue, minimumFinalHeight, limit, offset, reverse);
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
fail("HSQLDB bug #1580");
|
fail("HSQLDB bug #1580");
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ public class CrossChainApiTests extends ApiCommon {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetCompletedTrades() {
|
public void testGetCompletedTrades() {
|
||||||
long minimumTimestamp = System.currentTimeMillis();
|
long minimumTimestamp = System.currentTimeMillis();
|
||||||
assertNoApiError((limit, offset, reverse) -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, minimumTimestamp, null, null, limit, offset, reverse));
|
assertNoApiError((limit, offset, reverse) -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, minimumTimestamp, limit, offset, reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -35,8 +35,8 @@ public class CrossChainApiTests extends ApiCommon {
|
|||||||
Integer offset = null;
|
Integer offset = null;
|
||||||
Boolean reverse = null;
|
Boolean reverse = null;
|
||||||
|
|
||||||
assertApiError(ApiError.INVALID_CRITERIA, () -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, -1L /*minimumTimestamp*/, null, null, limit, offset, reverse));
|
assertApiError(ApiError.INVALID_CRITERIA, () -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, -1L /*minimumTimestamp*/, limit, offset, reverse));
|
||||||
assertApiError(ApiError.INVALID_CRITERIA, () -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, 0L /*minimumTimestamp*/, null, null, limit, offset, reverse));
|
assertApiError(ApiError.INVALID_CRITERIA, () -> this.crossChainResource.getCompletedTrades(SPECIFIC_BLOCKCHAIN, 0L /*minimumTimestamp*/, limit, offset, reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,10 @@ package org.qortal.test.api;
|
|||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.api.model.CrossChainTradeLedgerEntry;
|
|
||||||
import org.qortal.api.resource.CrossChainUtils;
|
import org.qortal.api.resource.CrossChainUtils;
|
||||||
import org.qortal.test.common.ApiCommon;
|
import org.qortal.test.common.ApiCommon;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class CrossChainUtilsTests extends ApiCommon {
|
public class CrossChainUtilsTests extends ApiCommon {
|
||||||
@ -142,53 +137,4 @@ public class CrossChainUtilsTests extends ApiCommon {
|
|||||||
Assert.assertEquals(5, versionDecimal, 0.001);
|
Assert.assertEquals(5, versionDecimal, 0.001);
|
||||||
Assert.assertFalse(thrown);
|
Assert.assertFalse(thrown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteToLedgerHeaderOnly() throws IOException {
|
|
||||||
CrossChainUtils.writeToLedger(new PrintWriter(System.out), new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteToLedgerOneRow() throws IOException {
|
|
||||||
CrossChainUtils.writeToLedger(
|
|
||||||
new PrintWriter(System.out),
|
|
||||||
List.of(
|
|
||||||
new CrossChainTradeLedgerEntry(
|
|
||||||
"QORT",
|
|
||||||
"LTC",
|
|
||||||
1000,
|
|
||||||
0,
|
|
||||||
"LTC",
|
|
||||||
1,
|
|
||||||
System.currentTimeMillis())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWriteToLedgerTwoRows() throws IOException {
|
|
||||||
CrossChainUtils.writeToLedger(
|
|
||||||
new PrintWriter(System.out),
|
|
||||||
List.of(
|
|
||||||
new CrossChainTradeLedgerEntry(
|
|
||||||
"QORT",
|
|
||||||
"LTC",
|
|
||||||
1000,
|
|
||||||
0,
|
|
||||||
"LTC",
|
|
||||||
1,
|
|
||||||
System.currentTimeMillis()
|
|
||||||
),
|
|
||||||
new CrossChainTradeLedgerEntry(
|
|
||||||
"LTC",
|
|
||||||
"QORT",
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
"LTC",
|
|
||||||
1000,
|
|
||||||
System.currentTimeMillis()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -145,6 +145,56 @@ public class ArbitraryDataStorageCapacityTests extends Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteRandomFilesForName() throws DataException, IOException, InterruptedException, IllegalAccessException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
String identifier = null; // Not used for this test
|
||||||
|
Service service = Service.ARBITRARY_DATA;
|
||||||
|
int chunkSize = 100;
|
||||||
|
int dataLength = 900; // Actual data length will be longer due to encryption
|
||||||
|
|
||||||
|
// Set originalCopyIndicatorFileEnabled to false, otherwise nothing will be deleted as it all originates from this node
|
||||||
|
FieldUtils.writeField(Settings.getInstance(), "originalCopyIndicatorFileEnabled", false, true);
|
||||||
|
|
||||||
|
// Alice hosts some data (with 10 chunks)
|
||||||
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
|
String aliceName = "alice";
|
||||||
|
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), aliceName, "");
|
||||||
|
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||||
|
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||||
|
Path alicePath = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
ArbitraryDataFile aliceArbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, Base58.encode(alice.getPublicKey()), alicePath, aliceName, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize);
|
||||||
|
|
||||||
|
// Bob hosts some data too (also with 10 chunks)
|
||||||
|
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||||
|
String bobName = "bob";
|
||||||
|
transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(bob), bobName, "");
|
||||||
|
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||||
|
TransactionUtils.signAndMint(repository, transactionData, bob);
|
||||||
|
Path bobPath = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
ArbitraryDataFile bobArbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, Base58.encode(bob.getPublicKey()), bobPath, bobName, identifier, ArbitraryTransactionData.Method.PUT, service, bob, chunkSize);
|
||||||
|
|
||||||
|
// All 20 chunks should exist
|
||||||
|
assertEquals(10, aliceArbitraryDataFile.chunkCount());
|
||||||
|
assertTrue(aliceArbitraryDataFile.allChunksExist());
|
||||||
|
assertEquals(10, bobArbitraryDataFile.chunkCount());
|
||||||
|
assertTrue(bobArbitraryDataFile.allChunksExist());
|
||||||
|
|
||||||
|
// Now pretend that Bob has reached his storage limit - this should delete random files
|
||||||
|
// Run it 10 times to remove the likelihood of the randomizer always picking Alice's files
|
||||||
|
for (int i=0; i<10; i++) {
|
||||||
|
ArbitraryDataCleanupManager.getInstance().storageLimitReachedForName(repository, bobName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alice should still have all chunks
|
||||||
|
assertTrue(aliceArbitraryDataFile.allChunksExist());
|
||||||
|
|
||||||
|
// Bob should be missing some chunks
|
||||||
|
assertFalse(bobArbitraryDataFile.allChunksExist());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void deleteListsDirectory() {
|
private void deleteListsDirectory() {
|
||||||
// Delete lists directory if exists
|
// Delete lists directory if exists
|
||||||
Path listsPath = Paths.get(Settings.getInstance().getListsPath());
|
Path listsPath = Paths.get(Settings.getInstance().getListsPath());
|
||||||
|
@ -73,14 +73,14 @@ public class ArbitraryDataStoragePolicyTests extends Common {
|
|||||||
// We should store and pre-fetch data for this transaction
|
// We should store and pre-fetch data for this transaction
|
||||||
assertEquals(StoragePolicy.FOLLOWED_OR_VIEWED, Settings.getInstance().getStoragePolicy());
|
assertEquals(StoragePolicy.FOLLOWED_OR_VIEWED, Settings.getInstance().getStoragePolicy());
|
||||||
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
|
|
||||||
// Now unfollow the name
|
// Now unfollow the name
|
||||||
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
||||||
|
|
||||||
// We should store but not pre-fetch data for this transaction
|
// We should store but not pre-fetch data for this transaction
|
||||||
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,14 +108,14 @@ public class ArbitraryDataStoragePolicyTests extends Common {
|
|||||||
// We should store and pre-fetch data for this transaction
|
// We should store and pre-fetch data for this transaction
|
||||||
assertEquals(StoragePolicy.FOLLOWED, Settings.getInstance().getStoragePolicy());
|
assertEquals(StoragePolicy.FOLLOWED, Settings.getInstance().getStoragePolicy());
|
||||||
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
|
|
||||||
// Now unfollow the name
|
// Now unfollow the name
|
||||||
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
||||||
|
|
||||||
// We shouldn't store or pre-fetch data for this transaction
|
// We shouldn't store or pre-fetch data for this transaction
|
||||||
assertFalse(storageManager.canStoreData(arbitraryTransactionData));
|
assertFalse(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,14 +143,14 @@ public class ArbitraryDataStoragePolicyTests extends Common {
|
|||||||
// We should store but not pre-fetch data for this transaction
|
// We should store but not pre-fetch data for this transaction
|
||||||
assertEquals(StoragePolicy.VIEWED, Settings.getInstance().getStoragePolicy());
|
assertEquals(StoragePolicy.VIEWED, Settings.getInstance().getStoragePolicy());
|
||||||
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
|
|
||||||
// Now unfollow the name
|
// Now unfollow the name
|
||||||
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
||||||
|
|
||||||
// We should store but not pre-fetch data for this transaction
|
// We should store but not pre-fetch data for this transaction
|
||||||
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,14 +178,14 @@ public class ArbitraryDataStoragePolicyTests extends Common {
|
|||||||
// We should store and pre-fetch data for this transaction
|
// We should store and pre-fetch data for this transaction
|
||||||
assertEquals(StoragePolicy.ALL, Settings.getInstance().getStoragePolicy());
|
assertEquals(StoragePolicy.ALL, Settings.getInstance().getStoragePolicy());
|
||||||
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
|
|
||||||
// Now unfollow the name
|
// Now unfollow the name
|
||||||
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
||||||
|
|
||||||
// We should store and pre-fetch data for this transaction
|
// We should store and pre-fetch data for this transaction
|
||||||
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
assertTrue(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertTrue(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,14 +213,14 @@ public class ArbitraryDataStoragePolicyTests extends Common {
|
|||||||
// We shouldn't store or pre-fetch data for this transaction
|
// We shouldn't store or pre-fetch data for this transaction
|
||||||
assertEquals(StoragePolicy.NONE, Settings.getInstance().getStoragePolicy());
|
assertEquals(StoragePolicy.NONE, Settings.getInstance().getStoragePolicy());
|
||||||
assertFalse(storageManager.canStoreData(arbitraryTransactionData));
|
assertFalse(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
|
|
||||||
// Now unfollow the name
|
// Now unfollow the name
|
||||||
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
assertTrue(ResourceListManager.getInstance().removeFromList("followedNames", name, false));
|
||||||
|
|
||||||
// We shouldn't store or pre-fetch data for this transaction
|
// We shouldn't store or pre-fetch data for this transaction
|
||||||
assertFalse(storageManager.canStoreData(arbitraryTransactionData));
|
assertFalse(storageManager.canStoreData(arbitraryTransactionData));
|
||||||
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData).isPass());
|
assertFalse(storageManager.shouldPreFetchData(repository, arbitraryTransactionData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ public class ArbitraryDataStoragePolicyTests extends Common {
|
|||||||
|
|
||||||
// We should store but not pre-fetch data for this transaction
|
// We should store but not pre-fetch data for this transaction
|
||||||
assertTrue(storageManager.canStoreData(transactionData));
|
assertTrue(storageManager.canStoreData(transactionData));
|
||||||
assertFalse(storageManager.shouldPreFetchData(repository, transactionData).isPass());
|
assertFalse(storageManager.shouldPreFetchData(repository, transactionData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,8 +218,6 @@ public class AtRepositoryTests extends Common {
|
|||||||
|
|
||||||
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(
|
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(
|
||||||
codeHash,
|
codeHash,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
isFinished,
|
isFinished,
|
||||||
dataByteOffset,
|
dataByteOffset,
|
||||||
expectedValue,
|
expectedValue,
|
||||||
@ -266,8 +264,6 @@ public class AtRepositoryTests extends Common {
|
|||||||
|
|
||||||
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(
|
List<ATStateData> atStates = repository.getATRepository().getMatchingFinalATStates(
|
||||||
codeHash,
|
codeHash,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
isFinished,
|
isFinished,
|
||||||
dataByteOffset,
|
dataByteOffset,
|
||||||
expectedValue,
|
expectedValue,
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
package org.qortal.test.block;
|
|
||||||
|
|
||||||
import org.checkerframework.checker.units.qual.K;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class BlockTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDistributeToAccountsOneDistribution(){
|
|
||||||
List<String> addresses = new ArrayList<>();
|
|
||||||
addresses.add("a");
|
|
||||||
addresses.add("b");
|
|
||||||
addresses.add("c");
|
|
||||||
|
|
||||||
HashMap<String, Long> balanceByAddress = new HashMap<>();
|
|
||||||
long total = Block.distributeToAccounts( 10L, addresses, balanceByAddress);
|
|
||||||
|
|
||||||
Assert.assertEquals(9, total);
|
|
||||||
|
|
||||||
Assert.assertEquals(3, balanceByAddress.size());
|
|
||||||
Assert.assertTrue(balanceByAddress.containsKey("a"));
|
|
||||||
Assert.assertTrue(balanceByAddress.containsKey("b"));
|
|
||||||
Assert.assertTrue(balanceByAddress.containsKey("c"));
|
|
||||||
Assert.assertEquals(3L, balanceByAddress.getOrDefault("a", 0L).longValue());
|
|
||||||
Assert.assertEquals(3L, balanceByAddress.getOrDefault("b", 0L).longValue());
|
|
||||||
Assert.assertEquals(3L, balanceByAddress.getOrDefault("c", 0L).longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDistributeToAccountsTwoDistributions(){
|
|
||||||
List<String> addresses = new ArrayList<>();
|
|
||||||
addresses.add("a");
|
|
||||||
addresses.add("b");
|
|
||||||
addresses.add("c");
|
|
||||||
|
|
||||||
HashMap<String, Long> balanceByAddress = new HashMap<>();
|
|
||||||
long total1 = Block.distributeToAccounts( 10L, addresses, balanceByAddress);
|
|
||||||
long total2 = Block.distributeToAccounts( 20L, addresses, balanceByAddress);
|
|
||||||
|
|
||||||
Assert.assertEquals(9, total1);
|
|
||||||
Assert.assertEquals(18, total2);
|
|
||||||
|
|
||||||
Assert.assertEquals(3, balanceByAddress.size());
|
|
||||||
Assert.assertTrue(balanceByAddress.containsKey("a"));
|
|
||||||
Assert.assertTrue(balanceByAddress.containsKey("b"));
|
|
||||||
Assert.assertTrue(balanceByAddress.containsKey("c"));
|
|
||||||
Assert.assertEquals(9L, balanceByAddress.getOrDefault("a", 0L).longValue());
|
|
||||||
Assert.assertEquals(9L, balanceByAddress.getOrDefault("b", 0L).longValue());
|
|
||||||
Assert.assertEquals(9L, balanceByAddress.getOrDefault("c", 0L).longValue());
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,11 +4,7 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.data.group.GroupAdminData;
|
|
||||||
import org.qortal.data.transaction.*;
|
import org.qortal.data.transaction.*;
|
||||||
import org.qortal.group.Group;
|
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
@ -20,8 +16,6 @@ import org.qortal.test.common.transaction.TestTransaction;
|
|||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,14 +40,8 @@ import static org.junit.Assert.*;
|
|||||||
*/
|
*/
|
||||||
public class DevGroupAdminTests extends Common {
|
public class DevGroupAdminTests extends Common {
|
||||||
|
|
||||||
public static final int NULL_GROUP_MEMBERSHIP_HEIGHT = BlockChain.getInstance().getNullGroupMembershipHeight();
|
|
||||||
private static final int DEV_GROUP_ID = 1;
|
private static final int DEV_GROUP_ID = 1;
|
||||||
|
|
||||||
public static final String ALICE = "alice";
|
|
||||||
public static final String BOB = "bob";
|
|
||||||
public static final String CHLOE = "chloe";
|
|
||||||
public static final String DILBERT = "dilbert";
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void beforeTest() throws DataException {
|
public void beforeTest() throws DataException {
|
||||||
Common.useDefaultSettings();
|
Common.useDefaultSettings();
|
||||||
@ -67,8 +55,8 @@ public class DevGroupAdminTests extends Common {
|
|||||||
@Test
|
@Test
|
||||||
public void testGroupKickMember() throws DataException {
|
public void testGroupKickMember() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
// Dev group
|
// Dev group
|
||||||
int groupId = DEV_GROUP_ID;
|
int groupId = DEV_GROUP_ID;
|
||||||
@ -92,10 +80,16 @@ public class DevGroupAdminTests extends Common {
|
|||||||
|
|
||||||
// Attempt to kick Bob
|
// Attempt to kick Bob
|
||||||
result = groupKick(repository, alice, groupId, bob.getAddress());
|
result = groupKick(repository, alice, groupId, bob.getAddress());
|
||||||
// Should not be OK, cannot kick member out of null owned group
|
// Should be OK
|
||||||
assertNotSame(ValidationResult.OK, result);
|
assertEquals(ValidationResult.OK, result);
|
||||||
|
|
||||||
// Confirm Bob remains a member
|
// Confirm Bob no longer a member
|
||||||
|
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||||
|
|
||||||
|
// Orphan last block
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
// Confirm Bob now a member
|
||||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,8 +97,8 @@ public class DevGroupAdminTests extends Common {
|
|||||||
@Test
|
@Test
|
||||||
public void testGroupKickAdmin() throws DataException {
|
public void testGroupKickAdmin() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
// Dev group
|
// Dev group
|
||||||
int groupId = DEV_GROUP_ID;
|
int groupId = DEV_GROUP_ID;
|
||||||
@ -129,7 +123,7 @@ public class DevGroupAdminTests extends Common {
|
|||||||
assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus);
|
assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus);
|
||||||
|
|
||||||
// Have Alice approve Bob's approval-needed transaction
|
// Have Alice approve Bob's approval-needed transaction
|
||||||
GroupUtils.approveTransaction(repository, ALICE, addGroupAdminTransactionData.getSignature(), true);
|
GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true);
|
||||||
|
|
||||||
// Mint a block so that the transaction becomes approved
|
// Mint a block so that the transaction becomes approved
|
||||||
BlockUtils.mintBlock(repository);
|
BlockUtils.mintBlock(repository);
|
||||||
@ -173,8 +167,8 @@ public class DevGroupAdminTests extends Common {
|
|||||||
@Test
|
@Test
|
||||||
public void testGroupBanMember() throws DataException {
|
public void testGroupBanMember() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
// Dev group
|
// Dev group
|
||||||
int groupId = DEV_GROUP_ID;
|
int groupId = DEV_GROUP_ID;
|
||||||
@ -189,13 +183,18 @@ public class DevGroupAdminTests extends Common {
|
|||||||
|
|
||||||
// Attempt to ban Bob
|
// Attempt to ban Bob
|
||||||
result = groupBan(repository, alice, groupId, bob.getAddress());
|
result = groupBan(repository, alice, groupId, bob.getAddress());
|
||||||
// Should not be OK, cannot ban someone from a null owned group
|
// Should be OK
|
||||||
|
assertEquals(ValidationResult.OK, result);
|
||||||
|
|
||||||
|
// Bob attempts to rejoin
|
||||||
|
result = joinGroup(repository, bob, groupId);
|
||||||
|
// Should NOT be OK
|
||||||
assertNotSame(ValidationResult.OK, result);
|
assertNotSame(ValidationResult.OK, result);
|
||||||
|
|
||||||
// Bob attempts to join
|
// Orphan last block (Bob ban)
|
||||||
result = joinGroup(repository, bob, groupId);
|
BlockUtils.orphanLastBlock(repository);
|
||||||
// Should be OK, but won't actually get him in the group
|
// Delete unconfirmed group-ban transaction
|
||||||
assertEquals(ValidationResult.OK, result);
|
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||||
|
|
||||||
// Confirm Bob is not a member
|
// Confirm Bob is not a member
|
||||||
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||||
@ -205,38 +204,65 @@ public class DevGroupAdminTests extends Common {
|
|||||||
|
|
||||||
// Bob to join
|
// Bob to join
|
||||||
result = joinGroup(repository, bob, groupId);
|
result = joinGroup(repository, bob, groupId);
|
||||||
// Should not be OK, bob should already be a member, he joined before the invite and
|
// Should be OK
|
||||||
// the invite served as an approval
|
assertEquals(ValidationResult.OK, result);
|
||||||
assertEquals(ValidationResult.ALREADY_GROUP_MEMBER, result);
|
|
||||||
|
|
||||||
// Confirm Bob now a member, now that he got an invite
|
// Confirm Bob now a member
|
||||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||||
|
|
||||||
// Attempt to ban Bob
|
// Attempt to ban Bob
|
||||||
result = groupBan(repository, alice, groupId, bob.getAddress());
|
result = groupBan(repository, alice, groupId, bob.getAddress());
|
||||||
// Should not be OK, because you can ban a member of a null owned group
|
// Should be OK
|
||||||
assertNotSame(ValidationResult.OK, result);
|
assertEquals(ValidationResult.OK, result);
|
||||||
|
|
||||||
// Confirm Bob is still a member
|
// Confirm Bob no longer a member
|
||||||
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
assertFalse(isMember(repository, bob.getAddress(), groupId));
|
||||||
|
|
||||||
// Bob attempts to rejoin
|
// Bob attempts to rejoin
|
||||||
result = joinGroup(repository, bob, groupId);
|
result = joinGroup(repository, bob, groupId);
|
||||||
// Should NOT be OK, because he is already a member
|
// Should NOT be OK
|
||||||
assertNotSame(ValidationResult.OK, result);
|
assertNotSame(ValidationResult.OK, result);
|
||||||
|
|
||||||
// Cancel Bob's ban
|
// Cancel Bob's ban
|
||||||
result = cancelGroupBan(repository, alice, groupId, bob.getAddress());
|
result = cancelGroupBan(repository, alice, groupId, bob.getAddress());
|
||||||
// Should not be OK, because there was no ban to begin with
|
// Should be OK
|
||||||
|
assertEquals(ValidationResult.OK, result);
|
||||||
|
|
||||||
|
// Bob attempts to rejoin
|
||||||
|
result = joinGroup(repository, bob, groupId);
|
||||||
|
// Should be OK
|
||||||
|
assertEquals(ValidationResult.OK, result);
|
||||||
|
|
||||||
|
// Orphan last block (Bob join)
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
// Delete unconfirmed join-group transaction
|
||||||
|
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||||
|
|
||||||
|
// Orphan last block (Cancel Bob ban)
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
// Delete unconfirmed cancel-ban transaction
|
||||||
|
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||||
|
|
||||||
|
// Bob attempts to rejoin
|
||||||
|
result = joinGroup(repository, bob, groupId);
|
||||||
|
// Should NOT be OK
|
||||||
assertNotSame(ValidationResult.OK, result);
|
assertNotSame(ValidationResult.OK, result);
|
||||||
|
|
||||||
|
// Orphan last block (Bob ban)
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
// Delete unconfirmed group-ban transaction
|
||||||
|
TransactionUtils.deleteUnconfirmedTransactions(repository);
|
||||||
|
|
||||||
|
// Confirm Bob now a member
|
||||||
|
assertTrue(isMember(repository, bob.getAddress(), groupId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGroupBanAdmin() throws DataException {
|
public void testGroupBanAdmin() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
// Dev group
|
// Dev group
|
||||||
int groupId = DEV_GROUP_ID;
|
int groupId = DEV_GROUP_ID;
|
||||||
@ -260,7 +286,7 @@ public class DevGroupAdminTests extends Common {
|
|||||||
assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus);
|
assertEquals("incorrect transaction approval status", Transaction.ApprovalStatus.PENDING, approvalStatus);
|
||||||
|
|
||||||
// Have Alice approve Bob's approval-needed transaction
|
// Have Alice approve Bob's approval-needed transaction
|
||||||
GroupUtils.approveTransaction(repository, ALICE, addGroupAdminTransactionData.getSignature(), true);
|
GroupUtils.approveTransaction(repository, "alice", addGroupAdminTransactionData.getSignature(), true);
|
||||||
|
|
||||||
// Mint a block so that the transaction becomes approved
|
// Mint a block so that the transaction becomes approved
|
||||||
BlockUtils.mintBlock(repository);
|
BlockUtils.mintBlock(repository);
|
||||||
@ -295,322 +321,6 @@ public class DevGroupAdminTests extends Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddAdmin2of3() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
// establish accounts
|
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
|
||||||
PrivateKeyAccount chloe = Common.getTestAccount(repository, CHLOE);
|
|
||||||
PrivateKeyAccount dilbert = Common.getTestAccount(repository, DILBERT);
|
|
||||||
|
|
||||||
// assert admin statuses
|
|
||||||
assertEquals(2, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue());
|
|
||||||
assertTrue(isAdmin(repository, Group.NULL_OWNER_ADDRESS, DEV_GROUP_ID));
|
|
||||||
assertTrue(isAdmin(repository, alice.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, chloe.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, dilbert.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// confirm Bob is not a member
|
|
||||||
assertFalse(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// alice invites bob
|
|
||||||
ValidationResult result = groupInvite(repository, alice, DEV_GROUP_ID, bob.getAddress(), 3600);
|
|
||||||
assertSame(ValidationResult.OK, result);
|
|
||||||
|
|
||||||
// bob joins
|
|
||||||
joinGroup(repository, bob, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// confirm Bob is a member now, but still not an admin
|
|
||||||
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob creates transaction to add himself as an admin
|
|
||||||
TransactionData addGroupAdminTransactionData1 = addGroupAdmin(repository, bob, DEV_GROUP_ID, bob.getAddress());
|
|
||||||
|
|
||||||
// bob creates add admin transaction for himself, alice signs which is 50% approval while 40% is needed
|
|
||||||
signForGroupApproval(repository, addGroupAdminTransactionData1, List.of(alice));
|
|
||||||
|
|
||||||
// assert 3 admins in group and bob is an admin now
|
|
||||||
assertEquals(3, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue() );
|
|
||||||
assertTrue(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob invites chloe
|
|
||||||
result = groupInvite(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
|
|
||||||
assertSame(ValidationResult.OK, result);
|
|
||||||
|
|
||||||
// chloe joins
|
|
||||||
joinGroup(repository, chloe, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// confirm Chloe is a member now, but still not an admin
|
|
||||||
assertTrue(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, chloe.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// chloe creates transaction to add herself as an admin
|
|
||||||
TransactionData addChloeAsGroupAdmin = addGroupAdmin(repository, chloe, DEV_GROUP_ID, chloe.getAddress());
|
|
||||||
|
|
||||||
// no one has signed, so it should be pending
|
|
||||||
Transaction.ApprovalStatus addChloeAsGroupAdminStatus1 = GroupUtils.getApprovalStatus(repository, addChloeAsGroupAdmin.getSignature());
|
|
||||||
assertEquals( Transaction.ApprovalStatus.PENDING, addChloeAsGroupAdminStatus1);
|
|
||||||
|
|
||||||
// signer 1
|
|
||||||
Transaction.ApprovalStatus addChloeAsGroupAdminStatus2 = signForGroupApproval(repository, addChloeAsGroupAdmin, List.of(alice));
|
|
||||||
|
|
||||||
// 1 out of 3 has signed, so it should be pending, because it is less than 40%
|
|
||||||
assertEquals( Transaction.ApprovalStatus.PENDING, addChloeAsGroupAdminStatus2);
|
|
||||||
|
|
||||||
// signer 2
|
|
||||||
Transaction.ApprovalStatus addChloeAsGroupAdminStatus3 = signForGroupApproval(repository, addChloeAsGroupAdmin, List.of(bob));
|
|
||||||
|
|
||||||
// 2 out of 3 has signed, so it should be approved, because it is more than 40%
|
|
||||||
assertEquals( Transaction.ApprovalStatus.APPROVED, addChloeAsGroupAdminStatus3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNullOwnershipMembership() throws DataException{
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
Block block = BlockUtils.mintBlocks(repository, NULL_GROUP_MEMBERSHIP_HEIGHT);
|
|
||||||
assertEquals(NULL_GROUP_MEMBERSHIP_HEIGHT + 1, block.getBlockData().getHeight().intValue());
|
|
||||||
|
|
||||||
// establish accounts
|
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
|
||||||
PrivateKeyAccount chloe = Common.getTestAccount(repository, CHLOE);
|
|
||||||
PrivateKeyAccount dilbert = Common.getTestAccount(repository, DILBERT);
|
|
||||||
|
|
||||||
// assert admin statuses
|
|
||||||
assertEquals(2, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue());
|
|
||||||
assertTrue(isAdmin(repository, Group.NULL_OWNER_ADDRESS, DEV_GROUP_ID));
|
|
||||||
assertTrue(isAdmin(repository, alice.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, chloe.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, dilbert.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// confirm Bob is not a member
|
|
||||||
assertFalse(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// alice invites bob, alice signs which is 50% approval while 40% is needed
|
|
||||||
TransactionData createInviteTransactionData = createGroupInviteForGroupApproval(repository, alice, DEV_GROUP_ID, bob.getAddress(), 3600);
|
|
||||||
Transaction.ApprovalStatus bobsInviteStatus = signForGroupApproval(repository, createInviteTransactionData, List.of(alice));
|
|
||||||
|
|
||||||
// assert approval
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, bobsInviteStatus);
|
|
||||||
|
|
||||||
// bob joins
|
|
||||||
joinGroup(repository, bob, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// confirm Bob is a member now, but still not an admin
|
|
||||||
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
assertFalse(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob creates transaction to add himself as an admin
|
|
||||||
TransactionData addGroupAdminTransactionData1 = addGroupAdmin(repository, bob, DEV_GROUP_ID, bob.getAddress());
|
|
||||||
|
|
||||||
// bob creates add admin transaction for himself, alice signs which is 50% approval while 40% is needed
|
|
||||||
signForGroupApproval(repository, addGroupAdminTransactionData1, List.of(alice));
|
|
||||||
|
|
||||||
// assert 3 admins in group and bob is an admin now
|
|
||||||
assertEquals(3, repository.getGroupRepository().countGroupAdmins(DEV_GROUP_ID).intValue());
|
|
||||||
assertTrue(isAdmin(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob invites chloe, bob signs which is 33% approval while 40% is needed
|
|
||||||
TransactionData chloeInvite = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
|
|
||||||
Transaction.ApprovalStatus chloeInviteStatus = signForGroupApproval(repository, chloeInvite, List.of(bob));
|
|
||||||
|
|
||||||
// assert pending
|
|
||||||
assertEquals(Transaction.ApprovalStatus.PENDING, chloeInviteStatus);
|
|
||||||
|
|
||||||
// alice signs which is 66% approval while 40% is needed
|
|
||||||
chloeInviteStatus = signForGroupApproval(repository, chloeInvite, List.of(alice));
|
|
||||||
|
|
||||||
// assert approval
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeInviteStatus);
|
|
||||||
|
|
||||||
// chloe joins
|
|
||||||
joinGroup(repository, chloe, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// assert chloe is in the group
|
|
||||||
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// alice kicks chloe, alice signs which is 33% approval while 40% is needed
|
|
||||||
TransactionData chloeKick = createGroupKickForGroupApproval(repository, alice, DEV_GROUP_ID, chloe.getAddress(),"testing chloe kick");
|
|
||||||
Transaction.ApprovalStatus chloeKickStatus = signForGroupApproval(repository, chloeKick, List.of(alice));
|
|
||||||
|
|
||||||
// assert pending
|
|
||||||
assertEquals(Transaction.ApprovalStatus.PENDING, chloeKickStatus);
|
|
||||||
|
|
||||||
// assert chloe is still in the group
|
|
||||||
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob signs which is 66% approval while 40% is needed
|
|
||||||
chloeKickStatus = signForGroupApproval(repository, chloeKick, List.of(bob));
|
|
||||||
|
|
||||||
// assert approval
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeKickStatus);
|
|
||||||
|
|
||||||
// assert chloe is not in the group
|
|
||||||
assertFalse(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob invites chloe, alice and bob signs which is 66% approval while 40% is needed
|
|
||||||
TransactionData chloeInviteAgain = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
|
|
||||||
Transaction.ApprovalStatus chloeInviteAgainStatus = signForGroupApproval(repository, chloeInviteAgain, List.of(alice, bob));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeInviteAgainStatus);
|
|
||||||
|
|
||||||
// chloe joins again
|
|
||||||
joinGroup(repository, chloe, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// assert chloe is in the group
|
|
||||||
assertTrue(isMember(repository, bob.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// alice bans chloe, alice signs which is 33% approval while 40% is needed
|
|
||||||
TransactionData chloeBan = createGroupBanForGroupApproval(repository, alice, DEV_GROUP_ID, chloe.getAddress(), "testing group ban", 3600);
|
|
||||||
Transaction.ApprovalStatus chloeBanStatus1 = signForGroupApproval(repository, chloeBan, List.of(alice));
|
|
||||||
|
|
||||||
// assert pending
|
|
||||||
assertEquals(Transaction.ApprovalStatus.PENDING, chloeBanStatus1);
|
|
||||||
|
|
||||||
// bob signs which 66% approval while 40% is needed
|
|
||||||
Transaction.ApprovalStatus chloeBanStatus2 = signForGroupApproval(repository, chloeBan, List.of(bob));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeBanStatus2);
|
|
||||||
|
|
||||||
// assert chloe is not in the group
|
|
||||||
assertFalse(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob invites chloe, alice and bob signs which is 66% approval while 40% is needed
|
|
||||||
ValidationResult chloeInviteValidation = signAndImportGroupInvite(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
|
|
||||||
|
|
||||||
// assert banned status on invite attempt
|
|
||||||
assertEquals(ValidationResult.BANNED_FROM_GROUP, chloeInviteValidation);
|
|
||||||
|
|
||||||
// bob cancel ban on chloe, bob signs which is 33% approval while 40% is needed
|
|
||||||
TransactionData chloeCancelBan = createCancelGroupBanForGroupApproval( repository, bob, DEV_GROUP_ID, chloe.getAddress());
|
|
||||||
Transaction.ApprovalStatus chloeCancelBanStatus1 = signForGroupApproval(repository, chloeCancelBan, List.of(bob));
|
|
||||||
|
|
||||||
// assert pending
|
|
||||||
assertEquals(Transaction.ApprovalStatus.PENDING, chloeCancelBanStatus1);
|
|
||||||
|
|
||||||
// alice signs which is 66% approval while 40% is needed
|
|
||||||
Transaction.ApprovalStatus chloeCancelBanStatus2 = signForGroupApproval(repository, chloeCancelBan, List.of(alice));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeCancelBanStatus2);
|
|
||||||
|
|
||||||
// bob invites chloe, alice and bob signs which is 66% approval while 40% is needed
|
|
||||||
TransactionData chloeInvite4 = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, chloe.getAddress(), 3600);
|
|
||||||
Transaction.ApprovalStatus chloeInvite4Status = signForGroupApproval(repository, chloeInvite4, List.of(alice, bob));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, chloeInvite4Status);
|
|
||||||
|
|
||||||
// chloe joins again
|
|
||||||
joinGroup(repository, chloe, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// assert chloe is in the group
|
|
||||||
assertTrue(isMember(repository, chloe.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob invites dilbert, alice and bob signs which is 66% approval while 40% is needed
|
|
||||||
TransactionData dilbertInvite1 = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, dilbert.getAddress(), 3600);
|
|
||||||
Transaction.ApprovalStatus dibertInviteStatus1 = signForGroupApproval(repository, dilbertInvite1, List.of(alice, bob));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, dibertInviteStatus1);
|
|
||||||
|
|
||||||
// alice cancels dilbert's invite, alice signs which is 33% approval while 40% is needed
|
|
||||||
TransactionData cancelDilbertInvite = createCancelInviteForGroupApproval(repository, alice, DEV_GROUP_ID, dilbert.getAddress());
|
|
||||||
Transaction.ApprovalStatus cancelDilbertInviteStatus1 = signForGroupApproval(repository, cancelDilbertInvite, List.of(alice));
|
|
||||||
|
|
||||||
// assert pending
|
|
||||||
assertEquals(Transaction.ApprovalStatus.PENDING, cancelDilbertInviteStatus1);
|
|
||||||
|
|
||||||
// dilbert joins before the group approves cancellation
|
|
||||||
joinGroup(repository, dilbert, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// assert dilbert is in the group
|
|
||||||
assertTrue(isMember(repository, dilbert.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// alice kicks out dilbert, alice and bob sign which is 66% approval while 40% is needed
|
|
||||||
TransactionData kickDilbert = createGroupKickForGroupApproval(repository, alice, DEV_GROUP_ID, dilbert.getAddress(), "he is sneaky");
|
|
||||||
Transaction.ApprovalStatus kickDilbertStatus = signForGroupApproval(repository, kickDilbert, List.of(alice, bob));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, kickDilbertStatus);
|
|
||||||
|
|
||||||
// assert dilbert is out of the group
|
|
||||||
assertFalse(isMember(repository, dilbert.getAddress(), DEV_GROUP_ID));
|
|
||||||
|
|
||||||
// bob invites dilbert again, alice and bob signs which is 66% approval while 40% is needed
|
|
||||||
TransactionData dilbertInvite2 = createGroupInviteForGroupApproval(repository, bob, DEV_GROUP_ID, dilbert.getAddress(), 3600);
|
|
||||||
Transaction.ApprovalStatus dibertInviteStatus2 = signForGroupApproval(repository, dilbertInvite2, List.of(alice, bob));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, dibertInviteStatus2);
|
|
||||||
|
|
||||||
// alice cancels dilbert's invite, alice and bob signs which is 66% approval while 40% is needed
|
|
||||||
TransactionData cancelDilbertInvite2 = createCancelInviteForGroupApproval(repository, alice, DEV_GROUP_ID, dilbert.getAddress());
|
|
||||||
Transaction.ApprovalStatus cancelDilbertInviteStatus2 = signForGroupApproval(repository, cancelDilbertInvite2, List.of(alice, bob));
|
|
||||||
|
|
||||||
// assert approved
|
|
||||||
assertEquals(Transaction.ApprovalStatus.APPROVED, cancelDilbertInviteStatus2);
|
|
||||||
|
|
||||||
// dilbert tries to join after the group approves cancellation
|
|
||||||
joinGroup(repository, dilbert, DEV_GROUP_ID);
|
|
||||||
|
|
||||||
// assert dilbert is not in the group
|
|
||||||
assertFalse(isMember(repository, dilbert.getAddress(), DEV_GROUP_ID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetAdmin() throws DataException{
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
// establish accounts
|
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
|
||||||
|
|
||||||
GroupAdminData aliceAdminData = repository.getGroupRepository().getAdmin(DEV_GROUP_ID, alice.getAddress());
|
|
||||||
|
|
||||||
assertNotNull(aliceAdminData);
|
|
||||||
assertEquals( alice.getAddress(), aliceAdminData.getAdmin() );
|
|
||||||
assertEquals( DEV_GROUP_ID, aliceAdminData.getGroupId());
|
|
||||||
|
|
||||||
GroupAdminData bobAdminData = repository.getGroupRepository().getAdmin(DEV_GROUP_ID, bob.getAddress());
|
|
||||||
|
|
||||||
assertNull(bobAdminData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Transaction.ApprovalStatus signForGroupApproval(Repository repository, TransactionData data, List<PrivateKeyAccount> signers) throws DataException {
|
|
||||||
|
|
||||||
for (PrivateKeyAccount signer : signers) {
|
|
||||||
signTransactionDataForGroupApproval(repository, signer, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockUtils.mintBlocks(repository, 2);
|
|
||||||
|
|
||||||
// return approval status
|
|
||||||
return GroupUtils.getApprovalStatus(repository, data.getSignature());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void signTransactionDataForGroupApproval(Repository repository, PrivateKeyAccount signer, TransactionData transactionData) throws DataException {
|
|
||||||
byte[] reference = signer.getLastReference();
|
|
||||||
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1;
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData
|
|
||||||
= new BaseTransactionData(timestamp, Group.NO_GROUP, reference, signer.getPublicKey(), GroupUtils.fee, null);
|
|
||||||
TransactionData groupApprovalTransactionData
|
|
||||||
= new GroupApprovalTransactionData(baseTransactionData, transactionData.getSignature(), true);
|
|
||||||
|
|
||||||
TransactionUtils.signAndImportValid(repository, groupApprovalTransactionData, signer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValidationResult joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException {
|
private ValidationResult joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException {
|
||||||
JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId);
|
JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId);
|
||||||
@ -622,31 +332,9 @@ public class DevGroupAdminTests extends Common {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationResult groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
|
private void groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
|
||||||
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive);
|
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive);
|
||||||
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
|
|
||||||
|
|
||||||
if (result == ValidationResult.OK)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TransactionData createGroupInviteForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
|
|
||||||
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin, groupId), groupId, invitee, timeToLive);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, admin);
|
TransactionUtils.signAndMint(repository, transactionData, admin);
|
||||||
return transactionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TransactionData createCancelInviteForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String inviteeToCancel) throws DataException {
|
|
||||||
CancelGroupInviteTransactionData transactionData = new CancelGroupInviteTransactionData(TestTransaction.generateBase(admin, groupId), groupId, inviteeToCancel);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, admin);
|
|
||||||
return transactionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValidationResult signAndImportGroupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
|
|
||||||
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin, groupId), groupId, invitee, timeToLive);
|
|
||||||
return TransactionUtils.signAndImport(repository, transactionData, admin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationResult groupKick(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
|
private ValidationResult groupKick(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
|
||||||
@ -659,13 +347,6 @@ public class DevGroupAdminTests extends Common {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransactionData createGroupKickForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String kicked, String reason) throws DataException {
|
|
||||||
GroupKickTransactionData transactionData = new GroupKickTransactionData(TestTransaction.generateBase(admin, groupId), groupId, kicked, reason);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, admin);
|
|
||||||
|
|
||||||
return transactionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
|
private ValidationResult groupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
|
||||||
GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0);
|
GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member, "testing", 0);
|
||||||
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
|
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
|
||||||
@ -676,13 +357,6 @@ public class DevGroupAdminTests extends Common {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransactionData createGroupBanForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String banned, String reason, int timeToLive) throws DataException {
|
|
||||||
GroupBanTransactionData transactionData = new GroupBanTransactionData(TestTransaction.generateBase(admin, groupId), groupId, banned, reason, timeToLive);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, admin);
|
|
||||||
|
|
||||||
return transactionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValidationResult cancelGroupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
|
private ValidationResult cancelGroupBan(Repository repository, PrivateKeyAccount admin, int groupId, String member) throws DataException {
|
||||||
CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member);
|
CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData(TestTransaction.generateBase(admin), groupId, member);
|
||||||
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
|
ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, admin);
|
||||||
@ -693,14 +367,6 @@ public class DevGroupAdminTests extends Common {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransactionData createCancelGroupBanForGroupApproval(Repository repository, PrivateKeyAccount admin, int groupId, String unbanned ) throws DataException {
|
|
||||||
CancelGroupBanTransactionData transactionData = new CancelGroupBanTransactionData( TestTransaction.generateBase(admin, groupId), groupId, unbanned);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, admin);
|
|
||||||
|
|
||||||
return transactionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TransactionData addGroupAdmin(Repository repository, PrivateKeyAccount owner, int groupId, String member) throws DataException {
|
private TransactionData addGroupAdmin(Repository repository, PrivateKeyAccount owner, int groupId, String member) throws DataException {
|
||||||
AddGroupAdminTransactionData transactionData = new AddGroupAdminTransactionData(TestTransaction.generateBase(owner), groupId, member);
|
AddGroupAdminTransactionData transactionData = new AddGroupAdminTransactionData(TestTransaction.generateBase(owner), groupId, member);
|
||||||
transactionData.setTxGroupId(groupId);
|
transactionData.setTxGroupId(groupId);
|
||||||
|
@ -26,7 +26,6 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
private static final String DESCRIPTION = "description";
|
private static final String DESCRIPTION = "description";
|
||||||
private static final String PREFIX_ONLY = "prefixOnly";
|
private static final String PREFIX_ONLY = "prefixOnly";
|
||||||
private static final String EXACT_MATCH_NAMES = "exactMatchNames";
|
private static final String EXACT_MATCH_NAMES = "exactMatchNames";
|
||||||
private static final String KEYWORDS = "keywords";
|
|
||||||
private static final String DEFAULT_RESOURCE = "defaultResource";
|
private static final String DEFAULT_RESOURCE = "defaultResource";
|
||||||
private static final String MODE = "mode";
|
private static final String MODE = "mode";
|
||||||
private static final String MIN_LEVEL = "minLevel";
|
private static final String MIN_LEVEL = "minLevel";
|
||||||
@ -300,19 +299,6 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAfterNegative() {
|
|
||||||
ArbitraryResourceData data = new ArbitraryResourceData();
|
|
||||||
data.created = 10L;
|
|
||||||
data.name = "Joe";
|
|
||||||
|
|
||||||
filterListByMap(
|
|
||||||
List.of(data),
|
|
||||||
NAME_LEVEL, new HashMap<>(Map.of(AFTER, 11L)),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBeforePositive(){
|
public void testBeforePositive(){
|
||||||
ArbitraryResourceData data = new ArbitraryResourceData();
|
ArbitraryResourceData data = new ArbitraryResourceData();
|
||||||
@ -326,19 +312,6 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBeforeNegative(){
|
|
||||||
ArbitraryResourceData data = new ArbitraryResourceData();
|
|
||||||
data.created = 10L;
|
|
||||||
data.name = "Joe";
|
|
||||||
|
|
||||||
filterListByMap(
|
|
||||||
List.of(data),
|
|
||||||
NAME_LEVEL, new HashMap<>(Map.of(BEFORE, 9L)),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTitlePositive() {
|
public void testTitlePositive() {
|
||||||
|
|
||||||
@ -369,25 +342,6 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMetadataNullificationBugSolution(){
|
|
||||||
|
|
||||||
ArbitraryResourceData data = new ArbitraryResourceData();
|
|
||||||
data.metadata = new ArbitraryResourceMetadata();
|
|
||||||
data.metadata.setDescription("Once upon a time.");
|
|
||||||
data.name = "Joe";
|
|
||||||
|
|
||||||
List<ArbitraryResourceData> list = List.of(data);
|
|
||||||
|
|
||||||
filterListByMap(
|
|
||||||
List.of(data),
|
|
||||||
NAME_LEVEL, new HashMap<>(Map.of(DESCRIPTION, "Once upon a time.")),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert.assertNotNull(data.metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMinLevelPositive() {
|
public void testMinLevelPositive() {
|
||||||
|
|
||||||
@ -445,7 +399,7 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExcludeBlockedNegative() {
|
public void testExcludeBlockedPositive() {
|
||||||
|
|
||||||
ArbitraryResourceData data = new ArbitraryResourceData();
|
ArbitraryResourceData data = new ArbitraryResourceData();
|
||||||
data.name = "Joe";
|
data.name = "Joe";
|
||||||
@ -459,21 +413,6 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExcludeBlockedPositive() {
|
|
||||||
|
|
||||||
ArbitraryResourceData data = new ArbitraryResourceData();
|
|
||||||
data.name = "Joe";
|
|
||||||
|
|
||||||
Supplier<List<String>> supplier = () -> List.of("Joe");
|
|
||||||
|
|
||||||
filterListByMap(
|
|
||||||
List.of(data),
|
|
||||||
NAME_LEVEL, new HashMap<>(Map.of(EXCLUDE_BLOCKED, supplier)),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncludeMetadataPositive() {
|
public void testIncludeMetadataPositive() {
|
||||||
|
|
||||||
@ -661,7 +600,6 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
Optional<String> description = Optional.ofNullable((String) valueByKey.get(DESCRIPTION));
|
Optional<String> description = Optional.ofNullable((String) valueByKey.get(DESCRIPTION));
|
||||||
boolean prefixOnly = valueByKey.containsKey(PREFIX_ONLY);
|
boolean prefixOnly = valueByKey.containsKey(PREFIX_ONLY);
|
||||||
Optional<List<String>> exactMatchNames = Optional.ofNullable((List<String>) valueByKey.get(EXACT_MATCH_NAMES));
|
Optional<List<String>> exactMatchNames = Optional.ofNullable((List<String>) valueByKey.get(EXACT_MATCH_NAMES));
|
||||||
Optional<List<String>> keywords = Optional.ofNullable((List<String>) valueByKey.get(KEYWORDS));
|
|
||||||
boolean defaultResource = valueByKey.containsKey(DEFAULT_RESOURCE);
|
boolean defaultResource = valueByKey.containsKey(DEFAULT_RESOURCE);
|
||||||
Optional<SearchMode> mode = Optional.of((SearchMode) valueByKey.getOrDefault(MODE, SearchMode.ALL));
|
Optional<SearchMode> mode = Optional.of((SearchMode) valueByKey.getOrDefault(MODE, SearchMode.ALL));
|
||||||
Optional<Integer> minLevel = Optional.ofNullable((Integer) valueByKey.get(MIN_LEVEL));
|
Optional<Integer> minLevel = Optional.ofNullable((Integer) valueByKey.get(MIN_LEVEL));
|
||||||
@ -688,7 +626,6 @@ public class HSQLDBCacheUtilsTests {
|
|||||||
description,
|
description,
|
||||||
prefixOnly,
|
prefixOnly,
|
||||||
exactMatchNames,
|
exactMatchNames,
|
||||||
keywords,
|
|
||||||
defaultResource,
|
defaultResource,
|
||||||
minLevel,
|
minLevel,
|
||||||
followedOnly,
|
followedOnly,
|
||||||
|
@ -1,763 +0,0 @@
|
|||||||
package org.qortal.test.utils;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crypto.Crypto;
|
|
||||||
import org.qortal.data.PaymentData;
|
|
||||||
import org.qortal.data.account.AccountBalanceData;
|
|
||||||
import org.qortal.data.account.AddressAmountData;
|
|
||||||
import org.qortal.data.account.BlockHeightRange;
|
|
||||||
import org.qortal.data.account.BlockHeightRangeAddressAmounts;
|
|
||||||
import org.qortal.data.transaction.ATTransactionData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.BuyNameTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MultiPaymentTransactionData;
|
|
||||||
import org.qortal.data.transaction.PaymentTransactionData;
|
|
||||||
import org.qortal.data.transaction.RegisterNameTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.data.transaction.TransferAssetTransactionData;
|
|
||||||
import org.qortal.utils.BalanceRecorderUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class BalanceRecorderUtilsTests {
|
|
||||||
|
|
||||||
public static final String RECIPIENT_ADDRESS = "recipient";
|
|
||||||
public static final String AT_ADDRESS = "atAddress";
|
|
||||||
public static final String OTHER = "Other";
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNotZeroForZero() {
|
|
||||||
boolean test = BalanceRecorderUtils.ADDRESS_AMOUNT_DATA_NOT_ZERO.test( new AddressAmountData("", 0));
|
|
||||||
|
|
||||||
Assert.assertFalse(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNotZeroForPositive() {
|
|
||||||
boolean test = BalanceRecorderUtils.ADDRESS_AMOUNT_DATA_NOT_ZERO.test(new AddressAmountData("", 1));
|
|
||||||
|
|
||||||
Assert.assertTrue(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNotZeroForNegative() {
|
|
||||||
boolean test = BalanceRecorderUtils.ADDRESS_AMOUNT_DATA_NOT_ZERO.test( new AddressAmountData("", -10));
|
|
||||||
|
|
||||||
Assert.assertTrue(test);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddressAmountComparatorReverseOrder() {
|
|
||||||
|
|
||||||
BlockHeightRangeAddressAmounts addressAmounts1 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(2, 3, false), new ArrayList<>(0));
|
|
||||||
BlockHeightRangeAddressAmounts addressAmounts2 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 2, false), new ArrayList<>(0));
|
|
||||||
|
|
||||||
int compare = BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR.compare(addressAmounts1, addressAmounts2);
|
|
||||||
|
|
||||||
Assert.assertTrue( compare > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddressAmountComparatorForwardOrder() {
|
|
||||||
|
|
||||||
BlockHeightRangeAddressAmounts addressAmounts1 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 2, false), new ArrayList<>(0));
|
|
||||||
BlockHeightRangeAddressAmounts addressAmounts2 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(2, 3, false), new ArrayList<>(0));
|
|
||||||
|
|
||||||
int compare = BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR.compare(addressAmounts1, addressAmounts2);
|
|
||||||
|
|
||||||
Assert.assertTrue( compare < 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddressAmountDataComparator() {
|
|
||||||
|
|
||||||
AddressAmountData addressAmount1 = new AddressAmountData("a", 10);
|
|
||||||
AddressAmountData addressAmount2 = new AddressAmountData("b", 20);
|
|
||||||
|
|
||||||
int compare = BalanceRecorderUtils.ADDRESS_AMOUNT_DATA_COMPARATOR.compare(addressAmount1, addressAmount2);
|
|
||||||
|
|
||||||
Assert.assertTrue( compare < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveRecordingsBelowHeightNoBalances() {
|
|
||||||
|
|
||||||
int currentHeight = 5;
|
|
||||||
ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeRecordingsBelowHeight(currentHeight, balancesByHeight);
|
|
||||||
|
|
||||||
Assert.assertEquals(0, balancesByHeight.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveRecordingsBelowHeightOneBalanceBelow() {
|
|
||||||
int currentHeight = 5;
|
|
||||||
|
|
||||||
ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight = new ConcurrentHashMap<>(1);
|
|
||||||
|
|
||||||
balancesByHeight.put(1, new ArrayList<>(0));
|
|
||||||
|
|
||||||
Assert.assertEquals(1, balancesByHeight.size());
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeRecordingsBelowHeight(currentHeight, balancesByHeight);
|
|
||||||
|
|
||||||
Assert.assertEquals(0, balancesByHeight.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveRecordingsBelowHeightOneBalanceAbove() {
|
|
||||||
int currentHeight = 5;
|
|
||||||
|
|
||||||
ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight = new ConcurrentHashMap<>(1);
|
|
||||||
|
|
||||||
balancesByHeight.put(10, new ArrayList<>(0));
|
|
||||||
|
|
||||||
Assert.assertEquals(1, balancesByHeight.size());
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeRecordingsBelowHeight(currentHeight, balancesByHeight);
|
|
||||||
|
|
||||||
Assert.assertEquals(1, balancesByHeight.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBuildBalanceDynamicsOneAccountOneChange() {
|
|
||||||
|
|
||||||
String address = "a";
|
|
||||||
|
|
||||||
List<AccountBalanceData> balances = new ArrayList<>(1);
|
|
||||||
balances.add(new AccountBalanceData(address, 0, 2));
|
|
||||||
|
|
||||||
List<AccountBalanceData> priorBalances = new ArrayList<>(1);
|
|
||||||
priorBalances.add(new AccountBalanceData(address, 0, 1));
|
|
||||||
|
|
||||||
List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, 0, new ArrayList<>(0));
|
|
||||||
|
|
||||||
Assert.assertNotNull(dynamics);
|
|
||||||
Assert.assertEquals(1, dynamics.size());
|
|
||||||
|
|
||||||
AddressAmountData addressAmountData = dynamics.get(0);
|
|
||||||
Assert.assertNotNull(addressAmountData);
|
|
||||||
Assert.assertEquals(address, addressAmountData.getAddress());
|
|
||||||
Assert.assertEquals(1, addressAmountData.getAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBuildBalanceDynamicsOneAccountNoPrior() {
|
|
||||||
|
|
||||||
String address = "a";
|
|
||||||
|
|
||||||
List<AccountBalanceData> balances = new ArrayList<>(1);
|
|
||||||
balances.add(new AccountBalanceData(address, 0, 2));
|
|
||||||
|
|
||||||
List<AccountBalanceData> priorBalances = new ArrayList<>(0);
|
|
||||||
|
|
||||||
List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, 0, new ArrayList<>(0));
|
|
||||||
|
|
||||||
Assert.assertNotNull(dynamics);
|
|
||||||
Assert.assertEquals(1, dynamics.size());
|
|
||||||
|
|
||||||
AddressAmountData addressAmountData = dynamics.get(0);
|
|
||||||
Assert.assertNotNull(addressAmountData);
|
|
||||||
Assert.assertEquals(address, addressAmountData.getAddress());
|
|
||||||
Assert.assertEquals(2, addressAmountData.getAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBuildBalanceDynamicOneAccountAdjustment() {
|
|
||||||
List<AccountBalanceData> balances = new ArrayList<>(1);
|
|
||||||
balances.add(new AccountBalanceData(RECIPIENT_ADDRESS, 0, 20));
|
|
||||||
|
|
||||||
List<AccountBalanceData> priorBalances = new ArrayList<>(0);
|
|
||||||
priorBalances.add(new AccountBalanceData(RECIPIENT_ADDRESS, 0, 12));
|
|
||||||
|
|
||||||
List<TransactionData> transactions = new ArrayList<>();
|
|
||||||
|
|
||||||
final long amount = 5L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
|
|
||||||
PaymentTransactionData paymentData
|
|
||||||
= new PaymentTransactionData(
|
|
||||||
new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
RECIPIENT_ADDRESS,
|
|
||||||
amount
|
|
||||||
);
|
|
||||||
|
|
||||||
transactions.add(paymentData);
|
|
||||||
|
|
||||||
List<AddressAmountData> dynamics
|
|
||||||
= BalanceRecorderUtils.buildBalanceDynamics(
|
|
||||||
balances,
|
|
||||||
priorBalances,
|
|
||||||
0,
|
|
||||||
transactions
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert.assertNotNull(dynamics);
|
|
||||||
Assert.assertEquals(1, dynamics.size());
|
|
||||||
|
|
||||||
AddressAmountData addressAmountData = dynamics.get(0);
|
|
||||||
Assert.assertNotNull(addressAmountData);
|
|
||||||
Assert.assertEquals(RECIPIENT_ADDRESS, addressAmountData.getAddress());
|
|
||||||
Assert.assertEquals(3, addressAmountData.getAmount());
|
|
||||||
} catch( Exception e ) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBuildBalanceDynamicsTwoAccountsNegativeValues() {
|
|
||||||
|
|
||||||
String address1 = "a";
|
|
||||||
String address2 = "b";
|
|
||||||
|
|
||||||
List<AccountBalanceData> balances = new ArrayList<>(2);
|
|
||||||
balances.add(new AccountBalanceData(address1, 0, 10_000));
|
|
||||||
balances.add(new AccountBalanceData(address2, 0, 100));
|
|
||||||
|
|
||||||
List<AccountBalanceData> priorBalances = new ArrayList<>(2);
|
|
||||||
priorBalances.add(new AccountBalanceData(address2, 0, 200));
|
|
||||||
priorBalances.add(new AccountBalanceData(address1, 0, 5000));
|
|
||||||
|
|
||||||
List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, -100L, new ArrayList<>(0));
|
|
||||||
|
|
||||||
Assert.assertNotNull(dynamics);
|
|
||||||
Assert.assertEquals(2, dynamics.size());
|
|
||||||
|
|
||||||
Map<String, Long> amountByAddress
|
|
||||||
= dynamics.stream()
|
|
||||||
.collect(Collectors.toMap(dynamic -> dynamic.getAddress(), dynamic -> dynamic.getAmount()));
|
|
||||||
|
|
||||||
Assert.assertTrue(amountByAddress.containsKey(address1));
|
|
||||||
|
|
||||||
long amount1 = amountByAddress.get(address1);
|
|
||||||
|
|
||||||
Assert.assertNotNull(amount1);
|
|
||||||
Assert.assertEquals(5000L, amount1 );
|
|
||||||
|
|
||||||
Assert.assertTrue(amountByAddress.containsKey(address2));
|
|
||||||
|
|
||||||
long amount2 = amountByAddress.get(address2);
|
|
||||||
|
|
||||||
Assert.assertNotNull(amount2);
|
|
||||||
Assert.assertEquals(-100L, amount2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBuildBalanceDynamicsForAccountNoPriorAnyAccount() {
|
|
||||||
List<AccountBalanceData> priorBalances = new ArrayList<>(0);
|
|
||||||
AccountBalanceData accountBalance = new AccountBalanceData("a", 0, 10);
|
|
||||||
|
|
||||||
AddressAmountData dynamic = BalanceRecorderUtils.buildBalanceDynamicsForAccount(priorBalances, accountBalance);
|
|
||||||
|
|
||||||
Assert.assertNotNull(dynamic);
|
|
||||||
Assert.assertEquals(10, dynamic.getAmount());
|
|
||||||
Assert.assertEquals("a", dynamic.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBuildBalanceDynamicsForAccountNoPriorThisAccount() {
|
|
||||||
List<AccountBalanceData> priorBalances = new ArrayList<>(2);
|
|
||||||
priorBalances.add(new AccountBalanceData("b", 0, 100));
|
|
||||||
|
|
||||||
AccountBalanceData accountBalanceData = new AccountBalanceData("a", 0, 10);
|
|
||||||
|
|
||||||
AddressAmountData dynamic = BalanceRecorderUtils.buildBalanceDynamicsForAccount(priorBalances, accountBalanceData);
|
|
||||||
|
|
||||||
Assert.assertNotNull(dynamic);
|
|
||||||
Assert.assertEquals(10, dynamic.getAmount());
|
|
||||||
Assert.assertEquals("a", dynamic.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBuildBalanceDynamicsForAccountPriorForThisAndOthers() {
|
|
||||||
List<AccountBalanceData> priorBalances = new ArrayList<>(2);
|
|
||||||
priorBalances.add(new AccountBalanceData("a", 0, 100));
|
|
||||||
priorBalances.add(new AccountBalanceData("b", 0, 200));
|
|
||||||
priorBalances.add(new AccountBalanceData("c", 0, 300));
|
|
||||||
|
|
||||||
AccountBalanceData accountBalance = new AccountBalanceData("b", 0, 1000);
|
|
||||||
|
|
||||||
AddressAmountData dynamic = BalanceRecorderUtils.buildBalanceDynamicsForAccount(priorBalances, accountBalance);
|
|
||||||
|
|
||||||
Assert.assertNotNull(dynamic);
|
|
||||||
Assert.assertEquals(800, dynamic.getAmount());
|
|
||||||
Assert.assertEquals("b", dynamic.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveRecordingAboveHeightOneOfTwo() {
|
|
||||||
|
|
||||||
int currentHeight = 10;
|
|
||||||
ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
balancesByHeight.put(3, new ArrayList<>());
|
|
||||||
balancesByHeight.put(20, new ArrayList<>());
|
|
||||||
|
|
||||||
Assert.assertEquals(2, balancesByHeight.size());
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeRecordingsAboveHeight(currentHeight, balancesByHeight);
|
|
||||||
|
|
||||||
Assert.assertEquals(1, balancesByHeight.size());
|
|
||||||
Assert.assertTrue( balancesByHeight.containsKey(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPriorHeightBeforeAfter() {
|
|
||||||
|
|
||||||
int currentHeight = 10;
|
|
||||||
ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight = new ConcurrentHashMap<>();
|
|
||||||
balancesByHeight.put( 2, new ArrayList<>());
|
|
||||||
balancesByHeight.put(7, new ArrayList<>());
|
|
||||||
balancesByHeight.put(12, new ArrayList<>());
|
|
||||||
|
|
||||||
Optional<Integer> priorHeight = BalanceRecorderUtils.getPriorHeight(currentHeight, balancesByHeight);
|
|
||||||
|
|
||||||
Assert.assertNotNull(priorHeight);
|
|
||||||
Assert.assertTrue(priorHeight.isPresent());
|
|
||||||
Assert.assertEquals( 7, priorHeight.get().intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPriorHeightNoPriorAfterOnly() {
|
|
||||||
|
|
||||||
int currentHeight = 10;
|
|
||||||
ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight = new ConcurrentHashMap<>();
|
|
||||||
balancesByHeight.put(12, new ArrayList<>());
|
|
||||||
|
|
||||||
Optional<Integer> priorHeight = BalanceRecorderUtils.getPriorHeight(currentHeight, balancesByHeight);
|
|
||||||
|
|
||||||
Assert.assertNotNull(priorHeight);
|
|
||||||
Assert.assertTrue(priorHeight.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPriorHeightPriorOnly() {
|
|
||||||
|
|
||||||
int currentHeight = 10;
|
|
||||||
|
|
||||||
ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight = new ConcurrentHashMap<>();
|
|
||||||
balancesByHeight.put(7, new ArrayList<>());
|
|
||||||
|
|
||||||
Optional<Integer> priorHeight = BalanceRecorderUtils.getPriorHeight(currentHeight, balancesByHeight);
|
|
||||||
|
|
||||||
Assert.assertNotNull(priorHeight);
|
|
||||||
Assert.assertTrue(priorHeight.isPresent());
|
|
||||||
Assert.assertEquals(7, priorHeight.get().intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveDynamicsOnOrAboveHeightOneAbove() {
|
|
||||||
|
|
||||||
int currentHeight = 10;
|
|
||||||
|
|
||||||
CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> dynamics = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
BlockHeightRange range1 = new BlockHeightRange(10, 20, false);
|
|
||||||
dynamics.add(new BlockHeightRangeAddressAmounts(range1, new ArrayList<>()));
|
|
||||||
|
|
||||||
BlockHeightRange range2 = new BlockHeightRange(1, 4, false);
|
|
||||||
dynamics.add(new BlockHeightRangeAddressAmounts(range2, new ArrayList<>()));
|
|
||||||
|
|
||||||
Assert.assertEquals(2, dynamics.size());
|
|
||||||
BalanceRecorderUtils.removeDynamicsOnOrAboveHeight(currentHeight, dynamics);
|
|
||||||
|
|
||||||
Assert.assertEquals(1, dynamics.size());
|
|
||||||
Assert.assertEquals(range2, dynamics.get(0).getRange());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveDynamicsOnOrAboveOneOnOneAbove() {
|
|
||||||
int currentHeight = 11;
|
|
||||||
|
|
||||||
CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> dynamics = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
BlockHeightRange range1 = new BlockHeightRange(1,5, false);
|
|
||||||
dynamics.add(new BlockHeightRangeAddressAmounts(range1, new ArrayList<>()));
|
|
||||||
|
|
||||||
BlockHeightRange range2 = new BlockHeightRange(6, 11, false);
|
|
||||||
dynamics.add((new BlockHeightRangeAddressAmounts(range2, new ArrayList<>())));
|
|
||||||
|
|
||||||
BlockHeightRange range3 = new BlockHeightRange(22, 16, false);
|
|
||||||
dynamics.add(new BlockHeightRangeAddressAmounts(range3, new ArrayList<>()));
|
|
||||||
|
|
||||||
Assert.assertEquals(3, dynamics.size());
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeDynamicsOnOrAboveHeight(currentHeight, dynamics);
|
|
||||||
|
|
||||||
Assert.assertEquals(1, dynamics.size());
|
|
||||||
Assert.assertTrue( dynamics.get(0).getRange().equals(range1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveOldestDynamicsTwice() {
|
|
||||||
CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> dynamics = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
dynamics.add(new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 5, false), new ArrayList<>()));
|
|
||||||
dynamics.add(new BlockHeightRangeAddressAmounts(new BlockHeightRange(5, 9, false), new ArrayList<>()));
|
|
||||||
|
|
||||||
Assert.assertEquals(2, dynamics.size());
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeOldestDynamics(dynamics);
|
|
||||||
|
|
||||||
Assert.assertEquals(1, dynamics.size());
|
|
||||||
Assert.assertTrue(dynamics.get(0).getRange().equals(new BlockHeightRange(5, 9, false)));
|
|
||||||
|
|
||||||
BalanceRecorderUtils.removeOldestDynamics(dynamics);
|
|
||||||
|
|
||||||
Assert.assertEquals(0, dynamics.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForPaymentTransaction() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final long amount = 1L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
|
|
||||||
PaymentTransactionData paymentData
|
|
||||||
= new PaymentTransactionData(
|
|
||||||
new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
RECIPIENT_ADDRESS,
|
|
||||||
amount
|
|
||||||
);
|
|
||||||
|
|
||||||
// map balance modifications for addresses in the transaction
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
BalanceRecorderUtils.mapBalanceModicationsForPaymentTransaction(amountsByAddress, paymentData);
|
|
||||||
|
|
||||||
// this will not add the fee, that is done in a different place
|
|
||||||
assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
|
|
||||||
} catch (Exception e) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForAssetOrderTransaction() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try{
|
|
||||||
final long amount = 1L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
|
|
||||||
TransferAssetTransactionData transferAssetData
|
|
||||||
= new TransferAssetTransactionData(
|
|
||||||
new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
RECIPIENT_ADDRESS,
|
|
||||||
amount,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
// map balance modifications for addresses in the transaction
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForTransferAssetTransaction(amountsByAddress, transferAssetData);
|
|
||||||
|
|
||||||
assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
|
|
||||||
} catch( Exception e) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForATTransactionMessageType() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
ATTransactionData atTransactionData = new ATTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
AT_ADDRESS,
|
|
||||||
RECIPIENT_ADDRESS,
|
|
||||||
new byte[0]);
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForAtTransaction( amountsByAddress, atTransactionData);
|
|
||||||
|
|
||||||
// no balance changes for AT message
|
|
||||||
Assert.assertTrue(amountsByAddress.size() == 0);
|
|
||||||
} catch( Exception e) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForATTransactionPaymentType() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try{
|
|
||||||
final long amount = 1L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
ATTransactionData atTransactionData
|
|
||||||
= new ATTransactionData(
|
|
||||||
new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
AT_ADDRESS,
|
|
||||||
RECIPIENT_ADDRESS,
|
|
||||||
amount,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForAtTransaction( amountsByAddress, atTransactionData);
|
|
||||||
|
|
||||||
assertAmountByAddress(amountsByAddress, amount, RECIPIENT_ADDRESS);
|
|
||||||
|
|
||||||
assertAmountByAddress(amountsByAddress, -amount, AT_ADDRESS);
|
|
||||||
} catch( Exception e) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForBuyNameTransaction() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try{
|
|
||||||
final long amount = 100L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
BuyNameTransactionData buyNameData
|
|
||||||
= new BuyNameTransactionData(
|
|
||||||
new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
"null",
|
|
||||||
amount,
|
|
||||||
RECIPIENT_ADDRESS
|
|
||||||
);
|
|
||||||
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForBuyNameTransaction(amountsByAddress, buyNameData);
|
|
||||||
|
|
||||||
assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
|
|
||||||
} catch( Exception e) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForMultiPaymentTransaction() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try{
|
|
||||||
final long amount = 100L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
List<PaymentData> payments = new ArrayList<>();
|
|
||||||
|
|
||||||
payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
|
|
||||||
|
|
||||||
MultiPaymentTransactionData multiPayment
|
|
||||||
= new MultiPaymentTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
payments);
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress,multiPayment);
|
|
||||||
assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
|
|
||||||
} catch( Exception e ) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForMultiPaymentTransaction2PaymentsOneAddress() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try{
|
|
||||||
final long amount = 100L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
List<PaymentData> payments = new ArrayList<>();
|
|
||||||
|
|
||||||
payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
|
|
||||||
payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
|
|
||||||
|
|
||||||
MultiPaymentTransactionData multiPayment
|
|
||||||
= new MultiPaymentTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
payments);
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress,multiPayment);
|
|
||||||
assertAmountsByAddress(amountsByAddress, 2*amount, creatorPublicKey, RECIPIENT_ADDRESS);
|
|
||||||
} catch( Exception e ) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForMultiPaymentTransaction2PaymentsTwoAddresses() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try{
|
|
||||||
final long amount = 100L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
List<PaymentData> payments = new ArrayList<>();
|
|
||||||
|
|
||||||
payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
|
|
||||||
payments.add(new PaymentData(OTHER, 0, amount));
|
|
||||||
|
|
||||||
MultiPaymentTransactionData multiPayment
|
|
||||||
= new MultiPaymentTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
payments);
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress,multiPayment);
|
|
||||||
assertAmountByAddress(amountsByAddress, amount, RECIPIENT_ADDRESS);
|
|
||||||
assertAmountByAddress(amountsByAddress, amount, OTHER);
|
|
||||||
|
|
||||||
String creatorAddress = Crypto.toAddress(creatorPublicKey);
|
|
||||||
|
|
||||||
assertAmountByAddress(amountsByAddress, 2*-amount, creatorAddress);
|
|
||||||
} catch( Exception e ) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForDeployAtTransaction() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try{
|
|
||||||
final long amount = 3L;
|
|
||||||
final long fee = 1L;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
DeployAtTransactionData deployAt
|
|
||||||
= new DeployAtTransactionData(
|
|
||||||
new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
AT_ADDRESS, "name", "description", "type", "tags", new byte[0], amount, Asset.QORT
|
|
||||||
);
|
|
||||||
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForDeployAtTransaction(amountsByAddress,deployAt);
|
|
||||||
assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, AT_ADDRESS);
|
|
||||||
} catch( Exception e) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMapBalanceModificationsForTransaction() {
|
|
||||||
|
|
||||||
boolean exceptionThrown = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final long fee = 2;
|
|
||||||
|
|
||||||
byte[] creatorPublicKey = TestUtils.generatePublicKey();
|
|
||||||
Map<String, Long> amountsByAddress = new HashMap<>();
|
|
||||||
|
|
||||||
BalanceRecorderUtils.mapBalanceModificationsForTransaction(
|
|
||||||
amountsByAddress,
|
|
||||||
new RegisterNameTransactionData(
|
|
||||||
new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
|
|
||||||
"aaa", "data", "aaa")
|
|
||||||
);
|
|
||||||
|
|
||||||
String creatorAddress = Crypto.toAddress(creatorPublicKey);
|
|
||||||
|
|
||||||
assertAmountByAddress(amountsByAddress, -fee, creatorAddress);
|
|
||||||
} catch(Exception e) {
|
|
||||||
exceptionThrown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertFalse(exceptionThrown);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBlockHeightRangeEqualityTrue() {
|
|
||||||
|
|
||||||
BlockHeightRange range1 = new BlockHeightRange(2, 4, false);
|
|
||||||
BlockHeightRange range2 = new BlockHeightRange(2, 4, true);
|
|
||||||
|
|
||||||
Assert.assertTrue(range1.equals(range2));
|
|
||||||
Assert.assertEquals(range1, range2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBloHeightRangeEqualityFalse() {
|
|
||||||
|
|
||||||
BlockHeightRange range1 = new BlockHeightRange(2, 3, true);
|
|
||||||
BlockHeightRange range2 = new BlockHeightRange(2, 4, true);
|
|
||||||
|
|
||||||
Assert.assertFalse(range1.equals(range2));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void assertAmountsByAddress(Map<String, Long> amountsByAddress, long amount, byte[] creatorPublicKey, String recipientAddress) {
|
|
||||||
assertAmountByAddress(amountsByAddress, amount, recipientAddress);
|
|
||||||
|
|
||||||
String creatorAddress = Crypto.toAddress(creatorPublicKey);
|
|
||||||
|
|
||||||
assertAmountByAddress(amountsByAddress, -amount, creatorAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void assertAmountByAddress(Map<String, Long> amountsByAddress, long amount, String address) {
|
|
||||||
Long amountForAddress = amountsByAddress.get(address);
|
|
||||||
|
|
||||||
Assert.assertTrue(amountsByAddress.containsKey(address));
|
|
||||||
Assert.assertNotNull(amountForAddress);
|
|
||||||
Assert.assertEquals(amount, amountForAddress.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
package org.qortal.test.utils;
|
|
||||||
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.data.transaction.CreateGroupTransactionData;
|
|
||||||
import org.qortal.data.transaction.GroupInviteTransactionData;
|
|
||||||
import org.qortal.data.transaction.JoinGroupTransactionData;
|
|
||||||
import org.qortal.data.transaction.LeaveGroupTransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.repository.Repository;
|
|
||||||
import org.qortal.test.common.TransactionUtils;
|
|
||||||
import org.qortal.test.common.transaction.TestTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class GroupsTestUtils
|
|
||||||
*
|
|
||||||
* Utility methods for testing the Groups class.
|
|
||||||
*/
|
|
||||||
public class GroupsTestUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create Group
|
|
||||||
*
|
|
||||||
* @param repository the data repository
|
|
||||||
* @param owner the group owner
|
|
||||||
* @param groupName the group name
|
|
||||||
* @param isOpen true if the group is public, false for private
|
|
||||||
*
|
|
||||||
* @return the group Id
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static Integer createGroup(Repository repository, PrivateKeyAccount owner, String groupName, boolean isOpen) throws DataException {
|
|
||||||
String description = groupName + " (description)";
|
|
||||||
|
|
||||||
Group.ApprovalThreshold approvalThreshold = Group.ApprovalThreshold.ONE;
|
|
||||||
int minimumBlockDelay = 10;
|
|
||||||
int maximumBlockDelay = 1440;
|
|
||||||
|
|
||||||
CreateGroupTransactionData transactionData = new CreateGroupTransactionData(TestTransaction.generateBase(owner), groupName, description, isOpen, approvalThreshold, minimumBlockDelay, maximumBlockDelay);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, owner);
|
|
||||||
|
|
||||||
return repository.getGroupRepository().fromGroupName(groupName).getGroupId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Join Group
|
|
||||||
*
|
|
||||||
* @param repository the data repository
|
|
||||||
* @param joiner the address for the account joining the group
|
|
||||||
* @param groupId the Id for the group to join
|
|
||||||
*
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static void joinGroup(Repository repository, PrivateKeyAccount joiner, int groupId) throws DataException {
|
|
||||||
JoinGroupTransactionData transactionData = new JoinGroupTransactionData(TestTransaction.generateBase(joiner), groupId);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, joiner);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Group Invite
|
|
||||||
*
|
|
||||||
* @param repository the data repository
|
|
||||||
* @param admin the admin account to sign the invite
|
|
||||||
* @param groupId the Id of the group to invite to
|
|
||||||
* @param invitee the recipient address for the invite
|
|
||||||
* @param timeToLive the time length of the invite
|
|
||||||
*
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static void groupInvite(Repository repository, PrivateKeyAccount admin, int groupId, String invitee, int timeToLive) throws DataException {
|
|
||||||
GroupInviteTransactionData transactionData = new GroupInviteTransactionData(TestTransaction.generateBase(admin), groupId, invitee, timeToLive);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, admin);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Leave Group
|
|
||||||
*
|
|
||||||
* @param repository the data repository
|
|
||||||
* @param leaver the account leaving
|
|
||||||
* @param groupId the Id of the group being left
|
|
||||||
*
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static void leaveGroup(Repository repository, PrivateKeyAccount leaver, int groupId) throws DataException {
|
|
||||||
LeaveGroupTransactionData transactionData = new LeaveGroupTransactionData(TestTransaction.generateBase(leaver), groupId);
|
|
||||||
TransactionUtils.signAndMint(repository, transactionData, leaver);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is Member?
|
|
||||||
*
|
|
||||||
* @param repository the data repository
|
|
||||||
* @param address the account address
|
|
||||||
* @param groupId the group Id
|
|
||||||
*
|
|
||||||
* @return true if the account is a member of the group, otherwise false
|
|
||||||
* @throws DataException
|
|
||||||
*/
|
|
||||||
public static boolean isMember(Repository repository, String address, int groupId) throws DataException {
|
|
||||||
return repository.getGroupRepository().memberExists(groupId, address);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,199 +0,0 @@
|
|||||||
package org.qortal.test.utils;
|
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.block.BlockChain;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.repository.Repository;
|
|
||||||
import org.qortal.repository.RepositoryManager;
|
|
||||||
import org.qortal.test.common.BlockUtils;
|
|
||||||
import org.qortal.test.common.Common;
|
|
||||||
import org.qortal.utils.Groups;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class GroupsTests extends Common {
|
|
||||||
|
|
||||||
public static final String ALICE = "alice";
|
|
||||||
public static final String BOB = "bob";
|
|
||||||
public static final String CHLOE = "chloe";
|
|
||||||
public static final String DILBERT = "dilbert";
|
|
||||||
|
|
||||||
|
|
||||||
private static final int HEIGHT_1 = 5;
|
|
||||||
private static final int HEIGHT_2 = 8;
|
|
||||||
private static final int HEIGHT_3 = 12;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void afterTest() throws DataException {
|
|
||||||
Common.orphanCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetGroupIdsToMintSimple() {
|
|
||||||
List<Integer> ids = Groups.getGroupIdsToMint(BlockChain.getInstance(), 0);
|
|
||||||
|
|
||||||
Assert.assertNotNull(ids);
|
|
||||||
Assert.assertEquals(0, ids.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetGroupIdsToMintComplex() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
Block block1 = BlockUtils.mintBlocks(repository, HEIGHT_1);
|
|
||||||
int height1 = block1.getBlockData().getHeight().intValue();
|
|
||||||
assertEquals(HEIGHT_1 + 1, height1);
|
|
||||||
|
|
||||||
List<Integer> ids1 = Groups.getGroupIdsToMint(BlockChain.getInstance(), height1);
|
|
||||||
|
|
||||||
Assert.assertEquals(1, ids1.size() );
|
|
||||||
Assert.assertTrue( ids1.contains( 694 ) );
|
|
||||||
|
|
||||||
Block block2 = BlockUtils.mintBlocks(repository, HEIGHT_2 - HEIGHT_1);
|
|
||||||
int height2 = block2.getBlockData().getHeight().intValue();
|
|
||||||
assertEquals( HEIGHT_2 + 1, height2);
|
|
||||||
|
|
||||||
List<Integer> ids2 = Groups.getGroupIdsToMint(BlockChain.getInstance(), height2);
|
|
||||||
|
|
||||||
Assert.assertEquals(2, ids2.size() );
|
|
||||||
|
|
||||||
Assert.assertTrue( ids2.contains( 694 ) );
|
|
||||||
Assert.assertTrue( ids2.contains( 800 ) );
|
|
||||||
|
|
||||||
Block block3 = BlockUtils.mintBlocks(repository, HEIGHT_3 - HEIGHT_2);
|
|
||||||
int height3 = block3.getBlockData().getHeight().intValue();
|
|
||||||
assertEquals( HEIGHT_3 + 1, height3);
|
|
||||||
|
|
||||||
List<Integer> ids3 = Groups.getGroupIdsToMint(BlockChain.getInstance(), height3);
|
|
||||||
|
|
||||||
Assert.assertEquals( 1, ids3.size() );
|
|
||||||
|
|
||||||
Assert.assertTrue( ids3.contains( 800 ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMemberExistsInAnyGroupSimple() throws DataException {
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
// Create group
|
|
||||||
int groupId = GroupsTestUtils.createGroup(repository, alice, "closed-group", false);
|
|
||||||
|
|
||||||
// Confirm Bob is not a member
|
|
||||||
Assert.assertFalse( Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(groupId), bob.getAddress()) );
|
|
||||||
|
|
||||||
// Bob to join
|
|
||||||
GroupsTestUtils.joinGroup(repository, bob, groupId);
|
|
||||||
|
|
||||||
// Confirm Bob still not a member
|
|
||||||
assertFalse(GroupsTestUtils.isMember(repository, bob.getAddress(), groupId));
|
|
||||||
|
|
||||||
// Have Alice 'invite' Bob to confirm membership
|
|
||||||
GroupsTestUtils.groupInvite(repository, alice, groupId, bob.getAddress(), 0); // non-expiring invite
|
|
||||||
|
|
||||||
// Confirm Bob now a member
|
|
||||||
Assert.assertTrue( Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(groupId), bob.getAddress()) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGroupsListedFunctionality() throws DataException {
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, ALICE);
|
|
||||||
PrivateKeyAccount bob = Common.getTestAccount(repository, BOB);
|
|
||||||
PrivateKeyAccount chloe = Common.getTestAccount(repository, CHLOE);
|
|
||||||
PrivateKeyAccount dilbert = Common.getTestAccount(repository, DILBERT);
|
|
||||||
|
|
||||||
// Create groups
|
|
||||||
int group1Id = GroupsTestUtils.createGroup(repository, alice, "group-1", false);
|
|
||||||
int group2Id = GroupsTestUtils.createGroup(repository, bob, "group-2", false);
|
|
||||||
|
|
||||||
// test memberExistsInAnyGroup
|
|
||||||
Assert.assertTrue(Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(group1Id, group2Id), alice.getAddress()));
|
|
||||||
Assert.assertFalse(Groups.memberExistsInAnyGroup(repository.getGroupRepository(), List.of(group1Id, group2Id), chloe.getAddress()));
|
|
||||||
|
|
||||||
// alice is a member
|
|
||||||
Assert.assertTrue(GroupsTestUtils.isMember(repository, alice.getAddress(), group1Id));
|
|
||||||
List<String> allMembersBeforeJoin = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id));
|
|
||||||
|
|
||||||
// assert one member
|
|
||||||
Assert.assertNotNull(allMembersBeforeJoin);
|
|
||||||
Assert.assertEquals(1, allMembersBeforeJoin.size());
|
|
||||||
|
|
||||||
List<String> allAdminsBeforeJoin = Groups.getAllAdmins(repository.getGroupRepository(), List.of(group1Id));
|
|
||||||
|
|
||||||
// assert one admin
|
|
||||||
Assert.assertNotNull(allAdminsBeforeJoin);
|
|
||||||
Assert.assertEquals( 1, allAdminsBeforeJoin.size());
|
|
||||||
|
|
||||||
// Bob to join
|
|
||||||
GroupsTestUtils.joinGroup(repository, bob, group1Id);
|
|
||||||
|
|
||||||
// Have Alice 'invite' Bob to confirm membership
|
|
||||||
GroupsTestUtils.groupInvite(repository, alice, group1Id, bob.getAddress(), 0); // non-expiring invite
|
|
||||||
|
|
||||||
List<String> allMembersAfterJoin = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id));
|
|
||||||
|
|
||||||
// alice and bob are members
|
|
||||||
Assert.assertNotNull(allMembersAfterJoin);
|
|
||||||
Assert.assertEquals(2, allMembersAfterJoin.size());
|
|
||||||
|
|
||||||
List<String> allAdminsAfterJoin = Groups.getAllAdmins(repository.getGroupRepository(), List.of(group1Id));
|
|
||||||
|
|
||||||
// assert still one admin
|
|
||||||
Assert.assertNotNull(allAdminsAfterJoin);
|
|
||||||
Assert.assertEquals(1, allAdminsAfterJoin.size());
|
|
||||||
|
|
||||||
List<String> allAdminsFor2Groups = Groups.getAllAdmins(repository.getGroupRepository(), List.of(group1Id, group2Id));
|
|
||||||
|
|
||||||
// assert 2 admins when including the second group
|
|
||||||
Assert.assertNotNull(allAdminsFor2Groups);
|
|
||||||
Assert.assertEquals(2, allAdminsFor2Groups.size());
|
|
||||||
|
|
||||||
List<String> allMembersFor2Groups = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id, group2Id));
|
|
||||||
|
|
||||||
// assert 2 members when including the seconds group
|
|
||||||
Assert.assertNotNull(allMembersFor2Groups);
|
|
||||||
Assert.assertEquals(2, allMembersFor2Groups.size());
|
|
||||||
|
|
||||||
GroupsTestUtils.leaveGroup(repository, bob, group1Id);
|
|
||||||
|
|
||||||
List<String> allMembersForAfterBobLeavesGroup1InAllGroups = Groups.getAllMembers(repository.getGroupRepository(), List.of(group1Id, group2Id));
|
|
||||||
|
|
||||||
// alice and bob are members of one group still
|
|
||||||
Assert.assertNotNull(allMembersForAfterBobLeavesGroup1InAllGroups);
|
|
||||||
Assert.assertEquals(2, allMembersForAfterBobLeavesGroup1InAllGroups.size());
|
|
||||||
|
|
||||||
GroupsTestUtils.groupInvite(repository, alice, group1Id, chloe.getAddress(), 3600);
|
|
||||||
GroupsTestUtils.groupInvite(repository, bob, group2Id, chloe.getAddress(), 3600);
|
|
||||||
|
|
||||||
GroupsTestUtils.joinGroup(repository, chloe, group1Id);
|
|
||||||
GroupsTestUtils.joinGroup(repository, chloe, group2Id);
|
|
||||||
|
|
||||||
List<String> allMembersAfterDilbert = Groups.getAllMembers((repository.getGroupRepository()), List.of(group1Id, group2Id));
|
|
||||||
|
|
||||||
// 3 accounts are now members of one group or another
|
|
||||||
Assert.assertNotNull(allMembersAfterDilbert);
|
|
||||||
Assert.assertEquals(3, allMembersAfterDilbert.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package org.qortal.test.utils;
|
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
||||||
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.Security;
|
|
||||||
|
|
||||||
public class TestUtils {
|
|
||||||
public static byte[] generatePublicKey() throws Exception {
|
|
||||||
// Add the Bouncy Castle provider
|
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
|
||||||
|
|
||||||
// Generate a key pair
|
|
||||||
KeyPair keyPair = generateKeyPair();
|
|
||||||
|
|
||||||
// Get the public key
|
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
|
||||||
|
|
||||||
// Get the public key as a byte array
|
|
||||||
byte[] publicKeyBytes = publicKey.getEncoded();
|
|
||||||
|
|
||||||
// Generate a RIPEMD160 message digest from the public key
|
|
||||||
byte[] ripeMd160Digest = generateRipeMd160Digest(publicKeyBytes);
|
|
||||||
|
|
||||||
return ripeMd160Digest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static KeyPair generateKeyPair() throws Exception {
|
|
||||||
// Generate a key pair using the RSA algorithm
|
|
||||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
|
||||||
keyGen.initialize(2048); // Key size (bits)
|
|
||||||
return keyGen.generateKeyPair();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] generateRipeMd160Digest(byte[] input) throws Exception {
|
|
||||||
// Create a RIPEMD160 message digest instance
|
|
||||||
MessageDigest ripeMd160 = MessageDigest.getInstance("RIPEMD160", new BouncyCastleProvider());
|
|
||||||
|
|
||||||
// Update the message digest with the input bytes
|
|
||||||
ripeMd160.update(input);
|
|
||||||
|
|
||||||
// Get the message digest bytes
|
|
||||||
return ripeMd160.digest();
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,12 +31,6 @@
|
|||||||
"blockRewardBatchStartHeight": 999999000,
|
"blockRewardBatchStartHeight": 999999000,
|
||||||
"blockRewardBatchSize": 10,
|
"blockRewardBatchSize": 10,
|
||||||
"blockRewardBatchAccountsBlockCount": 3,
|
"blockRewardBatchAccountsBlockCount": 3,
|
||||||
"mintingGroupIds": [
|
|
||||||
{ "height": 0, "ids": []},
|
|
||||||
{ "height": 5, "ids": [694]},
|
|
||||||
{ "height": 8, "ids": [694, 800]},
|
|
||||||
{ "height": 12, "ids": [800]}
|
|
||||||
],
|
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
@ -109,12 +103,7 @@
|
|||||||
"onlyMintWithNameHeight": 9999999999990,
|
"onlyMintWithNameHeight": 9999999999990,
|
||||||
"groupMemberCheckHeight": 9999999999999,
|
"groupMemberCheckHeight": 9999999999999,
|
||||||
"decreaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
"decreaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||||
"removeOnlyMintWithNameHeight": 9999999999999,
|
"removeOnlyMintWithNameHeight": 9999999999999
|
||||||
"fixBatchRewardHeight": 9999999999999,
|
|
||||||
"adminsReplaceFoundersHeight": 9999999999999,
|
|
||||||
"ignoreLevelForRewardShareHeight": 9999999999999,
|
|
||||||
"nullGroupMembershipHeight": 20,
|
|
||||||
"adminQueryFixHeight": 9999999999999
|
|
||||||
},
|
},
|
||||||
"genesisInfo": {
|
"genesisInfo": {
|
||||||
"version": 4,
|
"version": 4,
|
||||||
|
@ -69,8 +69,7 @@ die("Can't calculate SHA256 of ${project}.update\n") unless $sha256 =~ m/(\S{64}
|
|||||||
chomp $sha256;
|
chomp $sha256;
|
||||||
|
|
||||||
# long-form commit hash of HEAD on auto-update branch
|
# long-form commit hash of HEAD on auto-update branch
|
||||||
#my $update_hash = `git rev-parse refs/heads/auto-update-${commit_hash}`;
|
my $update_hash = `git rev-parse refs/heads/auto-update-${commit_hash}`;
|
||||||
my $update_hash = `git rev-parse origin/auto-update-${commit_hash}`;
|
|
||||||
die("Can't find commit hash for HEAD on auto-update-${commit_hash} branch\n") if ! defined $update_hash;
|
die("Can't find commit hash for HEAD on auto-update-${commit_hash} branch\n") if ! defined $update_hash;
|
||||||
chomp $update_hash;
|
chomp $update_hash;
|
||||||
|
|
||||||
@ -133,57 +132,11 @@ my $signed_tx = `curl --silent -H "accept: text/plain" -H "Content-Type: applica
|
|||||||
die("Can't sign raw transaction:\n$signed_tx\n") unless $signed_tx =~ m/^\w{390,410}$/; # +90ish longer than $raw_tx
|
die("Can't sign raw transaction:\n$signed_tx\n") unless $signed_tx =~ m/^\w{390,410}$/; # +90ish longer than $raw_tx
|
||||||
printf "\nSigned transaction:\n%s\n", $signed_tx;
|
printf "\nSigned transaction:\n%s\n", $signed_tx;
|
||||||
|
|
||||||
# Get the origin URL - So that we will be able to TEST the obtaining of the qortal.update...
|
# Check we can actually fetch update
|
||||||
my $origin = `git remote get-url origin`;
|
my $origin = `git remote get-url origin`;
|
||||||
chomp $origin; # Remove any trailing newlines
|
die("Unable to get github url for 'origin'?\n") unless $origin && $origin =~ m/:(.*)\.git$/;
|
||||||
die("Unable to get github url for 'origin'?\n") unless $origin;
|
my $repo = $1;
|
||||||
|
|
||||||
# Debug: Print the origin URL
|
|
||||||
print "Full Origin URL: $origin\n";
|
|
||||||
|
|
||||||
# Extract the repository path (e.g., Qortal/qortal) NOTE - github is case-sensitive with repo names
|
|
||||||
my $repo;
|
|
||||||
if ($origin =~ m/[:\/]([\w\-]+\/[\w\-]+)\.git$/) {
|
|
||||||
$repo = $1;
|
|
||||||
print "Extracted direct repository path: $repo\n";
|
|
||||||
if ($repo =~ m/^qortal\//i) {
|
|
||||||
$repo =~ s/^qortal\//Qortal\//;
|
|
||||||
print "Corrected repository path capitalization: $repo\n";
|
|
||||||
}
|
|
||||||
print "Please verify the direct repository path. Current: '$repo'\n";
|
|
||||||
print "If incorrect, input the correct direct repository path (e.g., 'Qortal/qortal' or 'bob/qortal').NOTE - github is CASE SENSITIVE for repository urls... Press Enter to keep the extracted version: ";
|
|
||||||
my $input = <STDIN>;
|
|
||||||
if ($input =~ m/^qortal\//i) {
|
|
||||||
$input =~ s/^qortal\//Qortal\//;
|
|
||||||
print "Corrected repository path capitalization: $repo\n";
|
|
||||||
}
|
|
||||||
chomp $input;
|
|
||||||
$repo = $input if $input; # Update repo if user provides input
|
|
||||||
|
|
||||||
} else {
|
|
||||||
# Default to qortal/qortal if extraction fails
|
|
||||||
$repo = "Qortal/qortal";
|
|
||||||
print "Failed to extract repository path from origin URL. Using default: $repo\n";
|
|
||||||
|
|
||||||
# Prompt the user for confirmation or input
|
|
||||||
print "Please verify the repository path. Current: '$repo'\n";
|
|
||||||
print "If incorrect, input the correct repository path (e.g., 'Qortal/qortal' or 'BobsCodeburgers/qortal'). NOTE - GitHub is CASE SENSITIVE for repository urls... Press Enter to keep the default: ";
|
|
||||||
my $input = <STDIN>;
|
|
||||||
if ($input =~ m/^qortal\//i) {
|
|
||||||
$input =~ s/^qortal\//Qortal\//;
|
|
||||||
print "Corrected repository path capitalization: $repo\n";
|
|
||||||
}
|
|
||||||
chomp $input;
|
|
||||||
$repo = $input if $input; # Update repo if user provides input
|
|
||||||
}
|
|
||||||
|
|
||||||
# Debug: Print the final repository path
|
|
||||||
print "Final direct repository path: $repo\n";
|
|
||||||
|
|
||||||
# Construct the update URL
|
|
||||||
my $update_url = "https://github.com/${repo}/raw/${update_hash}/${project}.update";
|
my $update_url = "https://github.com/${repo}/raw/${update_hash}/${project}.update";
|
||||||
print "Final update URL: $update_url\n";
|
|
||||||
|
|
||||||
|
|
||||||
my $fetch_result = `curl --silent -o /dev/null --location --range 0-1 --head --write-out '%{http_code}' --url ${update_url}`;
|
my $fetch_result = `curl --silent -o /dev/null --location --range 0-1 --head --write-out '%{http_code}' --url ${update_url}`;
|
||||||
die("\nUnable to fetch update from ${update_url}\n") if $fetch_result ne '200';
|
die("\nUnable to fetch update from ${update_url}\n") if $fetch_result ne '200';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user