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 {
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user