/*
* Copyright 2012 Matt Corallo.
* Copyright 2014 Kalpesh Parmar.
*
* 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 java.sql.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// Originally written for Apache Derby, but its DELETE (and general) performance was awful
/**
* A full pruned block store using the H2 pure-java embedded database.
*
* Note that because of the heavy delete load on the database, during IBD,
* you may see the database files grow quite large (around 1.5G).
* H2 automatically frees some space at shutdown, so close()ing the database
* decreases the space usage somewhat (to only around 1.3G).
*/
public class H2FullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
private static final String H2_DUPLICATE_KEY_ERROR_CODE = "23505";
private static final String DATABASE_DRIVER_CLASS = "org.h2.Driver";
private static final String DATABASE_CONNECTION_URL_PREFIX = "jdbc:h2:";
// create table SQL
private static final String CREATE_SETTINGS_TABLE = "CREATE TABLE settings ( "
+ "name VARCHAR(32) NOT NULL CONSTRAINT settings_pk PRIMARY KEY,"
+ "value BLOB"
+ ")";
private static final String CREATE_HEADERS_TABLE = "CREATE TABLE headers ( "
+ "hash BINARY(28) NOT NULL CONSTRAINT headers_pk PRIMARY KEY,"
+ "chainwork BLOB NOT NULL,"
+ "height INT NOT NULL,"
+ "header BLOB NOT NULL,"
+ "wasundoable BOOL NOT NULL"
+ ")";
private static final String CREATE_UNDOABLE_TABLE = "CREATE TABLE undoableblocks ( "
+ "hash BINARY(28) NOT NULL CONSTRAINT undoableblocks_pk PRIMARY KEY,"
+ "height INT NOT NULL,"
+ "txoutchanges BLOB,"
+ "transactions BLOB"
+ ")";
private static final String CREATE_OPEN_OUTPUT_TABLE = "CREATE TABLE openoutputs ("
+ "hash BINARY(32) NOT NULL,"
+ "index INT NOT NULL,"
+ "height INT NOT NULL,"
+ "value BIGINT NOT NULL,"
+ "scriptbytes BLOB NOT NULL,"
+ "toaddress VARCHAR(35),"
+ "addresstargetable TINYINT,"
+ "coinbase BOOLEAN,"
+ "PRIMARY KEY (hash, index),"
+ ")";
// Some indexes to speed up inserts
private static final String CREATE_OUTPUTS_ADDRESS_MULTI_INDEX = "CREATE INDEX openoutputs_hash_index_height_toaddress_idx ON openoutputs (hash, index, height, toaddress)";
private static final String CREATE_OUTPUTS_TOADDRESS_INDEX = "CREATE INDEX openoutputs_toaddress_idx ON openoutputs (toaddress)";
private static final String CREATE_OUTPUTS_ADDRESSTARGETABLE_INDEX = "CREATE INDEX openoutputs_addresstargetable_idx ON openoutputs (addresstargetable)";
private static final String CREATE_OUTPUTS_HASH_INDEX = "CREATE INDEX openoutputs_hash_idx ON openoutputs (hash)";
private static final String CREATE_UNDOABLE_TABLE_INDEX = "CREATE INDEX undoableblocks_height_idx ON undoableblocks (height)";
/**
* Creates a new H2FullPrunedBlockStore, with given credentials for H2 database
* @param params A copy of the NetworkParameters used
* @param dbName The path to the database on disk
* @param username The username to use in the database
* @param password The username's password to use in the database
* @param fullStoreDepth The number of blocks of history stored in full (something like 1000 is pretty safe)
* @throws BlockStoreException if the database fails to open for any reason
*/
public H2FullPrunedBlockStore(NetworkParameters params, String dbName, String username, String password,
int fullStoreDepth) throws BlockStoreException {
super(params, DATABASE_CONNECTION_URL_PREFIX + dbName + ";create=true;LOCK_TIMEOUT=60000;DB_CLOSE_ON_EXIT=FALSE", fullStoreDepth, username, password, null);
}
/**
* Creates a new H2FullPrunedBlockStore
* @param params A copy of the NetworkParameters used
* @param dbName The path to the database on disk
* @param fullStoreDepth The number of blocks of history stored in full (something like 1000 is pretty safe)
* @throws BlockStoreException if the database fails to open for any reason
*/
public H2FullPrunedBlockStore(NetworkParameters params, String dbName, int fullStoreDepth)
throws BlockStoreException {
this(params, dbName, null, null, fullStoreDepth);
}
/**
* Creates a new H2FullPrunedBlockStore with the given cache size
* @param params A copy of the NetworkParameters used
* @param dbName The path to the database on disk
* @param fullStoreDepth The number of blocks of history stored in full (something like 1000 is pretty safe)
* @param cacheSize The number of kilobytes to dedicate to H2 Cache (the default value of 16MB (16384) is a safe bet
* to achieve good performance/cost when importing blocks from disk, past 32MB makes little sense,
* and below 4MB sees a sharp drop in performance)
* @throws BlockStoreException if the database fails to open for any reason
*/
public H2FullPrunedBlockStore(NetworkParameters params, String dbName, int fullStoreDepth, int cacheSize)
throws BlockStoreException {
this(params, dbName, fullStoreDepth);
try {
Statement s = conn.get().createStatement();
s.executeUpdate("SET CACHE_SIZE " + cacheSize);
s.close();
} catch (SQLException e) {
throw new BlockStoreException(e);
}
}
@Override
protected String getDuplicateKeyErrorCode() {
return H2_DUPLICATE_KEY_ERROR_CODE;
}
@Override
protected List<String> getCreateTablesSQL() {
List<String> sqlStatements = new ArrayList<String>();
sqlStatements.add(CREATE_SETTINGS_TABLE);
sqlStatements.add(CREATE_HEADERS_TABLE);
sqlStatements.add(CREATE_UNDOABLE_TABLE);
sqlStatements.add(CREATE_OPEN_OUTPUT_TABLE);
return sqlStatements;
}
@Override
protected List<String> getCreateIndexesSQL() {
List<String> sqlStatements = new ArrayList<String>();
sqlStatements.add(CREATE_UNDOABLE_TABLE_INDEX);
sqlStatements.add(CREATE_OUTPUTS_ADDRESS_MULTI_INDEX);
sqlStatements.add(CREATE_OUTPUTS_ADDRESSTARGETABLE_INDEX);
sqlStatements.add(CREATE_OUTPUTS_HASH_INDEX);
sqlStatements.add(CREATE_OUTPUTS_TOADDRESS_INDEX);
return sqlStatements;
}
@Override
protected List<String> getCreateSchemeSQL() {
// do nothing
return Collections.emptyList();
}
@Override
protected String getDatabaseDriverClass() {
return DATABASE_DRIVER_CLASS;
}
}