diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index b2c6c182..a0ca1d05 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -67,8 +67,8 @@ import org.qortal.gui.SysTray; import org.qortal.network.Network; import org.qortal.network.Peer; import org.qortal.network.message.ArbitraryDataMessage; -import org.qortal.network.message.BlockMessage; import org.qortal.network.message.BlockSummariesMessage; +import org.qortal.network.message.CachedBlockMessage; import org.qortal.network.message.GetArbitraryDataMessage; import org.qortal.network.message.GetBlockMessage; import org.qortal.network.message.GetBlockSummariesMessage; @@ -148,9 +148,9 @@ public class Controller extends Thread { /** Cache of BlockMessages, indexed by block signature */ @SuppressWarnings("serial") - private final LinkedHashMap blockMessageCache = new LinkedHashMap<>() { + private final LinkedHashMap blockMessageCache = new LinkedHashMap<>() { @Override - protected boolean removeEldestEntry(Map.Entry eldest) { + protected boolean removeEldestEntry(Map.Entry eldest) { return this.size() > Settings.getInstance().getBlockCacheSize(); } }; @@ -1151,7 +1151,7 @@ public class Controller extends Thread { ByteArray signatureAsByteArray = new ByteArray(signature); - BlockMessage cachedBlockMessage = this.blockMessageCache.get(signatureAsByteArray); + CachedBlockMessage cachedBlockMessage = this.blockMessageCache.get(signatureAsByteArray); int blockCacheSize = Settings.getInstance().getBlockCacheSize(); // Check cached latest block message @@ -1159,7 +1159,7 @@ public class Controller extends Thread { this.stats.getBlockMessageStats.cacheHits.incrementAndGet(); // We need to duplicate it to prevent multiple threads setting ID on the same message - BlockMessage clonedBlockMessage = cachedBlockMessage.cloneWithNewId(message.getId()); + CachedBlockMessage clonedBlockMessage = cachedBlockMessage.cloneWithNewId(message.getId()); if (!peer.sendMessage(clonedBlockMessage)) peer.disconnect("failed to send block"); @@ -1187,12 +1187,15 @@ public class Controller extends Thread { Block block = new Block(repository, blockData); - BlockMessage blockMessage = new BlockMessage(block); + CachedBlockMessage blockMessage = new CachedBlockMessage(block); blockMessage.setId(message.getId()); // This call also causes the other needed data to be pulled in from repository - if (!peer.sendMessage(blockMessage)) + if (!peer.sendMessage(blockMessage)) { peer.disconnect("failed to send block"); + // Don't fall-through to caching because failure to send might be from failure to build message + return; + } // If request is for a recent block, cache it if (getChainHeight() - blockData.getHeight() <= blockCacheSize) { diff --git a/src/main/java/org/qortal/network/message/CachedBlockMessage.java b/src/main/java/org/qortal/network/message/CachedBlockMessage.java new file mode 100644 index 00000000..7a175810 --- /dev/null +++ b/src/main/java/org/qortal/network/message/CachedBlockMessage.java @@ -0,0 +1,70 @@ +package org.qortal.network.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; + +import org.qortal.block.Block; +import org.qortal.transform.TransformationException; +import org.qortal.transform.block.BlockTransformer; + +import com.google.common.primitives.Ints; + +// This is an OUTGOING-only Message which more readily lends itself to being cached +public class CachedBlockMessage extends Message { + + private Block block = null; + private byte[] cachedBytes = null; + + public CachedBlockMessage(Block block) { + super(MessageType.BLOCK); + + this.block = block; + } + + private CachedBlockMessage(byte[] cachedBytes) { + super(MessageType.BLOCK); + + this.block = null; + this.cachedBytes = cachedBytes; + } + + public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws UnsupportedEncodingException { + throw new UnsupportedOperationException("CachedBlockMessage is for outgoing messages only"); + } + + @Override + protected byte[] toData() { + // Already serialized? + if (this.cachedBytes != null) + return cachedBytes; + + if (this.block == null) + return null; + + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(this.block.getBlockData().getHeight())); + + bytes.write(BlockTransformer.toBytes(this.block)); + + this.cachedBytes = bytes.toByteArray(); + // We no longer need source Block + // and Block contains repository handle which is highly likely to be invalid after this call + this.block = null; + + return this.cachedBytes; + } catch (TransformationException | IOException e) { + return null; + } + } + + public CachedBlockMessage cloneWithNewId(int newId) { + CachedBlockMessage clone = new CachedBlockMessage(this.cachedBytes); + clone.setId(newId); + return clone; + } + +}