3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-14 11:15:49 +00:00

WIP: PRESENCE transactions - repository support, removing older versions, tests

This commit is contained in:
catbref 2020-11-26 16:11:11 +00:00
parent e0210635e3
commit 15d25649b2
5 changed files with 120 additions and 21 deletions

View File

@ -23,9 +23,6 @@ public class PresenceTransactionData extends TransactionData {
@Schema(description = "sender's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] senderPublicKey;
@Schema(accessMode = AccessMode.READ_ONLY)
private String sender;
@Schema(accessMode = AccessMode.READ_ONLY)
private int nonce;

View File

@ -771,6 +771,13 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CHECKPOINT");
break;
case 31:
// PRESENCE transactions
stmt.execute("CREATE TABLE IF NOT EXISTS PresenceTransactions ("
+ "signature Signature, nonce INT NOT NULL, presence_type INT NOT NULL, "
+ "timestamp_signature Signature NOT NULL, " + TRANSACTION_KEYS + ")");
break;
default:
// nothing to do
return false;

View File

@ -0,0 +1,57 @@
package org.qortal.repository.hsqldb.transaction;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.qortal.data.transaction.BaseTransactionData;
import org.qortal.data.transaction.PresenceTransactionData;
import org.qortal.data.transaction.PresenceTransactionData.PresenceType;
import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException;
import org.qortal.repository.hsqldb.HSQLDBRepository;
import org.qortal.repository.hsqldb.HSQLDBSaver;
public class HSQLDBPresenceTransactionRepository extends HSQLDBTransactionRepository {
public HSQLDBPresenceTransactionRepository(HSQLDBRepository repository) {
this.repository = repository;
}
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT nonce, presence_type, timestamp_signature FROM PresenceTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null)
return null;
int nonce = resultSet.getInt(1);
int presenceTypeValue = resultSet.getInt(2);
PresenceType presenceType = PresenceType.valueOf(presenceTypeValue);
byte[] timestampSignature = resultSet.getBytes(3);
return new PresenceTransactionData(baseTransactionData, nonce, presenceType, timestampSignature);
} catch (SQLException e) {
throw new DataException("Unable to fetch presence transaction from repository", e);
}
}
@Override
public void save(TransactionData transactionData) throws DataException {
PresenceTransactionData presenceTransactionData = (PresenceTransactionData) transactionData;
HSQLDBSaver saveHelper = new HSQLDBSaver("PresenceTransactions");
saveHelper.bind("signature", presenceTransactionData.getSignature())
.bind("nonce", presenceTransactionData.getNonce())
.bind("presence_type", presenceTransactionData.getPresenceType().value)
.bind("timestamp_signature", presenceTransactionData.getTimestampSignature());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save chat transaction into repository", e);
}
}
}

View File

@ -3,8 +3,9 @@ package org.qortal.transaction;
import java.util.Collections;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.Account;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.crypto.MemoryPoW;
import org.qortal.data.transaction.PresenceTransactionData;
@ -15,19 +16,20 @@ import org.qortal.repository.Repository;
import org.qortal.transform.TransformationException;
import org.qortal.transform.transaction.PresenceTransactionTransformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.Base58;
import com.google.common.primitives.Longs;
public class PresenceTransaction extends Transaction {
private static final Logger LOGGER = LogManager.getLogger(PresenceTransaction.class);
// Properties
private PresenceTransactionData presenceTransactionData;
// Other useful constants
public static final int MAX_DATA_SIZE = 256;
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
public static final int POW_DIFFICULTY_WITH_QORT = 8; // leading zero bits
public static final int POW_DIFFICULTY_NO_QORT = 14; // leading zero bits
public static final int POW_DIFFICULTY = 8; // leading zero bits
// Constructors
@ -64,10 +66,8 @@ public class PresenceTransaction extends Transaction {
// Clear nonce from transactionBytes
PresenceTransactionTransformer.clearNonce(transactionBytes);
int difficulty = this.getSender().getConfirmedBalance(Asset.QORT) > 0 ? POW_DIFFICULTY_WITH_QORT : POW_DIFFICULTY_NO_QORT;
// Calculate nonce
this.presenceTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, difficulty));
this.presenceTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY));
}
/**
@ -135,15 +135,27 @@ public class PresenceTransaction extends Transaction {
// Clear nonce from transactionBytes
PresenceTransactionTransformer.clearNonce(transactionBytes);
int difficulty;
try {
difficulty = this.getSender().getConfirmedBalance(Asset.QORT) > 0 ? POW_DIFFICULTY_WITH_QORT : POW_DIFFICULTY_NO_QORT;
} catch (DataException e) {
return false;
}
// Check nonce
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, difficulty, nonce);
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce);
}
/**
* Remove any PRESENCE transactions by the same signer that have older timestamps.
*/
@Override
protected void onImportAsUnconfirmed() throws DataException {
byte[] creatorPublicKey = this.transactionData.getCreatorPublicKey();
List<TransactionData> creatorsPresenceTransactions = this.repository.getTransactionRepository().getUnconfirmedTransactions(TransactionType.PRESENCE, creatorPublicKey);
if (creatorsPresenceTransactions.isEmpty())
return;
// List should contain oldest transaction first, so remove all but last from repository.
creatorsPresenceTransactions.remove(creatorsPresenceTransactions.size() - 1);
for (TransactionData transactionData : creatorsPresenceTransactions) {
LOGGER.info(() -> String.format("Deleting older PRESENCE transaction %s", Base58.encode(transactionData.getSignature())));
this.repository.getTransactionRepository().delete(transactionData);
}
}
@Override

View File

@ -12,6 +12,7 @@ import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.test.common.Common;
import org.qortal.test.common.TransactionUtils;
import org.qortal.transaction.PresenceTransaction;
import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.ValidationResult;
@ -51,19 +52,44 @@ public class PresenceTests extends Common {
assertTrue(isValid(Group.NO_GROUP, this.signer, timestamp, timestampSignature));
}
@Test
public void newestOnlyTests() throws DataException {
long OLDER_TIMESTAMP = System.currentTimeMillis() - 2000L;
long NEWER_TIMESTAMP = OLDER_TIMESTAMP + 1000L;
PresenceTransaction older = buildPresenceTransaction(Group.NO_GROUP, this.signer, OLDER_TIMESTAMP, null);
older.computeNonce();
TransactionUtils.signAndImportValid(repository, older.getTransactionData(), this.signer);
assertTrue(this.repository.getTransactionRepository().exists(older.getTransactionData().getSignature()));
PresenceTransaction newer = buildPresenceTransaction(Group.NO_GROUP, this.signer, NEWER_TIMESTAMP, null);
newer.computeNonce();
TransactionUtils.signAndImportValid(repository, newer.getTransactionData(), this.signer);
assertTrue(this.repository.getTransactionRepository().exists(newer.getTransactionData().getSignature()));
assertFalse(this.repository.getTransactionRepository().exists(older.getTransactionData().getSignature()));
}
private boolean isValid(int txGroupId, PrivateKeyAccount signer, long timestamp, byte[] timestampSignature) throws DataException {
Transaction transaction = buildPresenceTransaction(txGroupId, signer, timestamp, timestampSignature);
return transaction.isValidUnconfirmed() == ValidationResult.OK;
}
private PresenceTransaction buildPresenceTransaction(int txGroupId, PrivateKeyAccount signer, long timestamp, byte[] timestampSignature) throws DataException {
int nonce = 0;
byte[] reference = signer.getLastReference();
byte[] creatorPublicKey = signer.getPublicKey();
long fee = 0L;
if (timestampSignature == null)
timestampSignature = this.signer.sign(Longs.toByteArray(timestamp));
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, null);
PresenceTransactionData transactionData = new PresenceTransactionData(baseTransactionData, nonce, PresenceType.REWARD_SHARE, timestampSignature);
Transaction transaction = new PresenceTransaction(this.repository, transactionData);
return transaction.isValidUnconfirmed() == ValidationResult.OK;
return new PresenceTransaction(this.repository, transactionData);
}
}