Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

[PAN-2949] Add rewind to block functionality #1814

Merged
merged 4 commits into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

public class DefaultMutableBlockchain implements MutableBlockchain {

private final BlockchainStorage blockchainStorage;
protected final BlockchainStorage blockchainStorage;

private final Subscribers<BlockAddedObserver> blockAddedObservers = Subscribers.create();

Expand Down Expand Up @@ -211,10 +211,7 @@ private BlockAddedEvent appendBlockHelper(

updater.commit();
if (blockAddedEvent.isNewCanonicalHead()) {
chainHeader = block.getHeader();
totalDifficulty = td;
chainHeadTransactionCount = block.getBody().getTransactions().size();
chainHeadOmmerCount = block.getBody().getOmmers().size();
updateCacheForNewCanonicalHead(block, td);
}

return blockAddedEvent;
Expand Down Expand Up @@ -362,6 +359,39 @@ private BlockAddedEvent handleChainReorg(
removedTransactions);
}

public boolean rewindToBlock(final long blockNumber) {
final Optional<Hash> blockHash = blockchainStorage.getBlockHash(blockNumber);
if (blockHash.isEmpty()) {
return false;
}

final BlockchainStorage.Updater updater = blockchainStorage.updater();
try {
final Optional<BlockHeader> oldBlockHeader =
blockchainStorage.getBlockHeader(blockHash.get());
final Optional<BlockBody> oldBlockBody = blockchainStorage.getBlockBody(blockHash.get());
final Block block = new Block(oldBlockHeader.get(), oldBlockBody.get());

handleChainReorg(updater, block);
updater.commit();

updateCacheForNewCanonicalHead(block, calculateTotalDifficulty(block));
return true;
} catch (final NoSuchElementException e) {
// Any Optional.get() calls in this block should be present, missing data means data
// corruption or a bug.
updater.rollback();
throw new IllegalStateException("Blockchain is missing data that should be present.", e);
}
}

void updateCacheForNewCanonicalHead(final Block block, final UInt256 uInt256) {
chainHeader = block.getHeader();
totalDifficulty = uInt256;
chainHeadTransactionCount = block.getBody().getTransactions().size();
chainHeadOmmerCount = block.getBody().getOmmers().size();
}

private static void indexTransactionForBlock(
final BlockchainStorage.Updater updater, final Hash hash, final List<Transaction> txs) {
for (int i = 0; i < txs.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,44 @@ public void reorgWithOverlappingTransactions() {
}
}

@Test
public void rewindChain() {
final BlockDataGenerator gen = new BlockDataGenerator(2);

// Setup an initial blockchain
final int originalChainLength = 4;
final List<Block> chain = gen.blockSequence(originalChainLength);
final List<List<TransactionReceipt>> blockReceipts =
chain.stream().map(gen::receipts).collect(Collectors.toList());
final KeyValueStorage kvStore = new InMemoryKeyValueStorage();
final DefaultMutableBlockchain blockchain = createBlockchain(kvStore, chain.get(0));
for (int i = 1; i < chain.size(); i++) {
blockchain.appendBlock(chain.get(i), blockReceipts.get(i));
}
final Block originalHead = blockchain.getChainHeadBlock();
final Block targetHead = blockchain.getBlockByHash(originalHead.getHeader().getParentHash());

// rewind it by 1 block
blockchain.rewindToBlock(targetHead.getHeader().getNumber());

// Check chain has the expected blocks
for (int i = 0; i < chain.size() - 1; i++) {
assertBlockDataIsStored(blockchain, chain.get(i), blockReceipts.get(i));
}
assertBlockIsHead(blockchain, targetHead);

// Check transactions were not indexed
for (final Transaction tx : originalHead.getBody().getTransactions()) {
assertThat(blockchain.getTransactionByHash(tx.hash())).isNotPresent();
}

// Check that blockNumber index for previous chain head has been removed
assertThat(blockchain.getBlockHashByNumber(originalHead.getHeader().getNumber()))
.isNotPresent();
// Old chain head should not be tracked.
assertThat(blockchain.blockIsOnCanonicalChain(originalHead.getHash())).isFalse();
}

@Test
public void appendBlockForFork() {
final BlockDataGenerator gen = new BlockDataGenerator(2);
Expand Down Expand Up @@ -614,10 +652,7 @@ public void blockAddedObserver_invokedSingle() {
final List<TransactionReceipt> receipts = gen.receipts(newBlock);

final AtomicBoolean observerInvoked = new AtomicBoolean(false);
blockchain.observeBlockAdded(
(block, chain) -> {
observerInvoked.set(true);
});
blockchain.observeBlockAdded((block, chain) -> observerInvoked.set(true));

blockchain.appendBlock(newBlock, receipts);

Expand All @@ -638,22 +673,13 @@ public void blockAddedObserver_invokedMultiple() {
final List<TransactionReceipt> receipts = gen.receipts(newBlock);

final AtomicBoolean observer1Invoked = new AtomicBoolean(false);
blockchain.observeBlockAdded(
(block, chain) -> {
observer1Invoked.set(true);
});
blockchain.observeBlockAdded((block, chain) -> observer1Invoked.set(true));

final AtomicBoolean observer2Invoked = new AtomicBoolean(false);
blockchain.observeBlockAdded(
(block, chain) -> {
observer2Invoked.set(true);
});
blockchain.observeBlockAdded((block, chain) -> observer2Invoked.set(true));

final AtomicBoolean observer3Invoked = new AtomicBoolean(false);
blockchain.observeBlockAdded(
(block, chain) -> {
observer3Invoked.set(true);
});
blockchain.observeBlockAdded((block, chain) -> observer3Invoked.set(true));

blockchain.appendBlock(newBlock, receipts);

Expand Down