Synchronization, peer management + fixes

Peers now broadcast height after successful synchronization.

Added support for sending unconfirmed transactions to other peers.
This is done on connect and also after a new unconfirmed transaction is submitted via API.

Fixed synchronizer to handle blocks with transactions correctly.

Fixed network-related PoW to not use class-global SHA256 message digester!
(It was being corrupted by simulataneous access by different threads - whoops)

Surrounded Network.mergePeers with a lock to prevent HSQLDB deadlocks.
Also changed HSQLDB concurrency model to MVCC (only takes effect if DB rebuilt).

Added support for logging other HSQLDB sessions in the event of exception.
(Currently only used by HSQLDBSaver)

Transaction transformer modifications to help deserialize TransactionMessages.
This commit is contained in:
catbref 2019-02-11 17:37:52 +00:00
parent 7f4511cb7b
commit 7a53ac17a6
43 changed files with 298 additions and 87 deletions

View File

@ -28,6 +28,7 @@ import org.qora.api.ApiErrors;
import org.qora.api.ApiException;
import org.qora.api.ApiExceptionFactory;
import org.qora.api.model.SimpleTransactionSignRequest;
import org.qora.controller.Controller;
import org.qora.data.transaction.TransactionData;
import org.qora.globalization.Translator;
import org.qora.repository.DataException;
@ -388,6 +389,9 @@ public class TransactionsResource {
repository.getTransactionRepository().unconfirmTransaction(transactionData);
repository.saveChanges();
// Notify controller of new transaction
Controller.getInstance().onNewTransaction(transactionData);
return "true";
} catch (NumberFormatException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA, e);

View File

@ -23,6 +23,7 @@ import org.qora.block.BlockChain;
import org.qora.block.BlockGenerator;
import org.qora.data.block.BlockData;
import org.qora.data.network.PeerData;
import org.qora.data.transaction.TransactionData;
import org.qora.network.Network;
import org.qora.network.Peer;
import org.qora.network.message.BlockMessage;
@ -31,12 +32,15 @@ import org.qora.network.message.GetSignaturesMessage;
import org.qora.network.message.HeightMessage;
import org.qora.network.message.Message;
import org.qora.network.message.SignaturesMessage;
import org.qora.network.message.TransactionMessage;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryFactory;
import org.qora.repository.RepositoryManager;
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
import org.qora.settings.Settings;
import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.ValidationResult;
import org.qora.utils.Base58;
import org.qora.utils.NTP;
@ -216,26 +220,39 @@ public class Controller extends Thread {
// If we have enough peers, potentially synchronize
List<Peer> peers = Network.getInstance().getHandshakeCompletedPeers();
if (peers.size() >= Settings.getInstance().getMinPeers()) {
peers.removeIf(peer -> peer.getPeerData().getLastHeight() <= ourHeight);
if (peers.size() < Settings.getInstance().getMinPeers())
return;
if (!peers.isEmpty()) {
// Pick random peer to sync with
int index = new SecureRandom().nextInt(peers.size());
Peer peer = peers.get(index);
for(Peer peer : peers)
LOGGER.trace(String.format("Peer %s is at height %d", peer, peer.getPeerData().getLastHeight()));
if (!Synchronizer.getInstance().synchronize(peer)) {
// Failure so don't use this peer again for a while
try (final Repository repository = RepositoryManager.getRepository()) {
PeerData peerData = peer.getPeerData();
peerData.setLastMisbehaved(NTP.getTime());
repository.getNetworkRepository().save(peerData);
repository.saveChanges();
} catch (DataException e) {
LOGGER.warn("Repository issue while updating peer synchronization info", e);
}
peers.removeIf(peer -> peer.getPeerData().getLastHeight() <= ourHeight);
if (!peers.isEmpty()) {
// Pick random peer to sync with
int index = new SecureRandom().nextInt(peers.size());
Peer peer = peers.get(index);
if (!Synchronizer.getInstance().synchronize(peer)) {
LOGGER.debug(String.format("Failed to synchronize with peer %s", peer));
// Failure so don't use this peer again for a while
try (final Repository repository = RepositoryManager.getRepository()) {
PeerData peerData = peer.getPeerData();
peerData.setLastMisbehaved(NTP.getTime());
repository.getNetworkRepository().save(peerData);
repository.saveChanges();
} catch (DataException e) {
LOGGER.warn("Repository issue while updating peer synchronization info", e);
}
return;
}
LOGGER.debug(String.format("Synchronized with peer %s", peer));
// Broadcast our new height
Network.getInstance().broadcast(recipientPeer -> new HeightMessage(getChainHeight()));
}
}
@ -366,9 +383,42 @@ public class Controller extends Thread {
}
break;
case TRANSACTION:
try (final Repository repository = RepositoryManager.getRepository()) {
TransactionMessage transactionMessage = (TransactionMessage) message;
TransactionData transactionData = transactionMessage.getTransactionData();
Transaction transaction = Transaction.fromData(repository, transactionData);
// Check signature
if (!transaction.isSignatureValid())
break;
// Do we have it already?
if (repository.getTransactionRepository().exists(transactionData.getSignature()))
break;
// Is it valid?
if (transaction.isValidUnconfirmed() != ValidationResult.OK)
break;
// Seems ok - add to unconfirmed pile
repository.getTransactionRepository().save(transactionData);
repository.getTransactionRepository().unconfirmTransaction(transactionData);
repository.saveChanges();
} catch (DataException e) {
LOGGER.error(String.format("Repository issue while responding to %s from peer %s", message.getType().name(), peer), e);
}
break;
default:
break;
}
}
public void onNewTransaction(TransactionData transactionData) {
// Send round to all peers
Network.getInstance().broadcast(peer -> new TransactionMessage(transactionData));
}
}

View File

@ -109,15 +109,13 @@ public class Synchronizer {
signatures.remove(0);
++this.ourHeight;
BlockData newBlockData = this.fetchBlockData(peer, signature);
Block newBlock = this.fetchBlock(repository, peer, signature);
if (newBlockData == null) {
if (newBlock == null) {
LOGGER.info(String.format("Peer %s failed to respond with block for height %d", peer, this.ourHeight));
return false;
}
Block newBlock = new Block(repository, newBlockData);
if (!newBlock.isSignatureValid()) {
LOGGER.info(String.format("Peer %s sent block with invalid signature for height %d", peer, this.ourHeight));
return false;
@ -227,8 +225,8 @@ public class Synchronizer {
return blockSignatures;
}
private List<byte[]> getBlockSignatures(Peer peer, byte[] parentSignature, int countRequested) {
// TODO countRequested is v2+ feature
private List<byte[]> getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) {
// TODO numberRequested is v2+ feature
Message getSignaturesMessage = new GetSignaturesMessage(parentSignature);
Message message = peer.getResponse(getSignaturesMessage);
@ -240,7 +238,7 @@ public class Synchronizer {
return signaturesMessage.getSignatures();
}
private BlockData fetchBlockData(Peer peer, byte[] signature) {
private Block fetchBlock(Repository repository, Peer peer, byte[] signature) {
Message getBlockMessage = new GetBlockMessage(signature);
Message message = peer.getResponse(getBlockMessage);
@ -249,7 +247,12 @@ public class Synchronizer {
BlockMessage blockMessage = (BlockMessage) message;
return blockMessage.getBlockData();
try {
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
} catch (DataException e) {
LOGGER.debug("Failed to create block", e);
return null;
}
}
}

View File

@ -105,7 +105,7 @@ public enum Handshake {
}
};
private static final long MAX_TIMESTAMP_DELTA = 1000; // ms
private static final long MAX_TIMESTAMP_DELTA = 2000; // ms
public final MessageType expectedMessageType;

View File

@ -13,6 +13,8 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -21,11 +23,13 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qora.controller.Controller;
import org.qora.data.network.PeerData;
import org.qora.data.transaction.TransactionData;
import org.qora.network.message.HeightMessage;
import org.qora.network.message.Message;
import org.qora.network.message.PeersMessage;
import org.qora.network.message.PeersV2Message;
import org.qora.network.message.PingMessage;
import org.qora.network.message.TransactionMessage;
import org.qora.repository.DataException;
import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager;
@ -56,6 +60,7 @@ public class Network extends Thread {
private int maxPeers;
private ExecutorService peerExecutor;
private long nextBroadcast;
private Lock mergePeersLock;
// Constructors
@ -92,6 +97,8 @@ public class Network extends Thread {
peerExecutor = Executors.newCachedThreadPool();
nextBroadcast = System.currentTimeMillis();
mergePeersLock = new ReentrantLock();
}
// Getters / setters
@ -377,18 +384,35 @@ public class Network extends Thread {
// Make a note that we've successfully completed handshake (and when)
peer.getPeerData().setLastConnected(NTP.getTime());
// Start regular pings
peer.startPings();
// Send our height
Message heightMessage = new HeightMessage(Controller.getInstance().getChainHeight());
if (!peer.sendMessage(heightMessage)) {
peer.disconnect();
return;
}
// Send our peers list
Message peersMessage = this.buildPeersMessage(peer);
if (!peer.sendMessage(peersMessage))
peer.disconnect();
// Send our unconfirmed transactions
try (final Repository repository = RepositoryManager.getRepository()) {
List<TransactionData> transactions = repository.getTransactionRepository().getUnconfirmedTransactions();
for (TransactionData transactionData : transactions) {
Message transactionMessage = new TransactionMessage(transactionData);
if (!peer.sendMessage(transactionMessage)) {
peer.disconnect();
return;
}
}
} catch (DataException e) {
LOGGER.error("Repository issue while sending unconfirmed transactions", e);
}
}
/** Returns PEERS message made from peers we've connected to recently, and this node's details */
@ -441,27 +465,42 @@ public class Network extends Thread {
}
private void mergePeers(List<InetSocketAddress> peerAddresses) {
try (final Repository repository = RepositoryManager.getRepository()) {
List<PeerData> knownPeers = repository.getNetworkRepository().getAllPeers();
mergePeersLock.lock();
// Resolve known peer hostnames
Function<PeerData, InetSocketAddress> peerDataToSocketAddress = peerData -> new InetSocketAddress(peerData.getSocketAddress().getHostString(),
peerData.getSocketAddress().getPort());
List<InetSocketAddress> knownPeerAddresses = knownPeers.stream().map(peerDataToSocketAddress).collect(Collectors.toList());
try {
try (final Repository repository = RepositoryManager.getRepository()) {
List<PeerData> knownPeers = repository.getNetworkRepository().getAllPeers();
// Filter out duplicates
Predicate<InetSocketAddress> addressKnown = peerAddress -> knownPeerAddresses.stream().anyMatch(knownAddress -> knownAddress.equals(peerAddress));
peerAddresses.removeIf(addressKnown);
for (PeerData peerData : knownPeers)
LOGGER.trace(String.format("Known peer %s", peerData.getSocketAddress()));
// Save the rest into database
for (InetSocketAddress peerAddress : peerAddresses) {
PeerData peerData = new PeerData(peerAddress);
repository.getNetworkRepository().save(peerData);
// Resolve known peer hostnames
Function<PeerData, InetSocketAddress> peerDataToSocketAddress = peerData -> new InetSocketAddress(peerData.getSocketAddress().getHostString(),
peerData.getSocketAddress().getPort());
List<InetSocketAddress> knownPeerAddresses = knownPeers.stream().map(peerDataToSocketAddress).collect(Collectors.toList());
for (InetSocketAddress address : knownPeerAddresses)
LOGGER.trace(String.format("Resolved known peer %s", address));
// Filter out duplicates
// We have to use our own Peer.addressEquals as InetSocketAddress.equals isn't quite right for us
Predicate<InetSocketAddress> addressKnown = peerAddress -> knownPeerAddresses.stream()
.anyMatch(knownAddress -> Peer.addressEquals(knownAddress, peerAddress));
peerAddresses.removeIf(addressKnown);
// Save the rest into database
for (InetSocketAddress peerAddress : peerAddresses) {
PeerData peerData = new PeerData(peerAddress);
LOGGER.trace(String.format("Adding new peer %s to repository", peerAddress));
repository.getNetworkRepository().save(peerData);
}
repository.saveChanges();
} catch (DataException e) {
LOGGER.error("Repository issue while merging peers list from remote node", e);
}
repository.saveChanges();
} catch (DataException e) {
LOGGER.error("Repository issue while merging peers list from remote node", e);
} finally {
mergePeersLock.unlock();
}
}

View File

@ -180,6 +180,8 @@ public class Peer implements Runnable {
if (message == null)
return;
LOGGER.trace(String.format("Received %s message with ID %d from peer %s", message.getType().name(), message.getId(), this));
// Find potential blocking queue for this id (expect null if id is -1)
BlockingQueue<Message> queue = this.messages.get(message.getId());
if (queue != null) {
@ -209,7 +211,7 @@ public class Peer implements Runnable {
try {
// Send message
LOGGER.trace(String.format("Sending %s message to peer %s", message.getType().name(), this));
LOGGER.trace(String.format("Sending %s message with ID %d to peer %s", message.getType().name(), message.getId(), this));
synchronized (this.out) {
this.out.write(message.toBytes());
@ -309,4 +311,12 @@ public class Peer implements Runnable {
Network.getInstance().onDisconnect(this);
}
/** Returns true if ports and addresses (or hostnames) match */
public static boolean addressEquals(InetSocketAddress knownAddress, InetSocketAddress peerAddress) {
if (knownAddress.getPort() != peerAddress.getPort())
return false;
return knownAddress.getHostString().equalsIgnoreCase(peerAddress.getHostString());
}
}

View File

@ -12,16 +12,6 @@ import com.google.common.primitives.Longs;
public class Proof extends Thread {
private static final int MIN_PROOF_ZEROS = 2;
private static final MessageDigest sha256;
static {
try {
sha256 = MessageDigest.getInstance("SHA256");
} catch (NoSuchAlgorithmException e) {
// Can't progress
throw new RuntimeException("Message digest SHA256 not available");
}
}
private static final HashSet<Long> seenSalts = new HashSet<>();
private Peer peer;
@ -63,6 +53,14 @@ public class Proof extends Thread {
byte[] timestampBytes = Longs.toByteArray(timestamp);
System.arraycopy(timestampBytes, 0, message, 8 + 8, timestampBytes.length);
MessageDigest sha256;
try {
sha256 = MessageDigest.getInstance("SHA256");
} catch (NoSuchAlgorithmException e) {
// Can't progress
throw new RuntimeException("Message digest SHA256 not available");
}
long nonce;
for (nonce = 0; nonce < Long.MAX_VALUE; ++nonce) {
// Check whether we're shutting down every so often
@ -77,6 +75,8 @@ public class Proof extends Thread {
if (check(digest))
break;
sha256.reset();
}
ProofMessage proofMessage = new ProofMessage(timestamp, salt, nonce);
@ -104,6 +104,14 @@ public class Proof extends Thread {
byte[] nonceBytes = Longs.toByteArray(nonce);
System.arraycopy(nonceBytes, 0, message, 0, nonceBytes.length);
MessageDigest sha256;
try {
sha256 = MessageDigest.getInstance("SHA256");
} catch (NoSuchAlgorithmException e) {
// Can't progress
throw new RuntimeException("Message digest SHA256 not available");
}
byte[] digest = sha256.digest(message);
return check(digest);

View File

@ -0,0 +1,50 @@
package org.qora.network.message;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import org.qora.data.transaction.TransactionData;
import org.qora.transform.TransformationException;
import org.qora.transform.transaction.TransactionTransformer;
public class TransactionMessage extends Message {
private TransactionData transactionData;
public TransactionMessage(TransactionData transactionData) {
this(-1, transactionData);
}
private TransactionMessage(int id, TransactionData transactionData) {
super(id, MessageType.TRANSACTION);
this.transactionData = transactionData;
}
public TransactionData getTransactionData() {
return this.transactionData;
}
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException {
try {
TransactionData transactionData = TransactionTransformer.fromByteBuffer(byteBuffer);
return new TransactionMessage(id, transactionData);
} catch (TransformationException e) {
return null;
}
}
@Override
protected byte[] toData() {
if (this.transactionData == null)
return null;
try {
return TransactionTransformer.toBytes(this.transactionData);
} catch (TransformationException e) {
return null;
}
}
}

View File

@ -19,6 +19,8 @@ public interface TransactionRepository {
/** Returns block height containing transaction or 0 if not in a block or transaction doesn't exist */
public int getHeightFromSignature(byte[] signature) throws DataException;
public boolean exists(byte[] signature) throws DataException;
// Transaction participants
public List<byte[]> getSignaturesInvolvingAddress(String address) throws DataException;

View File

@ -77,6 +77,7 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("SET DATABASE SQL NAMES TRUE"); // SQL keywords cannot be used as DB object names, e.g. table names
stmt.execute("SET DATABASE SQL SYNTAX MYS TRUE"); // Required for our use of INSERT ... ON DUPLICATE KEY UPDATE ... syntax
stmt.execute("SET DATABASE SQL RESTRICT EXEC TRUE"); // No multiple-statement execute() or DDL/DML executeQuery()
stmt.execute("SET DATABASE TRANSACTION CONTROL MVCC"); // Use MVCC over default two-phase locking, a-k-a "LOCKS"
stmt.execute("SET DATABASE DEFAULT TABLE TYPE CACHED");
stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD"); // Do not pad strings to same length before comparison
stmt.execute("CREATE COLLATION SQL_TEXT_UCC_NO_PAD FOR SQL_TEXT FROM SQL_TEXT_UCC NO PAD");

View File

@ -357,4 +357,33 @@ public class HSQLDBRepository implements Repository {
return sql;
}
}
/** Logs other HSQLDB sessions then re-throws passed exception */
public SQLException examineException(SQLException e) throws SQLException {
LOGGER.error("SQL error: " + e.getMessage());
// Serialization failure / potential deadlock - so list other sessions
try (ResultSet resultSet = this.checkedExecute(
"SELECT session_id, transaction, transaction_size, waiting_for_this, this_waiting_for, current_statement FROM Information_schema.system_sessions")) {
if (resultSet == null)
return e;
do {
long sessionId = resultSet.getLong(1);
boolean inTransaction = resultSet.getBoolean(2);
long transactionSize = resultSet.getLong(3);
String waitingForThis = resultSet.getString(4);
String thisWaitingFor = resultSet.getString(5);
String currentStatement = resultSet.getString(6);
LOGGER.error(String.format("Session %d, %s transaction (size %d), waiting for this '%s', this waiting for '%s', current statement: %s",
sessionId, (inTransaction ? "in" : "not in"), transactionSize, waitingForThis, thisWaitingFor, currentStatement));
} while (resultSet.next());
} catch (SQLException de) {
// Throw original exception instead
return e;
}
return e;
}
}

View File

@ -62,6 +62,8 @@ public class HSQLDBSaver {
this.bindValues(preparedStatement);
return preparedStatement.execute();
} catch (SQLException e) {
throw repository.examineException(e);
}
}

View File

@ -251,6 +251,15 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
}
}
@Override
public boolean exists(byte[] signature) throws DataException {
try {
return this.repository.exists("Transactions", "signature = ?", signature);
} catch (SQLException e) {
throw new DataException("Unable to check for transaction in repository", e);
}
}
@Override
public List<byte[]> getSignaturesInvolvingAddress(String address) throws DataException {
List<byte[]> signatures = new ArrayList<byte[]>();

View File

@ -40,7 +40,7 @@ public class AddGroupAdminTransactionTransformer extends TransactionTransformer
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -59,7 +59,7 @@ public class ArbitraryTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
int version = ArbitraryTransaction.getVersionByTimestamp(timestamp);

View File

@ -26,7 +26,7 @@ public class AtTransactionTransformer extends TransactionTransformer {
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH + ASSET_ID_LENGTH
+ DATA_SIZE_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
throw new TransformationException("Serialized AT Transactions should not exist!");
}

View File

@ -45,7 +45,7 @@ public class BuyNameTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -39,7 +39,7 @@ public class CancelAssetOrderTransactionTransformer extends TransactionTransform
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -40,7 +40,7 @@ public class CancelGroupBanTransactionTransformer extends TransactionTransformer
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -40,7 +40,7 @@ public class CancelGroupInviteTransactionTransformer extends TransactionTransfor
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -41,7 +41,7 @@ public class CancelSellNameTransactionTransformer extends TransactionTransformer
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -43,7 +43,7 @@ public class CreateAssetOrderTransactionTransformer extends TransactionTransform
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -48,7 +48,7 @@ public class CreateGroupTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -55,7 +55,7 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -59,7 +59,7 @@ public class DeployAtTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
int version = DeployAtTransaction.getVersionByTimestamp(timestamp);

View File

@ -41,7 +41,7 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
String recipient = Serialization.deserializeAddress(byteBuffer);

View File

@ -47,7 +47,7 @@ public class GroupBanTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -42,7 +42,7 @@ public class GroupInviteTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -45,7 +45,7 @@ public class GroupKickTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -54,7 +54,7 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -38,7 +38,7 @@ public class JoinGroupTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -38,7 +38,7 @@ public class LeaveGroupTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -55,7 +55,7 @@ public class MessageTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
int version = MessageTransaction.getVersionByTimestamp(timestamp);

View File

@ -48,7 +48,7 @@ public class MultiPaymentTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -40,7 +40,7 @@ public class PaymentTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -46,7 +46,7 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -40,7 +40,7 @@ public class RemoveGroupAdminTransactionTransformer extends TransactionTransform
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -43,7 +43,7 @@ public class SellNameTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -190,13 +190,17 @@ public abstract class TransactionTransformer extends Transformer {
if (bytes == null)
return null;
if (bytes.length < TYPE_LENGTH)
throw new TransformationException("Byte data too short to determine transaction type");
LOGGER.trace("tx hex: " + HashCode.fromBytes(bytes).toString());
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return fromByteBuffer(byteBuffer);
}
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPE_LENGTH)
throw new TransformationException("Byte data too short to determine transaction type");
TransactionType type = TransactionType.valueOf(byteBuffer.getInt());
if (type == null)
return null;

View File

@ -41,7 +41,7 @@ public class TransferAssetTransactionTransformer extends TransactionTransformer
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -48,7 +48,7 @@ public class UpdateGroupTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -46,7 +46,7 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];

View File

@ -42,7 +42,7 @@ public class VoteOnPollTransactionTransformer extends TransactionTransformer {
layout.add("signature", TransformationType.SIGNATURE);
}
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];