mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-02 21:47:18 +00:00
When a wallet is added to a block chain that has a lower block height than the chain, try to repair.
Adds a "crash simulation" unit test.
This commit is contained in:
committed by
Mike Hearn
parent
d8944b922f
commit
483b3bbc9b
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -158,9 +159,23 @@ public abstract class AbstractBlockChain {
|
||||
*/
|
||||
public void addWallet(Wallet wallet) {
|
||||
addListener(wallet, Threading.SAME_THREAD);
|
||||
if (wallet.getLastBlockSeenHeight() != getBestChainHeight()) {
|
||||
log.warn("Wallet/chain height mismatch: {} vs {}", wallet.getLastBlockSeenHeight(), getBestChainHeight());
|
||||
int walletHeight = wallet.getLastBlockSeenHeight();
|
||||
int chainHeight = getBestChainHeight();
|
||||
if (walletHeight != chainHeight) {
|
||||
log.warn("Wallet/chain height mismatch: {} vs {}", walletHeight, chainHeight);
|
||||
log.warn("Hashes: {} vs {}", wallet.getLastBlockSeenHash(), getChainHead().getHeader().getHash());
|
||||
|
||||
// This special case happens when the VM crashes because of a transaction received. It causes the updated
|
||||
// block store to persist, but not the wallet. In order to fix the issue, we roll back the block store to
|
||||
// the wallet height to make it look like as if the block has never been received.
|
||||
if (walletHeight < chainHeight && walletHeight > -1) {
|
||||
try {
|
||||
rollbackBlockStore(walletHeight);
|
||||
log.info("Rolled back block store to height {}.", walletHeight);
|
||||
} catch (BlockStoreException x) {
|
||||
log.warn("Rollback of block store failed, continuing with mismatched heights.", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +234,15 @@ public abstract class AbstractBlockChain {
|
||||
protected abstract StoredBlock addToBlockStore(StoredBlock storedPrev, Block header,
|
||||
@Nullable TransactionOutputChanges txOutputChanges)
|
||||
throws BlockStoreException, VerificationException;
|
||||
|
||||
|
||||
/**
|
||||
* Rollback the block store to a given height. This is currently only supported by {@link BlockChain} instances.
|
||||
*
|
||||
* @throws BlockStoreException
|
||||
* if the operation fails or is unsupported.
|
||||
*/
|
||||
protected abstract void rollbackBlockStore(int height) throws BlockStoreException;
|
||||
|
||||
/**
|
||||
* Called before setting chain head in memory.
|
||||
* Should write the new head to block store and then commit any database transactions
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +17,8 @@
|
||||
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.bitcoin.store.BlockStore;
|
||||
import com.google.bitcoin.store.BlockStoreException;
|
||||
|
||||
@@ -80,6 +83,31 @@ public class BlockChain extends AbstractBlockChain {
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rollbackBlockStore(int height) throws BlockStoreException {
|
||||
lock.lock();
|
||||
try {
|
||||
int currentHeight = getBestChainHeight();
|
||||
checkArgument(height >= 0 && height <= currentHeight, "Bad height: %s", height);
|
||||
if (height == currentHeight)
|
||||
return; // nothing to do
|
||||
|
||||
// Look for the block we want to be the new chain head
|
||||
StoredBlock newChainHead = blockStore.getChainHead();
|
||||
while (newChainHead.getHeight() > height) {
|
||||
newChainHead = newChainHead.getPrev(blockStore);
|
||||
if (newChainHead == null)
|
||||
throw new BlockStoreException("Unreachable height");
|
||||
}
|
||||
|
||||
// Modify store directly
|
||||
blockStore.put(newChainHead);
|
||||
this.setChainHead(newChainHead);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldVerifyTransactions() {
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright 2012 Matt Corallo.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -93,6 +94,11 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rollbackBlockStore(int height) throws BlockStoreException {
|
||||
throw new BlockStoreException("Unsupported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldVerifyTransactions() {
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -424,4 +425,33 @@ public class BlockChainTest {
|
||||
chain.trackFilteredTransactions(550);
|
||||
assertEquals(rate1, chain.getFalsePositiveRate(), 1e-4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rollbackBlockStore() throws Exception {
|
||||
// This test simulates an issue on Android, that causes the VM to crash while receiving a block, so that the
|
||||
// block store is persisted but the wallet is not.
|
||||
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinbaseTo);
|
||||
Block b2 = b1.createNextBlock(coinbaseTo);
|
||||
// Add block 1, no frills.
|
||||
assertTrue(chain.add(b1));
|
||||
assertEquals(b1.cloneAsHeader(), chain.getChainHead().getHeader());
|
||||
assertEquals(1, chain.getBestChainHeight());
|
||||
assertEquals(1, wallet.getLastBlockSeenHeight());
|
||||
// Add block 2 while wallet is disconnected, to simulate crash.
|
||||
chain.removeWallet(wallet);
|
||||
assertTrue(chain.add(b2));
|
||||
assertEquals(b2.cloneAsHeader(), chain.getChainHead().getHeader());
|
||||
assertEquals(2, chain.getBestChainHeight());
|
||||
assertEquals(1, wallet.getLastBlockSeenHeight());
|
||||
// Add wallet back. This will detect the height mismatch and repair the damage done.
|
||||
chain.addWallet(wallet);
|
||||
assertEquals(b1.cloneAsHeader(), chain.getChainHead().getHeader());
|
||||
assertEquals(1, chain.getBestChainHeight());
|
||||
assertEquals(1, wallet.getLastBlockSeenHeight());
|
||||
// Now add block 2 correctly.
|
||||
assertTrue(chain.add(b2));
|
||||
assertEquals(b2.cloneAsHeader(), chain.getChainHead().getHeader());
|
||||
assertEquals(2, chain.getBestChainHeight());
|
||||
assertEquals(2, wallet.getLastBlockSeenHeight());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user