forked from Qortal/qortal
API, switchable MD160,
Now uses working RIPE-MD160 by default but can be switched to broken MD160 using flag in blockchain config, e.g. for Qora v1 blockchain. Replaced API signature/reference examples with descriptive text as they weren't very useful. Replaced API address examples with ones generated using working MD160. Added GET /transactions/signature/{signature}/raw that returns raw transaction in base58 encoding. Added "ignoreValidityChecks" query param to POST /transactions/decode to bypass INVALID_REFERENCE errors if supplying an old/speculative transaction that can't be added to unconfirmed transaction pile. Finally fixed creating inital assets in BlockChain. Controller now inserts BouncyCastle as highest priority Security Provider. TransactionData & transaction repository now tries to return transaction's block height in data when possible.
This commit is contained in:
parent
b2ca63ce88
commit
7998166c0a
@ -6,9 +6,10 @@
|
||||
"blockTimestampMargin": 500,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "1.0",
|
||||
"useBrokenMD160ForAddresses": true,
|
||||
"genesis": {
|
||||
"assets": [
|
||||
{ "name": "QORA", "description": "QORA coin" }
|
||||
{ "name": "QORA", "description": "QORA coin", "quantity": 10000000000, "isDivisible": true, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC" }
|
||||
]
|
||||
"timestamp": "1400247274336",
|
||||
"generatingBalance": "10000000",
|
||||
|
@ -28,10 +28,10 @@ public class AdminResource {
|
||||
|
||||
@GET
|
||||
@Path("/unused")
|
||||
@Parameter(in = ParameterIn.PATH, name = "blockSignature", description = "Block signature", schema = @Schema(type = "string", format = "byte"), example = "ZZZZ==")
|
||||
@Parameter(in = ParameterIn.PATH, name = "blockSignature", description = "Block signature", schema = @Schema(type = "string", format = "byte"), example = "very_long_block_signature_in_base58")
|
||||
@Parameter(in = ParameterIn.PATH, name = "assetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
||||
@Parameter(in = ParameterIn.PATH, name = "otherAssetId", description = "Asset ID, 0 is native coin", schema = @Schema(type = "string", format = "byte"))
|
||||
@Parameter(in = ParameterIn.PATH, name = "address", description = "an account address", example = "QRHDHASWAXarqTvB2X4SNtJCWbxGf68M2o")
|
||||
@Parameter(in = ParameterIn.PATH, name = "address", description = "an account address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
||||
@Parameter(in = ParameterIn.QUERY, name = "count", description = "Maximum number of entries to return, 0 means none", schema = @Schema(type = "integer", defaultValue = "20"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "limit", description = "Maximum number of entries to return, 0 means unlimited", schema = @Schema(type = "integer", defaultValue = "20"))
|
||||
@Parameter(in = ParameterIn.QUERY, name = "offset", description = "Starting entry in results, 0 is first entry", schema = @Schema(type = "integer"))
|
||||
|
@ -66,7 +66,7 @@ public class AnnotationPostProcessor implements ReaderListener {
|
||||
if (apiErrors == null)
|
||||
continue;
|
||||
|
||||
LOGGER.info("Found @ApiErrors annotation on " + clazz.getSimpleName() + "." + method.getName());
|
||||
LOGGER.trace("Found @ApiErrors annotation on " + clazz.getSimpleName() + "." + method.getName());
|
||||
PathItem pathItem = getPathItemFromMethod(openAPI, classPathString, method);
|
||||
|
||||
for (Operation operation : pathItem.readOperations())
|
||||
|
@ -69,7 +69,7 @@ public class TransactionsResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.TRANSACTION_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||
public TransactionData getTransactions(@PathParam("signature") String signature58) {
|
||||
public TransactionData getTransaction(@PathParam("signature") String signature58) {
|
||||
byte[] signature;
|
||||
try {
|
||||
signature = Base58.decode(signature58);
|
||||
@ -90,6 +90,47 @@ public class TransactionsResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/signature/{signature}/raw")
|
||||
@Operation(
|
||||
summary = "Fetch raw, base58-encoded, transaction using transaction signature",
|
||||
description = "Returns transaction",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "raw transaction encoded in Base58",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(type = "string")
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.TRANSACTION_NO_EXISTS, ApiError.REPOSITORY_ISSUE, ApiError.TRANSFORMATION_ERROR})
|
||||
public String getRawTransaction(@PathParam("signature") String signature58) {
|
||||
byte[] signature;
|
||||
try {
|
||||
signature = Base58.decode(signature58);
|
||||
} catch (NumberFormatException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
||||
if (transactionData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_NO_EXISTS);
|
||||
|
||||
byte[] transactionBytes = TransactionTransformer.toBytes(transactionData);
|
||||
|
||||
return Base58.encode(transactionBytes);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
} catch (TransformationException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/block/{signature}")
|
||||
@Operation(
|
||||
@ -372,7 +413,7 @@ public class TransactionsResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.INVALID_DATA, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||
public TransactionData decodeTransaction(String rawBytes58) {
|
||||
public TransactionData decodeTransaction(String rawBytes58, @QueryParam("ignoreValidityChecks") boolean ignoreValidityChecks) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
byte[] rawBytes = Base58.decode(rawBytes58);
|
||||
boolean hasSignature = true;
|
||||
@ -388,12 +429,15 @@ public class TransactionsResource {
|
||||
}
|
||||
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
|
||||
if (!ignoreValidityChecks) {
|
||||
if (hasSignature && !transaction.isSignatureValid())
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE);
|
||||
|
||||
ValidationResult result = transaction.isValid();
|
||||
if (result != ValidationResult.OK)
|
||||
throw createTransactionInvalidException(request, result);
|
||||
}
|
||||
|
||||
if (!hasSignature)
|
||||
transactionData.setSignature(null);
|
||||
|
@ -11,7 +11,6 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
import org.qora.asset.Asset;
|
||||
import org.qora.data.asset.AssetData;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.repository.BlockRepository;
|
||||
@ -52,6 +51,9 @@ public class BlockChain {
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
private Map<String, Map<FeatureValueType, Long>> featureTriggers;
|
||||
|
||||
// This property is slightly different as we need it early and we want to avoid getInstance() loop
|
||||
private static boolean useBrokenMD160ForAddresses = false;
|
||||
|
||||
// Constructors, etc.
|
||||
|
||||
private BlockChain() {
|
||||
@ -59,7 +61,8 @@ public class BlockChain {
|
||||
|
||||
public static BlockChain getInstance() {
|
||||
if (instance == null)
|
||||
Settings.getInstance();
|
||||
// This will call BlockChain.fromJSON in turn
|
||||
Settings.getInstance(); // synchronized
|
||||
|
||||
return instance;
|
||||
}
|
||||
@ -98,6 +101,10 @@ public class BlockChain {
|
||||
return this.blockTimestampMargin;
|
||||
}
|
||||
|
||||
public static boolean getUseBrokenMD160ForAddresses() {
|
||||
return useBrokenMD160ForAddresses;
|
||||
}
|
||||
|
||||
private long getFeatureTrigger(String feature, FeatureValueType valueType) {
|
||||
Map<FeatureValueType, Long> featureTrigger = featureTriggers.get(feature);
|
||||
if (featureTrigger == null)
|
||||
@ -143,6 +150,14 @@ public class BlockChain {
|
||||
// Blockchain config from JSON
|
||||
|
||||
public static void fromJSON(JSONObject json) {
|
||||
// Determine hash function for generating addresses as we need that to build genesis block, etc.
|
||||
Boolean useBrokenMD160 = null;
|
||||
if (json.containsKey("useBrokenMD160ForAddresses"))
|
||||
useBrokenMD160 = (Boolean) Settings.getTypedJson(json, "useBrokenMD160ForAddresses", Boolean.class);
|
||||
|
||||
if (useBrokenMD160 != null)
|
||||
useBrokenMD160ForAddresses = useBrokenMD160.booleanValue();
|
||||
|
||||
Object genesisJson = json.get("genesis");
|
||||
if (genesisJson == null) {
|
||||
LOGGER.error("No \"genesis\" entry found in blockchain config");
|
||||
@ -228,15 +243,15 @@ public class BlockChain {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
repository.rebuild();
|
||||
|
||||
// Add Genesis Block
|
||||
GenesisBlock genesisBlock = GenesisBlock.getInstance(repository);
|
||||
genesisBlock.process();
|
||||
|
||||
// Add QORA asset.
|
||||
// NOTE: Asset's transaction reference is Genesis Block's generator signature which doesn't exist as a transaction!
|
||||
AssetData qoraAssetData = new AssetData(Asset.QORA, genesisBlock.getGenerator().getAddress(), "Qora", "This is the simulated Qora asset.",
|
||||
BlockChain.getInstance().getMaxBalance().longValue(), true, genesisBlock.getBlockData().getGeneratorSignature());
|
||||
repository.getAssetRepository().save(qoraAssetData);
|
||||
// Add initial assets
|
||||
// NOTE: Asset's [transaction] reference doesn't exist as a transaction!
|
||||
for (AssetData assetData : genesisBlock.getInitialAssets())
|
||||
repository.getAssetRepository().save(assetData);
|
||||
|
||||
// Add Genesis Block to blockchain
|
||||
genesisBlock.process();
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
@ -10,10 +10,12 @@ import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bitcoinj.core.Base58;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.qora.account.GenesisAccount;
|
||||
import org.qora.crypto.Crypto;
|
||||
import org.qora.data.asset.AssetData;
|
||||
import org.qora.data.block.BlockData;
|
||||
import org.qora.data.transaction.GenesisTransactionData;
|
||||
import org.qora.data.transaction.TransactionData;
|
||||
@ -37,6 +39,7 @@ public class GenesisBlock extends Block {
|
||||
// Properties
|
||||
private static BlockData blockData;
|
||||
private static List<TransactionData> transactionsData;
|
||||
private static List<AssetData> initialAssets;
|
||||
|
||||
// Constructors
|
||||
|
||||
@ -51,6 +54,8 @@ public class GenesisBlock extends Block {
|
||||
// Construction from JSON
|
||||
|
||||
public static void fromJSON(JSONObject json) {
|
||||
// All parsing first, then if successful we can proceed to construction
|
||||
|
||||
// Version
|
||||
int version = 1; // but could be bumped later
|
||||
|
||||
@ -68,6 +73,9 @@ public class GenesisBlock extends Block {
|
||||
throw new RuntimeException("Unable to parse genesis timestamp");
|
||||
}
|
||||
|
||||
// Generating balance
|
||||
BigDecimal generatingBalance = Settings.getJsonBigDecimal(json, "generatingBalance");
|
||||
|
||||
// Transactions
|
||||
JSONArray transactionsJson = (JSONArray) Settings.getTypedJson(json, "transactions", JSONArray.class);
|
||||
List<TransactionData> transactions = new ArrayList<>();
|
||||
@ -96,8 +104,28 @@ public class GenesisBlock extends Block {
|
||||
}
|
||||
}
|
||||
|
||||
// Generating balance
|
||||
BigDecimal generatingBalance = Settings.getJsonBigDecimal(json, "generatingBalance");
|
||||
// Assets
|
||||
JSONArray assetsJson = (JSONArray) Settings.getTypedJson(json, "assets", JSONArray.class);
|
||||
String genesisAddress = Crypto.toAddress(GenesisAccount.PUBLIC_KEY);
|
||||
List<AssetData> assets = new ArrayList<>();
|
||||
|
||||
for (Object assetObj : assetsJson) {
|
||||
if (!(assetObj instanceof JSONObject)) {
|
||||
LOGGER.error("Genesis asset malformed in blockchain config file");
|
||||
throw new RuntimeException("Genesis asset malformed in blockchain config file");
|
||||
}
|
||||
|
||||
JSONObject assetJson = (JSONObject) assetObj;
|
||||
|
||||
String name = (String) Settings.getTypedJson(assetJson, "name", String.class);
|
||||
String description = (String) Settings.getTypedJson(assetJson, "description", String.class);
|
||||
String reference58 = (String) Settings.getTypedJson(assetJson, "reference", String.class);
|
||||
byte[] reference = Base58.decode(reference58);
|
||||
long quantity = (Long) Settings.getTypedJson(assetJson, "quantity", Long.class);
|
||||
boolean isDivisible = (Boolean) Settings.getTypedJson(assetJson, "isDivisible", Boolean.class);
|
||||
|
||||
assets.add(new AssetData(genesisAddress, name, description, quantity, isDivisible, reference));
|
||||
}
|
||||
|
||||
byte[] reference = GENESIS_REFERENCE;
|
||||
int transactionCount = transactions.size();
|
||||
@ -113,6 +141,7 @@ public class GenesisBlock extends Block {
|
||||
blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||
generatorPublicKey, generatorSignature, atCount, atFees);
|
||||
transactionsData = transactions;
|
||||
initialAssets = assets;
|
||||
}
|
||||
|
||||
// More information
|
||||
@ -134,6 +163,10 @@ public class GenesisBlock extends Block {
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<AssetData> getInitialAssets() {
|
||||
return Collections.unmodifiableList(initialAssets);
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
@Override
|
||||
|
@ -1,7 +1,10 @@
|
||||
package org.qora.controller;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.qora.api.ApiService;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.block.BlockGenerator;
|
||||
@ -27,6 +30,8 @@ public class Controller {
|
||||
public static void main(String args[]) {
|
||||
LOGGER.info("Starting up...");
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
|
||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||
Settings.getInstance();
|
||||
|
||||
|
@ -5,6 +5,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.qora.account.Account;
|
||||
import org.qora.block.BlockChain;
|
||||
import org.qora.utils.Base58;
|
||||
|
||||
public class Crypto {
|
||||
@ -28,7 +29,7 @@ public class Crypto {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
return sha256.digest(input);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
throw new RuntimeException("SHA-256 message digest not available");
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,9 +49,20 @@ public class Crypto {
|
||||
// SHA2-256 input to create new data and of known size
|
||||
byte[] inputHash = digest(input);
|
||||
|
||||
// Use BROKEN RIPEMD160 to create shorter address
|
||||
// Use RIPEMD160 to create shorter address
|
||||
if (BlockChain.getUseBrokenMD160ForAddresses()) {
|
||||
// Legacy BROKEN MD160
|
||||
BrokenMD160 brokenMD160 = new BrokenMD160();
|
||||
inputHash = brokenMD160.digest(inputHash);
|
||||
} else {
|
||||
// Use legit MD160
|
||||
try {
|
||||
MessageDigest md160 = MessageDigest.getInstance("RIPEMD160");
|
||||
inputHash = md160.digest(inputHash);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("RIPEMD160 message digest not available");
|
||||
}
|
||||
}
|
||||
|
||||
// Create address data using above hash and addressVersion (prepended)
|
||||
byte[] addressBytes = new byte[inputHash.length + 1];
|
||||
|
@ -17,7 +17,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
public class CancelOrderTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
@Schema(description = "order ID to cancel", example = "2zYCM8P3PSzUxFNPAKFsSdwg9dWQcYTPCuKkuQbx3GVxTUVjXAUwEmEnvUUss11SZ3p38C16UfYb3cbXP9sRuqFx")
|
||||
@Schema(description = "order ID to cancel", example = "real_order_ID_in_base58")
|
||||
private byte[] orderId;
|
||||
|
||||
// Constructors
|
||||
|
@ -19,11 +19,17 @@ public class IssueAssetTransactionData extends TransactionData {
|
||||
// assetId can be null but assigned during save() or during load from repository
|
||||
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||
private Long assetId = null;
|
||||
@Schema(description = "asset issuer's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
private byte[] issuerPublicKey;
|
||||
@Schema(description = "asset owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
||||
private String owner;
|
||||
@Schema(description = "asset name", example = "GOLD")
|
||||
private String assetName;
|
||||
@Schema(description = "asset description", example = "Gold asset - 1 unit represents one 1kg of gold")
|
||||
private String description;
|
||||
@Schema(description = "total supply of asset in existence (integer)", example = "1000")
|
||||
private long quantity;
|
||||
@Schema(description = "whether asset quantities can be fractional", example = "true")
|
||||
private boolean isDivisible;
|
||||
|
||||
// Constructors
|
||||
|
@ -18,7 +18,7 @@ public class PaymentTransactionData extends TransactionData {
|
||||
// Properties
|
||||
@Schema(description = "sender's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
private byte[] senderPublicKey;
|
||||
@Schema(description = "recipient's address", example = "Qj2Stco8ziE3ZQN2AdpWCmkBFfYjuz8fGu")
|
||||
@Schema(description = "recipient's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
||||
private String recipient;
|
||||
@Schema(description = "amount to send", example = "123.456")
|
||||
@XmlJavaTypeAdapter(
|
||||
|
@ -17,7 +17,7 @@ public class RegisterNameTransactionData extends TransactionData {
|
||||
// Properties
|
||||
@Schema(description = "registrant's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
private byte[] registrantPublicKey;
|
||||
@Schema(description = "new owner's address", example = "Qj2Stco8ziE3ZQN2AdpWCmkBFfYjuz8fGu")
|
||||
@Schema(description = "new owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
||||
private String owner;
|
||||
@Schema(description = "requested name", example = "my-name")
|
||||
private String name;
|
||||
|
@ -43,13 +43,17 @@ public abstract class TransactionData {
|
||||
protected byte[] creatorPublicKey;
|
||||
@Schema(description = "timestamp when transaction created, in milliseconds since unix epoch", example = "1545062012000")
|
||||
protected long timestamp;
|
||||
@Schema(description = "sender's last transaction ID", example = "47fw82McxnTQ8wtTS5A51Qojhg62b8px1rF3FhJp5a3etKeb5Y2DniL4Q6E7GbVCs6BAjHVe6sA4gTPxtYzng3AX")
|
||||
@Schema(description = "sender's last transaction ID", example = "real_transaction_reference_in_base58")
|
||||
protected byte[] reference;
|
||||
@Schema(description = "fee for processing transaction", example = "1.0")
|
||||
protected BigDecimal fee;
|
||||
@Schema(accessMode = AccessMode.READ_ONLY, description = "signature for transaction's raw bytes, using sender's private key", example = "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC")
|
||||
@Schema(accessMode = AccessMode.READ_ONLY, description = "signature for transaction's raw bytes, using sender's private key", example = "real_transaction_signature_in_base58")
|
||||
protected byte[] signature;
|
||||
|
||||
// For JAX-RS use
|
||||
@Schema(accessMode = AccessMode.READ_ONLY, description = "height of block containing transaction")
|
||||
protected Integer blockHeight;
|
||||
|
||||
// Constructors
|
||||
|
||||
// For JAX-RS
|
||||
@ -116,6 +120,10 @@ public abstract class TransactionData {
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
}
|
||||
|
||||
public void setBlockHeight(int blockHeight) {
|
||||
this.blockHeight = blockHeight;
|
||||
}
|
||||
|
||||
// Comparison
|
||||
|
||||
@Override
|
||||
|
@ -76,7 +76,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||
BigDecimal fee = resultSet.getBigDecimal(5).setScale(8);
|
||||
|
||||
return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee);
|
||||
TransactionData transactionData = this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee);
|
||||
return maybeIncludeBlockHeight(transactionData);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch transaction from repository", e);
|
||||
}
|
||||
@ -95,12 +96,21 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||
BigDecimal fee = resultSet.getBigDecimal(5).setScale(8);
|
||||
|
||||
return this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee);
|
||||
TransactionData transactionData = this.fromBase(type, signature, reference, creatorPublicKey, timestamp, fee);
|
||||
return maybeIncludeBlockHeight(transactionData);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch transaction from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
private TransactionData maybeIncludeBlockHeight(TransactionData transactionData) throws DataException {
|
||||
int blockHeight = getHeightFromSignature(transactionData.getSignature());
|
||||
if (blockHeight != 0)
|
||||
transactionData.setBlockHeight(blockHeight);
|
||||
|
||||
return transactionData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionData fromHeightAndSequence(int height, int sequence) throws DataException {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||
|
Loading…
Reference in New Issue
Block a user