diff --git a/src/main/java/org/qora/account/Account.java b/src/main/java/org/qora/account/Account.java index d907629e..1f760fb9 100644 --- a/src/main/java/org/qora/account/Account.java +++ b/src/main/java/org/qora/account/Account.java @@ -226,4 +226,16 @@ public class Account { LOGGER.trace(String.format("Account %s defaultGroupId now %d", accountData.getAddress(), defaultGroupId)); } + // Account flags + + public Integer getFlags() throws DataException { + return this.repository.getAccountRepository().getFlags(this.address); + } + + public void setFlags(int flags) throws DataException { + AccountData accountData = this.buildAccountData(); + accountData.setFlags(flags); + this.repository.getAccountRepository().setFlags(accountData); + } + } diff --git a/src/main/java/org/qora/data/account/AccountData.java b/src/main/java/org/qora/data/account/AccountData.java index 0ea79259..71de7c3b 100644 --- a/src/main/java/org/qora/data/account/AccountData.java +++ b/src/main/java/org/qora/data/account/AccountData.java @@ -14,6 +14,7 @@ public class AccountData { protected byte[] reference; protected byte[] publicKey; protected int defaultGroupId; + protected int flags; // Constructors @@ -21,15 +22,16 @@ public class AccountData { protected AccountData() { } - public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId) { + public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags) { this.address = address; this.reference = reference; this.publicKey = publicKey; this.defaultGroupId = defaultGroupId; + this.flags = flags; } public AccountData(String address) { - this(address, null, null, Group.NO_GROUP); + this(address, null, null, Group.NO_GROUP, 0); } // Getters/Setters @@ -62,6 +64,14 @@ public class AccountData { this.defaultGroupId = defaultGroupId; } + public int getFlags() { + return this.flags; + } + + public void setFlags(int flags) { + this.flags = flags; + } + // Comparison @Override diff --git a/src/main/java/org/qora/data/transaction/AccountFlagsTransactionData.java b/src/main/java/org/qora/data/transaction/AccountFlagsTransactionData.java new file mode 100644 index 00000000..e346d5f0 --- /dev/null +++ b/src/main/java/org/qora/data/transaction/AccountFlagsTransactionData.java @@ -0,0 +1,104 @@ +package org.qora.data.transaction; + +import java.math.BigDecimal; + +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; +import org.qora.account.GenesisAccount; +import org.qora.block.GenesisBlock; +import org.qora.transaction.Transaction.TransactionType; + +import io.swagger.v3.oas.annotations.media.Schema; + +// All properties to be converted to JSON via JAXB +@XmlAccessorType(XmlAccessType.FIELD) +@Schema(allOf = {TransactionData.class}) +//JAXB: use this subclass if XmlDiscriminatorNode matches XmlDiscriminatorValue below: +@XmlDiscriminatorValue("ACCOUNT_FLAGS") +public class AccountFlagsTransactionData extends TransactionData { + + private String target; + private int andMask; + private int orMask; + private int xorMask; + private Integer previousFlags; + + // Constructors + + // For JAXB + protected AccountFlagsTransactionData() { + super(TransactionType.ACCOUNT_FLAGS); + } + + public void afterUnmarshal(Unmarshaller u, Object parent) { + /* + * If we're being constructed as part of the genesis block info inside blockchain config + * and no specific creator's public key is supplied + * then use genesis account's public key. + */ + if (parent instanceof GenesisBlock.GenesisInfo && this.creatorPublicKey == null) + this.creatorPublicKey = GenesisAccount.PUBLIC_KEY; + } + + public AccountFlagsTransactionData(long timestamp, int groupId, byte[] reference, byte[] creatorPublicKey, String target, int andMask, int orMask, + int xorMask, Integer previousFlags, BigDecimal fee, byte[] signature) { + super(TransactionType.ACCOUNT_FLAGS, timestamp, groupId, reference, creatorPublicKey, fee, signature); + + this.target = target; + this.andMask = andMask; + this.orMask = orMask; + this.xorMask = xorMask; + this.previousFlags = previousFlags; + } + + // Typically used in deserialization context + public AccountFlagsTransactionData(long timestamp, int groupId, byte[] reference, byte[] creatorPublicKey, String target, int andMask, int orMask, + int xorMask, BigDecimal fee, byte[] signature) { + this(timestamp, groupId, reference, creatorPublicKey, target, andMask, orMask, xorMask, null, fee, signature); + } + + // Getters / setters + + public String getTarget() { + return this.target; + } + + public int getAndMask() { + return this.andMask; + } + + public int getOrMask() { + return this.orMask; + } + + public int getXorMask() { + return this.xorMask; + } + + public Integer getPreviousFlags() { + return this.previousFlags; + } + + public void setPreviousFlags(Integer previousFlags) { + this.previousFlags = previousFlags; + } + + // Re-expose to JAXB + + @Override + @XmlElement + public byte[] getCreatorPublicKey() { + return super.getCreatorPublicKey(); + } + + @Override + @XmlElement + public void setCreatorPublicKey(byte[] creatorPublicKey) { + super.setCreatorPublicKey(creatorPublicKey); + } + +} diff --git a/src/main/java/org/qora/data/transaction/TransactionData.java b/src/main/java/org/qora/data/transaction/TransactionData.java index 2c102440..aaa20e6e 100644 --- a/src/main/java/org/qora/data/transaction/TransactionData.java +++ b/src/main/java/org/qora/data/transaction/TransactionData.java @@ -37,7 +37,8 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode; GroupKickTransactionData.class, GroupInviteTransactionData.class, JoinGroupTransactionData.class, LeaveGroupTransactionData.class, GroupApprovalTransactionData.class, SetGroupTransactionData.class, - UpdateAssetTransactionData.class + UpdateAssetTransactionData.class, + AccountFlagsTransactionData.class }) //All properties to be converted to JSON via JAXB @XmlAccessorType(XmlAccessType.FIELD) diff --git a/src/main/java/org/qora/repository/AccountRepository.java b/src/main/java/org/qora/repository/AccountRepository.java index fcbceff4..50950441 100644 --- a/src/main/java/org/qora/repository/AccountRepository.java +++ b/src/main/java/org/qora/repository/AccountRepository.java @@ -18,6 +18,9 @@ public interface AccountRepository { /** Returns account's default groupID or null if account not found. */ public Integer getDefaultGroupId(String address) throws DataException; + /** Returns account's flags or null if account not found. */ + public Integer getFlags(String address) throws DataException; + /** * Ensures at least minimal account info in repository. *
@@ -39,6 +42,13 @@ public interface AccountRepository { */ public void setDefaultGroupId(AccountData accountData) throws DataException; + /** + * Saves account's flags, and public key if present, in repository. + *
+ * Note: ignores other fields like last reference, default groupID.
+ */
+ public void setFlags(AccountData accountData) throws DataException;
+
public void delete(String address) throws DataException;
// Account balances
diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java
index 5e933fb0..d7d1bf1f 100644
--- a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java
+++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java
@@ -26,15 +26,16 @@ public class HSQLDBAccountRepository implements AccountRepository {
@Override
public AccountData getAccount(String address) throws DataException {
- try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference, public_key, default_group_id FROM Accounts WHERE account = ?", address)) {
+ try (ResultSet resultSet = this.repository.checkedExecute("SELECT reference, public_key, default_group_id, flags FROM Accounts WHERE account = ?", address)) {
if (resultSet == null)
return null;
byte[] reference = resultSet.getBytes(1);
byte[] publicKey = resultSet.getBytes(2);
int defaultGroupId = resultSet.getInt(3);
+ int flags = resultSet.getInt(4);
- return new AccountData(address, reference, publicKey, defaultGroupId);
+ return new AccountData(address, reference, publicKey, defaultGroupId, flags);
} catch (SQLException e) {
throw new DataException("Unable to fetch account info from repository", e);
}
@@ -65,6 +66,19 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
+ @Override
+ public Integer getFlags(String address) throws DataException {
+ try (ResultSet resultSet = this.repository.checkedExecute("SELECT flags FROM Accounts WHERE account = ?", address)) {
+ if (resultSet == null)
+ return null;
+
+ // Column is NOT NULL so this should never implicitly convert to 0
+ return resultSet.getInt(1);
+ } catch (SQLException e) {
+ throw new DataException("Unable to fetch account's flags from repository", e);
+ }
+ }
+
@Override
public void ensureAccount(AccountData accountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
@@ -116,6 +130,23 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
+ @Override
+ public void setFlags(AccountData accountData) throws DataException {
+ HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
+
+ saveHelper.bind("account", accountData.getAddress()).bind("flags", accountData.getFlags());
+
+ byte[] publicKey = accountData.getPublicKey();
+ if (publicKey != null)
+ saveHelper.bind("public_key", publicKey);
+
+ try {
+ saveHelper.execute(this.repository);
+ } catch (SQLException e) {
+ throw new DataException("Unable to save account's flags into repository", e);
+ }
+ }
+
@Override
public void delete(String address) throws DataException {
// NOTE: Account balances are deleted automatically by the database thanks to "ON DELETE CASCADE" in AccountBalances' FOREIGN KEY
diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java
index 363efce6..536de9d0 100644
--- a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java
+++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java
@@ -690,6 +690,14 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("ALTER TABLE AssetTrades ADD initiator_saving QoraAmount NOT NULL DEFAULT 0");
break;
+ case 44:
+ // Account flags
+ stmt.execute("ALTER TABLE Accounts ADD COLUMN flags INT NOT NULL DEFAULT 0");
+ // Corresponding transaction to set/clear flags
+ stmt.execute("CREATE TABLE AccountFlagsTransactions (signature Signature, creator QoraPublicKey NOT NULL, target QoraAddress NOT NULL, and_mask INT NOT NULL, or_mask INT NOT NULL, xor_mask INT NOT NULL, "
+ + "previous_flags INT, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ break;
+
default:
// nothing to do
return false;
diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBAccountFlagsTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBAccountFlagsTransactionRepository.java
new file mode 100644
index 00000000..c70fc600
--- /dev/null
+++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBAccountFlagsTransactionRepository.java
@@ -0,0 +1,59 @@
+package org.qora.repository.hsqldb.transaction;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.qora.data.transaction.AccountFlagsTransactionData;
+import org.qora.data.transaction.TransactionData;
+import org.qora.repository.DataException;
+import org.qora.repository.hsqldb.HSQLDBRepository;
+import org.qora.repository.hsqldb.HSQLDBSaver;
+
+public class HSQLDBAccountFlagsTransactionRepository extends HSQLDBTransactionRepository {
+
+ public HSQLDBAccountFlagsTransactionRepository(HSQLDBRepository repository) {
+ this.repository = repository;
+ }
+
+ TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException {
+ try (ResultSet resultSet = this.repository
+ .checkedExecute("SELECT target, and_mask, or_mask, xor_mask, previous_flags FROM AccountFlagsTransactions WHERE signature = ?", signature)) {
+ if (resultSet == null)
+ return null;
+
+ String target = resultSet.getString(1);
+ int andMask = resultSet.getInt(2);
+ int orMask = resultSet.getInt(3);
+ int xorMask = resultSet.getInt(4);
+
+ Integer previousFlags = resultSet.getInt(5);
+ if (resultSet.wasNull())
+ previousFlags = null;
+
+ return new AccountFlagsTransactionData(timestamp, txGroupId, reference, creatorPublicKey, target, andMask, orMask, xorMask, previousFlags, fee,
+ signature);
+ } catch (SQLException e) {
+ throw new DataException("Unable to fetch account flags transaction from repository", e);
+ }
+ }
+
+ @Override
+ public void save(TransactionData transactionData) throws DataException {
+ AccountFlagsTransactionData accountFlagsTransactionData = (AccountFlagsTransactionData) transactionData;
+
+ HSQLDBSaver saveHelper = new HSQLDBSaver("AccountFlagsTransactions");
+
+ saveHelper.bind("signature", accountFlagsTransactionData.getSignature()).bind("creator", accountFlagsTransactionData.getCreatorPublicKey())
+ .bind("target", accountFlagsTransactionData.getTarget()).bind("and_mask", accountFlagsTransactionData.getAndMask())
+ .bind("or_mask", accountFlagsTransactionData.getOrMask()).bind("xor_mask", accountFlagsTransactionData.getXorMask())
+ .bind("previous_flags", accountFlagsTransactionData.getPreviousFlags());
+
+ try {
+ saveHelper.execute(this.repository);
+ } catch (SQLException e) {
+ throw new DataException("Unable to save account flags transaction into repository", e);
+ }
+ }
+
+}
diff --git a/src/main/java/org/qora/transaction/AccountFlagsTransaction.java b/src/main/java/org/qora/transaction/AccountFlagsTransaction.java
new file mode 100644
index 00000000..3ba20986
--- /dev/null
+++ b/src/main/java/org/qora/transaction/AccountFlagsTransaction.java
@@ -0,0 +1,134 @@
+package org.qora.transaction;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.qora.account.Account;
+import org.qora.account.GenesisAccount;
+import org.qora.asset.Asset;
+import org.qora.data.transaction.AccountFlagsTransactionData;
+import org.qora.data.transaction.TransactionData;
+import org.qora.repository.DataException;
+import org.qora.repository.Repository;
+
+public class AccountFlagsTransaction extends Transaction {
+
+ // Properties
+ private AccountFlagsTransactionData accountFlagsTransactionData;
+
+ // Constructors
+
+ public AccountFlagsTransaction(Repository repository, TransactionData transactionData) {
+ super(repository, transactionData);
+
+ this.accountFlagsTransactionData = (AccountFlagsTransactionData) this.transactionData;
+ }
+
+ // More information
+
+ @Override
+ public List