Arbitrary Transaction support + various fixes

NOTE: requires HSQLDB built from svn rev 5836 or later

Fixed BuyNameTransactionData constructors not picking up nameReference.

Added new "orphan" tool to regress blockchain back to specified block.

Added new Block constructor for when receiving a block from the network.
Fixed Block generatingBalance/forging code to be compliant with v1.
Added logging of transactions that fail validation during block validation.

Fixed buyer/seller balances not being updated during name purchase.

Generally replace BigDecimal.compareTo expressions with "<operator> 0" form.
e.g. instead of someBigDecimal.compareTo(anotherBigDecimal) == 1
we now have someBigDecimal.compareTo(anotherBigDecimal) > 0

Fix amounts involved in BuyNameTransactions.

Renamed Transaction.calcSignature to .sign

Refactored Transaction.toBytesLessSignature to TransactionTransformer.toBytesForSigning,
which itself calls subclass' toBytesForSigningImpl,
which might override Transaction.toBytesForSigningImpl when special v1 mangling is required.

Corrected more cases of NTP.getTime in transaction processing
which should really be transaction's timestmap instead.

Fixed HSQLDB-related issue where strings were padded with spaces during comparison.
Some column types no longer case-insensitive as that mode of comparison
is done during transaction validation.

Added missing option_index column to CreatePollTransactionOptions which was causing
out-of-order options during fetching from repository and hence signature failures.

Added unit tests for v1-special mangled transaction signature checking.

Removed checks for remaining bytes to ByteBuffer in various transaction transformers'
fromByteBuffer() methods as the buffer underflow exception is now caught in
TransactionTransformer.fromBytes.

Corrected byte-related transformations of CreatePollTransactions that were missing
voter counts (albeit always zero).

Corrected byte-related transformations of IssueAssetTransactions that were missing
duplicate signature/reference (v1-special).

Added "txhex" tool to output transaction in hex form, given base58 tx signature.

Added "v1feeder" tool to fetch blocks from v1 node and process them.
This commit is contained in:
catbref 2018-08-02 10:02:33 +01:00
parent b401adcc55
commit 7da84b2b85
52 changed files with 1650 additions and 286 deletions

View File

@ -0,0 +1,91 @@
package data.transaction;
import java.math.BigDecimal;
import java.util.List;
import data.PaymentData;
import qora.transaction.Transaction.TransactionType;
public class ArbitraryTransactionData extends TransactionData {
// "data" field types
public enum DataType {
RAW_DATA, DATA_HASH;
}
// Properties
private int version;
private byte[] senderPublicKey;
private int service;
private byte[] data;
private DataType dataType;
private List<PaymentData> payments;
// Constructors
/** Reconstructing a V3 arbitrary transaction with signature */
public ArbitraryTransactionData(int version, byte[] senderPublicKey, int service, byte[] data, DataType dataType, List<PaymentData> payments,
BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
super(TransactionType.ARBITRARY, fee, senderPublicKey, timestamp, reference, signature);
this.version = version;
this.senderPublicKey = senderPublicKey;
this.service = service;
this.data = data;
this.dataType = dataType;
this.payments = payments;
}
/** Constructing a new V3 arbitrary transaction without signature */
public ArbitraryTransactionData(int version, byte[] senderPublicKey, int service, byte[] data, DataType dataType, List<PaymentData> payments,
BigDecimal fee, long timestamp, byte[] reference) {
this(version, senderPublicKey, service, data, dataType, payments, fee, timestamp, reference, null);
}
/** Reconstructing a V1 arbitrary transaction with signature */
public ArbitraryTransactionData(int version, byte[] senderPublicKey, int service, byte[] data, DataType dataType, BigDecimal fee, long timestamp,
byte[] reference, byte[] signature) {
this(version, senderPublicKey, service, data, dataType, null, fee, timestamp, reference, signature);
}
/** Constructing a new V1 arbitrary transaction without signature */
public ArbitraryTransactionData(int version, byte[] senderPublicKey, int service, byte[] data, DataType dataType, BigDecimal fee, long timestamp,
byte[] reference) {
this(version, senderPublicKey, service, data, dataType, null, fee, timestamp, reference, null);
}
// Getters/Setters
public int getVersion() {
return this.version;
}
public byte[] getSenderPublicKey() {
return this.senderPublicKey;
}
public int getService() {
return this.service;
}
public byte[] getData() {
return this.data;
}
public void setData(byte[] data) {
this.data = data;
}
public DataType getDataType() {
return this.dataType;
}
public void setDataType(DataType dataType) {
this.dataType = dataType;
}
public List<PaymentData> getPayments() {
return this.payments;
}
}

View File

@ -23,6 +23,7 @@ public class BuyNameTransactionData extends TransactionData {
this.name = name;
this.amount = amount;
this.seller = seller;
this.nameReference = nameReference;
}
public BuyNameTransactionData(byte[] buyerPublicKey, String name, BigDecimal amount, String seller, BigDecimal fee, long timestamp, byte[] reference,

View File

@ -8,14 +8,14 @@ import qora.transaction.Transaction.TransactionType;
public class MessageTransactionData extends TransactionData {
// Properties
protected int version;
protected byte[] senderPublicKey;
protected String recipient;
protected Long assetId;
protected BigDecimal amount;
protected byte[] data;
protected boolean isText;
protected boolean isEncrypted;
private int version;
private byte[] senderPublicKey;
private String recipient;
private Long assetId;
private BigDecimal amount;
private byte[] data;
private boolean isText;
private boolean isEncrypted;
// Constructors

52
src/orphan.java Normal file
View File

@ -0,0 +1,52 @@
import data.block.BlockData;
import qora.block.Block;
import qora.block.BlockChain;
import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
public class orphan {
public static void main(String[] args) {
if (args.length == 0) {
System.err.println("usage: orphan <new-blockchain-tip-height>");
System.exit(1);
}
int targetHeight = Integer.parseInt(args[0]);
try {
test.Common.setRepository();
} catch (DataException e) {
System.err.println("Couldn't connect to repository: " + e.getMessage());
System.exit(2);
}
try {
BlockChain.validate();
} catch (DataException e) {
System.err.println("Couldn't validate repository: " + e.getMessage());
System.exit(2);
}
try (final Repository repository = RepositoryManager.getRepository()) {
for (int height = repository.getBlockRepository().getBlockchainHeight(); height > targetHeight; --height) {
System.out.println("Orphaning block " + height);
BlockData blockData = repository.getBlockRepository().fromHeight(height);
Block block = new Block(repository, blockData);
block.orphan();
repository.saveChanges();
}
} catch (DataException e) {
e.printStackTrace();
}
try {
test.Common.closeRepository();
} catch (DataException e) {
e.printStackTrace();
}
}
}

View File

@ -94,6 +94,19 @@ public class Block {
this.generator = new PublicKeyAccount(repository, blockData.getGeneratorPublicKey());
}
// When receiving a block over network?
public Block(Repository repository, BlockData blockData, List<TransactionData> transactions) throws DataException {
this(repository, blockData);
this.transactions = new ArrayList<Transaction>();
// We have to sum fees too
for (TransactionData transactionData : transactions) {
this.transactions.add(Transaction.fromData(repository, transactionData));
this.blockData.setTotalFees(this.blockData.getTotalFees().add(transactionData.getFee()));
}
}
// For creating a new block?
public Block(Repository repository, int version, byte[] reference, long timestamp, BigDecimal generatingBalance, PrivateKeyAccount generator,
byte[] atBytes, BigDecimal atFees) {
@ -211,8 +224,11 @@ public class Block {
long expectedGeneratingTime = Block.calcForgingDelay(this.blockData.getGeneratingBalance()) * BlockChain.BLOCK_RETARGET_INTERVAL * 1000;
// Finally, scale generating balance such that faster than expected previous intervals produce larger generating balances.
BigDecimal multiplier = BigDecimal.valueOf((double) expectedGeneratingTime / (double) previousGeneratingTime);
this.cachedNextGeneratingBalance = BlockChain.minMaxBalance(this.blockData.getGeneratingBalance().multiply(multiplier));
// NOTE: we have to use doubles and longs here to keep compatibility with Qora v1 results
double multiplier = (double) expectedGeneratingTime / (double) previousGeneratingTime;
long nextGeneratingBalance = (long) (this.blockData.getGeneratingBalance().doubleValue() * multiplier);
this.cachedNextGeneratingBalance = BlockChain.minMaxBalance(BigDecimal.valueOf(nextGeneratingBalance).setScale(8));
return this.cachedNextGeneratingBalance;
}
@ -257,9 +273,9 @@ public class Block {
byte[] hashData;
if (this.blockData.getVersion() < 3)
hashData = this.blockData.getSignature();
hashData = this.blockData.getGeneratorSignature();
else
hashData = Bytes.concat(this.blockData.getSignature(), generator.getPublicKey());
hashData = Bytes.concat(this.blockData.getReference(), generator.getPublicKey());
// Calculate 32-byte hash as pseudo-random, but deterministic, integer (unique to this generator for v3+ blocks)
byte[] hash = Crypto.digest(hashData);
@ -501,11 +517,11 @@ public class Block {
return ValidationResult.FEATURE_NOT_YET_RELEASED;
// Check generating balance
if (this.blockData.getGeneratingBalance() != parentBlock.calcNextBlockGeneratingBalance())
if (this.blockData.getGeneratingBalance().compareTo(parentBlock.calcNextBlockGeneratingBalance()) != 0)
return ValidationResult.GENERATING_BALANCE_INCORRECT;
// Check generator is allowed to forge this block at this time
BigInteger hashValue = parentBlock.calcBlockHash();
BigInteger hashValue = this.calcBlockHash();
BigInteger target = parentBlock.calcGeneratorsTarget(this.generator);
// Multiply target by guesses
@ -547,15 +563,19 @@ public class Block {
// Check transaction is even valid
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
if (transaction.isValid() != Transaction.ValidationResult.OK)
Transaction.ValidationResult validationResult = transaction.isValid();
if (validationResult != Transaction.ValidationResult.OK) {
System.err.println("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
+ validationResult.value);
return ValidationResult.TRANSACTION_INVALID;
}
// Process transaction to make sure other transactions validate properly
try {
transaction.process();
} catch (Exception e) {
// LOGGER.error("Exception during transaction processing, tx " + Base58.encode(transaction.getSignature()), e);
System.err.println("Exception during transaction processing, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
// LOGGER.error("Exception during transaction validation, tx " + Base58.encode(transaction.getSignature()), e);
System.err.println("Exception during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
+ e.getMessage());
e.printStackTrace();
return ValidationResult.TRANSACTION_PROCESSING_FAILED;
@ -587,7 +607,7 @@ public class Block {
// If fees are non-zero then add fees to generator's balance
BigDecimal blockFee = this.blockData.getTotalFees();
if (blockFee.compareTo(BigDecimal.ZERO) == 1)
if (blockFee.compareTo(BigDecimal.ZERO) > 0)
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
@ -629,7 +649,7 @@ public class Block {
// If fees are non-zero then remove fees from generator's balance
BigDecimal blockFee = this.blockData.getTotalFees();
if (blockFee.compareTo(BigDecimal.ZERO) == 1)
if (blockFee.compareTo(BigDecimal.ZERO) > 0)
this.generator.setConfirmedBalance(Asset.QORA, this.generator.getConfirmedBalance(Asset.QORA).subtract(blockFee));
// Delete block from blockchain

View File

@ -37,6 +37,10 @@ public class BlockChain {
private static final long POWFIX_RELEASE_TIMESTAMP = 1456426800000L; // Block Version 3 // 2016-02-25T19:00:00+00:00
private static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch
private static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00
private static final long ARBITRARY_RELEASE_TIMESTAMP = 1405702800000L; // 2014-07-18T17:00:00+00:00
private static final long CREATE_POLL_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 CREATE POLL transactions
private static final long ISSUE_ASSET_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 ISSUE ASSET transactions
private static final long CREATE_ORDER_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 CREATE ORDER transactions
/**
* Some sort start-up/initialization/checking method.
@ -132,4 +136,32 @@ public class BlockChain {
return VOTING_RELEASE_TIMESTAMP;
}
public static long getArbitraryReleaseTimestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return ARBITRARY_RELEASE_TIMESTAMP;
}
public static long getCreatePollV2Timestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return CREATE_POLL_V2_TIMESTAMP;
}
public static long getIssueAssetV2Timestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return ISSUE_ASSET_V2_TIMESTAMP;
}
public static long getCreateOrderV2Timestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return CREATE_ORDER_V2_TIMESTAMP;
}
}

View File

@ -9,6 +9,7 @@ import data.transaction.TransactionData;
import data.transaction.UpdateNameTransactionData;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import repository.DataException;
import repository.Repository;
@ -152,9 +153,15 @@ public class Name {
// Mark not for-sale but leave price in case we want to orphan
this.nameData.setIsForSale(false);
// Update seller's balance
Account seller = new Account(this.repository, this.nameData.getOwner());
seller.setConfirmedBalance(Asset.QORA, seller.getConfirmedBalance(Asset.QORA).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()));
// Update reference in transaction data
buyNameTransactionData.setNameReference(this.nameData.getReference());
@ -173,9 +180,17 @@ public class Name {
// Previous name reference is taken from this transaction's cached copy
this.nameData.setReference(buyNameTransactionData.getNameReference());
// Revert buyer's balance
Account buyer = new PublicKeyAccount(this.repository, buyNameTransactionData.getBuyerPublicKey());
buyer.setConfirmedBalance(Asset.QORA, buyer.getConfirmedBalance(Asset.QORA).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()));
// Save reverted name data
this.repository.getNameRepository().save(this.nameData);
}

View File

@ -46,35 +46,36 @@ public class Payment {
amountsByAssetId.put(Asset.QORA, fee);
// Check payments, and calculate amount total by assetId
for (PaymentData paymentData : payments) {
// Check amount is positive
if (paymentData.getAmount().compareTo(BigDecimal.ZERO) < 0)
return ValidationResult.NEGATIVE_AMOUNT;
if (payments != null)
for (PaymentData paymentData : payments) {
// Check amount is positive
if (paymentData.getAmount().compareTo(BigDecimal.ZERO) < 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Optional zero-amount check
if (!isZeroAmountValid && paymentData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Optional zero-amount check
if (!isZeroAmountValid && paymentData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Check recipient address is valid
if (!Crypto.isValidAddress(paymentData.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
// Check recipient address is valid
if (!Crypto.isValidAddress(paymentData.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
AssetData assetData = assetRepository.fromAssetId(paymentData.getAssetId());
// Check asset even exists
if (assetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
AssetData assetData = assetRepository.fromAssetId(paymentData.getAssetId());
// Check asset even exists
if (assetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
// Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && paymentData.getAmount().stripTrailingZeros().scale() > 0)
return ValidationResult.INVALID_AMOUNT;
// Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && paymentData.getAmount().stripTrailingZeros().scale() > 0)
return ValidationResult.INVALID_AMOUNT;
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? amount : amount.add(paymentData.getAmount()));
}
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? amount : amount.add(paymentData.getAmount()));
}
// 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()) == -1)
if (sender.getConfirmedBalance(pair.getKey()).compareTo(pair.getValue()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -103,21 +104,22 @@ public class Payment {
sender.setLastReference(signature);
// Process all payments
for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
BigDecimal amount = paymentData.getAmount();
if (payments != null)
for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
BigDecimal amount = paymentData.getAmount();
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
if (assetId == Asset.QORA && recipient.getLastReference() == null)
recipient.setLastReference(signature);
}
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
if (assetId == Asset.QORA && recipient.getLastReference() == null)
recipient.setLastReference(signature);
}
}
public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature) throws DataException {
@ -133,24 +135,26 @@ public class Payment {
// Update sender's reference
sender.setLastReference(reference);
for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
BigDecimal amount = paymentData.getAmount();
// Orphan all payments
if (payments != null)
for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
BigDecimal amount = paymentData.getAmount();
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
/*
* 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(), signature))
recipient.setLastReference(null);
}
/*
* 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(), signature))
recipient.setLastReference(null);
}
}
public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference) throws DataException {

View File

@ -0,0 +1,195 @@
package qora.transaction;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import data.PaymentData;
import data.transaction.ArbitraryTransactionData;
import data.transaction.TransactionData;
import data.transaction.ArbitraryTransactionData.DataType;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import qora.block.BlockChain;
import qora.crypto.Crypto;
import qora.payment.Payment;
import repository.DataException;
import repository.Repository;
import settings.Settings;
import utils.Base58;
public class ArbitraryTransaction extends Transaction {
// Properties
private ArbitraryTransactionData arbitraryTransactionData;
// Other useful constants
public static final int MAX_DATA_SIZE = 4000;
// Constructors
public ArbitraryTransaction(Repository repository, TransactionData transactionData) {
super(repository, transactionData);
this.arbitraryTransactionData = (ArbitraryTransactionData) this.transactionData;
}
// More information
public List<Account> getRecipientAccounts() throws DataException {
List<Account> recipients = new ArrayList<Account>();
if (arbitraryTransactionData.getVersion() != 1)
for (PaymentData paymentData : arbitraryTransactionData.getPayments())
recipients.add(new Account(this.repository, paymentData.getRecipient()));
return recipients;
}
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
if (arbitraryTransactionData.getVersion() != 1)
for (PaymentData paymentData : arbitraryTransactionData.getPayments())
if (address.equals(paymentData.getRecipient()))
return true;
return false;
}
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String senderAddress = this.getSender().getAddress();
if (address.equals(senderAddress))
amount = amount.subtract(this.transactionData.getFee());
if (arbitraryTransactionData.getVersion() != 1)
for (PaymentData paymentData : arbitraryTransactionData.getPayments())
// We're only interested in QORA
if (paymentData.getAssetId() == Asset.QORA) {
if (address.equals(paymentData.getRecipient()))
amount = amount.add(paymentData.getAmount());
else if (address.equals(senderAddress))
amount = amount.subtract(paymentData.getAmount());
}
return amount;
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.arbitraryTransactionData.getSenderPublicKey());
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
// Are arbitrary transactions even allowed at this point?
if (arbitraryTransactionData.getVersion() != ArbitraryTransaction.getVersionByTimestamp(arbitraryTransactionData.getTimestamp()))
return ValidationResult.NOT_YET_RELEASED;
if (this.arbitraryTransactionData.getTimestamp() < BlockChain.getArbitraryReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED;
// Check data length
if (arbitraryTransactionData.getData().length < 1 || arbitraryTransactionData.getData().length > MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check reference is correct
Account sender = getSender();
if (!Arrays.equals(sender.getLastReference(), arbitraryTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
// Wrap and delegate final payment checks to Payment class
return new Payment(this.repository).isValid(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee());
}
@Override
public void process() throws DataException {
/*
* We might have either raw data or only a hash of data, depending on content filtering.
*
* If we have raw data then we need to save it somewhere and store the hash in the repository.
*/
if (arbitraryTransactionData.getDataType() == DataType.RAW_DATA) {
byte[] rawData = arbitraryTransactionData.getData();
// Calculate hash of data and update our transaction to use that
byte[] dataHash = Crypto.digest(rawData);
arbitraryTransactionData.setData(dataHash);
arbitraryTransactionData.setDataType(DataType.DATA_HASH);
// Now store actual data somewhere, e.g. <userpath>/arbitrary/<sender address>/<block height>/<tx-sig>-<service>.raw
Account sender = this.getSender();
int blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
String dataPathname = Settings.getInstance().getUserpath() + "arbitrary" + File.separator + sender.getAddress() + File.separator + blockHeight
+ File.separator + Base58.encode(arbitraryTransactionData.getSignature()) + "-" + arbitraryTransactionData.getService() + ".raw";
Path dataPath = Paths.get(dataPathname);
// Make sure directory structure exists
try {
Files.createDirectories(dataPath.getParent());
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// Output actual transaction data
try (OutputStream dataOut = Files.newOutputStream(dataPath)) {
dataOut.write(rawData);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// Wrap and delegate payment processing to Payment class
new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Delete corresponding data file (if any - storing raw data is optional)
Account sender = this.getSender();
int blockHeight = this.repository.getBlockRepository().getBlockchainHeight();
String dataPathname = Settings.getInstance().getUserpath() + "arbitrary" + File.separator + sender.getAddress() + File.separator + blockHeight
+ File.separator + Base58.encode(arbitraryTransactionData.getSignature()) + "-" + arbitraryTransactionData.getService() + ".raw";
Path dataPath = Paths.get(dataPathname);
try {
Files.deleteIfExists(dataPath);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);
// Wrap and delegate payment processing to Payment class
new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference());
}
}

View File

@ -56,7 +56,10 @@ public class BuyNameTransaction extends Transaction {
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (address.equals(this.getBuyer().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
amount = amount.subtract(this.transactionData.getFee()).subtract(this.buyNameTransactionData.getAmount());
if (address.equals(this.buyNameTransactionData.getSeller()))
amount = amount.add(this.buyNameTransactionData.getAmount());
return amount;
}
@ -112,7 +115,7 @@ public class BuyNameTransaction extends Transaction {
return ValidationResult.INVALID_REFERENCE;
// Check issuer has enough funds
if (buyer.getConfirmedBalance(Asset.QORA).compareTo(buyNameTransactionData.getFee()) == -1)
if (buyer.getConfirmedBalance(Asset.QORA).compareTo(buyNameTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@ -83,7 +83,7 @@ public class CancelOrderTransaction extends Transaction {
return ValidationResult.INVALID_ORDER_CREATOR;
// Check creator has enough QORA for fee
if (creator.getConfirmedBalance(Asset.QORA).compareTo(cancelOrderTransactionData.getFee()) == -1)
if (creator.getConfirmedBalance(Asset.QORA).compareTo(cancelOrderTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
// Check reference is correct

View File

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

View File

@ -64,6 +64,7 @@ public class CreateOrderTransaction extends Transaction {
// Processing
@Override
public ValidationResult isValid() throws DataException {
long haveAssetId = createOrderTransactionData.getHaveAssetId();
long wantAssetId = createOrderTransactionData.getWantAssetId();
@ -106,17 +107,17 @@ public class CreateOrderTransaction extends Transaction {
// If asset is QORA then we need to check amount + fee in one go
if (haveAssetId == Asset.QORA) {
// Check creator has enough funds for amount + fee in QORA
if (creator.getConfirmedBalance(Asset.QORA).compareTo(createOrderTransactionData.getAmount().add(createOrderTransactionData.getFee())) == -1)
if (creator.getConfirmedBalance(Asset.QORA).compareTo(createOrderTransactionData.getAmount().add(createOrderTransactionData.getFee())) < 0)
return ValidationResult.NO_BALANCE;
} else {
// Check creator has enough funds for amount in whatever asset
if (creator.getConfirmedBalance(haveAssetId).compareTo(createOrderTransactionData.getAmount()) == -1)
if (creator.getConfirmedBalance(haveAssetId).compareTo(createOrderTransactionData.getAmount()) < 0)
return ValidationResult.NO_BALANCE;
// 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.getPowFixReleaseTimestamp()
&& creator.getConfirmedBalance(Asset.QORA).compareTo(createOrderTransactionData.getFee()) == -1)
&& creator.getConfirmedBalance(Asset.QORA).compareTo(createOrderTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
}
@ -132,6 +133,7 @@ public class CreateOrderTransaction extends Transaction {
return ValidationResult.OK;
}
@Override
public void process() throws DataException {
Account creator = getCreator();
@ -155,6 +157,7 @@ public class CreateOrderTransaction extends Transaction {
new Order(this.repository, orderData).process();
}
@Override
public void orphan() throws DataException {
Account creator = getCreator();

View File

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

View File

@ -76,7 +76,7 @@ public class GenesisTransaction extends Transaction {
* @throws IllegalStateException
*/
@Override
public void calcSignature(PrivateKeyAccount signer) {
public void sign(PrivateKeyAccount signer) {
throw new IllegalStateException("There is no private key for genesis transactions");
}
@ -115,7 +115,7 @@ public class GenesisTransaction extends Transaction {
@Override
public ValidationResult isValid() {
// Check amount is zero or positive
if (genesisTransactionData.getAmount().compareTo(BigDecimal.ZERO) == -1)
if (genesisTransactionData.getAmount().compareTo(BigDecimal.ZERO) >= 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Check recipient address is valid

View File

@ -76,6 +76,7 @@ public class IssueAssetTransaction extends Transaction {
// Processing
@Override
public ValidationResult isValid() throws DataException {
// Are IssueAssetTransactions even allowed at this point?
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
@ -112,7 +113,7 @@ public class IssueAssetTransaction extends Transaction {
return ValidationResult.INVALID_REFERENCE;
// Check issuer has enough funds
if (issuer.getConfirmedBalance(Asset.QORA).compareTo(issueAssetTransactionData.getFee()) == -1)
if (issuer.getConfirmedBalance(Asset.QORA).compareTo(issueAssetTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
// XXX: Surely we want to check the asset name isn't already taken? This check is not present in gen1.
@ -122,6 +123,7 @@ public class IssueAssetTransaction extends Transaction {
return ValidationResult.OK;
}
@Override
public void process() throws DataException {
// Issue asset
Asset asset = new Asset(this.repository, issueAssetTransactionData);
@ -145,6 +147,7 @@ public class IssueAssetTransaction extends Transaction {
owner.setConfirmedBalance(issueAssetTransactionData.getAssetId(), BigDecimal.valueOf(issueAssetTransactionData.getQuantity()).setScale(8));
}
@Override
public void orphan() throws DataException {
// Remove asset from owner
Account owner = getOwner();

View File

@ -85,6 +85,7 @@ public class MessageTransaction extends Transaction {
return new PaymentData(messageTransactionData.getRecipient(), Asset.QORA, messageTransactionData.getAmount());
}
@Override
public ValidationResult isValid() throws DataException {
// Are message transactions even allowed at this point?
if (messageTransactionData.getVersion() != MessageTransaction.getVersionByTimestamp(messageTransactionData.getTimestamp()))
@ -110,6 +111,7 @@ public class MessageTransaction extends Transaction {
isZeroAmountValid);
}
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
@ -119,6 +121,7 @@ public class MessageTransaction extends Transaction {
messageTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);

View File

@ -15,7 +15,6 @@ import qora.block.BlockChain;
import qora.payment.Payment;
import repository.DataException;
import repository.Repository;
import utils.NTP;
public class MultiPaymentTransaction extends Transaction {
@ -90,7 +89,7 @@ public class MultiPaymentTransaction extends Transaction {
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
// Are MultiPaymentTransactions even allowed at this point?
if (NTP.getTime() < BlockChain.getAssetsReleaseTimestamp())
if (this.multiPaymentTransactionData.getTimestamp() < BlockChain.getAssetsReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED;
// Check number of payments
@ -106,7 +105,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.getPowFixReleaseTimestamp()
&& sender.getConfirmedBalance(Asset.QORA).compareTo(multiPaymentTransactionData.getFee()) == -1)
&& sender.getConfirmedBalance(Asset.QORA).compareTo(multiPaymentTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return new Payment(this.repository).isValid(multiPaymentTransactionData.getSenderPublicKey(), payments, multiPaymentTransactionData.getFee());

View File

@ -72,6 +72,7 @@ public class PaymentTransaction extends Transaction {
return new PaymentData(paymentTransactionData.getRecipient(), Asset.QORA, paymentTransactionData.getAmount());
}
@Override
public ValidationResult isValid() throws DataException {
// Check reference is correct
Account sender = getSender();
@ -82,6 +83,7 @@ public class PaymentTransaction extends Transaction {
return new Payment(this.repository).isValid(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee());
}
@Override
public void process() throws DataException {
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
@ -91,6 +93,7 @@ public class PaymentTransaction extends Transaction {
paymentTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Delete this transaction
this.repository.getTransactionRepository().delete(this.transactionData);

View File

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

View File

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

View File

@ -2,9 +2,9 @@ package qora.transaction;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
@ -18,7 +18,6 @@ import repository.DataException;
import repository.Repository;
import settings.Settings;
import transform.TransformationException;
import transform.Transformer;
import transform.transaction.TransactionTransformer;
public abstract class Transaction {
@ -127,6 +126,9 @@ public abstract class Transaction {
case VOTE_ON_POLL:
return new VoteOnPollTransaction(repository, transactionData);
case ARBITRARY:
return new ArbitraryTransaction(repository, transactionData);
case ISSUE_ASSET:
return new IssueAssetTransaction(repository, transactionData);
@ -322,30 +324,14 @@ public abstract class Transaction {
return this.repository.getTransactionRepository().fromReference(signature);
}
/**
* Serialize transaction as byte[], stripping off trailing signature.
* <p>
* Used by signature-related methods such as {@link TransactionHandler#calcSignature(PrivateKeyAccount)} and {@link TransactionHandler#isSignatureValid()}
*
* @return byte[]
*/
private byte[] toBytesLessSignature() {
try {
byte[] bytes = TransactionTransformer.toBytes(this.transactionData);
if (this.transactionData.getSignature() == null)
return bytes;
return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH);
} catch (TransformationException e) {
throw new RuntimeException("Unable to transform transaction to signature-less byte array", e);
}
}
// Processing
public void calcSignature(PrivateKeyAccount signer) {
this.transactionData.setSignature(signer.sign(this.toBytesLessSignature()));
public void sign(PrivateKeyAccount signer) {
try {
this.transactionData.setSignature(signer.sign(TransactionTransformer.toBytesForSigning(transactionData)));
} catch (TransformationException e) {
throw new RuntimeException("Unable to transform transaction to byte array for signing", e);
}
}
public boolean isSignatureValid() {
@ -353,7 +339,11 @@ public abstract class Transaction {
if (signature == null)
return false;
return PublicKeyAccount.verify(this.transactionData.getCreatorPublicKey(), signature, this.toBytesLessSignature());
try {
return PublicKeyAccount.verify(this.transactionData.getCreatorPublicKey(), signature, TransactionTransformer.toBytesForSigning(transactionData));
} catch (TransformationException e) {
throw new RuntimeException("Unable to transform transaction to byte array for verification", e);
}
}
/**

View File

@ -8,7 +8,6 @@ import java.util.List;
import data.PaymentData;
import data.transaction.TransactionData;
import data.transaction.TransferAssetTransactionData;
import utils.NTP;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
@ -82,10 +81,9 @@ public class TransferAssetTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
// Are IssueAssetTransactions even allowed at this point?
if (NTP.getTime() < BlockChain.getAssetsReleaseTimestamp())
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
if (this.transferAssetTransactionData.getTimestamp() < BlockChain.getVotingReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED;
// Check reference is correct
@ -100,8 +98,6 @@ public class TransferAssetTransaction extends Transaction {
@Override
public void process() throws DataException {
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
@ -112,8 +108,6 @@ public class TransferAssetTransaction extends Transaction {
@Override
public void orphan() throws DataException {
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);

View File

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

View File

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

View File

@ -76,6 +76,9 @@ public class HSQLDBDatabaseUpdates {
case 0:
// create from new
stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD");
stmt.execute("CREATE COLLATION SQL_TEXT_UCC_NO_PAD FOR SQL_TEXT FROM SQL_TEXT_UCC NO PAD");
stmt.execute("CREATE COLLATION SQL_TEXT_NO_PAD FOR SQL_TEXT FROM SQL_TEXT NO PAD");
stmt.execute("SET FILES SPACE TRUE");
stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )");
stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )");
@ -84,17 +87,17 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CREATE TYPE QoraAddress AS VARCHAR(36)");
stmt.execute("CREATE TYPE QoraPublicKey AS VARBINARY(32)");
stmt.execute("CREATE TYPE QoraAmount AS DECIMAL(19, 8)");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE PollOptionIndex AS INTEGER");
stmt.execute("CREATE TYPE DataHash AS VARCHAR(100)");
stmt.execute("CREATE TYPE DataHash AS VARBINARY(32)");
stmt.execute("CREATE TYPE AssetID AS BIGINT");
stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE AssetOrderID AS VARBINARY(64)");
stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC");
stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD");
break;
case 1:
@ -200,8 +203,8 @@ public class HSQLDBDatabaseUpdates {
+ "poll_name PollName NOT NULL, description VARCHAR(4000) NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option_name PollOption, "
+ "PRIMARY KEY (signature, option_name), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option_index TINYINT NOT NULL, option_name PollOption, "
+ "PRIMARY KEY (signature, option_index), FOREIGN KEY (signature) REFERENCES CreatePollTransactions (signature) ON DELETE CASCADE)");
// For the future: add flag to polls to allow one or multiple votes per voter
break;
@ -221,8 +224,8 @@ public class HSQLDBDatabaseUpdates {
case 13:
// Arbitrary Transactions
stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, creator QoraPublicKey NOT NULL, service TINYINT NOT NULL, "
+ "data_hash DataHash NOT NULL, "
stmt.execute("CREATE TABLE ArbitraryTransactions (signature Signature, sender QoraPublicKey NOT NULL, version TINYINT NOT NULL, "
+ "service TINYINT NOT NULL, data_hash DataHash NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// NB: Actual data payload stored elsewhere
// For the future: data payload should be encrypted, at the very least with transaction's reference as the seed for the encryption key

View File

@ -0,0 +1,67 @@
package repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import data.PaymentData;
import data.transaction.ArbitraryTransactionData;
import data.transaction.ArbitraryTransactionData.DataType;
import data.transaction.TransactionData;
import repository.DataException;
import repository.hsqldb.HSQLDBRepository;
import repository.hsqldb.HSQLDBSaver;
public class HSQLDBArbitraryTransactionRepository extends HSQLDBTransactionRepository {
public HSQLDBArbitraryTransactionRepository(HSQLDBRepository repository) {
this.repository = repository;
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT sender, version, service, data_hash from ArbitraryTransactions WHERE signature = ?",
signature);
if (rs == null)
return null;
byte[] senderPublicKey = rs.getBytes(1);
int version = rs.getInt(2);
int service = rs.getInt(3);
byte[] dataHash = rs.getBytes(4);
List<PaymentData> payments = this.getPaymentsFromSignature(signature);
return new ArbitraryTransactionData(version, senderPublicKey, service, dataHash, DataType.DATA_HASH, payments, fee, timestamp, reference,
signature);
} catch (SQLException e) {
throw new DataException("Unable to fetch arbitrary transaction from repository", e);
}
}
@Override
public void save(TransactionData transactionData) throws DataException {
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData;
// Refuse to store raw data in the repository - it needs to be saved elsewhere!
if (arbitraryTransactionData.getDataType() != DataType.DATA_HASH)
throw new DataException("Refusing to save arbitrary transaction data into repository");
HSQLDBSaver saveHelper = new HSQLDBSaver("ArbitraryTransactions");
saveHelper.bind("signature", arbitraryTransactionData.getSignature()).bind("sender", arbitraryTransactionData.getSenderPublicKey())
.bind("version", arbitraryTransactionData.getVersion()).bind("service", arbitraryTransactionData.getService()).bind("data_hash", arbitraryTransactionData.getData());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save arbitrary transaction into repository", e);
}
if (arbitraryTransactionData.getVersion() != 1)
// Save payments. If this fails then it is the caller's responsibility to catch the DataException as the underlying transaction will have been lost.
this.savePayments(transactionData.getSignature(), arbitraryTransactionData.getPayments());
}
}

View File

@ -29,7 +29,8 @@ public class HSQLDBCreatePollTransactionRepository extends HSQLDBTransactionRepo
String pollName = rs.getString(2);
String description = rs.getString(3);
rs = this.repository.checkedExecute("SELECT option_name FROM CreatePollTransactionOptions where signature = ?", signature);
rs = this.repository.checkedExecute("SELECT option_name FROM CreatePollTransactionOptions where signature = ? ORDER BY option_index ASC",
signature);
if (rs == null)
return null;
@ -65,10 +66,14 @@ public class HSQLDBCreatePollTransactionRepository extends HSQLDBTransactionRepo
}
// Now attempt to save poll options
for (PollOptionData pollOptionData : createPollTransactionData.getPollOptions()) {
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions();
for (int optionIndex = 0; optionIndex < pollOptions.size(); ++optionIndex) {
PollOptionData pollOptionData = pollOptions.get(optionIndex);
HSQLDBSaver optionSaveHelper = new HSQLDBSaver("CreatePollTransactionOptions");
optionSaveHelper.bind("signature", createPollTransactionData.getSignature()).bind("option_name", pollOptionData.getOptionName());
optionSaveHelper.bind("signature", createPollTransactionData.getSignature()).bind("option_name", pollOptionData.getOptionName())
.bind("option_index", optionIndex);
try {
optionSaveHelper.execute(this.repository);

View File

@ -28,6 +28,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
private HSQLDBBuyNameTransactionRepository buyNameTransactionRepository;
private HSQLDBCreatePollTransactionRepository createPollTransactionRepository;
private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository;
private HSQLDBArbitraryTransactionRepository arbitraryTransactionRepository;
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
private HSQLDBTransferAssetTransactionRepository transferAssetTransactionRepository;
private HSQLDBCreateOrderTransactionRepository createOrderTransactionRepository;
@ -46,6 +47,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
this.buyNameTransactionRepository = new HSQLDBBuyNameTransactionRepository(repository);
this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository);
this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository);
this.arbitraryTransactionRepository = new HSQLDBArbitraryTransactionRepository(repository);
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
this.transferAssetTransactionRepository = new HSQLDBTransferAssetTransactionRepository(repository);
this.createOrderTransactionRepository = new HSQLDBCreateOrderTransactionRepository(repository);
@ -123,6 +125,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
case VOTE_ON_POLL:
return this.voteOnPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
case ARBITRARY:
return this.arbitraryTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
case ISSUE_ASSET:
return this.issueAssetTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
@ -273,6 +278,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
this.voteOnPollTransactionRepository.save(transactionData);
break;
case ARBITRARY:
this.arbitraryTransactionRepository.save(transactionData);
break;
case ISSUE_ASSET:
this.issueAssetTransactionRepository.save(transactionData);
break;

View File

@ -18,6 +18,7 @@ public class Settings {
private static Settings instance;
private long genesisTimestamp = GenesisBlock.GENESIS_TIMESTAMP;
private int maxBytePerFee = 1024;
private String userpath = "";
// Constants
private static final String SETTINGS_FILENAME = "settings.json";
@ -66,6 +67,8 @@ public class Settings {
}
process(settingsJSON);
this.userpath = path;
break;
} while (true);
} catch (IOException | ClassCastException e) {
@ -115,4 +118,8 @@ public class Settings {
return this.genesisTimestamp;
}
public String getUserpath() {
return this.userpath;
}
}

View File

@ -10,7 +10,7 @@ import repository.hsqldb.HSQLDBRepositoryFactory;
public class Common {
public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true";
public static final String connectionUrl = "jdbc:hsqldb:file:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true;sql.pad_space=false";
@BeforeClass
public static void setRepository() throws DataException {

View File

@ -0,0 +1,67 @@
package test;
import static org.junit.Assert.*;
import org.junit.Test;
import com.google.common.hash.HashCode;
import data.transaction.TransactionData;
import qora.transaction.CreateOrderTransaction;
import qora.transaction.CreatePollTransaction;
import qora.transaction.IssueAssetTransaction;
import transform.TransformationException;
import transform.transaction.TransactionTransformer;
public class CompatibilityTests {
@Test
public void testCreateOrderTransactionSignature() throws TransformationException {
// 4EsGzQ87rXqXw2nic8LiihGCrM5iNErK53u9TRo2AJv4FWWyCK7bUKrCmswnrBbkB7Dsk7wfzi9hM2TGGqm6LVpd
byte[] rawTx = HashCode
.fromString("0000000d" + "000001489be3ef8e"
+ "10b52b229c73afb40a56df4f1c9f65072041011cf9ae25a053397d9fc5578bc8f1412eb404de4e318e24302863fc52889eb848af65a6b17cfc964267388f5802"
+ "bf497fa72ed16894f3acab6c4a101fd8b5fd42f0420dad45474388d5492d38d0" + "0000000000000000" + "0000000000000001"
+ "000000000000000005f5e100" + "000000000000000005f5e100" + "0000000005f5e100"
+ "a2025bfde5c90254e16150db6aef6189bb2856df51940b6a15b1d5f174451236062c982af4da3429941337abc7002a862782fb9c726bfc95aea31e30bf66a502")
.asBytes();
TransactionData transactionData = TransactionTransformer.fromBytes(rawTx);
CreateOrderTransaction transaction = new CreateOrderTransaction(null, transactionData);
assertTrue(transaction.isSignatureValid());
}
@Test
public void testCreatePollTransactionSignature() throws TransformationException {
// 5xo8YxDVTFVR1pdmtxYkRbq3PkcKVttyH7wCVAfgqokDMKE1XW6zrqFgJG8vRQz9qi5r8cqBoSgFKLnQRoSyzpgF
byte[] rawTx = HashCode
.fromString("00000008" + "00000146d4237f03"
+ "c201817ee2d4363801b63cbe154f6796719feb5a9673758dfda7b5e616cddd1263bbb75ce6a14ca116abe2d34ea68f353379d0c0d48da62180677053792f3b00"
+ "ef893a99782612754157d868fc4194577cca8ca5dd264c90855829f0e4bbec3a" + "3a91655f3c70d7a38980b449ccf7acd84de41f99dae6215ed5" + "0000000a"
+ "746869736973706f6c6c" + "00000004" + "74657374" + "00000002" + "00000011" + "546869732069732073706f6e6765626f62" + "00000000"
+ "0000000f" + "54686973206973207061747269636b" + "00000000" + "0000000005f5e100"
+ "f82f0c7421333c2cae5d0d0200e7f4726cda60baecad4ba067c7da17c681e2fb20612991f75763791b228c258f79ec2ecc40788fdda71b8f11a9032417ec7e08")
.asBytes();
TransactionData transactionData = TransactionTransformer.fromBytes(rawTx);
CreatePollTransaction transaction = new CreatePollTransaction(null, transactionData);
assertTrue(transaction.isSignatureValid());
}
@Test
public void testIssueAssetTransactionSignature() throws TransformationException {
// 3JeJ8yGnG8RCQH51S2qYJT5nfbokjHnBmM7KZsj61HPRy8K3ZWkGHh99QQ6HbRHxnknnjjAsffHRaeca1ap3tcFv
byte[] rawTx = HashCode
.fromString(
"0000000b000001489376bea34d4cbdb644be00b5848a2beeee087fdb98de49a010e686de9540f7d83720cdd182ca6efd1a6225f72f2821ed8a19f236002aef33afa4e2e419fe641c2bc4800a8dd3440f3ce0526c924f2cc15f3fdc1afcf4d57e4502c7a13bfed9851e81abc93a6a24ae1a453205b39d0c3bd24fb5eb675cd199e7cb5b316c00000003787878000000117878787878787878787878787878787878000000000000006400733fa8fa762c404ca1ddd799e93cc8ea292cd9fdd84d5c8b094050d4f576ea56071055f9fe337bf610624514f673e66462f8719759242b5635f19da088b311050000000005f5e100733fa8fa762c404ca1ddd799e93cc8ea292cd9fdd84d5c8b094050d4f576ea56071055f9fe337bf610624514f673e66462f8719759242b5635f19da088b31105")
.asBytes();
TransactionData transactionData = TransactionTransformer.fromBytes(rawTx);
IssueAssetTransaction transaction = new IssueAssetTransaction(null, transactionData);
assertTrue(transaction.isSignatureValid());
}
}

View File

@ -148,7 +148,7 @@ public class TransactionTests {
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(sender.getPublicKey(), recipient, amount, fee, timestamp, reference);
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
paymentTransaction.calcSignature(sender);
paymentTransaction.sign(sender);
return paymentTransaction;
}
@ -166,7 +166,7 @@ public class TransactionTests {
reference);
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
paymentTransaction.calcSignature(sender);
paymentTransaction.sign(sender);
assertTrue(paymentTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, paymentTransaction.isValid());
@ -227,7 +227,7 @@ public class TransactionTests {
timestamp, reference);
Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData);
registerNameTransaction.calcSignature(sender);
registerNameTransaction.sign(sender);
assertTrue(registerNameTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, registerNameTransaction.isValid());
@ -283,7 +283,7 @@ public class TransactionTests {
nameReference, fee, timestamp, reference);
Transaction updateNameTransaction = new UpdateNameTransaction(repository, updateNameTransactionData);
updateNameTransaction.calcSignature(sender);
updateNameTransaction.sign(sender);
assertTrue(updateNameTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, updateNameTransaction.isValid());
@ -328,7 +328,7 @@ public class TransactionTests {
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(sender.getPublicKey(), name, amount, fee, timestamp, reference);
Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
sellNameTransaction.calcSignature(sender);
sellNameTransaction.sign(sender);
assertTrue(sellNameTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, sellNameTransaction.isValid());
@ -379,7 +379,7 @@ public class TransactionTests {
CancelSellNameTransactionData cancelSellNameTransactionData = new CancelSellNameTransactionData(sender.getPublicKey(), name, fee, timestamp, reference);
Transaction cancelSellNameTransaction = new CancelSellNameTransaction(repository, cancelSellNameTransactionData);
cancelSellNameTransaction.calcSignature(sender);
cancelSellNameTransaction.sign(sender);
assertTrue(cancelSellNameTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, cancelSellNameTransaction.isValid());
@ -445,7 +445,7 @@ public class TransactionTests {
nameReference, fee, timestamp, buyersReference);
Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData);
buyNameTransaction.calcSignature(buyer);
buyNameTransaction.sign(buyer);
assertTrue(buyNameTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, buyNameTransaction.isValid());
@ -498,7 +498,7 @@ public class TransactionTests {
description, pollOptions, fee, timestamp, reference);
Transaction createPollTransaction = new CreatePollTransaction(repository, createPollTransactionData);
createPollTransaction.calcSignature(sender);
createPollTransaction.sign(sender);
assertTrue(createPollTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, createPollTransaction.isValid());
@ -552,7 +552,7 @@ public class TransactionTests {
reference);
Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData);
voteOnPollTransaction.calcSignature(sender);
voteOnPollTransaction.sign(sender);
assertTrue(voteOnPollTransaction.isSignatureValid());
if (optionIndex == pollOptionsSize) {
@ -624,7 +624,7 @@ public class TransactionTests {
quantity, isDivisible, fee, timestamp, reference);
Transaction issueAssetTransaction = new IssueAssetTransaction(repository, issueAssetTransactionData);
issueAssetTransaction.calcSignature(sender);
issueAssetTransaction.sign(sender);
assertTrue(issueAssetTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, issueAssetTransaction.isValid());
@ -714,7 +714,7 @@ public class TransactionTests {
assetId, fee, timestamp, reference);
Transaction transferAssetTransaction = new TransferAssetTransaction(repository, transferAssetTransactionData);
transferAssetTransaction.calcSignature(sender);
transferAssetTransaction.sign(sender);
assertTrue(transferAssetTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, transferAssetTransaction.isValid());
@ -818,7 +818,7 @@ public class TransactionTests {
CreateOrderTransactionData createOrderTransactionData = new CreateOrderTransactionData(buyer.getPublicKey(), haveAssetId, wantAssetId, amount, price,
fee, timestamp, buyersReference);
Transaction createOrderTransaction = new CreateOrderTransaction(this.repository, createOrderTransactionData);
createOrderTransaction.calcSignature(buyer);
createOrderTransaction.sign(buyer);
assertTrue(createOrderTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, createOrderTransaction.isValid());
@ -899,7 +899,7 @@ public class TransactionTests {
CancelOrderTransactionData cancelOrderTransactionData = new CancelOrderTransactionData(buyer.getPublicKey(), orderId, fee, timestamp, buyersReference);
Transaction cancelOrderTransaction = new CancelOrderTransaction(this.repository, cancelOrderTransactionData);
cancelOrderTransaction.calcSignature(buyer);
cancelOrderTransaction.sign(buyer);
assertTrue(cancelOrderTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, cancelOrderTransaction.isValid());
@ -970,7 +970,7 @@ public class TransactionTests {
CreateOrderTransactionData createOrderTransactionData = new CreateOrderTransactionData(sender.getPublicKey(), haveAssetId, wantAssetId, amount, price,
fee, timestamp, reference);
Transaction createOrderTransaction = new CreateOrderTransaction(this.repository, createOrderTransactionData);
createOrderTransaction.calcSignature(sender);
createOrderTransaction.sign(sender);
assertTrue(createOrderTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, createOrderTransaction.isValid());
@ -1073,7 +1073,7 @@ public class TransactionTests {
MultiPaymentTransactionData multiPaymentTransactionData = new MultiPaymentTransactionData(sender.getPublicKey(), payments, fee, timestamp, reference);
Transaction multiPaymentTransaction = new MultiPaymentTransaction(repository, multiPaymentTransactionData);
multiPaymentTransaction.calcSignature(sender);
multiPaymentTransaction.sign(sender);
assertTrue(multiPaymentTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, multiPaymentTransaction.isValid());
@ -1143,7 +1143,7 @@ public class TransactionTests {
data, isText, isEncrypted, fee, timestamp, reference);
Transaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
messageTransaction.calcSignature(sender);
messageTransaction.sign(sender);
assertTrue(messageTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, messageTransaction.isValid());

View File

@ -0,0 +1,155 @@
package transform.transaction;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.transaction.ArbitraryTransaction;
import data.PaymentData;
import data.transaction.ArbitraryTransactionData;
import data.transaction.ArbitraryTransactionData.DataType;
import transform.PaymentTransformer;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class ArbitraryTransactionTransformer extends TransactionTransformer {
// Property lengths
private static final int SENDER_LENGTH = PUBLIC_KEY_LENGTH;
private static final int SERVICE_LENGTH = INT_LENGTH;
private static final int DATA_SIZE_LENGTH = INT_LENGTH;
private static final int PAYMENTS_COUNT_LENGTH = INT_LENGTH;
private static final int TYPELESS_DATALESS_LENGTH_V1 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + SERVICE_LENGTH + DATA_SIZE_LENGTH;
private static final int TYPELESS_DATALESS_LENGTH_V3 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + PAYMENTS_COUNT_LENGTH + SERVICE_LENGTH + DATA_SIZE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
int version = ArbitraryTransaction.getVersionByTimestamp(timestamp);
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
// V3+ allows payments
List<PaymentData> payments = null;
if (version != 1) {
int paymentsCount = byteBuffer.getInt();
payments = new ArrayList<PaymentData>();
for (int i = 0; i < paymentsCount; ++i)
payments.add(PaymentTransformer.fromByteBuffer(byteBuffer));
}
int service = byteBuffer.getInt();
int dataSize = byteBuffer.getInt();
// Don't allow invalid dataSize here to avoid run-time issues
if (dataSize > ArbitraryTransaction.MAX_DATA_SIZE)
throw new TransformationException("ArbitraryTransaction data size too large");
byte[] data = new byte[dataSize];
byteBuffer.get(data);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
return new ArbitraryTransactionData(version, senderPublicKey, service, data, DataType.RAW_DATA, payments, fee, timestamp, reference, signature);
}
public static int getDataLength(TransactionData transactionData) throws TransformationException {
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData;
if (arbitraryTransactionData.getVersion() == 1)
return TYPE_LENGTH + TYPELESS_DATALESS_LENGTH_V1 + arbitraryTransactionData.getData().length;
else
return TYPE_LENGTH + TYPELESS_DATALESS_LENGTH_V3 + arbitraryTransactionData.getData().length
+ arbitraryTransactionData.getPayments().size() * PaymentTransformer.getDataLength();
}
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
try {
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(Ints.toByteArray(arbitraryTransactionData.getType().value));
bytes.write(Longs.toByteArray(arbitraryTransactionData.getTimestamp()));
bytes.write(arbitraryTransactionData.getReference());
bytes.write(arbitraryTransactionData.getSenderPublicKey());
if (arbitraryTransactionData.getVersion() != 1) {
List<PaymentData> payments = arbitraryTransactionData.getPayments();
bytes.write(Ints.toByteArray(payments.size()));
for (PaymentData paymentData : payments)
bytes.write(PaymentTransformer.toBytes(paymentData));
}
bytes.write(Ints.toByteArray(arbitraryTransactionData.getService()));
bytes.write(Ints.toByteArray(arbitraryTransactionData.getData().length));
bytes.write(arbitraryTransactionData.getData());
Serialization.serializeBigDecimal(bytes, arbitraryTransactionData.getFee());
if (arbitraryTransactionData.getSignature() != null)
bytes.write(arbitraryTransactionData.getSignature());
return bytes.toByteArray();
} catch (IOException | ClassCastException e) {
throw new TransformationException(e);
}
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
try {
ArbitraryTransactionData arbitraryTransactionData = (ArbitraryTransactionData) transactionData;
byte[] senderPublicKey = arbitraryTransactionData.getSenderPublicKey();
json.put("version", arbitraryTransactionData.getVersion());
json.put("sender", PublicKeyAccount.getAddress(senderPublicKey));
json.put("senderPublicKey", HashCode.fromBytes(senderPublicKey).toString());
json.put("service", arbitraryTransactionData.getService());
json.put("data", Base58.encode(arbitraryTransactionData.getData()));
if (arbitraryTransactionData.getVersion() != 1) {
List<PaymentData> payments = arbitraryTransactionData.getPayments();
JSONArray paymentsJson = new JSONArray();
for (PaymentData paymentData : payments)
paymentsJson.add(PaymentTransformer.toJSON(paymentData));
json.put("payments", paymentsJson);
}
} catch (ClassCastException e) {
throw new TransformationException(e);
}
return json;
}
}

View File

@ -30,9 +30,6 @@ public class BuyNameTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + BUYER_LENGTH + NAME_SIZE_LENGTH + AMOUNT_LENGTH + SELLER_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for BuyNameTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -42,10 +39,6 @@ public class BuyNameTransactionTransformer extends TransactionTransformer {
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < AMOUNT_LENGTH + SELLER_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for BuyNameTransaction");
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
String seller = Serialization.deserializeAddress(byteBuffer);

View File

@ -27,9 +27,6 @@ public class CancelOrderTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + ORDER_ID_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for CancelOrderTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -28,9 +28,6 @@ public class CancelSellNameTransactionTransformer extends TransactionTransformer
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for CancelSellNameTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -40,10 +37,6 @@ public class CancelSellNameTransactionTransformer extends TransactionTransformer
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for CancelSellNameTransaction");
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];

View File

@ -13,6 +13,7 @@ import com.google.common.primitives.Longs;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.block.BlockChain;
import data.transaction.CreateOrderTransactionData;
import transform.TransformationException;
import utils.Serialization;
@ -27,9 +28,6 @@ public class CreateOrderTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + (ASSET_ID_LENGTH + AMOUNT_LENGTH) * 2;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for CreateOrderTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -38,9 +36,11 @@ public class CreateOrderTransactionTransformer extends TransactionTransformer {
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
long haveAssetId = byteBuffer.getLong();
long wantAssetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH);
BigDecimal price = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
@ -82,6 +82,43 @@ public class CreateOrderTransactionTransformer extends TransactionTransformer {
}
}
/**
* In Qora v1, the bytes used for verification have mangled price so we need to test for v1-ness and adjust the bytes accordingly.
*
* @param transactionData
* @return byte[]
* @throws TransformationException
*/
public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
if (transactionData.getTimestamp() >= BlockChain.getCreateOrderV2Timestamp())
return TransactionTransformer.toBytesForSigningImpl(transactionData);
// Special v1 version
try {
CreateOrderTransactionData createOrderTransactionData = (CreateOrderTransactionData) transactionData;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(Ints.toByteArray(createOrderTransactionData.getType().value));
bytes.write(Longs.toByteArray(createOrderTransactionData.getTimestamp()));
bytes.write(createOrderTransactionData.getReference());
bytes.write(createOrderTransactionData.getCreatorPublicKey());
bytes.write(Longs.toByteArray(createOrderTransactionData.getHaveAssetId()));
bytes.write(Longs.toByteArray(createOrderTransactionData.getWantAssetId()));
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getAmount(), AMOUNT_LENGTH);
// This is the crucial difference
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getPrice(), FEE_LENGTH);
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getFee());
return bytes.toByteArray();
} catch (IOException | ClassCastException e) {
throw new TransformationException(e);
}
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);

View File

@ -19,6 +19,8 @@ import data.transaction.CreatePollTransactionData;
import data.transaction.TransactionData;
import data.voting.PollOptionData;
import qora.account.PublicKeyAccount;
import qora.block.BlockChain;
import qora.transaction.Transaction.TransactionType;
import qora.voting.Poll;
import transform.TransformationException;
import utils.Serialization;
@ -36,9 +38,6 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
+ OPTIONS_SIZE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for CreatePollTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -49,11 +48,8 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
String owner = Serialization.deserializeAddress(byteBuffer);
String pollName = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_NAME_SIZE);
String description = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_DESCRIPTION_SIZE);
// Make sure there are enough bytes left for poll options
if (byteBuffer.remaining() < OPTIONS_SIZE_LENGTH)
throw new TransformationException("Byte data too short for CreatePollTransaction");
String description = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_DESCRIPTION_SIZE);
int optionsCount = byteBuffer.getInt();
if (optionsCount < 1 || optionsCount > Poll.MAX_OPTIONS)
@ -64,11 +60,14 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
String optionName = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_NAME_SIZE);
pollOptions.add(new PollOptionData(optionName));
}
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for CreatePollTransaction");
// V1 only: voter count also present
if (timestamp < BlockChain.getCreatePollV2Timestamp()) {
int voterCount = byteBuffer.getInt();
if (voterCount != 0)
throw new TransformationException("Unexpected voter count in byte data for CreatePollTransaction");
}
}
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
@ -85,9 +84,15 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
+ Utf8.encodedLength(createPollTransactionData.getDescription());
// Add lengths for each poll options
for (PollOptionData pollOptionData : createPollTransactionData.getPollOptions())
for (PollOptionData pollOptionData : createPollTransactionData.getPollOptions()) {
// option-string-length, option-string
dataLength += INT_LENGTH + Utf8.encodedLength(pollOptionData.getOptionName());
if (transactionData.getTimestamp() < BlockChain.getCreatePollV2Timestamp())
// v1 only: voter-count (should always be zero)
dataLength += INT_LENGTH;
}
return dataLength;
}
@ -98,10 +103,13 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(Ints.toByteArray(createPollTransactionData.getType().value));
bytes.write(Longs.toByteArray(createPollTransactionData.getTimestamp()));
bytes.write(createPollTransactionData.getReference());
bytes.write(createPollTransactionData.getCreatorPublicKey());
Serialization.serializeAddress(bytes, createPollTransactionData.getOwner());
Serialization.serializeSizedString(bytes, createPollTransactionData.getPollName());
Serialization.serializeSizedString(bytes, createPollTransactionData.getDescription());
@ -109,9 +117,16 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions();
bytes.write(Ints.toByteArray(pollOptions.size()));
for (PollOptionData pollOptionData : pollOptions)
for (PollOptionData pollOptionData : pollOptions) {
Serialization.serializeSizedString(bytes, pollOptionData.getOptionName());
if (transactionData.getTimestamp() < BlockChain.getCreatePollV2Timestamp()) {
// In v1, CreatePollTransaction uses Poll.toBytes which serializes voters too.
// Zero voters as this is a new poll.
bytes.write(Ints.toByteArray(0));
}
}
Serialization.serializeBigDecimal(bytes, createPollTransactionData.getFee());
if (createPollTransactionData.getSignature() != null)
@ -123,6 +138,30 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
}
}
/**
* In Qora v1, the bytes used for verification have transaction type set to REGISTER_NAME_TRANSACTION so we need to test for v1-ness and adjust the bytes
* accordingly.
*
* @param transactionData
* @return byte[]
* @throws TransformationException
*/
public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
byte[] bytes = TransactionTransformer.toBytesForSigningImpl(transactionData);
if (transactionData.getTimestamp() >= BlockChain.getCreatePollV2Timestamp())
return bytes;
// Special v1 version
// Replace transaction type with incorrect Register Name value
System.arraycopy(Ints.toByteArray(TransactionType.REGISTER_NAME.value), 0, bytes, 0, TransactionTransformer.INT_LENGTH);
System.out.println(HashCode.fromBytes(bytes).toString());
return bytes;
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);

View File

@ -25,11 +25,10 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for GenesisTransaction");
long timestamp = byteBuffer.getLong();
String recipient = Serialization.deserializeAddress(byteBuffer);
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
return new GenesisTransactionData(recipient, amount, timestamp);

View File

@ -4,6 +4,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.json.simple.JSONObject;
@ -14,6 +15,7 @@ import com.google.common.primitives.Longs;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.block.BlockChain;
import qora.transaction.IssueAssetTransaction;
import data.transaction.IssueAssetTransactionData;
import transform.TransformationException;
@ -28,32 +30,34 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH;
private static final int QUANTITY_LENGTH = LONG_LENGTH;
private static final int IS_DIVISIBLE_LENGTH = BOOLEAN_LENGTH;
private static final int ASSET_REFERENCE_LENGTH = REFERENCE_LENGTH;
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + ISSUER_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH
+ QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for IssueAssetTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] issuerPublicKey = Serialization.deserializePublicKey(byteBuffer);
String owner = Serialization.deserializeAddress(byteBuffer);
String assetName = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_NAME_SIZE);
String description = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_DESCRIPTION_SIZE);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for IssueAssetTransaction");
long quantity = byteBuffer.getLong();
boolean isDivisible = byteBuffer.get() != 0;
byte[] assetReference = new byte[ASSET_REFERENCE_LENGTH];
// In v1, IssueAssetTransaction uses Asset.parse which also deserializes reference.
if (timestamp < BlockChain.getIssueAssetV2Timestamp())
byteBuffer.get(assetReference);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];
@ -65,8 +69,14 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
public static int getDataLength(TransactionData transactionData) throws TransformationException {
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
return TYPE_LENGTH + TYPELESS_LENGTH + Utf8.encodedLength(issueAssetTransactionData.getAssetName())
int dataLength = TYPE_LENGTH + TYPELESS_LENGTH + Utf8.encodedLength(issueAssetTransactionData.getAssetName())
+ Utf8.encodedLength(issueAssetTransactionData.getDescription());
// In v1, IssueAssetTransaction uses Asset.toBytes which also serializes reference.
if (transactionData.getTimestamp() < BlockChain.getIssueAssetV2Timestamp())
dataLength += ASSET_REFERENCE_LENGTH;
return dataLength;
}
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
@ -80,12 +90,19 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
bytes.write(issueAssetTransactionData.getReference());
bytes.write(issueAssetTransactionData.getIssuerPublicKey());
Serialization.serializeAddress(bytes, issueAssetTransactionData.getOwner());
Serialization.serializeSizedString(bytes, issueAssetTransactionData.getAssetName());
Serialization.serializeSizedString(bytes, issueAssetTransactionData.getDescription());
bytes.write(Longs.toByteArray(issueAssetTransactionData.getQuantity()));
bytes.write((byte) (issueAssetTransactionData.getIsDivisible() ? 1 : 0));
// In v1, IssueAssetTransaction uses Asset.toBytes which also serializes reference.
if (transactionData.getTimestamp() < BlockChain.getIssueAssetV2Timestamp())
bytes.write(issueAssetTransactionData.getSignature());
Serialization.serializeBigDecimal(bytes, issueAssetTransactionData.getFee());
if (issueAssetTransactionData.getSignature() != null)
@ -97,6 +114,30 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
}
}
/**
* In Qora v1, the bytes used for verification have transaction type set to REGISTER_NAME_TRANSACTION so we need to test for v1-ness and adjust the bytes
* accordingly.
*
* @param transactionData
* @return byte[]
* @throws TransformationException
*/
public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
byte[] bytes = TransactionTransformer.toBytesForSigningImpl(transactionData);
if (transactionData.getTimestamp() >= BlockChain.getIssueAssetV2Timestamp())
return bytes;
// Special v1 version
// Zero duplicate signature/reference
int start = bytes.length - TransactionTransformer.SIGNATURE_LENGTH - TransactionTransformer.BIG_DECIMAL_LENGTH;
int end = start + TransactionTransformer.SIGNATURE_LENGTH;
Arrays.fill(bytes, start, end, (byte) 0);
return bytes;
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);

View File

@ -37,22 +37,15 @@ public class MessageTransactionTransformer extends TransactionTransformer {
+ DATA_SIZE_LENGTH + IS_TEXT_LENGTH + IS_ENCRYPTED_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH_V1)
throw new TransformationException("Byte data too short for MessageTransaction");
long timestamp = byteBuffer.getLong();
int version = MessageTransaction.getVersionByTimestamp(timestamp);
int minimumRemaining = version == 1 ? TYPELESS_DATALESS_LENGTH_V1 : TYPELESS_DATALESS_LENGTH_V3;
minimumRemaining -= TIMESTAMP_LENGTH; // Already read above
if (byteBuffer.remaining() < minimumRemaining)
throw new TransformationException("Byte data too short for MessageTransaction");
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
long assetId;
@ -71,11 +64,8 @@ public class MessageTransactionTransformer extends TransactionTransformer {
byte[] data = new byte[dataSize];
byteBuffer.get(data);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for MessageTransaction");
boolean isEncrypted = byteBuffer.get() != 0;
boolean isText = byteBuffer.get() != 0;
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);

View File

@ -31,21 +31,14 @@ public class MultiPaymentTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + PAYMENTS_COUNT_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for MultiPaymentTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
int paymentsCount = byteBuffer.getInt();
// Check remaining buffer size
int minRemaining = paymentsCount * PaymentTransformer.getDataLength() + FEE_LENGTH + SIGNATURE_LENGTH;
if (byteBuffer.remaining() < minRemaining)
throw new TransformationException("Byte data too short for MultiPaymentTransaction");
int paymentsCount = byteBuffer.getInt();
List<PaymentData> payments = new ArrayList<PaymentData>();
for (int i = 0; i < paymentsCount; ++i)

View File

@ -27,16 +27,15 @@ public class PaymentTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for PaymentTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);

View File

@ -30,9 +30,6 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + REGISTRANT_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DATA_SIZE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for RegisterNameTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -43,11 +40,8 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
String owner = Serialization.deserializeAddress(byteBuffer);
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
String data = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for RegisterNameTransaction");
String data = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);

View File

@ -29,9 +29,6 @@ public class SellNameTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + AMOUNT_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for SellNameTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -41,10 +38,6 @@ public class SellNameTransactionTransformer extends TransactionTransformer {
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < AMOUNT_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for SellNameTransaction");
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);

View File

@ -1,10 +1,16 @@
package transform.transaction;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.json.simple.JSONObject;
import com.google.common.hash.HashCode;
import data.transaction.TransactionData;
import qora.account.PrivateKeyAccount;
import qora.transaction.Transaction;
import qora.transaction.Transaction.TransactionType;
import transform.TransformationException;
import transform.Transformer;
@ -24,60 +30,69 @@ public class TransactionTransformer extends Transformer {
if (bytes.length < TYPE_LENGTH)
throw new TransformationException("Byte data too short to determine transaction type");
System.out.println("v1 tx hex: " + HashCode.fromBytes(bytes).toString());
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
TransactionType type = TransactionType.valueOf(byteBuffer.getInt());
if (type == null)
return null;
switch (type) {
case GENESIS:
return GenesisTransactionTransformer.fromByteBuffer(byteBuffer);
try {
switch (type) {
case GENESIS:
return GenesisTransactionTransformer.fromByteBuffer(byteBuffer);
case PAYMENT:
return PaymentTransactionTransformer.fromByteBuffer(byteBuffer);
case PAYMENT:
return PaymentTransactionTransformer.fromByteBuffer(byteBuffer);
case REGISTER_NAME:
return RegisterNameTransactionTransformer.fromByteBuffer(byteBuffer);
case REGISTER_NAME:
return RegisterNameTransactionTransformer.fromByteBuffer(byteBuffer);
case UPDATE_NAME:
return UpdateNameTransactionTransformer.fromByteBuffer(byteBuffer);
case UPDATE_NAME:
return UpdateNameTransactionTransformer.fromByteBuffer(byteBuffer);
case SELL_NAME:
return SellNameTransactionTransformer.fromByteBuffer(byteBuffer);
case SELL_NAME:
return SellNameTransactionTransformer.fromByteBuffer(byteBuffer);
case CANCEL_SELL_NAME:
return CancelSellNameTransactionTransformer.fromByteBuffer(byteBuffer);
case CANCEL_SELL_NAME:
return CancelSellNameTransactionTransformer.fromByteBuffer(byteBuffer);
case BUY_NAME:
return BuyNameTransactionTransformer.fromByteBuffer(byteBuffer);
case BUY_NAME:
return BuyNameTransactionTransformer.fromByteBuffer(byteBuffer);
case CREATE_POLL:
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
case CREATE_POLL:
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
case VOTE_ON_POLL:
return VoteOnPollTransactionTransformer.fromByteBuffer(byteBuffer);
case VOTE_ON_POLL:
return VoteOnPollTransactionTransformer.fromByteBuffer(byteBuffer);
case ISSUE_ASSET:
return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer);
case ARBITRARY:
return ArbitraryTransactionTransformer.fromByteBuffer(byteBuffer);
case TRANSFER_ASSET:
return TransferAssetTransactionTransformer.fromByteBuffer(byteBuffer);
case ISSUE_ASSET:
return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer);
case CREATE_ASSET_ORDER:
return CreateOrderTransactionTransformer.fromByteBuffer(byteBuffer);
case TRANSFER_ASSET:
return TransferAssetTransactionTransformer.fromByteBuffer(byteBuffer);
case CANCEL_ASSET_ORDER:
return CancelOrderTransactionTransformer.fromByteBuffer(byteBuffer);
case CREATE_ASSET_ORDER:
return CreateOrderTransactionTransformer.fromByteBuffer(byteBuffer);
case MULTIPAYMENT:
return MultiPaymentTransactionTransformer.fromByteBuffer(byteBuffer);
case CANCEL_ASSET_ORDER:
return CancelOrderTransactionTransformer.fromByteBuffer(byteBuffer);
case MESSAGE:
return MessageTransactionTransformer.fromByteBuffer(byteBuffer);
case MULTIPAYMENT:
return MultiPaymentTransactionTransformer.fromByteBuffer(byteBuffer);
default:
throw new TransformationException("Unsupported transaction type [" + type.value + "] during conversion from bytes");
case MESSAGE:
return MessageTransactionTransformer.fromByteBuffer(byteBuffer);
default:
throw new TransformationException("Unsupported transaction type [" + type.value + "] during conversion from bytes");
}
} catch (BufferUnderflowException e) {
throw new TransformationException("Byte data too short for transaction type [" + type.value + "]");
}
}
@ -110,6 +125,9 @@ public class TransactionTransformer extends Transformer {
case VOTE_ON_POLL:
return VoteOnPollTransactionTransformer.getDataLength(transactionData);
case ARBITRARY:
return ArbitraryTransactionTransformer.getDataLength(transactionData);
case ISSUE_ASSET:
return IssueAssetTransactionTransformer.getDataLength(transactionData);
@ -162,6 +180,9 @@ public class TransactionTransformer extends Transformer {
case VOTE_ON_POLL:
return VoteOnPollTransactionTransformer.toBytes(transactionData);
case ARBITRARY:
return ArbitraryTransactionTransformer.toBytes(transactionData);
case ISSUE_ASSET:
return IssueAssetTransactionTransformer.toBytes(transactionData);
@ -185,6 +206,91 @@ public class TransactionTransformer extends Transformer {
}
}
/**
* Serialize transaction as byte[], stripping off trailing signature ready for signing/verification.
* <p>
* Used by signature-related methods such as {@link Transaction#sign(PrivateKeyAccount)} and {@link Transaction#isSignatureValid()}
*
* @param transactionData
* @return byte[] of transaction, without trailing signature
* @throws TransformationException
*/
public static byte[] toBytesForSigning(TransactionData transactionData) throws TransformationException {
switch (transactionData.getType()) {
case GENESIS:
return GenesisTransactionTransformer.toBytesForSigningImpl(transactionData);
case PAYMENT:
return PaymentTransactionTransformer.toBytesForSigningImpl(transactionData);
case REGISTER_NAME:
return RegisterNameTransactionTransformer.toBytesForSigningImpl(transactionData);
case UPDATE_NAME:
return UpdateNameTransactionTransformer.toBytesForSigningImpl(transactionData);
case SELL_NAME:
return SellNameTransactionTransformer.toBytesForSigningImpl(transactionData);
case CANCEL_SELL_NAME:
return CancelSellNameTransactionTransformer.toBytesForSigningImpl(transactionData);
case BUY_NAME:
return BuyNameTransactionTransformer.toBytesForSigningImpl(transactionData);
case CREATE_POLL:
return CreatePollTransactionTransformer.toBytesForSigningImpl(transactionData);
case VOTE_ON_POLL:
return VoteOnPollTransactionTransformer.toBytesForSigningImpl(transactionData);
case ARBITRARY:
return ArbitraryTransactionTransformer.toBytesForSigningImpl(transactionData);
case ISSUE_ASSET:
return IssueAssetTransactionTransformer.toBytesForSigningImpl(transactionData);
case TRANSFER_ASSET:
return TransferAssetTransactionTransformer.toBytesForSigningImpl(transactionData);
case CREATE_ASSET_ORDER:
return CreateOrderTransactionTransformer.toBytesForSigningImpl(transactionData);
case CANCEL_ASSET_ORDER:
return CancelOrderTransactionTransformer.toBytesForSigningImpl(transactionData);
case MULTIPAYMENT:
return MultiPaymentTransactionTransformer.toBytesForSigningImpl(transactionData);
case MESSAGE:
return MessageTransactionTransformer.toBytesForSigningImpl(transactionData);
default:
throw new TransformationException(
"Unsupported transaction type [" + transactionData.getType().value + "] during conversion to bytes for signing");
}
}
/**
* Generic serialization of transaction as byte[], stripping off trailing signature ready for signing/verification.
*
* @param transactionData
* @return byte[] of transaction, without trailing signature
* @throws TransformationException
*/
protected static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
try {
byte[] bytes = TransactionTransformer.toBytes(transactionData);
if (transactionData.getSignature() == null)
return bytes;
return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH);
} catch (TransformationException e) {
throw new RuntimeException("Unable to transform transaction to byte array for signing", e);
}
}
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
switch (transactionData.getType()) {
case GENESIS:
@ -214,6 +320,9 @@ public class TransactionTransformer extends Transformer {
case VOTE_ON_POLL:
return VoteOnPollTransactionTransformer.toJSON(transactionData);
case ARBITRARY:
return ArbitraryTransactionTransformer.toJSON(transactionData);
case ISSUE_ASSET:
return IssueAssetTransactionTransformer.toJSON(transactionData);

View File

@ -28,17 +28,17 @@ public class TransferAssetTransactionTransformer extends TransactionTransformer
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for TransferAssetTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
long assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);

View File

@ -30,9 +30,6 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + REGISTRANT_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DATA_SIZE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for UpdateNameTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -43,11 +40,8 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
String newOwner = Serialization.deserializeAddress(byteBuffer);
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
String newData = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for UpdateNameTransaction");
String newData = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);

View File

@ -24,14 +24,10 @@ public class VoteOnPollTransactionTransformer extends TransactionTransformer {
// Property lengths
private static final int VOTER_LENGTH = ADDRESS_LENGTH;
private static final int NAME_SIZE_LENGTH = INT_LENGTH;
private static final int OPTION_LENGTH = INT_LENGTH;
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + VOTER_LENGTH + NAME_SIZE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for VoteOnPollTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
@ -41,18 +37,10 @@ public class VoteOnPollTransactionTransformer extends TransactionTransformer {
String pollName = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_NAME_SIZE);
// Make sure there are enough bytes left for poll options
if (byteBuffer.remaining() < OPTION_LENGTH)
throw new TransformationException("Byte data too short for VoteOnPollTransaction");
int optionIndex = byteBuffer.getInt();
if (optionIndex < 0 || optionIndex >= Poll.MAX_OPTIONS)
throw new TransformationException("Invalid option number for VoteOnPollTransaction");
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for VoteOnPollTransaction");
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];
@ -81,7 +69,7 @@ public class VoteOnPollTransactionTransformer extends TransactionTransformer {
bytes.write(voteOnPollTransactionData.getVoterPublicKey());
Serialization.serializeSizedString(bytes, voteOnPollTransactionData.getPollName());
bytes.write(voteOnPollTransactionData.getOptionIndex());
bytes.write(Ints.toByteArray(voteOnPollTransactionData.getOptionIndex()));
Serialization.serializeBigDecimal(bytes, voteOnPollTransactionData.getFee());

51
src/txhex.java Normal file
View File

@ -0,0 +1,51 @@
import com.google.common.hash.HashCode;
import data.transaction.TransactionData;
import qora.block.BlockChain;
import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
import transform.TransformationException;
import transform.transaction.TransactionTransformer;
import utils.Base58;
public class txhex {
public static void main(String[] args) {
if (args.length == 0) {
System.err.println("usage: txhex <base58-tx-signature>");
System.exit(1);
}
byte[] signature = Base58.decode(args[0]);
try {
test.Common.setRepository();
} catch (DataException e) {
System.err.println("Couldn't connect to repository: " + e.getMessage());
System.exit(2);
}
try {
BlockChain.validate();
} catch (DataException e) {
System.err.println("Couldn't validate repository: " + e.getMessage());
System.exit(2);
}
try (final Repository repository = RepositoryManager.getRepository()) {
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
byte[] bytes = TransactionTransformer.toBytes(transactionData);
System.out.println(HashCode.fromBytes(bytes).toString());
} catch (DataException | TransformationException e) {
e.printStackTrace();
}
try {
test.Common.closeRepository();
} catch (DataException e) {
e.printStackTrace();
}
}
}

433
src/v1feeder.java Normal file
View File

@ -0,0 +1,433 @@
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.primitives.Ints;
import data.block.BlockData;
import data.transaction.TransactionData;
import qora.block.Block;
import qora.block.Block.ValidationResult;
import qora.block.BlockChain;
import qora.crypto.Crypto;
import repository.DataException;
import repository.Repository;
import repository.RepositoryManager;
import transform.TransformationException;
import transform.block.BlockTransformer;
import utils.Pair;
public class v1feeder extends Thread {
private static final int INACTIVITY_TIMEOUT = 60 * 1000; // milliseconds
private static final int CONNECTION_TIMEOUT = 2 * 1000; // milliseconds
private static final int PING_INTERVAL = 10 * 1000; // milliseconds
private static final int DEFAULT_PORT = 9084;
private static final int MAGIC_LENGTH = 4;
private static final int TYPE_LENGTH = 4;
private static final int HAS_ID_LENGTH = 1;
private static final int ID_LENGTH = 4;
private static final int DATA_SIZE_LENGTH = 4;
private static final int CHECKSUM_LENGTH = 4;
private static final int SIGNATURE_LENGTH = 128;
private static final byte[] MAINNET_MAGIC = { 0x12, 0x34, 0x56, 0x78 };
private static final int GET_PEERS_TYPE = 1;
private static final int PEERS_TYPE = 2;
private static final int HEIGHT_TYPE = 3;
private static final int GET_SIGNATURES_TYPE = 4;
private static final int SIGNATURES_TYPE = 5;
private static final int GET_BLOCK_TYPE = 6;
private static final int BLOCK_TYPE = 7;
private static final int TRANSACTION_TYPE = 8;
private static final int PING_TYPE = 9;
private static final int VERSION_TYPE = 10;
private static final int FIND_MYSELF_TYPE = 11;
private Socket socket;
private OutputStream out;
private static final int IDLE_STATE = 0;
private static final int AWAITING_HEADERS_STATE = 1;
private static final int HAVE_HEADERS_STATE = 2;
private static final int AWAITING_BLOCK_STATE = 3;
private static final int HAVE_BLOCK_STATE = 4;
private int feederState = IDLE_STATE;
private int messageId = -1;
private long lastPingTimestamp = System.currentTimeMillis();
private List<byte[]> signatures = new ArrayList<byte[]>();
private v1feeder(String address, int port) throws InterruptedException {
try {
for (int i = 0; i < 10; ++i)
try {
// Create new socket for connection to peer
this.socket = new Socket();
// Collate this.address and destination port
InetSocketAddress socketAddress = new InetSocketAddress(address, port);
// Attempt to connect, with timeout from settings
this.socket.connect(socketAddress, CONNECTION_TIMEOUT);
break;
} catch (SocketTimeoutException e) {
System.err.println("Timed out trying to connect to " + address + " - retrying");
Thread.sleep(1000);
this.socket = null;
} catch (Exception e) {
System.err.println("Failed to connect to " + address + ": " + e.getMessage());
return;
}
// No connection after retries?
if (this.socket == null)
return;
// Enable TCP keep-alive packets
this.socket.setKeepAlive(true);
// Inactivity timeout
this.socket.setSoTimeout(INACTIVITY_TIMEOUT);
// Grab reference to output stream
this.out = socket.getOutputStream();
// Start main communication thread
this.start();
} catch (SocketException e) {
System.err.println("Failed to set socket timeout for address " + address + ": " + e.getMessage());
} catch (IOException e) {
System.err.println("Failed to get output stream for address " + address + ": " + e.getMessage());
}
}
private byte[] createMessage(int type, boolean hasId, Integer id, byte[] data) throws IOException {
if (data == null)
data = new byte[0];
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(MAINNET_MAGIC);
bytes.write(Ints.toByteArray(type));
byte[] hasIdBytes = new byte[] { (byte) (hasId ? 1 : 0) };
bytes.write(hasIdBytes);
if (hasId) {
if (id == null)
id = (int) ((Math.random() * 1000000) + 1);
bytes.write(Ints.toByteArray(id));
}
bytes.write(Ints.toByteArray(data.length));
if (data.length > 0) {
byte[] checksum = Crypto.digest(data);
bytes.write(checksum, 0, CHECKSUM_LENGTH);
bytes.write(data);
}
// System.out.println("Creating message type [" + type + "] with " + (hasId ? "id [" + id + "]" : "no id") + " and data length " + data.length);
return bytes.toByteArray();
}
private void sendMessage(byte[] message) throws IOException {
synchronized (this.out) {
this.out.write(message);
this.out.flush();
}
}
private void processMessage(int type, int id, byte[] data) throws IOException {
// System.out.println("Received message type [" + type + "] with id [" + id + "] and data length " + data.length);
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
switch (type) {
case HEIGHT_TYPE:
int height = byteBuffer.getInt();
System.out.println("Peer height: " + height);
break;
case SIGNATURES_TYPE:
// shove into list
int numSignatures = byteBuffer.getInt();
while (numSignatures-- > 0) {
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
signatures.add(signature);
}
// System.out.println("We now have " + signatures.size() + " signature(s) to process");
feederState = HAVE_HEADERS_STATE;
break;
case BLOCK_TYPE:
// If messageId doesn't match then discard
if (id != this.messageId)
break;
// read block and process
int claimedHeight = byteBuffer.getInt();
System.out.println("Received block allegedly at height " + claimedHeight);
byte[] blockBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(blockBytes);
Pair<BlockData, List<TransactionData>> blockInfo = null;
try {
blockInfo = BlockTransformer.fromBytes(blockBytes);
} catch (TransformationException e) {
System.err.println("Couldn't parse block bytes from peer: " + e.getMessage());
System.exit(3);
}
try (final Repository repository = RepositoryManager.getRepository()) {
Block block = new Block(repository, blockInfo.getA(), blockInfo.getB());
if (!block.isSignatureValid()) {
System.err.println("Invalid block signature");
System.exit(4);
}
ValidationResult result = block.isValid();
if (result != ValidationResult.OK) {
System.err.println("Invalid block, validation result code: " + result.value);
System.exit(4);
}
block.process();
repository.saveChanges();
} catch (DataException e) {
System.err.println("Unable to process block: " + e.getMessage());
e.printStackTrace();
}
feederState = HAVE_BLOCK_STATE;
break;
case PING_TYPE:
// System.out.println("Sending pong for ping [" + id + "]");
byte[] pongMessage = createMessage(PING_TYPE, true, id, null);
sendMessage(pongMessage);
break;
case VERSION_TYPE:
long timestamp = byteBuffer.getLong();
int versionLength = byteBuffer.getInt();
byte[] versionBytes = new byte[versionLength];
byteBuffer.get(versionBytes);
String version = new String(versionBytes, Charset.forName("UTF-8"));
System.out.println("Peer version info: " + version);
break;
default:
System.out.println("Discarding message type [" + type + "] with id [" + id + "] and data length " + data.length);
}
}
private int parseBuffer(byte[] buffer, int bufferEnd) throws IOException {
int newBufferEnd = bufferEnd;
try {
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, bufferEnd);
// Check magic
byte[] magic = new byte[MAGIC_LENGTH];
byteBuffer.get(magic);
if (!Arrays.equals(magic, MAINNET_MAGIC)) {
// bad data - discard whole buffer
return 0;
}
int type = byteBuffer.getInt();
byte[] hasId = new byte[HAS_ID_LENGTH];
byteBuffer.get(hasId);
int id = -1;
if (hasId[0] == (byte) 1)
id = byteBuffer.getInt();
int dataSize = byteBuffer.getInt();
byte[] data = new byte[dataSize];
if (dataSize > 0) {
byte[] checksum = new byte[CHECKSUM_LENGTH];
byteBuffer.get(checksum);
byteBuffer.get(data);
}
// We have a full message - remove from buffer
int nextMessageOffset = byteBuffer.position();
newBufferEnd = bufferEnd - nextMessageOffset;
byteBuffer = null;
System.arraycopy(buffer, nextMessageOffset, buffer, 0, newBufferEnd);
// Process message
processMessage(type, id, data);
} catch (BufferUnderflowException e) {
// Not enough data
}
return newBufferEnd;
}
public void run() {
try {
DataInputStream in = new DataInputStream(socket.getInputStream());
byte[] buffer = new byte[2 * 1024 * 1024]; // 2MB
int bufferEnd = 0;
// Send our height
try (final Repository repository = RepositoryManager.getRepository()) {
int height = repository.getBlockRepository().getBlockchainHeight();
System.out.println("Sending our height " + height + " to peer");
byte[] heightMessage = createMessage(HEIGHT_TYPE, false, null, Ints.toByteArray(height));
sendMessage(heightMessage);
}
while (true) {
// Anything to read?
if (in.available() > 0) {
// read message
int numRead = in.read(buffer, bufferEnd, in.available());
if (numRead == -1) {
// input EOF
System.out.println("Socket EOF");
return;
}
bufferEnd += numRead;
}
if (bufferEnd > 0) {
// attempt to parse
bufferEnd = parseBuffer(buffer, bufferEnd);
}
// Do we need to send a ping message?
if (System.currentTimeMillis() - lastPingTimestamp >= PING_INTERVAL) {
byte[] pingMessage = createMessage(PING_TYPE, true, null, null);
sendMessage(pingMessage);
lastPingTimestamp = System.currentTimeMillis();
}
byte[] signature = null;
switch (feederState) {
case IDLE_STATE:
// Get signature from our highest block
try (final Repository repository = RepositoryManager.getRepository()) {
BlockData blockData = repository.getBlockRepository().getLastBlock();
if (blockData != null)
signature = blockData.getSignature();
}
// done?
if (signature == null) {
System.out.println("No last block in repository?");
return;
}
System.out.println("Requesting more signatures...");
byte[] getSignaturesMessage = createMessage(GET_SIGNATURES_TYPE, true, null, signature);
sendMessage(getSignaturesMessage);
feederState = AWAITING_HEADERS_STATE;
break;
case HAVE_HEADERS_STATE:
case HAVE_BLOCK_STATE:
// request next block?
if (signatures.size() == 0) {
feederState = IDLE_STATE;
break;
}
System.out.println("Requesting next block...");
signature = signatures.remove(0);
this.messageId = (int) ((Math.random() * 1000000) + 1);
byte[] getBlockMessage = createMessage(GET_BLOCK_TYPE, true, this.messageId, signature);
sendMessage(getBlockMessage);
feederState = AWAITING_BLOCK_STATE;
break;
}
}
} catch (IOException | DataException e) {
// give up
System.err.println("Exiting due to: " + e.getMessage());
}
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
if (args.length == 0) {
System.err.println("usage: v1feeder v1-node-address [port]");
System.exit(1);
}
try {
test.Common.setRepository();
} catch (DataException e) {
System.err.println("Couldn't connect to repository: " + e.getMessage());
System.exit(2);
}
try {
BlockChain.validate();
} catch (DataException e) {
System.err.println("Couldn't validate repository: " + e.getMessage());
System.exit(2);
}
// connect to v1 node
String address = args[0];
int port = args.length > 1 ? Integer.valueOf(args[1]) : DEFAULT_PORT;
try {
new v1feeder(address, port).join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Exiting v1feeder");
try {
test.Common.closeRepository();
} catch (DataException e) {
e.printStackTrace();
}
}
}