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:
parent
eae1130a31
commit
2ce328aa0b
@ -32,29 +32,31 @@ import org.slf4j.LoggerFactory;
|
||||
public class DiskBlockStore implements BlockStore {
|
||||
private static final Logger log = LoggerFactory.getLogger(DiskBlockStore.class);
|
||||
|
||||
private FileOutputStream stream;
|
||||
private RandomAccessFile file;
|
||||
private Map<Sha256Hash, StoredBlock> blockMap;
|
||||
private Sha256Hash chainHead;
|
||||
private NetworkParameters params;
|
||||
|
||||
public DiskBlockStore(NetworkParameters params, File file) throws BlockStoreException {
|
||||
public DiskBlockStore(NetworkParameters params, File theFile) throws BlockStoreException {
|
||||
this.params = params;
|
||||
blockMap = new HashMap<Sha256Hash, StoredBlock>();
|
||||
try {
|
||||
load(file);
|
||||
stream = new FileOutputStream(file, true); // Do append.
|
||||
file = new RandomAccessFile(theFile, "rwd");
|
||||
// The file position is at BOF
|
||||
load(theFile);
|
||||
// The file position is at EOF
|
||||
} catch (IOException 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.
|
||||
blockMap.clear();
|
||||
try {
|
||||
stream = new FileOutputStream(file, false); // Do not append, create fresh.
|
||||
stream.write(1); // Version.
|
||||
file.write(1); // Version.
|
||||
} catch (IOException e1) {
|
||||
// We could not load a block store nor could we create a new one!
|
||||
throw new BlockStoreException(e1);
|
||||
@ -64,7 +66,7 @@ public class DiskBlockStore implements BlockStore {
|
||||
Block genesis = params.genesisBlock.cloneAsHeader();
|
||||
StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0);
|
||||
this.chainHead = storedGenesis.getHeader().getHash();
|
||||
stream.write(this.chainHead.getBytes());
|
||||
file.write(this.chainHead.getBytes());
|
||||
put(storedGenesis);
|
||||
} catch (VerificationException e1) {
|
||||
throw new RuntimeException(e1); // Cannot happen.
|
||||
@ -73,23 +75,21 @@ public class DiskBlockStore implements BlockStore {
|
||||
}
|
||||
}
|
||||
|
||||
private void load(File file) throws IOException, BlockStoreException {
|
||||
log.info("Reading block store from {}", file);
|
||||
InputStream input = null;
|
||||
private void load(File theFile) throws IOException, BlockStoreException {
|
||||
log.info("Reading block store from {}", theFile);
|
||||
try {
|
||||
input = new BufferedInputStream(new FileInputStream(file));
|
||||
// Read a version byte.
|
||||
int version = input.read();
|
||||
int version = file.read();
|
||||
if (version == -1) {
|
||||
// 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) {
|
||||
throw new BlockStoreException("Bad version number: " + version);
|
||||
}
|
||||
// Chain head pointer is the first thing in the file.
|
||||
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");
|
||||
this.chainHead = new Sha256Hash(chainHeadHash);
|
||||
log.info("Read chain head from disk: {}", this.chainHead);
|
||||
@ -99,10 +99,14 @@ public class DiskBlockStore implements BlockStore {
|
||||
try {
|
||||
while (true) {
|
||||
// Read a block from disk.
|
||||
if (input.read(headerBytes) < 80) {
|
||||
int read = file.read(headerBytes);
|
||||
if (read == -1) {
|
||||
// End of file.
|
||||
break;
|
||||
}
|
||||
if (read < headerBytes.length) {
|
||||
throw new BlockStoreException("Truncated block store: partial block read");
|
||||
}
|
||||
// Parse it.
|
||||
Block b = new Block(params, headerBytes);
|
||||
// Look up the previous block it connects to.
|
||||
@ -135,7 +139,6 @@ public class DiskBlockStore implements BlockStore {
|
||||
long elapsed = System.currentTimeMillis() - now;
|
||||
log.info("Block chain read complete in {}ms", elapsed);
|
||||
} finally {
|
||||
if (input != null) input.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,8 +148,7 @@ public class DiskBlockStore implements BlockStore {
|
||||
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.
|
||||
byte[] bytes = block.getHeader().bitcoinSerialize();
|
||||
stream.write(bytes);
|
||||
stream.flush();
|
||||
file.write(bytes);
|
||||
blockMap.put(hash, block);
|
||||
} catch (IOException e) {
|
||||
throw new BlockStoreException(e);
|
||||
@ -165,7 +167,7 @@ public class DiskBlockStore implements BlockStore {
|
||||
try {
|
||||
this.chainHead = chainHead.getHeader().getHash();
|
||||
// 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) {
|
||||
throw new BlockStoreException(e);
|
||||
}
|
||||
|
@ -48,4 +48,32 @@ public class DiskBlockStoreTest {
|
||||
// Check the chain head was stored correctly also.
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user