More work on block rewards in relation to account level/legacy qora held.

Rename Asset.QORA to Asset.QORT so we can also have Asset.LEGACY_QORA
as another hard-coded asset.

Add "is unspendable" aspect to assets where only the asset owner can
transfer/pay asset to other people. Asset trading is barred regardless,
as is use of asset for ATs.

Added "initial level" to account data in preparation for accounts levelling
up from generating blocks.

Added distribution/removal of block reward based on legacy-QORA held.

Removed "previous level" from ACCOUNT_LEVEL transactions as they're
only ever valid in genesis block and so previous level is never needed.
This commit is contained in:
catbref
2019-10-08 14:58:00 +01:00
parent a1e83109a8
commit 54d49e0f1d
75 changed files with 340 additions and 213 deletions

View File

@@ -233,4 +233,11 @@ public class Account {
this.repository.getAccountRepository().setLevel(accountData);
}
public void setInitialLevel(int level) throws DataException {
AccountData accountData = this.buildAccountData();
accountData.setLevel(level);
accountData.setInitialLevel(level);
this.repository.getAccountRepository().setInitialLevel(accountData);
}
}

View File

@@ -207,7 +207,7 @@ public class AddressesResource {
try (final Repository repository = RepositoryManager.getRepository()) {
Account account = new Account(repository, address);
return account.getConfirmedBalance(Asset.QORA);
return account.getConfirmedBalance(Asset.QORT);
} catch (ApiException e) {
throw e;
} catch (DataException e) {

View File

@@ -318,14 +318,14 @@ public class AssetsResource {
) @QueryParam("reverse") Boolean reverse) {
try (final Repository repository = RepositoryManager.getRepository()) {
if (assetIds.isEmpty())
assetIds = Collections.singletonList(Asset.QORA);
assetIds = Collections.singletonList(Asset.QORT);
else
for (long assetId : assetIds)
if (!repository.getAssetRepository().assetExists(assetId))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
if (otherAssetIds.isEmpty())
otherAssetIds = Collections.singletonList(Asset.QORA);
otherAssetIds = Collections.singletonList(Asset.QORT);
else
for (long assetId : otherAssetIds)
if (!repository.getAssetRepository().assetExists(assetId))

View File

@@ -10,9 +10,12 @@ import org.qora.repository.Repository;
public class Asset {
/**
* QORA coins are just another asset but with fixed assetId of zero.
* QORT coins are just another asset but with fixed assetId of zero.
*/
public static final long QORA = 0L;
public static final long QORT = 0L;
/** Hard-coded asset representing legacy QORA held in old QORA1 blockchain. */
public static final long LEGACY_QORA = 1L;
// Other useful constants
@@ -41,6 +44,7 @@ public class Asset {
this.assetData = new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(),
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(),
issueAssetTransactionData.getIsDivisible(), issueAssetTransactionData.getData(),
issueAssetTransactionData.getIsUnspendable(),
issueAssetTransactionData.getTxGroupId(), issueAssetTransactionData.getSignature());
}

View File

@@ -97,7 +97,7 @@ public class AT {
boolean isFrozen = false;
Long frozenBalance = null;
this.atData = new ATData(atAddress, creatorPublicKey, creation, version, Asset.QORA, codeBytes, isSleeping, sleepUntilHeight, isFinished,
this.atData = new ATData(atAddress, creatorPublicKey, creation, version, Asset.QORT, codeBytes, isSleeping, sleepUntilHeight, isFinished,
hadFatalError, isFrozen, frozenBalance);
this.atStateData = new ATStateData(atAddress, height, creation, null, null, BigDecimal.ZERO.setScale(8));

View File

@@ -254,7 +254,7 @@ public class QoraATAPI extends API {
try {
Account atAccount = this.getATAccount();
return atAccount.getConfirmedBalance(Asset.QORA).unscaledValue().longValue();
return atAccount.getConfirmedBalance(Asset.QORT).unscaledValue().longValue();
} catch (DataException e) {
throw new RuntimeException("AT API unable to fetch AT's current balance?", e);
}

View File

@@ -26,6 +26,7 @@ import org.qora.block.BlockChain.BlockTimingByHeight;
import org.qora.block.BlockChain.ShareByLevel;
import org.qora.controller.Controller;
import org.qora.crypto.Crypto;
import org.qora.data.account.AccountBalanceData;
import org.qora.data.account.ProxyForgerData;
import org.qora.data.at.ATData;
import org.qora.data.at.ATStateData;
@@ -1193,7 +1194,7 @@ public class Block {
Account atAccount = new Account(this.repository, atState.getATAddress());
// Subtract AT-generated fees from AT accounts
atAccount.setConfirmedBalance(Asset.QORA, atAccount.getConfirmedBalance(Asset.QORA).subtract(atState.getFees()));
atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).subtract(atState.getFees()));
atRepository.save(atState);
}
@@ -1341,7 +1342,7 @@ public class Block {
Account atAccount = new Account(this.repository, atState.getATAddress());
// Return AT-generated fees to AT accounts
atAccount.setConfirmedBalance(Asset.QORA, atAccount.getConfirmedBalance(Asset.QORA).add(atState.getFees()));
atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).add(atState.getFees()));
}
// Delete ATStateData for this height
@@ -1359,6 +1360,7 @@ public class Block {
final boolean isFounder;
final int level;
final int shareBin;
final BigDecimal qoraAmount;
final Account recipientAccount;
AccountInfo(Repository repository, int accountIndex, List<ShareByLevel> sharesByLevel) throws DataException {
@@ -1367,6 +1369,12 @@ public class Block {
this.forgerAccount = new PublicKeyAccount(repository, this.proxyForgerData.getForgerPublicKey());
this.recipientAccount = new Account(repository, this.proxyForgerData.getRecipient());
AccountBalanceData qoraBalanceData = repository.getAccountRepository().getBalance(this.forgerAccount.getAddress(), Asset.LEGACY_QORA);
if (qoraBalanceData != null && qoraBalanceData.getBalance() != null && qoraBalanceData.getBalance().compareTo(BigDecimal.ZERO) > 0)
this.qoraAmount = qoraBalanceData.getBalance();
else
this.qoraAmount = null;
if (this.forgerAccount.isFounder()) {
this.isFounder = true;
this.level = 0;
@@ -1395,17 +1403,17 @@ public class Block {
if (forgerAccount.getAddress().equals(recipientAccount.getAddress())) {
// forger & recipient the same - simpler case
LOGGER.trace(() -> String.format("Forger/recipient account %s share: %s", forgerAccount.getAddress(), accountAmount.toPlainString()));
forgerAccount.setConfirmedBalance(Asset.QORA, forgerAccount.getConfirmedBalance(Asset.QORA).add(accountAmount));
forgerAccount.setConfirmedBalance(Asset.QORT, forgerAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
} else {
// forger & recipient different - extra work needed
BigDecimal recipientAmount = accountAmount.multiply(this.proxyForgerData.getShare()).divide(ONE_HUNDRED, RoundingMode.DOWN);
BigDecimal forgerAmount = accountAmount.subtract(recipientAmount);
LOGGER.trace(() -> String.format("Forger account %s share: %s", forgerAccount.getAddress(), forgerAmount.toPlainString()));
forgerAccount.setConfirmedBalance(Asset.QORA, forgerAccount.getConfirmedBalance(Asset.QORA).add(forgerAmount));
forgerAccount.setConfirmedBalance(Asset.QORT, forgerAccount.getConfirmedBalance(Asset.QORT).add(forgerAmount));
LOGGER.trace(() -> String.format("Recipient account %s share: %s", recipientAccount.getAddress(), recipientAmount.toPlainString()));
recipientAccount.setConfirmedBalance(Asset.QORA, recipientAccount.getConfirmedBalance(Asset.QORA).add(recipientAmount));
recipientAccount.setConfirmedBalance(Asset.QORT, recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount));
}
}
}
@@ -1445,6 +1453,34 @@ public class Block {
}
}
// Distribute share across legacy QORA holders
BigDecimal qoraHoldersAmount = BlockChain.getInstance().getQoraHoldersShare().multiply(totalAmount).setScale(8, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", totalAmount.toPlainString(), qoraHoldersAmount.toPlainString()));
List<AccountInfo> qoraHolderAccounts = new ArrayList<>();
BigDecimal totalQoraHeld = BigDecimal.ZERO;
for (int i = 0; i < expandedAccounts.size(); ++i) {
AccountInfo accountInfo = expandedAccounts.get(i);
if (accountInfo.qoraAmount == null)
continue;
qoraHolderAccounts.add(accountInfo);
totalQoraHeld = totalQoraHeld.add(accountInfo.qoraAmount);
}
final BigDecimal finalTotalQoraHeld = totalQoraHeld;
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString()));
for (int h = 0; h < qoraHolderAccounts.size(); ++h) {
AccountInfo accountInfo = qoraHolderAccounts.get(h);
final BigDecimal holderAmount = qoraHoldersAmount.multiply(totalQoraHeld).divide(accountInfo.qoraAmount, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Forger account %s has %s / %s QORA so share: %s",
accountInfo.forgerAccount.getAddress(), accountInfo.qoraAmount, finalTotalQoraHeld, holderAmount.toPlainString()));
accountInfo.distribute(holderAmount);
sharedAmount = sharedAmount.add(holderAmount);
}
// Spread remainder across founder accounts
BigDecimal foundersAmount = totalAmount.subtract(sharedAmount);
LOGGER.debug(String.format("Shared %s of %s, remaining %s to founders", sharedAmount.toPlainString(), totalAmount.toPlainString(), foundersAmount.toPlainString()));

View File

@@ -106,6 +106,9 @@ public class BlockChain {
}
List<ShareByLevel> sharesByLevel;
/** Share of block reward/fees to legacy QORA coin holders */
BigDecimal qoraHoldersShare;
/** Block times by block height */
public static class BlockTimingByHeight {
public int height;
@@ -284,6 +287,10 @@ public class BlockChain {
return this.sharesByLevel;
}
public BigDecimal getQoraHoldersShare() {
return this.qoraHoldersShare;
}
public List<ForgingTier> getForgingTiers() {
return this.forgingTiers;
}
@@ -368,6 +375,9 @@ public class BlockChain {
if (this.sharesByLevel == null)
Settings.throwValidationError("No \"sharesByLevel\" entry found in blockchain config");
if (this.qoraHoldersShare == null)
Settings.throwValidationError("No \"qoraHoldersShare\" entry found in blockchain config");
if (this.blockTimingsByHeight == null)
Settings.throwValidationError("No \"blockTimingsByHeight\" entry found in blockchain config");

View File

@@ -118,7 +118,7 @@ public class GenesisBlock extends Block {
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
return new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(), issueAssetTransactionData.getDescription(),
issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(), "", Group.NO_GROUP, issueAssetTransactionData.getReference());
issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(), "", false, Group.NO_GROUP, issueAssetTransactionData.getReference());
}).collect(Collectors.toList());
}

View File

@@ -16,6 +16,7 @@ public class AccountData {
protected int defaultGroupId;
protected int flags;
protected String forgingEnabler;
protected int initialLevel;
protected int level;
// Constructors
@@ -24,18 +25,19 @@ public class AccountData {
protected AccountData() {
}
public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, String forgingEnabler, int level) {
public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, String forgingEnabler, int initialLevel, int level) {
this.address = address;
this.reference = reference;
this.publicKey = publicKey;
this.defaultGroupId = defaultGroupId;
this.flags = flags;
this.forgingEnabler = forgingEnabler;
this.initialLevel = initialLevel;
this.level = level;
}
public AccountData(String address) {
this(address, null, null, Group.NO_GROUP, 0, null, 0);
this(address, null, null, Group.NO_GROUP, 0, null, 0, 0);
}
// Getters/Setters
@@ -84,6 +86,14 @@ public class AccountData {
this.forgingEnabler = forgingEnabler;
}
public int getInitialLevel() {
return this.initialLevel;
}
public void setInitialLevel(int level) {
this.initialLevel = level;
}
public int getLevel() {
return this.level;
}

View File

@@ -18,6 +18,7 @@ public class AssetData {
private long quantity;
private boolean isDivisible;
private String data;
private boolean isUnspendable;
private int creationGroupId;
// No need to expose this via API
@XmlTransient
@@ -31,7 +32,7 @@ public class AssetData {
}
// NOTE: key is Long, not long, because it can be null if asset ID/key not yet assigned.
public AssetData(Long assetId, String owner, String name, String description, long quantity, boolean isDivisible, String data, int creationGroupId, byte[] reference) {
public AssetData(Long assetId, String owner, String name, String description, long quantity, boolean isDivisible, String data, boolean isUnspendable, int creationGroupId, byte[] reference) {
this.assetId = assetId;
this.owner = owner;
this.name = name;
@@ -39,13 +40,14 @@ public class AssetData {
this.quantity = quantity;
this.isDivisible = isDivisible;
this.data = data;
this.isUnspendable = isUnspendable;
this.creationGroupId = creationGroupId;
this.reference = reference;
}
// New asset with unassigned assetId
public AssetData(String owner, String name, String description, long quantity, boolean isDivisible, String data, int creationGroupId, byte[] reference) {
this(null, owner, name, description, quantity, isDivisible, data, creationGroupId, reference);
public AssetData(String owner, String name, String description, long quantity, boolean isDivisible, String data, boolean isUnspendable, int creationGroupId, byte[] reference) {
this(null, owner, name, description, quantity, isDivisible, data, isUnspendable, creationGroupId, reference);
}
// Getters/Setters
@@ -94,6 +96,10 @@ public class AssetData {
this.data = data;
}
public boolean getIsUnspendable() {
return this.isUnspendable;
}
public int getCreationGroupId() {
return this.creationGroupId;
}

View File

@@ -47,7 +47,7 @@ public class GenesisTransactionData extends TransactionData {
/** From repository (V1, where asset locked to QORA) */
public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount) {
this(baseTransactionData, recipient, amount, Asset.QORA);
this(baseTransactionData, recipient, amount, Asset.QORT);
}
// Getters/Setters

View File

@@ -37,6 +37,8 @@ public class IssueAssetTransactionData extends TransactionData {
private boolean isDivisible;
@Schema(description = "non-human-readable asset-related data, typically JSON", example = "{\"logo\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgA==\"}")
private String data;
@Schema(description = "whether non-owner holders of asset are barred from using asset", example = "false")
private boolean isUnspendable;
// Constructors
@@ -59,7 +61,7 @@ public class IssueAssetTransactionData extends TransactionData {
/** From repository */
public IssueAssetTransactionData(BaseTransactionData baseTransactionData,
Long assetId, String owner, String assetName, String description, long quantity, boolean isDivisible, String data) {
Long assetId, String owner, String assetName, String description, long quantity, boolean isDivisible, String data, boolean isUnspendable) {
super(TransactionType.ISSUE_ASSET, baseTransactionData);
this.assetId = assetId;
@@ -70,11 +72,13 @@ public class IssueAssetTransactionData extends TransactionData {
this.quantity = quantity;
this.isDivisible = isDivisible;
this.data = data;
this.isUnspendable = isUnspendable;
}
/** From network/API */
public IssueAssetTransactionData(BaseTransactionData baseTransactionData, String owner, String assetName, String description, long quantity, boolean isDivisible, String data) {
this(baseTransactionData, null, owner, assetName, description, quantity, isDivisible, data);
public IssueAssetTransactionData(BaseTransactionData baseTransactionData, String owner, String assetName, String description,
long quantity, boolean isDivisible, String data, boolean isUnspendable) {
this(baseTransactionData, null, owner, assetName, description, quantity, isDivisible, data, isUnspendable);
}
// Getters/Setters
@@ -119,4 +123,8 @@ public class IssueAssetTransactionData extends TransactionData {
return this.data;
}
public boolean getIsUnspendable() {
return this.isUnspendable;
}
}

View File

@@ -48,7 +48,7 @@ public class MessageTransactionData extends TransactionData {
if (assetId != null)
this.assetId = assetId;
else
this.assetId = Asset.QORA;
this.assetId = Asset.QORT;
this.amount = amount;
this.data = data;

View File

@@ -158,13 +158,13 @@ public class Name {
// Update seller's balance
Account seller = new Account(this.repository, this.nameData.getOwner());
seller.setConfirmedBalance(Asset.QORA, seller.getConfirmedBalance(Asset.QORA).add(buyNameTransactionData.getAmount()));
seller.setConfirmedBalance(Asset.QORT, seller.getConfirmedBalance(Asset.QORT).add(buyNameTransactionData.getAmount()));
// Set new owner
Account buyer = new PublicKeyAccount(this.repository, buyNameTransactionData.getBuyerPublicKey());
this.nameData.setOwner(buyer.getAddress());
// Update buyer's balance
buyer.setConfirmedBalance(Asset.QORA, buyer.getConfirmedBalance(Asset.QORA).subtract(buyNameTransactionData.getAmount()));
buyer.setConfirmedBalance(Asset.QORT, buyer.getConfirmedBalance(Asset.QORT).subtract(buyNameTransactionData.getAmount()));
// Update reference in transaction data
buyNameTransactionData.setNameReference(this.nameData.getReference());
@@ -189,14 +189,14 @@ public class Name {
// Revert buyer's balance
Account buyer = new PublicKeyAccount(this.repository, buyNameTransactionData.getBuyerPublicKey());
buyer.setConfirmedBalance(Asset.QORA, buyer.getConfirmedBalance(Asset.QORA).add(buyNameTransactionData.getAmount()));
buyer.setConfirmedBalance(Asset.QORT, buyer.getConfirmedBalance(Asset.QORT).add(buyNameTransactionData.getAmount()));
// Previous Name's owner and/or data taken from referenced transaction
this.revert();
// Revert seller's balance
Account seller = new Account(this.repository, this.nameData.getOwner());
seller.setConfirmedBalance(Asset.QORA, seller.getConfirmedBalance(Asset.QORA).subtract(buyNameTransactionData.getAmount()));
seller.setConfirmedBalance(Asset.QORT, seller.getConfirmedBalance(Asset.QORT).subtract(buyNameTransactionData.getAmount()));
// Save reverted name data
this.repository.getNameRepository().save(this.nameData);

View File

@@ -47,7 +47,10 @@ public class Payment {
// Total up payment amounts by assetId
Map<Long, BigDecimal> amountsByAssetId = new HashMap<Long, BigDecimal>();
// Add transaction fee to start with
amountsByAssetId.put(Asset.QORA, fee);
amountsByAssetId.put(Asset.QORT, fee);
// Grab sender info
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Check payments, and calculate amount total by assetId
for (PaymentData paymentData : payments) {
@@ -82,6 +85,10 @@ public class Payment {
if (assetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
// Do not allow non-owner asset holders to use asset
if (assetData.getIsUnspendable() && !assetData.getOwner().equals(sender.getAddress()))
return ValidationResult.ASSET_NOT_SPENDABLE;
// If we're sending to an AT then assetId must match AT's assetId
if (atData != null && atData.getAssetId() != paymentData.getAssetId())
return ValidationResult.ASSET_DOES_NOT_MATCH_AT;
@@ -95,7 +102,6 @@ public class Payment {
}
// Check sender has enough of each asset
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
for (Entry<Long, BigDecimal> pair : amountsByAssetId.entrySet())
if (sender.getConfirmedBalance(pair.getKey()).compareTo(pair.getValue()) < 0)
return ValidationResult.NO_BALANCE;
@@ -177,7 +183,7 @@ public class Payment {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).subtract(fee));
sender.setConfirmedBalance(Asset.QORT, sender.getConfirmedBalance(Asset.QORT).subtract(fee));
// Update sender's reference
sender.setLastReference(signature);
@@ -189,7 +195,7 @@ public class Payment {
long assetId = paymentData.getAssetId();
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
if ((alwaysInitializeRecipientReference || assetId == Asset.QORA) && recipient.getLastReference() == null)
if ((alwaysInitializeRecipientReference || assetId == Asset.QORT) && recipient.getLastReference() == null)
recipient.setLastReference(signature);
}
}
@@ -230,7 +236,7 @@ public class Payment {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).add(fee));
sender.setConfirmedBalance(Asset.QORT, sender.getConfirmedBalance(Asset.QORT).add(fee));
// Update sender's reference
sender.setLastReference(reference);
@@ -244,7 +250,7 @@ public class Payment {
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
* (which would have changed their last reference) thus this is their first reference so remove it.
*/
if ((alwaysUninitializeRecipientReference || assetId == Asset.QORA) && Arrays.equals(recipient.getLastReference(), signature))
if ((alwaysUninitializeRecipientReference || assetId == Asset.QORT) && Arrays.equals(recipient.getLastReference(), signature))
recipient.setLastReference(null);
}
}

View File

@@ -67,6 +67,13 @@ public interface AccountRepository {
*/
public void setLevel(AccountData accountData) throws DataException;
/**
* Saves account's initial & current level, and public key if present, in repository.
* <p>
* Note: ignores other fields like last reference, default groupID.
*/
public void setInitialLevel(AccountData accountData) throws DataException;
/**
* Saves account's forging enabler, and public key if present, in repository.
* <p>

View File

@@ -26,7 +26,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
@Override
public AccountData getAccount(String address) throws DataException {
String sql = "SELECT reference, public_key, default_group_id, flags, forging_enabler, level FROM Accounts WHERE account = ?";
String sql = "SELECT reference, public_key, default_group_id, flags, forging_enabler, initial_level, level FROM Accounts WHERE account = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) {
if (resultSet == null)
@@ -37,9 +37,10 @@ public class HSQLDBAccountRepository implements AccountRepository {
int defaultGroupId = resultSet.getInt(3);
int flags = resultSet.getInt(4);
String forgingEnabler = resultSet.getString(5);
int level = resultSet.getInt(6);
int initialLevel = resultSet.getInt(6);
int level = resultSet.getInt(7);
return new AccountData(address, reference, publicKey, defaultGroupId, flags, forgingEnabler, level);
return new AccountData(address, reference, publicKey, defaultGroupId, flags, forgingEnabler, initialLevel, level);
} catch (SQLException e) {
throw new DataException("Unable to fetch account info from repository", e);
}
@@ -216,6 +217,24 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
}
@Override
public void setInitialLevel(AccountData accountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
saveHelper.bind("account", accountData.getAddress()).bind("level", accountData.getLevel())
.bind("initial_level", accountData.getInitialLevel());
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 initial level into repository", e);
}
}
@Override
public void setForgingEnabler(AccountData accountData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");

View File

@@ -28,7 +28,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override
public AssetData fromAssetId(long assetId) throws DataException {
String sql = "SELECT owner, asset_name, description, quantity, is_divisible, data, creation_group_id, reference FROM Assets WHERE asset_id = ?";
String sql = "SELECT owner, asset_name, description, quantity, is_divisible, data, is_unspendable, creation_group_id, reference FROM Assets WHERE asset_id = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetId)) {
if (resultSet == null)
@@ -40,11 +40,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
long quantity = resultSet.getLong(4);
boolean isDivisible = resultSet.getBoolean(5);
String data = resultSet.getString(6);
int creationGroupId = resultSet.getInt(7);
byte[] reference = resultSet.getBytes(8);
boolean isUnspendable = resultSet.getBoolean(7);
int creationGroupId = resultSet.getInt(8);
byte[] reference = resultSet.getBytes(9);
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, creationGroupId,
reference);
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, isUnspendable,
creationGroupId, reference);
} catch (SQLException e) {
throw new DataException("Unable to fetch asset from repository", e);
}
@@ -52,7 +53,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override
public AssetData fromAssetName(String assetName) throws DataException {
String sql = "SELECT owner, asset_id, description, quantity, is_divisible, data, creation_group_id, reference FROM Assets WHERE asset_name = ?";
String sql = "SELECT owner, asset_id, description, quantity, is_divisible, data, is_unspendable, creation_group_id, reference FROM Assets WHERE asset_name = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetName)) {
if (resultSet == null)
@@ -64,11 +65,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
long quantity = resultSet.getLong(4);
boolean isDivisible = resultSet.getBoolean(5);
String data = resultSet.getString(6);
int creationGroupId = resultSet.getInt(7);
byte[] reference = resultSet.getBytes(8);
boolean isUnspendable = resultSet.getBoolean(7);
int creationGroupId = resultSet.getInt(8);
byte[] reference = resultSet.getBytes(9);
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, creationGroupId,
reference);
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, isUnspendable,
creationGroupId, reference);
} catch (SQLException e) {
throw new DataException("Unable to fetch asset from repository", e);
}
@@ -95,7 +97,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override
public List<AssetData> getAllAssets(Integer limit, Integer offset, Boolean reverse) throws DataException {
StringBuilder sql = new StringBuilder(256);
sql.append("SELECT asset_id, owner, asset_name, description, quantity, is_divisible, data, creation_group_id, reference FROM Assets ORDER BY asset_id");
sql.append("SELECT asset_id, owner, asset_name, description, quantity, is_divisible, data, is_unspendable, creation_group_id, reference "
+ "FROM Assets ORDER BY asset_id");
if (reverse != null && reverse)
sql.append(" DESC");
@@ -115,11 +118,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
long quantity = resultSet.getLong(5);
boolean isDivisible = resultSet.getBoolean(6);
String data = resultSet.getString(7);
boolean isUnspendable = resultSet.getBoolean(7);
int creationGroupId = resultSet.getInt(8);
byte[] reference = resultSet.getBytes(9);
assets.add(new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data,
creationGroupId, reference));
isUnspendable,creationGroupId, reference));
} while (resultSet.next());
return assets;
@@ -159,8 +163,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
saveHelper.bind("asset_id", assetData.getAssetId()).bind("owner", assetData.getOwner())
.bind("asset_name", assetData.getName()).bind("description", assetData.getDescription())
.bind("quantity", assetData.getQuantity()).bind("is_divisible", assetData.getIsDivisible())
.bind("data", assetData.getData()).bind("creation_group_id", assetData.getCreationGroupId())
.bind("reference", assetData.getReference());
.bind("data", assetData.getData()).bind("is_unspendable", assetData.getIsUnspendable())
.bind("creation_group_id", assetData.getCreationGroupId()).bind("reference", assetData.getReference());
try {
saveHelper.execute(this.repository);

View File

@@ -778,10 +778,11 @@ public class HSQLDBDatabaseUpdates {
case 54:
// Account 'level'
stmt.execute("ALTER TABLE Accounts ADD COLUMN initial_level TINYINT NOT NULL DEFAULT 0");
stmt.execute("ALTER TABLE Accounts ADD COLUMN level TINYINT NOT NULL DEFAULT 0");
// Corresponding transaction to set level
stmt.execute("CREATE TABLE AccountLevelTransactions (signature Signature, creator QoraPublicKey NOT NULL, target QoraAddress NOT NULL, level INT NOT NULL, "
+ "previous_level INT, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;
case 55:
@@ -792,6 +793,12 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("ALTER TABLE Blocks ADD COLUMN online_accounts_signatures BLOB");
break;
case 56:
// Modify assets to support "unspendable" flag so we can implement the representative legacy QORA asset.
stmt.execute("ALTER TABLE Assets ADD COLUMN is_unspendable BOOLEAN NOT NULL DEFAULT FALSE BEFORE creation_group_id");
stmt.execute("ALTER TABLE IssueAssetTransactions ADD COLUMN is_unspendable BOOLEAN NOT NULL DEFAULT FALSE BEFORE asset_id");
break;
default:
// nothing to do
return false;

View File

@@ -17,7 +17,7 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
}
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT owner, asset_name, description, quantity, is_divisible, data, asset_id FROM IssueAssetTransactions WHERE signature = ?";
String sql = "SELECT owner, asset_name, description, quantity, is_divisible, data, is_unspendable, asset_id FROM IssueAssetTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null)
@@ -29,14 +29,15 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
long quantity = resultSet.getLong(4);
boolean isDivisible = resultSet.getBoolean(5);
String data = resultSet.getString(6);
boolean isUnspendable = resultSet.getBoolean(7);
// Special null-checking for asset ID
Long assetId = resultSet.getLong(7);
Long assetId = resultSet.getLong(8);
if (assetId == 0 && resultSet.wasNull())
assetId = null;
return new IssueAssetTransactionData(baseTransactionData, assetId, owner, assetName, description, quantity, isDivisible,
data);
data, isUnspendable);
} catch (SQLException e) {
throw new DataException("Unable to fetch issue asset transaction from repository", e);
}
@@ -51,8 +52,8 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey())
.bind("owner", issueAssetTransactionData.getOwner()).bind("asset_name", issueAssetTransactionData.getAssetName())
.bind("description", issueAssetTransactionData.getDescription()).bind("quantity", issueAssetTransactionData.getQuantity())
.bind("is_divisible", issueAssetTransactionData.getIsDivisible())
.bind("data", issueAssetTransactionData.getData()).bind("asset_id", issueAssetTransactionData.getAssetId());
.bind("is_divisible", issueAssetTransactionData.getIsDivisible()).bind("data", issueAssetTransactionData.getData())
.bind("is_unspendable", issueAssetTransactionData.getIsUnspendable()).bind("asset_id", issueAssetTransactionData.getAssetId());
try {
saveHelper.execute(this.repository);

View File

@@ -77,7 +77,7 @@ public class AccountFlagsTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(accountFlagsTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(accountFlagsTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -77,7 +77,7 @@ public class AccountLevelTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(accountLevelTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(accountLevelTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@@ -86,15 +86,12 @@ public class AccountLevelTransaction extends Transaction {
@Override
public void process() throws DataException {
Account target = getTarget();
Integer previousLevel = target.getLevel();
accountLevelTransactionData.setPreviousLevel(previousLevel);
// Save this transaction with target account's previous level value
// Save this transaction
this.repository.getTransactionRepository().save(accountLevelTransactionData);
// Set account's new level
target.setLevel(this.accountLevelTransactionData.getLevel());
// Set account's initial level
target.setInitialLevel(this.accountLevelTransactionData.getLevel());
}
@Override
@@ -102,17 +99,8 @@ public class AccountLevelTransaction extends Transaction {
// Revert
Account target = getTarget();
Integer previousLevel = accountLevelTransactionData.getPreviousLevel();
// If previousLevel are null then account didn't exist before this transaction
if (previousLevel == null)
this.repository.getAccountRepository().delete(target.getAddress());
else
target.setLevel(previousLevel);
// Remove previous level from transaction itself
accountLevelTransactionData.setPreviousLevel(null);
this.repository.getTransactionRepository().save(accountLevelTransactionData);
// This is only ever a genesis block transaction so simply delete account
this.repository.getAccountRepository().delete(target.getAddress());
}
}

View File

@@ -102,7 +102,7 @@ public class AddGroupAdminTransaction extends Transaction {
return ValidationResult.ALREADY_GROUP_ADMIN;
// Check group owner has enough funds
if (owner.getConfirmedBalance(Asset.QORA).compareTo(addGroupAdminTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT).compareTo(addGroupAdminTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -71,7 +71,7 @@ public class ArbitraryTransaction extends Transaction {
if (arbitraryTransactionData.getVersion() != 1)
for (PaymentData paymentData : arbitraryTransactionData.getPayments())
// We're only interested in QORA
if (paymentData.getAssetId() == Asset.QORA) {
if (paymentData.getAssetId() == Asset.QORT) {
if (address.equals(paymentData.getRecipient()))
amount = amount.add(paymentData.getAmount());
else if (address.equals(senderAddress))

View File

@@ -84,12 +84,12 @@ public class AtTransaction extends Transaction {
if (address.equals(atAddress)) {
amount = amount.subtract(this.atTransactionData.getFee());
if (this.atTransactionData.getAmount() != null && this.atTransactionData.getAssetId() == Asset.QORA)
if (this.atTransactionData.getAmount() != null && this.atTransactionData.getAssetId() == Asset.QORT)
amount = amount.subtract(this.atTransactionData.getAmount());
}
if (address.equals(this.atTransactionData.getRecipient()) && this.atTransactionData.getAmount() != null
&& this.atTransactionData.getAssetId() == Asset.QORA)
&& this.atTransactionData.getAssetId() == Asset.QORT)
amount = amount.add(this.atTransactionData.getAmount());
return amount;
@@ -183,7 +183,7 @@ public class AtTransaction extends Transaction {
long assetId = this.atTransactionData.getAssetId();
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
if (assetId == Asset.QORA && recipient.getLastReference() == null)
if (assetId == Asset.QORT && recipient.getLastReference() == null)
// In Qora1 last reference was set to 64-bytes of zero
// In Qora2 we use AT-Transction's signature, which makes more sense
recipient.setLastReference(this.atTransactionData.getSignature());
@@ -220,7 +220,7 @@ public class AtTransaction extends Transaction {
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
* (which would have changed their last reference) thus this is their first reference so remove it.
*/
if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), this.atTransactionData.getSignature()))
if (assetId == Asset.QORT && Arrays.equals(recipient.getLastReference(), this.atTransactionData.getSignature()))
recipient.setLastReference(null);
}
}

View File

@@ -110,7 +110,7 @@ public class BuyNameTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (buyer.getConfirmedBalance(Asset.QORA).compareTo(buyNameTransactionData.getFee()) < 0)
if (buyer.getConfirmedBalance(Asset.QORT).compareTo(buyNameTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -89,7 +89,7 @@ public class CancelAssetOrderTransaction extends Transaction {
return ValidationResult.INVALID_ORDER_CREATOR;
// Check creator has enough QORA for fee
if (creator.getConfirmedBalance(Asset.QORA).compareTo(cancelOrderTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(cancelOrderTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -100,7 +100,7 @@ public class CancelGroupBanTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupUnbanTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupUnbanTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -100,7 +100,7 @@ public class CancelGroupInviteTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORA).compareTo(cancelGroupInviteTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT).compareTo(cancelGroupInviteTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -96,7 +96,7 @@ public class CancelSellNameTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (owner.getConfirmedBalance(Asset.QORA).compareTo(cancelSellNameTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT).compareTo(cancelSellNameTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -101,6 +101,10 @@ public class CreateAssetOrderTransaction extends Transaction {
if (wantAssetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
// Unspendable assets are not tradable
if (haveAssetData.getIsUnspendable() || wantAssetData.getIsUnspendable())
return ValidationResult.ASSET_NOT_SPENDABLE;
Account creator = getCreator();
boolean isNewPricing = createOrderTransactionData.getTimestamp() >= BlockChain.getInstance().getNewAssetPricingTimestamp();
@@ -153,9 +157,9 @@ public class CreateAssetOrderTransaction extends Transaction {
// Check order creator has enough asset balance AFTER removing fee, in case asset is QORA
// If asset is QORA then we need to check amount + fee in one go
if (haveAssetId == Asset.QORA) {
if (haveAssetId == Asset.QORT) {
// Check creator has enough funds for amount + fee in QORA
if (creator.getConfirmedBalance(Asset.QORA).compareTo(committedCost.add(createOrderTransactionData.getFee())) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(committedCost.add(createOrderTransactionData.getFee())) < 0)
return ValidationResult.NO_BALANCE;
} else {
// Check creator has enough funds for amount in whatever asset
@@ -165,7 +169,7 @@ public class CreateAssetOrderTransaction extends Transaction {
// Check creator has enough funds for fee in QORA
// NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check
if (createOrderTransactionData.getTimestamp() >= BlockChain.getInstance().getPowFixReleaseTimestamp()
&& creator.getConfirmedBalance(Asset.QORA).compareTo(createOrderTransactionData.getFee()) < 0)
&& creator.getConfirmedBalance(Asset.QORT).compareTo(createOrderTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
}

View File

@@ -98,7 +98,7 @@ public class CreateGroupTransaction extends Transaction {
Account creator = getCreator();
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(createGroupTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(createGroupTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -133,7 +133,7 @@ public class CreatePollTransaction extends Transaction {
Account creator = getCreator();
// Check issuer has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(createPollTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(createPollTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -162,6 +162,10 @@ public class DeployAtTransaction extends Transaction {
if (assetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
// Unspendable assets are not valid
if (assetData.getIsUnspendable())
return ValidationResult.ASSET_NOT_SPENDABLE;
// Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && deployATTransactionData.getAmount().stripTrailingZeros().scale() > 0)
return ValidationResult.INVALID_AMOUNT;
@@ -173,14 +177,14 @@ public class DeployAtTransaction extends Transaction {
Account creator = getCreator();
// Check creator has enough funds
if (assetId == Asset.QORA) {
if (assetId == Asset.QORT) {
// Simple case: amount and fee both in Qora
BigDecimal minimumBalance = deployATTransactionData.getFee().add(deployATTransactionData.getAmount());
if (creator.getConfirmedBalance(Asset.QORA).compareTo(minimumBalance) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(minimumBalance) < 0)
return ValidationResult.NO_BALANCE;
} else {
if (creator.getConfirmedBalance(Asset.QORA).compareTo(deployATTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(deployATTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
if (creator.getConfirmedBalance(assetId).compareTo(deployATTransactionData.getAmount()) < 0)
@@ -209,14 +213,14 @@ public class DeployAtTransaction extends Transaction {
long assetId = deployATTransactionData.getAssetId();
// Check creator has enough funds
if (assetId == Asset.QORA) {
if (assetId == Asset.QORT) {
// Simple case: amount and fee both in Qora
BigDecimal minimumBalance = deployATTransactionData.getFee().add(deployATTransactionData.getAmount());
if (creator.getConfirmedBalance(Asset.QORA).compareTo(minimumBalance) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(minimumBalance) < 0)
return ValidationResult.NO_BALANCE;
} else {
if (creator.getConfirmedBalance(Asset.QORA).compareTo(deployATTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(deployATTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
if (creator.getConfirmedBalance(assetId).compareTo(deployATTransactionData.getAmount()) < 0)

View File

@@ -121,7 +121,7 @@ public class EnableForgingTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(enableForgingTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(enableForgingTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -139,7 +139,7 @@ public class GenesisTransaction extends Transaction {
Account recipient = new Account(repository, genesisTransactionData.getRecipient());
// Update recipient's balance
recipient.setConfirmedBalance(Asset.QORA, genesisTransactionData.getAmount());
recipient.setConfirmedBalance(Asset.QORT, genesisTransactionData.getAmount());
}
@Override

View File

@@ -87,7 +87,7 @@ public class GroupApprovalTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupApprovalTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupApprovalTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -101,7 +101,7 @@ public class GroupBanTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check admin has enough funds
if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupBanTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupBanTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -107,7 +107,7 @@ public class GroupInviteTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupInviteTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupInviteTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -107,7 +107,7 @@ public class GroupKickTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupKickTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupKickTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -120,7 +120,7 @@ public class IssueAssetTransaction extends Transaction {
Account issuer = getIssuer();
// Check issuer has enough funds
if (issuer.getConfirmedBalance(Asset.QORA).compareTo(issueAssetTransactionData.getFee()) < 0)
if (issuer.getConfirmedBalance(Asset.QORT).compareTo(issueAssetTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -87,7 +87,7 @@ public class JoinGroupTransaction extends Transaction {
if (joinGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (joiner.getConfirmedBalance(Asset.QORA).compareTo(joinGroupTransactionData.getFee()) < 0)
if (joiner.getConfirmedBalance(Asset.QORT).compareTo(joinGroupTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -86,7 +86,7 @@ public class LeaveGroupTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (leaver.getConfirmedBalance(Asset.QORA).compareTo(leaveGroupTransactionData.getFee()) < 0)
if (leaver.getConfirmedBalance(Asset.QORT).compareTo(leaveGroupTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -61,7 +61,7 @@ public class MessageTransaction extends Transaction {
amount = amount.subtract(this.transactionData.getFee());
// We're only interested in QORA
if (messageTransactionData.getAssetId() == Asset.QORA) {
if (messageTransactionData.getAssetId() == Asset.QORT) {
if (address.equals(messageTransactionData.getRecipient()))
amount = amount.add(messageTransactionData.getAmount());
else if (address.equals(senderAddress))

View File

@@ -68,7 +68,7 @@ public class MultiPaymentTransaction extends Transaction {
// We're only interested in QORA
for (PaymentData paymentData : multiPaymentTransactionData.getPayments())
if (paymentData.getAssetId() == Asset.QORA) {
if (paymentData.getAssetId() == Asset.QORT) {
if (address.equals(paymentData.getRecipient()))
amount = amount.add(paymentData.getAmount());
else if (address.equals(senderAddress))
@@ -104,7 +104,7 @@ public class MultiPaymentTransaction extends Transaction {
// Check sender has enough funds for fee
// NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check
if (multiPaymentTransactionData.getTimestamp() >= BlockChain.getInstance().getPowFixReleaseTimestamp()
&& sender.getConfirmedBalance(Asset.QORA).compareTo(multiPaymentTransactionData.getFee()) < 0)
&& sender.getConfirmedBalance(Asset.QORT).compareTo(multiPaymentTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return new Payment(this.repository).isValid(multiPaymentTransactionData.getSenderPublicKey(), payments, multiPaymentTransactionData.getFee());

View File

@@ -71,7 +71,7 @@ public class PaymentTransaction extends Transaction {
// Processing
private PaymentData getPaymentData() {
return new PaymentData(paymentTransactionData.getRecipient(), Asset.QORA, paymentTransactionData.getAmount());
return new PaymentData(paymentTransactionData.getRecipient(), Asset.QORT, paymentTransactionData.getAmount());
}
@Override

View File

@@ -120,7 +120,7 @@ public class ProxyForgingTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(proxyForgingTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(proxyForgingTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -100,7 +100,7 @@ public class RegisterNameTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (registrant.getConfirmedBalance(Asset.QORA).compareTo(registerNameTransactionData.getFee()) < 0)
if (registrant.getConfirmedBalance(Asset.QORT).compareTo(registerNameTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -100,7 +100,7 @@ public class RemoveGroupAdminTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (owner.getConfirmedBalance(Asset.QORA).compareTo(removeGroupAdminTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT).compareTo(removeGroupAdminTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -107,7 +107,7 @@ public class SellNameTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (owner.getConfirmedBalance(Asset.QORA).compareTo(sellNameTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT).compareTo(sellNameTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -74,7 +74,7 @@ public class SetGroupTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(setGroupTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT).compareTo(setGroupTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -239,6 +239,7 @@ public abstract class Transaction {
NO_BLOCKCHAIN_LOCK(86),
ORDER_ALREADY_CLOSED(87),
CLOCK_NOT_SYNCED(88),
ASSET_NOT_SPENDABLE(89),
NOT_YET_RELEASED(1000);
public final int value;
@@ -946,7 +947,7 @@ public abstract class Transaction {
Account creator = getCreator();
// Update transaction creator's balance
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(transactionData.getFee()));
creator.setConfirmedBalance(Asset.QORT, creator.getConfirmedBalance(Asset.QORT).subtract(transactionData.getFee()));
// Update transaction creator's reference
creator.setLastReference(transactionData.getSignature());
@@ -970,7 +971,7 @@ public abstract class Transaction {
Account creator = getCreator();
// Update transaction creator's balance
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(transactionData.getFee()));
creator.setConfirmedBalance(Asset.QORT, creator.getConfirmedBalance(Asset.QORT).add(transactionData.getFee()));
// Update transaction creator's reference
creator.setLastReference(transactionData.getReference());

View File

@@ -58,7 +58,7 @@ public class TransferAssetTransaction extends Transaction {
amount = amount.subtract(this.transactionData.getFee());
// We're only interested in QORA amounts
if (transferAssetTransactionData.getAssetId() == Asset.QORA) {
if (transferAssetTransactionData.getAssetId() == Asset.QORT) {
if (address.equals(transferAssetTransactionData.getRecipient()))
amount = amount.add(transferAssetTransactionData.getAmount());
else if (address.equals(senderAddress))

View File

@@ -110,7 +110,7 @@ public class UpdateAssetTransaction extends Transaction {
Account currentOwner = getOwner();
// Check current owner has enough funds
if (currentOwner.getConfirmedBalance(Asset.QORA).compareTo(updateAssetTransactionData.getFee()) < 0)
if (currentOwner.getConfirmedBalance(Asset.QORT).compareTo(updateAssetTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -105,7 +105,7 @@ public class UpdateGroupTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (owner.getConfirmedBalance(Asset.QORA).compareTo(updateGroupTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT).compareTo(updateGroupTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -110,7 +110,7 @@ public class UpdateNameTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (owner.getConfirmedBalance(Asset.QORA).compareTo(updateNameTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT).compareTo(updateNameTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -110,7 +110,7 @@ public class VoteOnPollTransaction extends Transaction {
Account voter = getVoter();
// Check voter has enough funds
if (voter.getConfirmedBalance(Asset.QORA).compareTo(voteOnPollTransactionData.getFee()) < 0)
if (voter.getConfirmedBalance(Asset.QORT).compareTo(voteOnPollTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@@ -87,7 +87,7 @@ public class DeployAtTransactionTransformer extends TransactionTransformer {
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
long assetId = Asset.QORA;
long assetId = Asset.QORT;
if (version >= 4)
assetId = byteBuffer.getLong();

View File

@@ -47,7 +47,7 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
long assetId = Asset.QORA;
long assetId = Asset.QORT;
if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp())
assetId = byteBuffer.getLong();

View File

@@ -28,6 +28,7 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
private static final int IS_DIVISIBLE_LENGTH = BOOLEAN_LENGTH;
private static final int ASSET_REFERENCE_LENGTH = REFERENCE_LENGTH;
private static final int DATA_SIZE_LENGTH = INT_LENGTH;
private static final int IS_UNSPENDABLE_LENGTH = BOOLEAN_LENGTH;
private static final int EXTRAS_LENGTH = OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + QUANTITY_LENGTH
+ IS_DIVISIBLE_LENGTH;
@@ -50,6 +51,7 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
layout.add("can asset quantities be fractional?", TransformationType.BOOLEAN);
layout.add("asset data length", TransformationType.INT);
layout.add("asset data", TransformationType.STRING);
layout.add("are non-owner holders barred from using asset?", TransformationType.BOOLEAN);
layout.add("fee", TransformationType.AMOUNT);
layout.add("signature", TransformationType.SIGNATURE);
}
@@ -76,16 +78,18 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
boolean isDivisible = byteBuffer.get() != 0;
// in v2, assets have "data" field
String data = "";
if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp())
data = Serialization.deserializeSizedString(byteBuffer, Asset.MAX_DATA_SIZE);
byte[] assetReference = new byte[ASSET_REFERENCE_LENGTH];
// In v1, IssueAssetTransaction uses Asset.parse which also deserializes
// reference.
if (timestamp < BlockChain.getInstance().getQoraV2Timestamp())
String data = "";
boolean isUnspendable = false;
if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp()) {
// in v2, assets have additional fields
data = Serialization.deserializeSizedString(byteBuffer, Asset.MAX_DATA_SIZE);
isUnspendable = byteBuffer.get() != 0;
} else {
// In v1, IssueAssetTransaction uses Asset.parse which also deserializes reference.
byteBuffer.get(assetReference);
}
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
@@ -94,7 +98,7 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, issuerPublicKey, fee, signature);
return new IssueAssetTransactionData(baseTransactionData, owner, assetName, description, quantity, isDivisible, data);
return new IssueAssetTransactionData(baseTransactionData, owner, assetName, description, quantity, isDivisible, data, isUnspendable);
}
public static int getDataLength(TransactionData transactionData) throws TransformationException {
@@ -104,14 +108,13 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
+ Utf8.encodedLength(issueAssetTransactionData.getAssetName())
+ Utf8.encodedLength(issueAssetTransactionData.getDescription());
// In v2, assets have "data" field
if (transactionData.getTimestamp() >= BlockChain.getInstance().getQoraV2Timestamp())
dataLength += DATA_SIZE_LENGTH + Utf8.encodedLength(issueAssetTransactionData.getData());
// In v1, IssueAssetTransaction uses Asset.toBytes which also serializes
// reference.
if (transactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp())
if (transactionData.getTimestamp() >= BlockChain.getInstance().getQoraV2Timestamp()) {
// In v2, assets have additional fields.
dataLength += DATA_SIZE_LENGTH + Utf8.encodedLength(issueAssetTransactionData.getData()) + IS_UNSPENDABLE_LENGTH;
} else {
// In v1, IssueAssetTransaction uses Asset.toBytes which also serializes reference.
dataLength += ASSET_REFERENCE_LENGTH;
}
return dataLength;
}
@@ -133,14 +136,14 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
bytes.write(Longs.toByteArray(issueAssetTransactionData.getQuantity()));
bytes.write((byte) (issueAssetTransactionData.getIsDivisible() ? 1 : 0));
// In v2, assets have "data"
if (transactionData.getTimestamp() >= BlockChain.getInstance().getQoraV2Timestamp())
// In v2, assets have additional fields.
if (transactionData.getTimestamp() >= BlockChain.getInstance().getQoraV2Timestamp()) {
Serialization.serializeSizedString(bytes, issueAssetTransactionData.getData());
// In v1, IssueAssetTransaction uses Asset.toBytes which also
// serializes Asset's reference which is the IssueAssetTransaction's
// signature
if (transactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp()) {
bytes.write((byte) (issueAssetTransactionData.getIsUnspendable() ? 1 : 0));
} else {
// In v1, IssueAssetTransaction uses Asset.toBytes which also
// serializes Asset's reference which is the IssueAssetTransaction's
// signature
byte[] assetReference = issueAssetTransactionData.getSignature();
if (assetReference != null)
bytes.write(assetReference);

View File

@@ -68,7 +68,7 @@ public class MessageTransactionTransformer extends TransactionTransformer {
long assetId;
if (version == 1)
assetId = Asset.QORA;
assetId = Asset.QORT;
else
assetId = byteBuffer.getLong();