Browse Source

Improve/fix use of latest block cache, for more cache hits, faster chain-tip response, etc.

AT-sleep-until-message
catbref 4 years ago
parent
commit
5549eded38
  1. 28
      src/main/java/org/qortal/controller/Controller.java
  2. 127
      src/test/java/org/qortal/test/BlockTests.java

28
src/main/java/org/qortal/controller/Controller.java

@ -1225,12 +1225,24 @@ public class Controller extends Thread {
final byte[] parentSignature = getBlockSummariesMessage.getParentSignature();
this.stats.getBlockSummariesStats.requests.incrementAndGet();
// If peer's parent signature matches our latest block signature
// then we can short-circuit with an empty response
BlockData chainTip = getChainTip();
if (chainTip != null && Arrays.equals(parentSignature, chainTip.getSignature())) {
Message blockSummariesMessage = new BlockSummariesMessage(Collections.emptyList());
blockSummariesMessage.setId(message.getId());
if (!peer.sendMessage(blockSummariesMessage))
peer.disconnect("failed to send block summaries");
return;
}
List<BlockSummaryData> blockSummaries = new ArrayList<>();
// Attempt to serve from our cache of latest blocks
synchronized (this.latestBlocks) {
blockSummaries = this.latestBlocks.stream()
.dropWhile(cachedBlockData -> Arrays.equals(cachedBlockData.getSignature(), parentSignature))
.dropWhile(cachedBlockData -> !Arrays.equals(cachedBlockData.getReference(), parentSignature))
.map(BlockSummaryData::new)
.collect(Collectors.toList());
}
@ -1268,12 +1280,24 @@ public class Controller extends Thread {
final byte[] parentSignature = getSignaturesMessage.getParentSignature();
this.stats.getBlockSignaturesV2Stats.requests.incrementAndGet();
// If peer's parent signature matches our latest block signature
// then we can short-circuit with an empty response
BlockData chainTip = getChainTip();
if (chainTip != null && Arrays.equals(parentSignature, chainTip.getSignature())) {
Message signaturesMessage = new SignaturesMessage(Collections.emptyList());
signaturesMessage.setId(message.getId());
if (!peer.sendMessage(signaturesMessage))
peer.disconnect("failed to send signatures (v2)");
return;
}
List<byte[]> signatures = new ArrayList<>();
// Attempt to serve from our cache of latest blocks
synchronized (this.latestBlocks) {
signatures = this.latestBlocks.stream()
.dropWhile(cachedBlockData -> Arrays.equals(cachedBlockData.getSignature(), parentSignature))
.dropWhile(cachedBlockData -> !Arrays.equals(cachedBlockData.getReference(), parentSignature))
.map(BlockData::getSignature)
.collect(Collectors.toList());
}

127
src/test/java/org/qortal/test/BlockTests.java

@ -1,6 +1,10 @@
package org.qortal.test;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
@ -133,6 +137,129 @@ public class BlockTests extends Common {
}
}
@Test
public void testLatestBlockCacheWithLatestBlock() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
Deque<BlockData> latestBlockCache = buildLatestBlockCache(repository, 20);
BlockData latestBlock = repository.getBlockRepository().getLastBlock();
byte[] parentSignature = latestBlock.getSignature();
List<BlockData> childBlocks = findCachedChildBlocks(latestBlockCache, parentSignature);
assertEquals(true, childBlocks.isEmpty());
}
}
@Test
public void testLatestBlockCacheWithPenultimateBlock() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
Deque<BlockData> latestBlockCache = buildLatestBlockCache(repository, 20);
BlockData latestBlock = repository.getBlockRepository().getLastBlock();
BlockData penultimateBlock = repository.getBlockRepository().fromHeight(latestBlock.getHeight() - 1);
byte[] parentSignature = penultimateBlock.getSignature();
List<BlockData> childBlocks = findCachedChildBlocks(latestBlockCache, parentSignature);
assertEquals(false, childBlocks.isEmpty());
assertEquals(1, childBlocks.size());
BlockData expectedBlock = latestBlock;
BlockData actualBlock = childBlocks.get(0);
assertArrayEquals(expectedBlock.getSignature(), actualBlock.getSignature());
}
}
@Test
public void testLatestBlockCacheWithMiddleBlock() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
Deque<BlockData> latestBlockCache = buildLatestBlockCache(repository, 20);
int tipOffset = 5;
BlockData latestBlock = repository.getBlockRepository().getLastBlock();
BlockData parentBlock = repository.getBlockRepository().fromHeight(latestBlock.getHeight() - tipOffset);
byte[] parentSignature = parentBlock.getSignature();
List<BlockData> childBlocks = findCachedChildBlocks(latestBlockCache, parentSignature);
assertEquals(false, childBlocks.isEmpty());
assertEquals(tipOffset, childBlocks.size());
BlockData expectedFirstBlock = repository.getBlockRepository().fromHeight(parentBlock.getHeight() + 1);
BlockData actualFirstBlock = childBlocks.get(0);
assertArrayEquals(expectedFirstBlock.getSignature(), actualFirstBlock.getSignature());
BlockData expectedLastBlock = latestBlock;
BlockData actualLastBlock = childBlocks.get(childBlocks.size() - 1);
assertArrayEquals(expectedLastBlock.getSignature(), actualLastBlock.getSignature());
}
}
@Test
public void testLatestBlockCacheWithFirstBlock() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
Deque<BlockData> latestBlockCache = buildLatestBlockCache(repository, 20);
int tipOffset = latestBlockCache.size();
BlockData latestBlock = repository.getBlockRepository().getLastBlock();
BlockData parentBlock = repository.getBlockRepository().fromHeight(latestBlock.getHeight() - tipOffset);
byte[] parentSignature = parentBlock.getSignature();
List<BlockData> childBlocks = findCachedChildBlocks(latestBlockCache, parentSignature);
assertEquals(false, childBlocks.isEmpty());
assertEquals(tipOffset, childBlocks.size());
BlockData expectedFirstBlock = repository.getBlockRepository().fromHeight(parentBlock.getHeight() + 1);
BlockData actualFirstBlock = childBlocks.get(0);
assertArrayEquals(expectedFirstBlock.getSignature(), actualFirstBlock.getSignature());
BlockData expectedLastBlock = latestBlock;
BlockData actualLastBlock = childBlocks.get(childBlocks.size() - 1);
assertArrayEquals(expectedLastBlock.getSignature(), actualLastBlock.getSignature());
}
}
@Test
public void testLatestBlockCacheWithNoncachedBlock() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
Deque<BlockData> latestBlockCache = buildLatestBlockCache(repository, 20);
int tipOffset = latestBlockCache.size() + 1; // outside of cache
BlockData latestBlock = repository.getBlockRepository().getLastBlock();
BlockData parentBlock = repository.getBlockRepository().fromHeight(latestBlock.getHeight() - tipOffset);
byte[] parentSignature = parentBlock.getSignature();
List<BlockData> childBlocks = findCachedChildBlocks(latestBlockCache, parentSignature);
assertEquals(true, childBlocks.isEmpty());
}
}
private Deque<BlockData> buildLatestBlockCache(Repository repository, int count) throws DataException {
Deque<BlockData> latestBlockCache = new LinkedList<>();
// Mint some blocks
for (int h = 0; h < count; ++h)
latestBlockCache.addLast(BlockUtils.mintBlock(repository).getBlockData());
// Reduce cache down to latest 10 blocks
while (latestBlockCache.size() > 10)
latestBlockCache.removeFirst();
return latestBlockCache;
}
private List<BlockData> findCachedChildBlocks(Deque<BlockData> latestBlockCache, byte[] parentSignature) {
return latestBlockCache.stream()
.dropWhile(cachedBlockData -> !Arrays.equals(cachedBlockData.getReference(), parentSignature))
.collect(Collectors.toList());
}
@Test
public void testCommonBlockSearch() {
// Given a list of block summaries, trim all trailing summaries after common block

Loading…
Cancel
Save