/* * Copyright by the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.bitcoinj.store; import org.bitcoinj.core.*; import org.fusesource.leveldbjni.*; import org.iq80.leveldb.*; import javax.annotation.*; import java.io.*; import java.nio.*; /** * An SPV block store that writes every header it sees to a <a href="https://github.com/fusesource/leveldbjni">LevelDB</a>. * This allows for fast lookup of block headers by block hash at the expense of more costly inserts and higher disk * usage than the {@link SPVBlockStore}. If all you want is a regular wallet you don't need this class: it exists for * specialised applications where you need to quickly verify a standalone SPV proof. */ public class LevelDBBlockStore implements BlockStore { private static final byte[] CHAIN_HEAD_KEY = "chainhead".getBytes(); private final Context context; private DB db; private final ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); private final File path; /** Creates a LevelDB SPV block store using the JNI/C++ version of LevelDB. */ public LevelDBBlockStore(Context context, File directory) throws BlockStoreException { this(context, directory, JniDBFactory.factory); } /** Creates a LevelDB SPV block store using the given factory, which is useful if you want a pure Java version. */ public LevelDBBlockStore(Context context, File directory, DBFactory dbFactory) throws BlockStoreException { this.context = context; this.path = directory; Options options = new Options(); options.createIfMissing(); try { tryOpen(directory, dbFactory, options); } catch (IOException e) { try { dbFactory.repair(directory, options); tryOpen(directory, dbFactory, options); } catch (IOException e1) { throw new BlockStoreException(e1); } } } private synchronized void tryOpen(File directory, DBFactory dbFactory, Options options) throws IOException, BlockStoreException { db = dbFactory.open(directory, options); initStoreIfNeeded(); } private synchronized void initStoreIfNeeded() throws BlockStoreException { if (db.get(CHAIN_HEAD_KEY) != null) return; // Already initialised. Block genesis = context.getParams().getGenesisBlock().cloneAsHeader(); StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0); put(storedGenesis); setChainHead(storedGenesis); } @Override public synchronized void put(StoredBlock block) throws BlockStoreException { buffer.clear(); block.serializeCompact(buffer); db.put(block.getHeader().getHash().getBytes(), buffer.array()); } @Override @Nullable public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException { byte[] bits = db.get(hash.getBytes()); if (bits == null) return null; return StoredBlock.deserializeCompact(context.getParams(), ByteBuffer.wrap(bits)); } @Override public synchronized StoredBlock getChainHead() throws BlockStoreException { return get(Sha256Hash.wrap(db.get(CHAIN_HEAD_KEY))); } @Override public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException { db.put(CHAIN_HEAD_KEY, chainHead.getHeader().getHash().getBytes()); } @Override public synchronized void close() throws BlockStoreException { try { db.close(); } catch (IOException e) { throw new BlockStoreException(e); } } /** Erases the contents of the database (but NOT the underlying files themselves) and then reinitialises with the genesis block. */ public synchronized void reset() throws BlockStoreException { try { WriteBatch batch = db.createWriteBatch(); try { DBIterator it = db.iterator(); try { it.seekToFirst(); while (it.hasNext()) batch.delete(it.next().getKey()); db.write(batch); } finally { it.close(); } } finally { batch.close(); } initStoreIfNeeded(); } catch (IOException e) { throw new BlockStoreException(e); } } public synchronized void destroy() throws IOException { JniDBFactory.factory.destroy(path, new Options()); } @Override public NetworkParameters getParams() { return context.getParams(); } }