3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 06:44:16 +00:00

Use RandomAccessFile in DiskBlockStore to fix corruption. Resolves issue 76

This commit is contained in:
Miron Cuperman (devrandom) 2011-09-06 20:40:15 +00:00
parent eae1130a31
commit 2ce328aa0b
2 changed files with 51 additions and 21 deletions

View File

@ -32,29 +32,31 @@ import org.slf4j.LoggerFactory;
public class DiskBlockStore implements BlockStore { public class DiskBlockStore implements BlockStore {
private static final Logger log = LoggerFactory.getLogger(DiskBlockStore.class); private static final Logger log = LoggerFactory.getLogger(DiskBlockStore.class);
private FileOutputStream stream; private RandomAccessFile file;
private Map<Sha256Hash, StoredBlock> blockMap; private Map<Sha256Hash, StoredBlock> blockMap;
private Sha256Hash chainHead; private Sha256Hash chainHead;
private NetworkParameters params; private NetworkParameters params;
public DiskBlockStore(NetworkParameters params, File file) throws BlockStoreException { public DiskBlockStore(NetworkParameters params, File theFile) throws BlockStoreException {
this.params = params; this.params = params;
blockMap = new HashMap<Sha256Hash, StoredBlock>(); blockMap = new HashMap<Sha256Hash, StoredBlock>();
try { try {
load(file); file = new RandomAccessFile(theFile, "rwd");
stream = new FileOutputStream(file, true); // Do append. // The file position is at BOF
load(theFile);
// The file position is at EOF
} catch (IOException e) { } catch (IOException e) {
log.error("failed to load block store from file", e); log.error("failed to load block store from file", e);
createNewStore(params, file); createNewStore(params);
// The file position is at EOF
} }
} }
private void createNewStore(NetworkParameters params, File file) throws BlockStoreException { private void createNewStore(NetworkParameters params) throws BlockStoreException {
// Create a new block store if the file wasn't found or anything went wrong whilst reading. // Create a new block store if the file wasn't found or anything went wrong whilst reading.
blockMap.clear(); blockMap.clear();
try { try {
stream = new FileOutputStream(file, false); // Do not append, create fresh. file.write(1); // Version.
stream.write(1); // Version.
} catch (IOException e1) { } catch (IOException e1) {
// We could not load a block store nor could we create a new one! // We could not load a block store nor could we create a new one!
throw new BlockStoreException(e1); throw new BlockStoreException(e1);
@ -64,7 +66,7 @@ public class DiskBlockStore implements BlockStore {
Block genesis = params.genesisBlock.cloneAsHeader(); Block genesis = params.genesisBlock.cloneAsHeader();
StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0); StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0);
this.chainHead = storedGenesis.getHeader().getHash(); this.chainHead = storedGenesis.getHeader().getHash();
stream.write(this.chainHead.getBytes()); file.write(this.chainHead.getBytes());
put(storedGenesis); put(storedGenesis);
} catch (VerificationException e1) { } catch (VerificationException e1) {
throw new RuntimeException(e1); // Cannot happen. throw new RuntimeException(e1); // Cannot happen.
@ -73,23 +75,21 @@ public class DiskBlockStore implements BlockStore {
} }
} }
private void load(File file) throws IOException, BlockStoreException { private void load(File theFile) throws IOException, BlockStoreException {
log.info("Reading block store from {}", file); log.info("Reading block store from {}", theFile);
InputStream input = null;
try { try {
input = new BufferedInputStream(new FileInputStream(file));
// Read a version byte. // Read a version byte.
int version = input.read(); int version = file.read();
if (version == -1) { if (version == -1) {
// No such file or the file was empty. // No such file or the file was empty.
throw new FileNotFoundException(file.getName() + " does not exist or is empty"); throw new FileNotFoundException(theFile.getName() + " is empty");
} }
if (version != 1) { if (version != 1) {
throw new BlockStoreException("Bad version number: " + version); throw new BlockStoreException("Bad version number: " + version);
} }
// Chain head pointer is the first thing in the file. // Chain head pointer is the first thing in the file.
byte[] chainHeadHash = new byte[32]; byte[] chainHeadHash = new byte[32];
if (input.read(chainHeadHash) < chainHeadHash.length) if (file.read(chainHeadHash) < chainHeadHash.length)
throw new BlockStoreException("Truncated block store: cannot read chain head hash"); throw new BlockStoreException("Truncated block store: cannot read chain head hash");
this.chainHead = new Sha256Hash(chainHeadHash); this.chainHead = new Sha256Hash(chainHeadHash);
log.info("Read chain head from disk: {}", this.chainHead); log.info("Read chain head from disk: {}", this.chainHead);
@ -99,10 +99,14 @@ public class DiskBlockStore implements BlockStore {
try { try {
while (true) { while (true) {
// Read a block from disk. // Read a block from disk.
if (input.read(headerBytes) < 80) { int read = file.read(headerBytes);
if (read == -1) {
// End of file. // End of file.
break; break;
} }
if (read < headerBytes.length) {
throw new BlockStoreException("Truncated block store: partial block read");
}
// Parse it. // Parse it.
Block b = new Block(params, headerBytes); Block b = new Block(params, headerBytes);
// Look up the previous block it connects to. // Look up the previous block it connects to.
@ -135,7 +139,6 @@ public class DiskBlockStore implements BlockStore {
long elapsed = System.currentTimeMillis() - now; long elapsed = System.currentTimeMillis() - now;
log.info("Block chain read complete in {}ms", elapsed); log.info("Block chain read complete in {}ms", elapsed);
} finally { } finally {
if (input != null) input.close();
} }
} }
@ -145,8 +148,7 @@ public class DiskBlockStore implements BlockStore {
assert blockMap.get(hash) == null : "Attempt to insert duplicate"; assert blockMap.get(hash) == null : "Attempt to insert duplicate";
// Append to the end of the file. The other fields in StoredBlock will be recalculated when it's reloaded. // Append to the end of the file. The other fields in StoredBlock will be recalculated when it's reloaded.
byte[] bytes = block.getHeader().bitcoinSerialize(); byte[] bytes = block.getHeader().bitcoinSerialize();
stream.write(bytes); file.write(bytes);
stream.flush();
blockMap.put(hash, block); blockMap.put(hash, block);
} catch (IOException e) { } catch (IOException e) {
throw new BlockStoreException(e); throw new BlockStoreException(e);
@ -165,7 +167,7 @@ public class DiskBlockStore implements BlockStore {
try { try {
this.chainHead = chainHead.getHeader().getHash(); this.chainHead = chainHead.getHeader().getHash();
// Write out new hash to the first 32 bytes of the file past one (first byte is version number). // Write out new hash to the first 32 bytes of the file past one (first byte is version number).
stream.getChannel().write(ByteBuffer.wrap(this.chainHead.getBytes()), 1); file.getChannel().write(ByteBuffer.wrap(this.chainHead.getBytes()), 1);
} catch (IOException e) { } catch (IOException e) {
throw new BlockStoreException(e); throw new BlockStoreException(e);
} }

View File

@ -48,4 +48,32 @@ public class DiskBlockStoreTest {
// Check the chain head was stored correctly also. // Check the chain head was stored correctly also.
assertEquals(b1, store.getChainHead()); assertEquals(b1, store.getChainHead());
} }
@Test
public void testStorage_existing() throws Exception {
File temp = File.createTempFile("bitcoinj-test", null, null);
System.out.println(temp.getAbsolutePath());
NetworkParameters params = NetworkParameters.unitTests();
Address to = new ECKey().toAddress(params);
DiskBlockStore store = new DiskBlockStore(params, temp);
// Check the first block in a new store is the genesis block.
StoredBlock genesis = store.getChainHead();
// Reopen.
store = new DiskBlockStore(params, temp);
// Build a new block.
StoredBlock b1 = genesis.build(genesis.getHeader().createNextBlock(to).cloneAsHeader());
store.put(b1);
store.setChainHead(b1);
// Check we can get it back out again if we reopen the store.
store = new DiskBlockStore(params, temp);
StoredBlock b2 = store.get(b1.getHeader().getHash());
assertEquals(b1, b2);
// Check the chain head was stored correctly also.
assertEquals(b1, store.getChainHead());
}
} }