More repository work

Moved more repository-like methods from qora.* classes to repository.

Removed qora.block.BlockTransaction as it's pretty much internal to the HSQLDB repository.

Fixing qora.account.*

Fixing genesis-related classes: block, account, transaction...
This commit is contained in:
catbref 2018-06-12 14:54:06 +01:00
parent 37d9bcbb53
commit 698c4b6cc9
15 changed files with 169 additions and 173 deletions

View File

@ -14,7 +14,8 @@ public class GenesisTransactionData extends TransactionData {
// Constructors
public GenesisTransactionData(String recipient, BigDecimal amount, long timestamp, byte[] signature) {
super(TransactionType.GENESIS, BigDecimal.ZERO, new GenesisAccount().getPublicKey(), timestamp, signature);
// Zero fee
super(TransactionType.GENESIS, BigDecimal.ZERO, GenesisAccount.PUBLIC_KEY, timestamp, signature);
this.recipient = recipient;
this.amount = amount;

View File

@ -19,9 +19,7 @@ public class Account {
public Account(Repository repository, String address) throws DataException {
this.repository = repository;
this.accountData = this.repository.getAccountRepository().getAccount(address);
if (this.accountData == null)
this.accountData = new AccountData(address);
this.accountData = new AccountData(address);
}
public String getAddress() {

View File

@ -1,9 +1,14 @@
package qora.account;
import repository.DataException;
import repository.Repository;
public final class GenesisAccount extends PublicKeyAccount {
public GenesisAccount() {
super(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 });
public static final byte[] PUBLIC_KEY = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 };
public GenesisAccount(Repository repository) throws DataException {
super(repository, PUBLIC_KEY);
}
}

View File

@ -1,7 +1,9 @@
package qora.account;
import data.account.AccountData;
import qora.crypto.Crypto;
import qora.crypto.Ed25519;
import repository.Repository;
import utils.Pair;
public class PrivateKeyAccount extends PublicKeyAccount {
@ -15,11 +17,12 @@ public class PrivateKeyAccount extends PublicKeyAccount {
* @param seed
* byte[32] used to create private/public key pair
*/
public PrivateKeyAccount(byte[] seed) {
public PrivateKeyAccount(Repository repository, byte[] seed) {
this.repository = repository;
this.seed = seed;
this.keyPair = Ed25519.createKeyPair(seed);
this.publicKey = keyPair.getB();
this.address = Crypto.toAddress(this.publicKey);
this.accountData = new AccountData(Crypto.toAddress(this.publicKey));
}
public byte[] getSeed() {

View File

@ -2,13 +2,15 @@ package qora.account;
import qora.crypto.Crypto;
import qora.crypto.Ed25519;
import repository.DataException;
import repository.Repository;
public class PublicKeyAccount extends Account {
protected byte[] publicKey;
public PublicKeyAccount(byte[] publicKey) {
super(Crypto.toAddress(publicKey));
public PublicKeyAccount(Repository repository, byte[] publicKey) throws DataException {
super(repository, Crypto.toAddress(publicKey));
this.publicKey = publicKey;
}
@ -28,4 +30,8 @@ public class PublicKeyAccount extends Account {
}
}
public static String getAddress(byte[] publicKey) {
return Crypto.toAddress(publicKey);
}
}

View File

@ -49,10 +49,10 @@ import utils.NTP;
public class Block {
// Properties
private Repository repository;
private BlockData blockData;
private PublicKeyAccount generator;
protected Repository repository;
protected BlockData blockData;
protected PublicKeyAccount generator;
// Other properties
protected List<Transaction> transactions;
protected BigDecimal cachedNextGeneratingBalance;
@ -76,10 +76,10 @@ public class Block {
// Constructors
public Block(Repository repository, BlockData blockData) {
public Block(Repository repository, BlockData blockData) throws DataException {
this.repository = repository;
this.blockData = blockData;
this.generator = new PublicKeyAccount(blockData.getGeneratorPublicKey());
this.generator = new PublicKeyAccount(repository, blockData.getGeneratorPublicKey());
}
// Getters/setters
@ -88,6 +88,10 @@ public class Block {
return this.blockData;
}
public PublicKeyAccount getGenerator() {
return this.generator;
}
// More information
/**
@ -142,7 +146,7 @@ public class Block {
// XXX: why can't we simply load using block height?
BlockRepository blockRepo = this.repository.getBlockRepository();
BlockData firstBlock = this.blockData;
try {
for (int i = 1; firstBlock != null && i < BLOCK_RETARGET_INTERVAL; ++i)
firstBlock = blockRepo.fromSignature(firstBlock.getReference());
@ -200,10 +204,10 @@ public class Block {
throw new IllegalStateException("Block's transactions from repository do not match block's transaction count");
this.transactions = new ArrayList<Transaction>();
for (TransactionData transactionData : transactionsData)
this.transactions.add(Transaction.fromData(transactionData));
return this.transactions;
}
@ -294,7 +298,6 @@ public class Block {
}
}
public boolean isSignatureValid() {
try {
// Check generator's signature first
@ -320,7 +323,7 @@ public class Block {
*
* @return true if block is valid, false otherwise.
* @throws SQLException
* @throws DataException
* @throws DataException
*/
public boolean isValid() throws SQLException, DataException {
// TODO
@ -334,7 +337,7 @@ public class Block {
return false;
Block parentBlock = new Block(this.repository, parentBlockData);
// Check timestamp is valid, i.e. later than parent timestamp and not in the future, within ~500ms margin
if (this.blockData.getTimestamp() < parentBlockData.getTimestamp() || this.blockData.getTimestamp() - BLOCK_TIMESTAMP_MARGIN > NTP.getTime())
return false;
@ -410,7 +413,7 @@ public class Block {
return true;
}
public void process() throws DataException, SQLException {
public void process() throws DataException {
// Process transactions (we'll link them to this block after saving the block itself)
List<Transaction> transactions = this.getTransactions();
for (Transaction transaction : transactions)
@ -422,7 +425,7 @@ public class Block {
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).add(blockFee));
// Link block into blockchain by fetching signature of highest block and setting that as our reference
int blockchainHeight = BlockChain.getHeight();
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
BlockData latestBlockData = this.repository.getBlockRepository().fromHeight(blockchainHeight);
if (latestBlockData != null)
this.blockData.setReference(latestBlockData.getSignature());
@ -435,7 +438,8 @@ public class Block {
Transaction transaction = transactions.get(sequence);
// Link transaction to this block
BlockTransactionData blockTransactionData = new BlockTransactionData(this.getSignature(), sequence, transaction.getTransactionData().getSignature());
BlockTransactionData blockTransactionData = new BlockTransactionData(this.getSignature(), sequence,
transaction.getTransactionData().getSignature());
this.repository.getBlockRepository().save(blockTransactionData);
}
}

View File

@ -1,11 +1,14 @@
package qora.block;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import database.DB;
import data.block.BlockData;
import qora.assets.Asset;
import repository.BlockRepository;
import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
/**
* Class representing the blockchain as a whole.
@ -35,69 +38,43 @@ public class BlockChain {
*
* @throws SQLException
*/
public static void validate() throws SQLException {
public static void validate() throws DataException {
// Check first block is Genesis Block
if (!isGenesisBlockValid())
rebuildBlockchain();
}
private static boolean isGenesisBlockValid() throws SQLException {
int blockchainHeight = getHeight();
private static boolean isGenesisBlockValid() throws DataException {
BlockRepository blockRepository = RepositoryManager.getRepository().getBlockRepository();
int blockchainHeight = blockRepository.getBlockchainHeight();
if (blockchainHeight < 1)
return false;
Block block = Block.fromHeight(1);
if (block == null)
BlockData blockData = blockRepository.fromHeight(1);
if (blockData == null)
return false;
return GenesisBlock.isGenesisBlock(block);
return GenesisBlock.isGenesisBlock(blockData);
}
private static void rebuildBlockchain() throws SQLException {
// (Re)build database
DB.rebuild();
private static void rebuildBlockchain() throws DataException {
// (Re)build repository
Repository repository = RepositoryManager.getRepository();
repository.rebuild();
// Add Genesis Block
GenesisBlock genesisBlock = GenesisBlock.getInstance();
GenesisBlock genesisBlock = new GenesisBlock(repository);
genesisBlock.process();
// Add QORA asset.
// NOTE: Asset's transaction reference is Genesis Block's generator signature which doesn't exist as a transaction!
// TODO construct Asset(repository, AssetData) then .save()?
Asset qoraAsset = new Asset(Asset.QORA, genesisBlock.getGenerator().getAddress(), "Qora", "This is the simulated Qora asset.", 10_000_000_000L, true,
genesisBlock.getGeneratorSignature());
genesisBlock.getBlockData().getGeneratorSignature());
qoraAsset.save();
}
/**
* Return block height from DB using signature.
*
* @param signature
* @return height, or 0 if block not found.
* @throws SQLException
*/
public static int getBlockHeightFromSignature(byte[] signature) throws SQLException {
ResultSet rs = DB.checkedExecute("SELECT height FROM Blocks WHERE signature = ?", signature);
if (rs == null)
return 0;
return rs.getInt(1);
}
/**
* Return highest block height from DB.
*
* @return height, or 0 if there are no blocks in DB (not very likely).
*/
public static int getHeight() {
try {
ResultSet rs = DB.checkedExecute("SELECT MAX(height) FROM Blocks");
if (rs == null)
return 0;
return rs.getInt(1);
} catch (SQLException e) {
return 0;
}
repository.saveChanges();
}
/**

View File

@ -1,5 +0,0 @@
package qora.block;
public class BlockTransaction {
}

View File

@ -3,37 +3,34 @@ package qora.block;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Longs;
import data.block.BlockData;
import data.transaction.GenesisTransactionData;
import data.transaction.TransactionData;
import qora.account.GenesisAccount;
import qora.crypto.Crypto;
import qora.transaction.GenesisTransaction;
import qora.transaction.TransactionHandler;
import qora.transaction.Transaction;
import repository.DataException;
import repository.Repository;
public class GenesisBlock extends Block {
private static GenesisBlock instance;
private static final int GENESIS_BLOCK_VERSION = 1;
private static final byte[] GENESIS_REFERENCE = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }; // NOTE: Neither 64 nor 128 bytes!
private static final BigDecimal GENESIS_GENERATING_BALANCE = BigDecimal.valueOf(10_000_000L).setScale(8);
private static final GenesisAccount GENESIS_GENERATOR = new GenesisAccount();
private static final byte[] GENESIS_GENERATOR_PUBLIC_KEY = GenesisAccount.PUBLIC_KEY; // NOTE: 8 bytes not 32 bytes!
private static final long GENESIS_TIMESTAMP = 1400247274336L; // QORA RELEASE: Fri May 16 13:34:34.336 2014 UTC
private static final byte[] GENESIS_GENERATOR_SIGNATURE = calcSignature();
private static final byte[] GENESIS_TRANSACTIONS_SIGNATURE = calcSignature();
// Constructors
protected GenesisBlock() {
super(GENESIS_BLOCK_VERSION, GENESIS_REFERENCE, GENESIS_TIMESTAMP, GENESIS_GENERATING_BALANCE, GENESIS_GENERATOR, GENESIS_GENERATOR_SIGNATURE,
GENESIS_TRANSACTIONS_SIGNATURE, null, null, new ArrayList<TransactionHandler>());
this.height = 1;
public GenesisBlock(Repository repository) throws DataException {
super(repository, new BlockData(GENESIS_BLOCK_VERSION, GENESIS_REFERENCE, 0, BigDecimal.ZERO.setScale(8), GENESIS_TRANSACTIONS_SIGNATURE, 1,
GENESIS_TIMESTAMP, GENESIS_GENERATING_BALANCE, GENESIS_GENERATOR_PUBLIC_KEY, GENESIS_GENERATOR_SIGNATURE, null, null));
// Genesis transactions
addGenesisTransaction("QUD9y7NZqTtNwvSAUfewd7zKUGoVivVnTW", "7032468.191");
@ -172,74 +169,35 @@ public class GenesisBlock extends Block {
addGenesisTransaction("QT79PhvBwE6vFzfZ4oh5wdKVsEazZuVJFy", "6360421.343");
}
/**
* Return cached GenesisBlock to save constructing one from scratch.
*
* @return GenesisBlock
*/
public static GenesisBlock getInstance() {
if (instance == null)
instance = new GenesisBlock();
return instance;
}
// Getters/setters
// More information
public static boolean isGenesisBlock(Block block) {
if (block.height != 1)
public static boolean isGenesisBlock(BlockData blockData) {
if (blockData.getHeight() != 1)
return false;
// Validate block signature
if (!Arrays.equals(GENESIS_GENERATOR_SIGNATURE, block.generatorSignature))
if (!Arrays.equals(GENESIS_GENERATOR_SIGNATURE, blockData.getGeneratorSignature()))
return false;
// Validate transactions signature
if (!Arrays.equals(GENESIS_TRANSACTIONS_SIGNATURE, block.transactionsSignature))
if (!Arrays.equals(GENESIS_TRANSACTIONS_SIGNATURE, blockData.getTransactionsSignature()))
return false;
return true;
}
// Load/Save
protected GenesisBlock(byte[] signature) throws SQLException {
super(signature);
}
protected GenesisBlock(ResultSet rs) throws SQLException {
super(rs);
}
// Navigation
/**
* Refuse to load parent of GenesisBlock from DB!
* <p>
* As the genesis block is the first block, this always returns null.
*
* @return null
* @throws SQLException
*/
@Override
public Block getParent() throws SQLException {
return null;
}
// Converters
// Processing
@Override
public boolean addTransaction(TransactionHandler transaction) {
public boolean addTransaction(TransactionData transactionData) {
// The genesis block has a fixed set of transactions so always refuse.
return false;
}
private void addGenesisTransaction(String recipient, String amount) {
this.transactions.add(new GenesisTransaction(recipient, new BigDecimal(amount).setScale(8), this.timestamp));
this.transactions
.add(Transaction.fromData(new GenesisTransactionData(recipient, new BigDecimal(amount).setScale(8), this.getBlockData().getTimestamp())));
this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1);
}
/**
@ -288,7 +246,7 @@ public class GenesisBlock extends Block {
try {
// Passing expected size to ByteArrayOutputStream avoids reallocation when adding more bytes than default 32.
// See below for explanation of some of the values used to calculated expected size.
ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 64 + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH);
ByteArrayOutputStream bytes = new ByteArrayOutputStream(8 + 64 + 8 + 32);
/*
* NOTE: Historic code had genesis block using Longs.toByteArray() compared to standard block's Ints.toByteArray. The subsequent
@ -306,7 +264,7 @@ public class GenesisBlock extends Block {
bytes.write(Longs.toByteArray(GENESIS_GENERATING_BALANCE.longValue()));
// NOTE: Genesis account's public key is only 8 bytes, not the usual 32, so we have to pad.
bytes.write(Bytes.ensureCapacity(GENESIS_GENERATOR.getPublicKey(), 32, 0));
bytes.write(Bytes.ensureCapacity(GENESIS_GENERATOR_PUBLIC_KEY, 32, 0));
return bytes.toByteArray();
} catch (IOException e) {
@ -317,25 +275,25 @@ public class GenesisBlock extends Block {
@Override
public boolean isSignatureValid() {
// Validate block signature
if (!Arrays.equals(GENESIS_GENERATOR_SIGNATURE, this.generatorSignature))
if (!Arrays.equals(GENESIS_GENERATOR_SIGNATURE, this.getBlockData().getGeneratorSignature()))
return false;
// Validate transactions signature
if (!Arrays.equals(GENESIS_TRANSACTIONS_SIGNATURE, this.transactionsSignature))
if (!Arrays.equals(GENESIS_TRANSACTIONS_SIGNATURE, this.getBlockData().getTransactionsSignature()))
return false;
return true;
}
@Override
public boolean isValid() throws SQLException {
public boolean isValid() throws DataException {
// Check there is no other block in DB
if (BlockChain.getHeight() != 0)
if (this.repository.getBlockRepository().getBlockchainHeight() != 0)
return false;
// Validate transactions
for (TransactionHandler transaction : this.getTransactions())
if (transaction.isValid() != TransactionHandler.ValidationResult.OK)
for (Transaction transaction : this.getTransactions())
if (transaction.isValid() != Transaction.ValidationResult.OK)
return false;
return true;

View File

@ -12,6 +12,7 @@ import data.transaction.TransactionData;
import qora.account.PrivateKeyAccount;
import qora.block.Block;
import qora.block.BlockChain;
import repository.Repository;
import repository.RepositoryManager;
import settings.Settings;
import transform.TransformationException;
@ -63,26 +64,27 @@ public abstract class Transaction {
protected static final BigDecimal maxBytePerFee = BigDecimal.valueOf(Settings.getInstance().getMaxBytePerFee());
protected static final BigDecimal minFeePerByte = BigDecimal.ONE.divide(maxBytePerFee, MathContext.DECIMAL32);
// Properties
protected TransactionData transactionData;
// Constructors
public static Transaction fromData(TransactionData transactionData) {
switch (transactionData.getType()) {
case GENESIS:
return new GenesisTransaction(transactionData);
default:
return null;
}
}
// Getters / Setters
public TransactionData getTransactionData() {
return this.transactionData;
}
// More information
public long getDeadline() {
@ -108,17 +110,18 @@ public abstract class Transaction {
public BigDecimal calcRecommendedFee() {
try {
BigDecimal recommendedFee = BigDecimal.valueOf(TransactionTransformer.getDataLength(this.transactionData)).divide(maxBytePerFee, MathContext.DECIMAL32).setScale(8);
BigDecimal recommendedFee = BigDecimal.valueOf(TransactionTransformer.getDataLength(this.transactionData))
.divide(maxBytePerFee, MathContext.DECIMAL32).setScale(8);
// security margin
recommendedFee = recommendedFee.add(new BigDecimal("0.0000001"));
if (recommendedFee.compareTo(MINIMUM_FEE) <= 0) {
recommendedFee = MINIMUM_FEE;
} else {
recommendedFee = recommendedFee.setScale(0, BigDecimal.ROUND_UP);
}
return recommendedFee.setScale(8);
} catch (TransformationException e) {
throw new IllegalStateException("Unable to get transaction byte length?");
@ -139,7 +142,7 @@ public abstract class Transaction {
* @return height, or 0 if not in blockchain (i.e. unconfirmed)
*/
public int getHeight() {
return RepositoryManager.getTransactionRepository().getHeight(this.transactionData);
return RepositoryManager.getRepository().getTransactionRepository().getHeight(this.transactionData);
}
/**
@ -148,14 +151,14 @@ public abstract class Transaction {
* @return confirmation count, or 0 if not in blockchain (i.e. unconfirmed)
*/
public int getConfirmations() {
int ourHeight = this.getHeight();
int ourHeight = getHeight();
if (ourHeight == 0)
return 0;
int blockChainHeight = BlockChain.getHeight();
if (blockChainHeight == 0)
return 0;
return blockChainHeight - ourHeight + 1;
}

View File

@ -14,6 +14,22 @@ public interface BlockRepository {
public BlockData fromHeight(int height) throws DataException;
/**
* Return height of block in blockchain using block's signature.
*
* @param signature
* @return height, or 0 if not found in blockchain.
* @throws DataException
*/
public int getHeightFromSignature(byte[] signature) throws DataException;
/**
* Return highest block height from DB.
*
* @return height, or 0 if there are no blocks in DB (not very likely).
*/
public int getBlockchainHeight() throws DataException;
public List<TransactionData> getTransactionsFromSignature(byte[] signature) throws DataException;
public void save(BlockData blockData) throws DataException;

View File

@ -14,4 +14,6 @@ public interface Repository {
public void close() throws DataException;
public void rebuild() throws DataException;
}

View File

@ -25,6 +25,31 @@ public class HSQLDBBlockRepository implements BlockRepository {
this.repository = repository;
}
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
if (rs == null)
return null;
try {
int version = rs.getInt(1);
byte[] reference = this.repository.getResultSetBytes(rs.getBinaryStream(2));
int transactionCount = rs.getInt(3);
BigDecimal totalFees = rs.getBigDecimal(4);
byte[] transactionsSignature = this.repository.getResultSetBytes(rs.getBinaryStream(5));
int height = rs.getInt(6);
long timestamp = rs.getTimestamp(7).getTime();
BigDecimal generatingBalance = rs.getBigDecimal(8);
byte[] generatorPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(9));
byte[] generatorSignature = this.repository.getResultSetBytes(rs.getBinaryStream(10));
byte[] atBytes = this.repository.getResultSetBytes(rs.getBinaryStream(11));
BigDecimal atFees = rs.getBigDecimal(12);
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
generatorPublicKey, generatorSignature, atBytes, atFees);
} catch (SQLException e) {
throw new DataException("Error extracting data from result set", e);
}
}
public BlockData fromSignature(byte[] signature) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
@ -52,28 +77,27 @@ public class HSQLDBBlockRepository implements BlockRepository {
}
}
private BlockData getBlockFromResultSet(ResultSet rs) throws DataException {
if (rs == null)
return null;
public int getHeightFromSignature(byte[] signature) throws DataException {
try {
int version = rs.getInt(1);
byte[] reference = this.repository.getResultSetBytes(rs.getBinaryStream(2));
int transactionCount = rs.getInt(3);
BigDecimal totalFees = rs.getBigDecimal(4);
byte[] transactionsSignature = this.repository.getResultSetBytes(rs.getBinaryStream(5));
int height = rs.getInt(6);
long timestamp = rs.getTimestamp(7).getTime();
BigDecimal generatingBalance = rs.getBigDecimal(8);
byte[] generatorPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(9));
byte[] generatorSignature = this.repository.getResultSetBytes(rs.getBinaryStream(10));
byte[] atBytes = this.repository.getResultSetBytes(rs.getBinaryStream(11));
BigDecimal atFees = rs.getBigDecimal(12);
ResultSet rs = this.repository.checkedExecute("SELECT height FROM Blocks WHERE signature = ?", signature);
if (rs == null)
return 0;
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
generatorPublicKey, generatorSignature, atBytes, atFees);
return rs.getInt(1);
} catch (SQLException e) {
throw new DataException("Error extracting data from result set", e);
throw new DataException("Error obtaining block height from repository", e);
}
}
public int getBlockchainHeight() throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT MAX(height) FROM Blocks");
if (rs == null)
return 0;
return rs.getInt(1);
} catch (SQLException e) {
throw new DataException("Error obtaining blockchain height from repository", e);
}
}

View File

@ -69,6 +69,10 @@ public class HSQLDBRepository implements Repository {
}
}
@Override
public void rebuild() throws DataException {
}
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[].
*

View File

@ -210,7 +210,7 @@ public class BlockTransformer extends Transformer {
json.put("version", blockData.getVersion());
json.put("timestamp", blockData.getTimestamp());
json.put("generatingBalance", blockData.getGeneratingBalance());
json.put("generator", new PublicKeyAccount(blockData.getGeneratorPublicKey()).getAddress());
json.put("generator", PublicKeyAccount.getAddress(blockData.getGeneratorPublicKey()));
json.put("generatorPublicKey", Base58.encode(blockData.getGeneratorPublicKey()));
json.put("fee", blockData.getTotalFees().toPlainString());
json.put("transactionsSignature", Base58.encode(blockData.getTransactionsSignature()));