Merge branch 'master' into q-apps

This commit is contained in:
CalDescent 2023-01-28 20:14:35 +00:00
commit ca09dd264f
14 changed files with 160 additions and 22 deletions

View File

@ -11,6 +11,7 @@ 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 java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -38,9 +39,12 @@ import org.qortal.crypto.Crypto;
import org.qortal.data.at.ATData; import org.qortal.data.at.ATData;
import org.qortal.data.crosschain.CrossChainTradeData; import org.qortal.data.crosschain.CrossChainTradeData;
import org.qortal.data.crosschain.TradeBotData; import org.qortal.data.crosschain.TradeBotData;
import org.qortal.data.transaction.MessageTransactionData;
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.Repository;
import org.qortal.repository.RepositoryManager; import org.qortal.repository.RepositoryManager;
import org.qortal.transaction.Transaction;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
import org.qortal.utils.NTP; import org.qortal.utils.NTP;
@ -223,6 +227,17 @@ public class CrossChainTradeBotResource {
if (crossChainTradeData.mode != AcctMode.OFFERING) if (crossChainTradeData.mode != AcctMode.OFFERING)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
// Check if there is a buy or a cancel request in progress for this trade
List<Transaction.TransactionType> txTypes = List.of(Transaction.TransactionType.MESSAGE);
List<TransactionData> unconfirmed = repository.getTransactionRepository().getUnconfirmedTransactions(txTypes, null, 0, 0, false);
for (TransactionData transactionData : unconfirmed) {
MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData;
if (Objects.equals(messageTransactionData.getRecipient(), atAddress)) {
// There is a pending request for this trade, so block this buy attempt to reduce the risk of refunds
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Trade has an existing buy request or is pending cancellation.");
}
}
AcctTradeBot.ResponseResult result = TradeBot.getInstance().startResponse(repository, atData, acct, crossChainTradeData, AcctTradeBot.ResponseResult result = TradeBot.getInstance().startResponse(repository, atData, acct, crossChainTradeData,
tradeBotRespondRequest.foreignKey, tradeBotRespondRequest.receivingAddress); tradeBotRespondRequest.foreignKey, tradeBotRespondRequest.receivingAddress);

View File

@ -60,6 +60,9 @@ public class ArbitraryDataReader {
private int layerCount; private int layerCount;
private byte[] latestSignature; private byte[] latestSignature;
// The resource being read
ArbitraryDataResource arbitraryDataResource = null;
public ArbitraryDataReader(String resourceId, ResourceIdType resourceIdType, Service service, String identifier) { public ArbitraryDataReader(String resourceId, ResourceIdType resourceIdType, Service service, String identifier) {
// Ensure names are always lowercase // Ensure names are always lowercase
if (resourceIdType == ResourceIdType.NAME) { if (resourceIdType == ResourceIdType.NAME) {
@ -116,6 +119,11 @@ public class ArbitraryDataReader {
return new ArbitraryDataBuildQueueItem(this.resourceId, this.resourceIdType, this.service, this.identifier); return new ArbitraryDataBuildQueueItem(this.resourceId, this.resourceIdType, this.service, this.identifier);
} }
private ArbitraryDataResource createArbitraryDataResource() {
return new ArbitraryDataResource(this.resourceId, this.resourceIdType, this.service, this.identifier);
}
/** /**
* loadAsynchronously * loadAsynchronously
* *
@ -163,6 +171,8 @@ public class ArbitraryDataReader {
return; return;
} }
this.arbitraryDataResource = this.createArbitraryDataResource();
this.preExecute(); this.preExecute();
this.deleteExistingFiles(); this.deleteExistingFiles();
this.fetch(); this.fetch();
@ -436,7 +446,7 @@ public class ArbitraryDataReader {
byte[] secret = this.secret58 != null ? Base58.decode(this.secret58) : null; byte[] secret = this.secret58 != null ? Base58.decode(this.secret58) : null;
if (secret != null && secret.length == Transformer.AES256_LENGTH) { if (secret != null && secret.length == Transformer.AES256_LENGTH) {
try { try {
LOGGER.info("Decrypting using algorithm {}...", algorithm); LOGGER.debug("Decrypting {} using algorithm {}...", this.arbitraryDataResource, algorithm);
Path unencryptedPath = Paths.get(this.workingPath.toString(), "zipped.zip"); Path unencryptedPath = Paths.get(this.workingPath.toString(), "zipped.zip");
SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, "AES"); SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, "AES");
AES.decryptFile(algorithm, aesKey, this.filePath.toString(), unencryptedPath.toString()); AES.decryptFile(algorithm, aesKey, this.filePath.toString(), unencryptedPath.toString());
@ -447,7 +457,7 @@ public class ArbitraryDataReader {
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException
| BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) { | BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) {
LOGGER.info(String.format("Exception when decrypting using algorithm %s", algorithm), e); LOGGER.info(String.format("Exception when decrypting %s using algorithm %s", this.arbitraryDataResource, algorithm), e);
throw new DataException(String.format("Unable to decrypt file at path %s using algorithm %s: %s", this.filePath, algorithm, e.getMessage())); throw new DataException(String.format("Unable to decrypt file at path %s using algorithm %s: %s", this.filePath, algorithm, e.getMessage()));
} }
} else { } else {

View File

@ -48,7 +48,6 @@ public class ArbitraryDataStorageManager extends Thread {
private List<ArbitraryTransactionData> hostedTransactions; private List<ArbitraryTransactionData> hostedTransactions;
private String searchQuery; private String searchQuery;
private List<ArbitraryTransactionData> searchResultsTransactions;
private static final long DIRECTORY_SIZE_CHECK_INTERVAL = 10 * 60 * 1000L; // 10 minutes private static final long DIRECTORY_SIZE_CHECK_INTERVAL = 10 * 60 * 1000L; // 10 minutes
@ -344,11 +343,6 @@ public class ArbitraryDataStorageManager extends Thread {
*/ */
public List<ArbitraryTransactionData> searchHostedTransactions(Repository repository, String query, Integer limit, Integer offset) { public List<ArbitraryTransactionData> searchHostedTransactions(Repository repository, String query, Integer limit, Integer offset) {
// Load from results cache if we can (results that exists for the same query), to avoid disk reads
if (this.searchResultsTransactions != null && this.searchQuery.equals(query.toLowerCase())) {
return ArbitraryTransactionUtils.limitOffsetTransactions(this.searchResultsTransactions, limit, offset);
}
// Using cache if we can, to avoid disk reads // Using cache if we can, to avoid disk reads
if (this.hostedTransactions == null) { if (this.hostedTransactions == null) {
this.hostedTransactions = this.loadAllHostedTransactions(repository); this.hostedTransactions = this.loadAllHostedTransactions(repository);
@ -376,10 +370,7 @@ public class ArbitraryDataStorageManager extends Thread {
// Sort by newest first // Sort by newest first
searchResultsList.sort(Comparator.comparingLong(ArbitraryTransactionData::getTimestamp).reversed()); searchResultsList.sort(Comparator.comparingLong(ArbitraryTransactionData::getTimestamp).reversed());
// Update cache return ArbitraryTransactionUtils.limitOffsetTransactions(searchResultsList, limit, offset);
this.searchResultsTransactions = searchResultsList;
return ArbitraryTransactionUtils.limitOffsetTransactions(this.searchResultsTransactions, limit, offset);
} }
/** /**

View File

@ -102,6 +102,21 @@ public class NamesDatabaseIntegrityCheck {
} }
} }
// Process CANCEL_SELL_NAME transactions
if (currentTransaction.getType() == TransactionType.CANCEL_SELL_NAME) {
CancelSellNameTransactionData cancelSellNameTransactionData = (CancelSellNameTransactionData) currentTransaction;
Name nameObj = new Name(repository, cancelSellNameTransactionData.getName());
if (nameObj != null && nameObj.getNameData() != null) {
nameObj.cancelSell(cancelSellNameTransactionData);
modificationCount++;
LOGGER.trace("Processed CANCEL_SELL_NAME transaction for name {}", name);
}
else {
// Something went wrong
throw new DataException(String.format("Name data not found for name %s", cancelSellNameTransactionData.getName()));
}
}
// Process BUY_NAME transactions // Process BUY_NAME transactions
if (currentTransaction.getType() == TransactionType.BUY_NAME) { if (currentTransaction.getType() == TransactionType.BUY_NAME) {
BuyNameTransactionData buyNameTransactionData = (BuyNameTransactionData) currentTransaction; BuyNameTransactionData buyNameTransactionData = (BuyNameTransactionData) currentTransaction;
@ -128,7 +143,7 @@ public class NamesDatabaseIntegrityCheck {
public int rebuildAllNames() { public int rebuildAllNames() {
int modificationCount = 0; int modificationCount = 0;
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
List<String> names = this.fetchAllNames(repository); List<String> names = this.fetchAllNames(repository); // TODO: de-duplicate, to speed up this process
for (String name : names) { for (String name : names) {
modificationCount += this.rebuildName(name, repository); modificationCount += this.rebuildName(name, repository);
} }
@ -326,6 +341,10 @@ public class NamesDatabaseIntegrityCheck {
TransactionType.BUY_NAME, Arrays.asList("name = ?"), Arrays.asList(name)); TransactionType.BUY_NAME, Arrays.asList("name = ?"), Arrays.asList(name));
signatures.addAll(buyNameTransactions); signatures.addAll(buyNameTransactions);
List<byte[]> cancelSellNameTransactions = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria(
TransactionType.CANCEL_SELL_NAME, Arrays.asList("name = ?"), Arrays.asList(name));
signatures.addAll(cancelSellNameTransactions);
List<TransactionData> transactions = new ArrayList<>(); List<TransactionData> transactions = new ArrayList<>();
for (byte[] signature : signatures) { for (byte[] signature : signatures) {
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
@ -390,6 +409,12 @@ public class NamesDatabaseIntegrityCheck {
names.add(sellNameTransactionData.getName()); names.add(sellNameTransactionData.getName());
} }
} }
if ((transactionData instanceof CancelSellNameTransactionData)) {
CancelSellNameTransactionData cancelSellNameTransactionData = (CancelSellNameTransactionData) transactionData;
if (!names.contains(cancelSellNameTransactionData.getName())) {
names.add(cancelSellNameTransactionData.getName());
}
}
} }
return names; return names;
} }

View File

@ -47,7 +47,8 @@ public class Dogecoin extends Bitcoiny {
// Servers chosen on NO BASIS WHATSOEVER from various sources! // Servers chosen on NO BASIS WHATSOEVER from various sources!
new Server("electrum1.cipig.net", ConnectionType.SSL, 20060), new Server("electrum1.cipig.net", ConnectionType.SSL, 20060),
new Server("electrum2.cipig.net", ConnectionType.SSL, 20060), new Server("electrum2.cipig.net", ConnectionType.SSL, 20060),
new Server("electrum3.cipig.net", ConnectionType.SSL, 20060)); new Server("electrum3.cipig.net", ConnectionType.SSL, 20060),
new Server("161.97.137.235", ConnectionType.SSL, 50002));
// TODO: add more mainnet servers. It's too centralized. // TODO: add more mainnet servers. It's too centralized.
} }

View File

@ -5,6 +5,7 @@ import java.math.BigDecimal;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.text.DecimalFormat;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -30,7 +31,11 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
private static final Logger LOGGER = LogManager.getLogger(ElectrumX.class); private static final Logger LOGGER = LogManager.getLogger(ElectrumX.class);
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
// See: https://electrumx.readthedocs.io/en/latest/protocol-changes.html
private static final double MIN_PROTOCOL_VERSION = 1.2; private static final double MIN_PROTOCOL_VERSION = 1.2;
private static final double MAX_PROTOCOL_VERSION = 2.0; // Higher than current latest, for hopeful future-proofing
private static final String CLIENT_NAME = "Qortal";
private static final int BLOCK_HEADER_LENGTH = 80; private static final int BLOCK_HEADER_LENGTH = 80;
// "message": "daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})" // "message": "daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})"
@ -679,6 +684,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
this.scanner = new Scanner(this.socket.getInputStream()); this.scanner = new Scanner(this.socket.getInputStream());
this.scanner.useDelimiter("\n"); this.scanner.useDelimiter("\n");
// All connections need to start with a version negotiation
this.connectedRpc("server.version");
// Check connection is suitable by asking for server features, including genesis block hash // Check connection is suitable by asking for server features, including genesis block hash
JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features"); JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features");
@ -725,6 +733,17 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
JSONArray requestParams = new JSONArray(); JSONArray requestParams = new JSONArray();
requestParams.addAll(Arrays.asList(params)); requestParams.addAll(Arrays.asList(params));
// server.version needs additional params to negotiate a version
if (method.equals("server.version")) {
requestParams.add(CLIENT_NAME);
List<String> versions = new ArrayList<>();
DecimalFormat df = new DecimalFormat("#.#");
versions.add(df.format(MIN_PROTOCOL_VERSION));
versions.add(df.format(MAX_PROTOCOL_VERSION));
requestParams.add(versions);
}
requestJson.put("params", requestParams); requestJson.put("params", requestParams);
String request = requestJson.toJSONString() + "\n"; String request = requestJson.toJSONString() + "\n";

View File

@ -54,7 +54,8 @@ public class Litecoin extends Bitcoiny {
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063), new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063), new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063),
new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022), new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022),
new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002)); new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002),
new Server("62.171.169.176", Server.ConnectionType.SSL, 50002));
} }
@Override @Override

View File

@ -3,6 +3,7 @@ package org.qortal.data.transaction;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;
import org.qortal.transaction.Transaction.TransactionType; import org.qortal.transaction.Transaction.TransactionType;
@ -19,6 +20,11 @@ public class CancelSellNameTransactionData extends TransactionData {
@Schema(description = "which name to cancel selling", example = "my-name") @Schema(description = "which name to cancel selling", example = "my-name")
private String name; private String name;
// For internal use when orphaning
@XmlTransient
@Schema(hidden = true)
private Long salePrice;
// Constructors // Constructors
// For JAXB // For JAXB
@ -30,11 +36,17 @@ public class CancelSellNameTransactionData extends TransactionData {
this.creatorPublicKey = this.ownerPublicKey; this.creatorPublicKey = this.ownerPublicKey;
} }
public CancelSellNameTransactionData(BaseTransactionData baseTransactionData, String name) { public CancelSellNameTransactionData(BaseTransactionData baseTransactionData, String name, Long salePrice) {
super(TransactionType.CANCEL_SELL_NAME, baseTransactionData); super(TransactionType.CANCEL_SELL_NAME, baseTransactionData);
this.ownerPublicKey = baseTransactionData.creatorPublicKey; this.ownerPublicKey = baseTransactionData.creatorPublicKey;
this.name = name; this.name = name;
this.salePrice = salePrice;
}
/** From network/API */
public CancelSellNameTransactionData(BaseTransactionData baseTransactionData, String name) {
this(baseTransactionData, name, null);
} }
// Getters / setters // Getters / setters
@ -47,4 +59,12 @@ public class CancelSellNameTransactionData extends TransactionData {
return this.name; return this.name;
} }
public Long getSalePrice() {
return this.salePrice;
}
public void setSalePrice(Long salePrice) {
this.salePrice = salePrice;
}
} }

View File

@ -180,8 +180,12 @@ public class Name {
} }
public void cancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException { public void cancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException {
// Mark not for-sale but leave price in case we want to orphan // Update previous sale price in transaction data
cancelSellNameTransactionData.setSalePrice(this.nameData.getSalePrice());
// Mark not for-sale
this.nameData.setIsForSale(false); this.nameData.setIsForSale(false);
this.nameData.setSalePrice(null);
// Save sale info into repository // Save sale info into repository
this.repository.getNameRepository().save(this.nameData); this.repository.getNameRepository().save(this.nameData);
@ -190,6 +194,7 @@ public class Name {
public void uncancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException { public void uncancelSell(CancelSellNameTransactionData cancelSellNameTransactionData) throws DataException {
// Mark as for-sale using existing price // Mark as for-sale using existing price
this.nameData.setIsForSale(true); this.nameData.setIsForSale(true);
this.nameData.setSalePrice(cancelSellNameTransactionData.getSalePrice());
// Save no-sale info into repository // Save no-sale info into repository
this.repository.getNameRepository().save(this.nameData); this.repository.getNameRepository().save(this.nameData);

View File

@ -339,7 +339,7 @@ public class Network {
try { try {
if (!isConnected) { if (!isConnected) {
// Add this signature to the list of pending requests for this peer // Add this signature to the list of pending requests for this peer
LOGGER.info("Making connection to peer {} to request files for signature {}...", peerAddressString, Base58.encode(signature)); LOGGER.debug("Making connection to peer {} to request files for signature {}...", peerAddressString, Base58.encode(signature));
Peer peer = new Peer(peerData); Peer peer = new Peer(peerData);
peer.setIsDataPeer(true); peer.setIsDataPeer(true);
peer.addPendingSignatureRequest(signature); peer.addPendingSignatureRequest(signature);

View File

@ -988,6 +988,11 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CREATE INDEX ChatTransactionsChatReferenceIndex ON ChatTransactions (chat_reference)"); stmt.execute("CREATE INDEX ChatTransactionsChatReferenceIndex ON ChatTransactions (chat_reference)");
break; break;
case 46:
// We need to track the sale price when canceling a name sale, so it can be put back when orphaned
stmt.execute("ALTER TABLE CancelSellNameTransactions ADD sale_price QortalAmount");
break;
default: default:
// nothing to do // nothing to do
return false; return false;

View File

@ -28,7 +28,6 @@ public class HSQLDBMessageRepository implements MessageRepository {
StringBuilder sql = new StringBuilder(1024); StringBuilder sql = new StringBuilder(1024);
sql.append("SELECT signature from MessageTransactions " sql.append("SELECT signature from MessageTransactions "
+ "JOIN Transactions USING (signature) " + "JOIN Transactions USING (signature) "
+ "JOIN BlockTransactions ON transaction_signature = signature "
+ "WHERE "); + "WHERE ");
List<String> whereClauses = new ArrayList<>(); List<String> whereClauses = new ArrayList<>();

View File

@ -17,15 +17,16 @@ public class HSQLDBCancelSellNameTransactionRepository extends HSQLDBTransaction
} }
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException { TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT name FROM CancelSellNameTransactions WHERE signature = ?"; String sql = "SELECT name, sale_price FROM CancelSellNameTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) { try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null) if (resultSet == null)
return null; return null;
String name = resultSet.getString(1); String name = resultSet.getString(1);
Long salePrice = resultSet.getLong(2);
return new CancelSellNameTransactionData(baseTransactionData, name); return new CancelSellNameTransactionData(baseTransactionData, name, salePrice);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to fetch cancel sell name transaction from repository", e); throw new DataException("Unable to fetch cancel sell name transaction from repository", e);
} }
@ -38,7 +39,7 @@ public class HSQLDBCancelSellNameTransactionRepository extends HSQLDBTransaction
HSQLDBSaver saveHelper = new HSQLDBSaver("CancelSellNameTransactions"); HSQLDBSaver saveHelper = new HSQLDBSaver("CancelSellNameTransactions");
saveHelper.bind("signature", cancelSellNameTransactionData.getSignature()).bind("owner", cancelSellNameTransactionData.getOwnerPublicKey()).bind("name", saveHelper.bind("signature", cancelSellNameTransactionData.getSignature()).bind("owner", cancelSellNameTransactionData.getOwnerPublicKey()).bind("name",
cancelSellNameTransactionData.getName()); cancelSellNameTransactionData.getName()).bind("sale_price", cancelSellNameTransactionData.getSalePrice());
try { try {
saveHelper.execute(this.repository); saveHelper.execute(this.repository);

View File

@ -165,6 +165,52 @@ public class BuySellTests extends Common {
assertEquals("price incorrect", price, nameData.getSalePrice()); assertEquals("price incorrect", price, nameData.getSalePrice());
} }
@Test
public void testCancelSellNameAndRelist() throws DataException {
// Register-name and sell-name
testSellName();
// Cancel Sell-name
CancelSellNameTransactionData transactionData = new CancelSellNameTransactionData(TestTransaction.generateBase(alice), name);
TransactionUtils.signAndMint(repository, transactionData, alice);
NameData nameData;
// Check name is no longer for sale
nameData = repository.getNameRepository().fromName(name);
assertFalse(nameData.isForSale());
assertNull(nameData.getSalePrice());
// Re-sell-name
Long newPrice = random.nextInt(1000) * Amounts.MULTIPLIER;
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), name, newPrice);
TransactionUtils.signAndMint(repository, sellNameTransactionData, alice);
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.isForSale());
assertEquals("price incorrect", newPrice, nameData.getSalePrice());
// Orphan sell-name
BlockUtils.orphanLastBlock(repository);
// Check name no longer for sale
nameData = repository.getNameRepository().fromName(name);
assertFalse(nameData.isForSale());
assertNull(nameData.getSalePrice());
// Orphan cancel-sell-name
BlockUtils.orphanLastBlock(repository);
// Check name is for sale (at original price)
nameData = repository.getNameRepository().fromName(name);
assertTrue(nameData.isForSale());
assertEquals("price incorrect", price, nameData.getSalePrice());
// Orphan sell-name and register-name
BlockUtils.orphanBlocks(repository, 2);
}
@Test @Test
public void testBuyName() throws DataException { public void testBuyName() throws DataException {
// Register-name and sell-name // Register-name and sell-name