From 041773cf41eb2441ac8f05aac7433849811f0b69 Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 30 May 2019 17:17:46 +0100 Subject: [PATCH] Synchronizer asks for approval-pending transaction from peer if needed --- .../java/org/qora/controller/Controller.java | 26 ++++++++- .../org/qora/controller/Synchronizer.java | 58 +++++++++++++++++++ .../message/GetTransactionMessage.java | 54 +++++++++++++++++ .../org/qora/network/message/Message.java | 3 +- 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/qora/network/message/GetTransactionMessage.java diff --git a/src/main/java/org/qora/controller/Controller.java b/src/main/java/org/qora/controller/Controller.java index 171da59d..ac0bedd5 100644 --- a/src/main/java/org/qora/controller/Controller.java +++ b/src/main/java/org/qora/controller/Controller.java @@ -37,6 +37,7 @@ import org.qora.network.message.GetBlockSummariesMessage; import org.qora.network.message.GetPeersMessage; import org.qora.network.message.GetSignaturesMessage; import org.qora.network.message.GetSignaturesV2Message; +import org.qora.network.message.GetTransactionMessage; import org.qora.network.message.HeightMessage; import org.qora.network.message.HeightV2Message; import org.qora.network.message.Message; @@ -500,7 +501,7 @@ public class Controller extends Thread { break; } - case GET_BLOCK: + case GET_BLOCK: { GetBlockMessage getBlockMessage = (GetBlockMessage) message; byte[] signature = getBlockMessage.getSignature(); @@ -522,6 +523,29 @@ public class Controller extends Thread { LOGGER.error(String.format("Repository issue while send block %s to peer %s", Base58.encode(signature), peer), e); } break; + } + + case GET_TRANSACTION: { + GetTransactionMessage getTransactionMessage = (GetTransactionMessage) message; + byte[] signature = getTransactionMessage.getSignature(); + + try (final Repository repository = RepositoryManager.getRepository()) { + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); + if (transactionData == null) { + LOGGER.debug(String.format("Ignoring GET_TRANSACTION request from peer %s for unknown transaction %s", peer, Base58.encode(signature))); + // Send no response at all??? + break; + } + + Message transactionMessage = new TransactionMessage(transactionData); + transactionMessage.setId(message.getId()); + if (!peer.sendMessage(transactionMessage)) + peer.disconnect("failed to send transaction"); + } catch (DataException e) { + LOGGER.error(String.format("Repository issue while send transaction %s to peer %s", Base58.encode(signature), peer), e); + } + break; + } case TRANSACTION: TransactionMessage transactionMessage = (TransactionMessage) message; diff --git a/src/main/java/org/qora/controller/Synchronizer.java b/src/main/java/org/qora/controller/Synchronizer.java index 9e848272..abdfaa21 100644 --- a/src/main/java/org/qora/controller/Synchronizer.java +++ b/src/main/java/org/qora/controller/Synchronizer.java @@ -13,6 +13,8 @@ import org.qora.block.BlockChain; import org.qora.block.GenesisBlock; import org.qora.data.block.BlockData; import org.qora.data.network.BlockSummaryData; +import org.qora.data.transaction.GroupApprovalTransactionData; +import org.qora.data.transaction.TransactionData; import org.qora.network.Peer; import org.qora.network.message.BlockMessage; import org.qora.network.message.BlockSummariesMessage; @@ -20,13 +22,17 @@ import org.qora.network.message.GetBlockMessage; import org.qora.network.message.GetBlockSummariesMessage; import org.qora.network.message.GetSignaturesMessage; import org.qora.network.message.GetSignaturesV2Message; +import org.qora.network.message.GetTransactionMessage; import org.qora.network.message.Message; import org.qora.network.message.Message.MessageType; 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.RepositoryManager; import org.qora.transaction.Transaction; +import org.qora.transaction.Transaction.TransactionType; +import org.qora.utils.Base58; import org.qora.utils.NTP; public class Synchronizer { @@ -231,6 +237,46 @@ public class Synchronizer { return SynchronizationResult.NO_REPLY; } + // If block contains GROUP_APPROVAL transactions then we need to make sure we have the relevant pending transactions too + for (Transaction transaction : newBlock.getTransactions()) { + TransactionData transactionData = transaction.getTransactionData(); + + if (transactionData.getType() != TransactionType.GROUP_APPROVAL) + continue; + + GroupApprovalTransactionData groupApprovalTransactionData = (GroupApprovalTransactionData) transactionData; + + byte[] pendingSignature = groupApprovalTransactionData.getPendingSignature(); + + if (repository.getTransactionRepository().exists(pendingSignature)) + continue; + + LOGGER.debug(String.format("Fetching unknown approval-pending transaction %s from peer %s, needed for block at height %d", Base58.encode(pendingSignature), peer, ourHeight)); + + TransactionData pendingTransactionData = this.fetchTransaction(peer, pendingSignature); + if (pendingTransactionData == null) { + LOGGER.info(String.format("Peer %s failed to respond with pending transaction %s", peer, Base58.encode(pendingSignature))); + return SynchronizationResult.NO_REPLY; + } + + // Check the signature is valid at least! + Transaction pendingTransaction = Transaction.fromData(repository, pendingTransactionData); + if (!pendingTransaction.isSignatureValid()) { + LOGGER.info(String.format("Peer %s sent pending transaction %s with invalid signature", peer, Base58.encode(pendingSignature))); + return SynchronizationResult.INVALID_DATA; + } + + Transaction.ValidationResult transactionResult = pendingTransaction.isValidUnconfirmed(); + if (transactionResult != Transaction.ValidationResult.OK) { + LOGGER.info(String.format("Peer %s sent invalid (%s) pending transaction %s", peer, transactionResult.name(), Base58.encode(pendingSignature))); + return SynchronizationResult.INVALID_DATA; + } + + // Add to our unconfirmed pile + this.repository.getTransactionRepository().save(pendingTransactionData); + this.repository.getTransactionRepository().unconfirmTransaction(pendingTransactionData); + } + if (!newBlock.isSignatureValid()) { LOGGER.info(String.format("Peer %s sent block with invalid signature for height %d", peer, ourHeight)); return SynchronizationResult.INVALID_DATA; @@ -391,4 +437,16 @@ public class Synchronizer { } } + private TransactionData fetchTransaction(Peer peer, byte[] signature) { + Message getTransactionMessage = new GetTransactionMessage(signature); + + Message message = peer.getResponse(getTransactionMessage); + if (message == null || message.getType() != MessageType.TRANSACTION) + return null; + + TransactionMessage transactionMessage = (TransactionMessage) message; + + return transactionMessage.getTransactionData(); + } + } diff --git a/src/main/java/org/qora/network/message/GetTransactionMessage.java b/src/main/java/org/qora/network/message/GetTransactionMessage.java new file mode 100644 index 00000000..0839130b --- /dev/null +++ b/src/main/java/org/qora/network/message/GetTransactionMessage.java @@ -0,0 +1,54 @@ +package org.qora.network.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; + +import org.qora.transform.Transformer; + +public class GetTransactionMessage extends Message { + + private static final int TRANSACTION_SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH; + + private byte[] signature; + + public GetTransactionMessage(byte[] signature) { + this(-1, signature); + } + + private GetTransactionMessage(int id, byte[] signature) { + super(id, MessageType.GET_TRANSACTION); + + this.signature = signature; + } + + public byte[] getSignature() { + return this.signature; + } + + public static Message fromByteBuffer(int id, ByteBuffer bytes) throws UnsupportedEncodingException { + if (bytes.remaining() != TRANSACTION_SIGNATURE_LENGTH) + return null; + + byte[] signature = new byte[TRANSACTION_SIGNATURE_LENGTH]; + + bytes.get(signature); + + return new GetTransactionMessage(id, signature); + } + + @Override + protected byte[] toData() { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(this.signature); + + return bytes.toByteArray(); + } catch (IOException e) { + return null; + } + } + +} diff --git a/src/main/java/org/qora/network/message/Message.java b/src/main/java/org/qora/network/message/Message.java index 2c9f50db..d806b2e6 100644 --- a/src/main/java/org/qora/network/message/Message.java +++ b/src/main/java/org/qora/network/message/Message.java @@ -64,7 +64,8 @@ public abstract class Message { GET_SIGNATURES_V2(16), PEER_VERIFY(17), VERIFICATION_CODES(18), - HEIGHT_V2(19); + HEIGHT_V2(19), + GET_TRANSACTION(20); public final int value; public final Method fromByteBuffer;