mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-23 19:37:51 +00:00
WIP: PRESENCE transactions - repository support, removing older versions, tests
This commit is contained in:
parent
e0210635e3
commit
15d25649b2
@ -23,9 +23,6 @@ public class PresenceTransactionData extends TransactionData {
|
|||||||
@Schema(description = "sender's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
@Schema(description = "sender's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||||
private byte[] senderPublicKey;
|
private byte[] senderPublicKey;
|
||||||
|
|
||||||
@Schema(accessMode = AccessMode.READ_ONLY)
|
|
||||||
private String sender;
|
|
||||||
|
|
||||||
@Schema(accessMode = AccessMode.READ_ONLY)
|
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||||
private int nonce;
|
private int nonce;
|
||||||
|
|
||||||
|
@ -771,6 +771,13 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
stmt.execute("CHECKPOINT");
|
stmt.execute("CHECKPOINT");
|
||||||
break;
|
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:
|
default:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return false;
|
return false;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,8 +3,9 @@ package org.qortal.transaction;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.crypto.MemoryPoW;
|
import org.qortal.crypto.MemoryPoW;
|
||||||
import org.qortal.data.transaction.PresenceTransactionData;
|
import org.qortal.data.transaction.PresenceTransactionData;
|
||||||
@ -15,19 +16,20 @@ import org.qortal.repository.Repository;
|
|||||||
import org.qortal.transform.TransformationException;
|
import org.qortal.transform.TransformationException;
|
||||||
import org.qortal.transform.transaction.PresenceTransactionTransformer;
|
import org.qortal.transform.transaction.PresenceTransactionTransformer;
|
||||||
import org.qortal.transform.transaction.TransactionTransformer;
|
import org.qortal.transform.transaction.TransactionTransformer;
|
||||||
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
public class PresenceTransaction extends Transaction {
|
public class PresenceTransaction extends Transaction {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(PresenceTransaction.class);
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private PresenceTransactionData presenceTransactionData;
|
private PresenceTransactionData presenceTransactionData;
|
||||||
|
|
||||||
// Other useful constants
|
// 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_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||||
public static final int POW_DIFFICULTY_WITH_QORT = 8; // leading zero bits
|
public static final int POW_DIFFICULTY = 8; // leading zero bits
|
||||||
public static final int POW_DIFFICULTY_NO_QORT = 14; // leading zero bits
|
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -64,10 +66,8 @@ public class PresenceTransaction extends Transaction {
|
|||||||
// Clear nonce from transactionBytes
|
// Clear nonce from transactionBytes
|
||||||
PresenceTransactionTransformer.clearNonce(transactionBytes);
|
PresenceTransactionTransformer.clearNonce(transactionBytes);
|
||||||
|
|
||||||
int difficulty = this.getSender().getConfirmedBalance(Asset.QORT) > 0 ? POW_DIFFICULTY_WITH_QORT : POW_DIFFICULTY_NO_QORT;
|
|
||||||
|
|
||||||
// Calculate nonce
|
// 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
|
// Clear nonce from transactionBytes
|
||||||
PresenceTransactionTransformer.clearNonce(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
|
// 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
|
@Override
|
||||||
|
@ -12,6 +12,7 @@ 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.test.common.Common;
|
import org.qortal.test.common.Common;
|
||||||
|
import org.qortal.test.common.TransactionUtils;
|
||||||
import org.qortal.transaction.PresenceTransaction;
|
import org.qortal.transaction.PresenceTransaction;
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -51,19 +52,44 @@ public class PresenceTests extends Common {
|
|||||||
assertTrue(isValid(Group.NO_GROUP, this.signer, timestamp, timestampSignature));
|
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 {
|
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;
|
int nonce = 0;
|
||||||
|
|
||||||
byte[] reference = signer.getLastReference();
|
byte[] reference = signer.getLastReference();
|
||||||
byte[] creatorPublicKey = signer.getPublicKey();
|
byte[] creatorPublicKey = signer.getPublicKey();
|
||||||
long fee = 0L;
|
long fee = 0L;
|
||||||
|
|
||||||
|
if (timestampSignature == null)
|
||||||
|
timestampSignature = this.signer.sign(Longs.toByteArray(timestamp));
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, null);
|
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, null);
|
||||||
PresenceTransactionData transactionData = new PresenceTransactionData(baseTransactionData, nonce, PresenceType.REWARD_SHARE, timestampSignature);
|
PresenceTransactionData transactionData = new PresenceTransactionData(baseTransactionData, nonce, PresenceType.REWARD_SHARE, timestampSignature);
|
||||||
|
|
||||||
Transaction transaction = new PresenceTransaction(this.repository, transactionData);
|
return new PresenceTransaction(this.repository, transactionData);
|
||||||
|
|
||||||
return transaction.isValidUnconfirmed() == ValidationResult.OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user