qortal/src/qora/account/Account.java
catbref ad9fa9bf9d More work on API plus basic block explorer
Added FATJAR packaging support to pom.xml

Added some "summary" fields to API calls but more need doing.
Corrected path clash from having unnecessary @OpenAPIDefinition annotations.
Added API "tags" to group similar calls (address-based, block-related, etc.)
Fixed addresses/lastreference/{address}
Implemented addresses/lastreference/{address}/unconfirmed
Implemented addresses/assets/{address}
Added /admin/stop and /admin/uptime API calls.
Moved general API info into new src/api/ApiDefinition.java
Added CORS support to ApiService
Added /transactions/address/{address} and /transactions/block/{signature}

Replaced references to test.Common.* to do with repository factory.
 This fixes issues with building FATJAR due to references to test classes
 that are omitted from FATJAR.

Changes to AccountBalanceData, BlockData and TransactionData
 to support JAX-RS rendering to JSON.

Added getUnconfirmedLastReference() to Account.

Added getAllBalances(address) to account repository
 - returns all asset balances for that address.

Added getAllSignaturesInvolvingAddress(address) to account repository
 but currently only uses TransactionRecipients HSQLDB table.
 (And even that wasn't automatically populated).

Included: very basic block explorer to be opened in browser as a file:
block-explorer.html
2018-12-04 16:34:55 +00:00

190 lines
5.9 KiB
Java

package qora.account;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import data.account.AccountBalanceData;
import data.account.AccountData;
import data.block.BlockData;
import data.transaction.TransactionData;
import qora.assets.Asset;
import qora.block.Block;
import qora.block.BlockChain;
import qora.transaction.Transaction;
import repository.BlockRepository;
import repository.DataException;
import repository.Repository;
public class Account {
private static final Logger LOGGER = LogManager.getLogger(Account.class);
public static final int ADDRESS_LENGTH = 25;
protected Repository repository;
protected AccountData accountData;
protected Account() {
}
public Account(Repository repository, String address) {
this.repository = repository;
this.accountData = new AccountData(address);
}
public String getAddress() {
return this.accountData.getAddress();
}
// More information
/**
* Calculate current generating balance for this account.
* <p>
* This is the current confirmed balance minus amounts received in the last <code>BlockChain.BLOCK_RETARGET_INTERVAL</code> blocks.
*
* @throws DataException
*/
public BigDecimal getGeneratingBalance() throws DataException {
BigDecimal balance = this.getConfirmedBalance(Asset.QORA);
BlockRepository blockRepository = this.repository.getBlockRepository();
BlockData blockData = blockRepository.getLastBlock();
for (int i = 1; i < BlockChain.BLOCK_RETARGET_INTERVAL && blockData != null && blockData.getHeight() > 1; ++i) {
Block block = new Block(this.repository, blockData);
// CIYAM AT transactions should be fetched from repository so no special handling needed here
for (Transaction transaction : block.getTransactions()) {
if (transaction.isInvolved(this)) {
final BigDecimal amount = transaction.getAmount(this);
// Subtract positive amounts only
if (amount.compareTo(BigDecimal.ZERO) > 0)
balance = balance.subtract(amount);
}
}
blockData = block.getParent();
}
// Do not go below 0
balance = balance.max(BigDecimal.ZERO);
return balance;
}
// Balance manipulations - assetId is 0 for QORA
public BigDecimal getBalance(long assetId, int confirmations) throws DataException {
// Simple case: we only need balance with 1 confirmation
if (confirmations == 1)
return this.getConfirmedBalance(assetId);
/*
* For a balance with more confirmations work back from last block, undoing transactions involving this account, until we have processed required number
* of blocks.
*/
BlockRepository blockRepository = this.repository.getBlockRepository();
BigDecimal balance = this.getConfirmedBalance(assetId);
BlockData blockData = blockRepository.getLastBlock();
// Note: "blockData.getHeight() > 1" to make sure we don't examine genesis block
for (int i = 1; i < confirmations && blockData != null && blockData.getHeight() > 1; ++i) {
Block block = new Block(this.repository, blockData);
// CIYAM AT transactions should be fetched from repository so no special handling needed here
for (Transaction transaction : block.getTransactions())
if (transaction.isInvolved(this))
balance = balance.subtract(transaction.getAmount(this));
blockData = block.getParent();
}
// Return balance
return balance;
}
public BigDecimal getConfirmedBalance(long assetId) throws DataException {
AccountBalanceData accountBalanceData = this.repository.getAccountRepository().getBalance(this.accountData.getAddress(), assetId);
if (accountBalanceData == null)
return BigDecimal.ZERO.setScale(8);
return accountBalanceData.getBalance();
}
public void setConfirmedBalance(long assetId, BigDecimal balance) throws DataException {
// Can't have a balance without an account - make sure it exists!
this.repository.getAccountRepository().create(this.accountData.getAddress());
AccountBalanceData accountBalanceData = new AccountBalanceData(this.accountData.getAddress(), assetId, balance);
this.repository.getAccountRepository().save(accountBalanceData);
LOGGER.trace(this.accountData.getAddress() + " balance now: " + balance.toPlainString() + " [assetId " + assetId + "]");
}
public void deleteBalance(long assetId) throws DataException {
this.repository.getAccountRepository().delete(this.accountData.getAddress(), assetId);
}
// Reference manipulations
/**
* Fetch last reference for account.
*
* @return byte[] reference, or null if no reference or account not found.
* @throws DataException
*/
public byte[] getLastReference() throws DataException {
AccountData accountData = this.repository.getAccountRepository().getAccount(this.accountData.getAddress());
if (accountData == null)
return null;
return accountData.getReference();
}
/**
* Fetch last reference for account, considering unconfirmed transactions.
*
* @return byte[] reference, or null if no reference or account not found.
* @throws DataException
*/
public byte[] getUnconfirmedLastReference() throws DataException {
// Newest unconfirmed transaction takes priority
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
byte[] reference = null;
for (TransactionData transactionData : unconfirmedTransactions) {
String address = PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey());
if (address.equals(this.accountData.getAddress()))
reference = transactionData.getSignature();
}
if (reference != null)
return reference;
// No unconfirmed transactions
return getLastReference();
}
/**
* Set last reference for account.
*
* @param reference
* -- null allowed
* @throws DataException
*/
public void setLastReference(byte[] reference) throws DataException {
accountData.setReference(reference);
this.repository.getAccountRepository().save(accountData);
}
}