DatabaseFullPrunedBlockStore: remove all database backed block stores

All of them are unmaintained and their tests are in the way of refactorings.
MySQL and Postgres had been disabled for a while, and one seems to care.
H2 is tested, but pulls a driver dependency into the test classpath.
This commit is contained in:
Andreas Schildbach 2022-08-02 16:41:13 +02:00
parent 4e3bf65865
commit 410b127176
13 changed files with 10 additions and 2050 deletions

View File

@ -19,7 +19,6 @@ dependencies {
testImplementation 'org.easymock:easymock:4.3'
testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.1'
testImplementation 'org.slf4j:slf4j-jdk14:1.7.36'
testImplementation 'com.h2database:h2:1.3.176'
testImplementation 'nl.jqno.equalsverifier:equalsverifier:3.10'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
}

View File

@ -25,7 +25,6 @@ import org.bitcoinj.core.listeners.TransactionReceivedInBlockListener;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.H2FullPrunedBlockStore;
import org.bitcoinj.store.SPVBlockStore;
import org.bitcoinj.utils.ListenableCompletableFuture;
import org.bitcoinj.utils.ListenerRegistration;
@ -74,7 +73,7 @@ import static com.google.common.base.Preconditions.checkState;
* <p>There are two subclasses of AbstractBlockChain that are useful: {@link BlockChain}, which is the simplest
* class and implements <i>simplified payment verification</i>. This is a lightweight and efficient mode that does
* not verify the contents of blocks, just their headers. A {@link FullPrunedBlockChain} paired with a
* {@link H2FullPrunedBlockStore} implements full verification, which is equivalent to
* {@link org.bitcoinj.store.MemoryFullPrunedBlockStore} implements full verification, which is equivalent to
* Bitcoin Core. To learn more about the alternative security models, please consult the articles on the
* website.</p>
*

View File

@ -1,167 +0,0 @@
/*
* Copyright 2012 Matt Corallo.
* Copyright 2014 Kalpesh Parmar.
* Copyright 2019 Andreas Schildbach
*
* 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.NetworkParameters;
import java.sql.SQLException;
import java.sql.Statement;
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(74),"
+ "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<>();
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<>();
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;
}
}

View File

@ -1,159 +0,0 @@
/*
* Copyright 2014 Kalpesh Parmar
* Copyright 2019 Andreas Schildbach
*
* 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.Address;
import org.bitcoinj.core.NetworkParameters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* <p>A full pruned block store using the MySQL database engine. As an added bonus an address index is calculated,
* so you can use {@link #calculateBalanceForAddress(Address)} to quickly look up
* the quantity of bitcoins controlled by that address.</p>
*/
public class MySQLFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
private static final String MYSQL_DUPLICATE_KEY_ERROR_CODE = "23000";
private static final String DATABASE_DRIVER_CLASS = "com.mysql.jdbc.Driver";
private static final String DATABASE_CONNECTION_URL_PREFIX = "jdbc:mysql://";
// create table SQL
private static final String CREATE_SETTINGS_TABLE = "CREATE TABLE settings (\n" +
" name varchar(32) NOT NULL,\n" +
" value blob,\n" +
" CONSTRAINT setting_pk PRIMARY KEY (name) \n" +
")\n";
private static final String CREATE_HEADERS_TABLE = "CREATE TABLE headers (\n" +
" hash varbinary(28) NOT NULL,\n" +
" chainwork varbinary(12) NOT NULL,\n" +
" height integer NOT NULL,\n" +
" header varbinary(80) NOT NULL,\n" +
" wasundoable tinyint(1) NOT NULL,\n" +
" CONSTRAINT headers_pk PRIMARY KEY (hash) USING BTREE \n" +
")";
private static final String CREATE_UNDOABLE_TABLE = "CREATE TABLE undoableblocks (\n" +
" hash varbinary(28) NOT NULL,\n" +
" height integer NOT NULL,\n" +
" txoutchanges mediumblob,\n" +
" transactions mediumblob,\n" +
" CONSTRAINT undoableblocks_pk PRIMARY KEY (hash) USING BTREE \n" +
")\n";
private static final String CREATE_OPEN_OUTPUT_TABLE = "CREATE TABLE openoutputs (\n" +
" hash varbinary(32) NOT NULL,\n" +
" `index` integer NOT NULL,\n" +
" height integer NOT NULL,\n" +
" value bigint NOT NULL,\n" +
" scriptbytes mediumblob NOT NULL,\n" +
" toaddress varchar(74),\n" +
" addresstargetable tinyint(1),\n" +
" coinbase boolean,\n" +
" CONSTRAINT openoutputs_pk PRIMARY KEY (hash, `index`) USING BTREE \n" +
")\n";
// 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) USING btree";
private static final String CREATE_OUTPUTS_TOADDRESS_INDEX = "CREATE INDEX openoutputs_toaddress_idx ON openoutputs (toaddress) USING btree";
private static final String CREATE_OUTPUTS_ADDRESSTARGETABLE_INDEX = "CREATE INDEX openoutputs_addresstargetable_idx ON openoutputs (addresstargetable) USING btree";
private static final String CREATE_OUTPUTS_HASH_INDEX = "CREATE INDEX openoutputs_hash_idx ON openoutputs (hash) USING btree";
private static final String CREATE_UNDOABLE_TABLE_INDEX = "CREATE INDEX undoableblocks_height_idx ON undoableblocks (height) USING btree";
// SQL involving index column (table openOutputs) overridden as it is a reserved word and must be back ticked in MySQL.
private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptbytes, coinbase, toaddress, addresstargetable FROM openoutputs WHERE hash = ? AND `index` = ?";
private static final String INSERT_OPENOUTPUTS_SQL = "INSERT INTO openoutputs (hash, `index`, height, value, scriptbytes, toaddress, addresstargetable, coinbase) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
private static final String DELETE_OPENOUTPUTS_SQL = "DELETE FROM openoutputs WHERE hash = ? AND `index`= ?";
private static final String SELECT_TRANSACTION_OUTPUTS_SQL = "SELECT hash, value, scriptbytes, height, `index`, coinbase, toaddress, addresstargetable FROM openoutputs where toaddress = ?";
/**
* Creates a new MySQLFullPrunedBlockStore.
*
* @param params A copy of the NetworkParameters used
* @param fullStoreDepth The number of blocks of history stored in full (something like 1000 is pretty safe)
* @param hostname The hostname of the database to connect to
* @param dbName The database to connect to
* @param username The database username
* @param password The password to the database
* @throws BlockStoreException if the database fails to open for any reason
*/
public MySQLFullPrunedBlockStore(NetworkParameters params, int fullStoreDepth, String hostname, String dbName,
String username, String password) throws BlockStoreException {
super(params, DATABASE_CONNECTION_URL_PREFIX + hostname + "/" + dbName, fullStoreDepth, username, password, null);
}
@Override
protected String getDuplicateKeyErrorCode() {
return MYSQL_DUPLICATE_KEY_ERROR_CODE;
}
@Override
protected String getSelectOpenoutputsSQL() {
return SELECT_OPENOUTPUTS_SQL;
}
@Override
protected String getInsertOpenoutputsSQL() {
return INSERT_OPENOUTPUTS_SQL;
}
@Override
protected String getDeleteOpenoutputsSQL() {
return DELETE_OPENOUTPUTS_SQL;
}
@Override
protected String getTransactionOutputSelectSQL() {
return SELECT_TRANSACTION_OUTPUTS_SQL;
}
@Override
protected List<String> getCreateTablesSQL() {
List<String> sqlStatements = new ArrayList<>();
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<>();
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;
}
}

View File

@ -1,263 +0,0 @@
/*
* Copyright 2014 BitPOS Pty Ltd.
* Copyright 2014 Andreas Schildbach
* 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.base.utils.ByteUtils;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.StoredUndoableBlock;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
/**
* <p>A full pruned block store using the Postgres database engine. As an added bonus an address index is calculated,
* so you can use {@link #calculateBalanceForAddress(Address)} to quickly look up
* the quantity of bitcoins controlled by that address.</p>
*/
public class PostgresFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
private static final Logger log = LoggerFactory.getLogger(PostgresFullPrunedBlockStore.class);
private static final String POSTGRES_DUPLICATE_KEY_ERROR_CODE = "23505";
private static final String DATABASE_DRIVER_CLASS = "org.postgresql.Driver";
private static final String DATABASE_CONNECTION_URL_PREFIX = "jdbc:postgresql://";
// create table SQL
private static final String CREATE_SETTINGS_TABLE = "CREATE TABLE settings (\n" +
" name character varying(32) NOT NULL,\n" +
" value bytea,\n" +
" CONSTRAINT setting_pk PRIMARY KEY (name)\n" +
")\n";
private static final String CREATE_HEADERS_TABLE = "CREATE TABLE headers (\n" +
" hash bytea NOT NULL,\n" +
" chainwork bytea NOT NULL,\n" +
" height integer NOT NULL,\n" +
" header bytea NOT NULL,\n" +
" wasundoable boolean NOT NULL,\n" +
" CONSTRAINT headers_pk PRIMARY KEY (hash)\n" +
")\n";
private static final String CREATE_UNDOABLE_TABLE = "CREATE TABLE undoableblocks (\n" +
" hash bytea NOT NULL,\n" +
" height integer NOT NULL,\n" +
" txoutchanges bytea,\n" +
" transactions bytea,\n" +
" CONSTRAINT undoableblocks_pk PRIMARY KEY (hash)\n" +
")\n";
private static final String CREATE_OPEN_OUTPUT_TABLE = "CREATE TABLE openoutputs (\n" +
" hash bytea NOT NULL,\n" +
" \"index\" integer NOT NULL,\n" +
" height integer NOT NULL,\n" +
" value bigint NOT NULL,\n" +
" scriptbytes bytea NOT NULL,\n" +
" toaddress character varying(74),\n" +
" addresstargetable smallint,\n" +
" coinbase boolean,\n" +
" CONSTRAINT openoutputs_pk PRIMARY KEY (hash,\"index\")\n" +
")\n";
// Some indexes to speed up inserts
private static final String CREATE_OUTPUTS_ADDRESS_MULTI_INDEX = "CREATE INDEX openoutputs_hash_index_num_height_toaddress_idx ON openoutputs USING btree (hash, \"index\", height, toaddress)";
private static final String CREATE_OUTPUTS_TOADDRESS_INDEX = "CREATE INDEX openoutputs_toaddress_idx ON openoutputs USING btree (toaddress)";
private static final String CREATE_OUTPUTS_ADDRESSTARGETABLE_INDEX = "CREATE INDEX openoutputs_addresstargetable_idx ON openoutputs USING btree (addresstargetable)";
private static final String CREATE_OUTPUTS_HASH_INDEX = "CREATE INDEX openoutputs_hash_idx ON openoutputs USING btree (hash)";
private static final String CREATE_UNDOABLE_TABLE_INDEX = "CREATE INDEX undoableblocks_height_idx ON undoableBlocks USING btree (height)";
private static final String SELECT_UNDOABLEBLOCKS_EXISTS_SQL = "select 1 from undoableblocks where hash = ?";
/**
* Creates a new PostgresFullPrunedBlockStore.
*
* @param params A copy of the NetworkParameters used
* @param fullStoreDepth The number of blocks of history stored in full (something like 1000 is pretty safe)
* @param hostname The hostname of the database to connect to
* @param dbName The database to connect to
* @param username The database username
* @param password The password to the database
* @throws BlockStoreException if the database fails to open for any reason
*/
public PostgresFullPrunedBlockStore(NetworkParameters params, int fullStoreDepth, String hostname, String dbName,
String username, String password) throws BlockStoreException {
super(params, DATABASE_CONNECTION_URL_PREFIX + hostname + "/" + dbName, fullStoreDepth, username, password, null);
}
/**
* <p>Create a new PostgresFullPrunedBlockStore, storing the tables in the schema specified. You may want to
* specify a schema to avoid name collisions, or just to keep the database better organized. The schema is not
* required, and if one is not provided than the default schema for the username will be used. See
* <a href="http://www.postgres.org/docs/9.3/static/ddl-schemas.html">the postgres schema docs</a> for more on
* schemas.</p>
*
* @param params A copy of the NetworkParameters used.
* @param fullStoreDepth The number of blocks of history stored in full (something like 1000 is pretty safe).
* @param hostname The hostname of the database to connect to.
* @param dbName The database to connect to.
* @param username The database username.
* @param password The password to the database.
* @param schemaName The name of the schema to put the tables in. May be null if no schema is being used.
* @throws BlockStoreException If the database fails to open for any reason.
*/
public PostgresFullPrunedBlockStore(NetworkParameters params, int fullStoreDepth, String hostname, String dbName,
String username, String password, @Nullable String schemaName) throws BlockStoreException {
super(params, DATABASE_CONNECTION_URL_PREFIX + hostname + "/" + dbName, fullStoreDepth, username, password, schemaName);
}
@Override
protected String getDuplicateKeyErrorCode() {
return POSTGRES_DUPLICATE_KEY_ERROR_CODE;
}
@Override
protected List<String> getCreateTablesSQL() {
List<String> sqlStatements = new ArrayList<>();
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<>();
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() {
List<String> sqlStatements = new ArrayList<>();
sqlStatements.add("CREATE SCHEMA IF NOT EXISTS " + schemaName);
sqlStatements.add("set search_path to '" + schemaName +"'");
return sqlStatements;
}
@Override
protected String getDatabaseDriverClass() {
return DATABASE_DRIVER_CLASS;
}
@Override
public void put(StoredBlock storedBlock, StoredUndoableBlock undoableBlock) throws BlockStoreException {
maybeConnect();
// We skip the first 4 bytes because (on mainnet) the minimum target has 4 0-bytes
byte[] hashBytes = new byte[28];
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 4, hashBytes, 0, 28);
int height = storedBlock.getHeight();
byte[] transactions = null;
byte[] txOutChanges = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
if (undoableBlock.getTxOutChanges() != null) {
undoableBlock.getTxOutChanges().serializeToStream(bos);
txOutChanges = bos.toByteArray();
} else {
int numTxn = undoableBlock.getTransactions().size();
ByteUtils.uint32ToByteStreamLE(numTxn, bos);
for (Transaction tx : undoableBlock.getTransactions())
tx.bitcoinSerialize(bos);
transactions = bos.toByteArray();
}
bos.close();
} catch (IOException e) {
throw new BlockStoreException(e);
}
try {
if (log.isDebugEnabled())
log.debug("Looking for undoable block with hash: " + ByteUtils.HEX.encode(hashBytes));
PreparedStatement findS = conn.get().prepareStatement(SELECT_UNDOABLEBLOCKS_EXISTS_SQL);
findS.setBytes(1, hashBytes);
ResultSet rs = findS.executeQuery();
if (rs.next())
{
// We already have this output, update it.
findS.close();
// Postgres insert-or-updates are very complex (and finnicky). This level of transaction isolation
// seems to work for bitcoinj
PreparedStatement s =
conn.get().prepareStatement(getUpdateUndoableBlocksSQL());
s.setBytes(3, hashBytes);
if (log.isDebugEnabled())
log.debug("Updating undoable block with hash: " + ByteUtils.HEX.encode(hashBytes));
if (transactions == null) {
s.setBytes(1, txOutChanges);
s.setNull(2, Types.BINARY);
} else {
s.setNull(1, Types.BINARY);
s.setBytes(2, transactions);
}
s.executeUpdate();
s.close();
return;
}
PreparedStatement s =
conn.get().prepareStatement(getInsertUndoableBlocksSQL());
s.setBytes(1, hashBytes);
s.setInt(2, height);
if (log.isDebugEnabled())
log.debug("Inserting undoable block with hash: " + ByteUtils.HEX.encode(hashBytes) + " at height " + height);
if (transactions == null) {
s.setBytes(3, txOutChanges);
s.setNull(4, Types.BINARY);
} else {
s.setNull(3, Types.BINARY);
s.setBytes(4, transactions);
}
s.executeUpdate();
s.close();
try {
putUpdateStoredBlock(storedBlock, true);
} catch (SQLException e) {
throw new BlockStoreException(e);
}
} catch (SQLException e) {
if (!e.getSQLState().equals(POSTGRES_DUPLICATE_KEY_ERROR_CODE))
throw new BlockStoreException(e);
}
}
}

View File

@ -16,8 +16,7 @@
/**
* Block stores persist blockchain data downloaded from remote peers. There is an SPV block store which preserves a ring
* buffer of headers on disk and is suitable for lightweight user wallets, a store that's backed by Postgres and which
* can calculate a full indexed UTXO set (i.e. it can query address balances), a store that's backed by the embedded H2
* database, and a memory only store useful for unit tests.
* buffer of headers on disk and is suitable for lightweight user wallets, a store that can calculate a full indexed
* UTXO set (i.e. it can query address balances), and a memory only store useful for unit tests.
*/
package org.bitcoinj.store;
package org.bitcoinj.store;

View File

@ -23,8 +23,9 @@ import org.bitcoinj.base.Sha256Hash;
import org.bitcoinj.net.NioClient;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.H2FullPrunedBlockStore;
import org.bitcoinj.store.FullPrunedBlockStore;
import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.store.MemoryFullPrunedBlockStore;
import org.bitcoinj.utils.BlockFileLoader;
import org.bitcoinj.utils.BriefLogFormatter;
import org.bitcoinj.utils.Threading;
@ -77,8 +78,7 @@ public class BitcoindComparisonTool {
final Iterator<Block> blocks = new BlockFileLoader(PARAMS, Arrays.asList(blockFile));
try {
H2FullPrunedBlockStore store = new H2FullPrunedBlockStore(PARAMS, args.length > 0 ? args[0] : "BitcoindComparisonTool", blockList.maximumReorgBlockCount);
store.resetStore();
FullPrunedBlockStore store = new MemoryFullPrunedBlockStore(PARAMS, blockList.maximumReorgBlockCount);
//store = new MemoryFullPrunedBlockStore(params, blockList.maximumReorgBlockCount);
chain = new FullPrunedBlockChain(PARAMS, store);
} catch (BlockStoreException e) {

View File

@ -1,55 +0,0 @@
/*
* 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.core;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.FullPrunedBlockStore;
import org.bitcoinj.store.H2FullPrunedBlockStore;
import org.junit.After;
import java.io.File;
/**
* An H2 implementation of the FullPrunedBlockStoreTest
*/
public class H2FullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest {
@After
public void tearDown() {
deleteFiles();
}
@Override
public FullPrunedBlockStore createStore(NetworkParameters params, int blockCount) throws BlockStoreException {
deleteFiles();
return new H2FullPrunedBlockStore(params, "test", "sa", "sa", blockCount);
}
private void deleteFiles() {
maybeDelete("test.h2.db");
maybeDelete("test.trace.db");
maybeDelete("test.lock.db");
}
private void maybeDelete(String s) {
new File(s).delete();
}
@Override
public void resetStore(FullPrunedBlockStore store) throws BlockStoreException {
((H2FullPrunedBlockStore)store).resetStore();
}
}

View File

@ -1,52 +0,0 @@
/*
* 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.core;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.FullPrunedBlockStore;
import org.bitcoinj.store.MySQLFullPrunedBlockStore;
import org.junit.After;
import org.junit.Ignore;
/**
* A MySQL implementation of the {@link AbstractFullPrunedBlockChainTest}
*/
@Ignore("enable the mysql driver dependency in the maven POM")
public class MySQLFullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest {
@After
public void tearDown() throws Exception {
((MySQLFullPrunedBlockStore)store).deleteStore();
}
// Replace these with your mysql location/credentials and remove @Ignore to test
private static final String DB_HOSTNAME = "localhost";
private static final String DB_NAME = "bitcoinj_test";
private static final String DB_USERNAME = "bitcoinj";
private static final String DB_PASSWORD = "password";
@Override
public FullPrunedBlockStore createStore(NetworkParameters params, int blockCount)
throws BlockStoreException {
return new MySQLFullPrunedBlockStore(params, blockCount, DB_HOSTNAME, DB_NAME, DB_USERNAME, DB_PASSWORD);
}
@Override
public void resetStore(FullPrunedBlockStore store) throws BlockStoreException {
((MySQLFullPrunedBlockStore)store).resetStore();
}
}

View File

@ -1,74 +0,0 @@
/*
* 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.core;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.FullPrunedBlockStore;
import org.bitcoinj.store.PostgresFullPrunedBlockStore;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
/**
* A Postgres implementation of the {@link AbstractFullPrunedBlockChainTest}
*/
@Ignore("enable the postgres driver dependency in the maven POM")
public class PostgresFullPrunedBlockChainTest extends AbstractFullPrunedBlockChainTest
{
// Replace these with your postgres location/credentials and remove @Ignore to test
// You can set up a fresh postgres with the command: create user bitcoinj superuser password 'password';
private static final String DB_HOSTNAME = "localhost";
private static final String DB_NAME = "bitcoinj_test";
private static final String DB_USERNAME = "bitcoinj";
private static final String DB_PASSWORD = "password";
private static final String DB_SCHEMA = "blockstore_schema";
// whether to run the test with a schema name
private boolean useSchema = false;
@After
public void tearDown() throws Exception {
((PostgresFullPrunedBlockStore)store).deleteStore();
}
@Override
public FullPrunedBlockStore createStore(NetworkParameters params, int blockCount)
throws BlockStoreException {
if(useSchema) {
return new PostgresFullPrunedBlockStore(params, blockCount, DB_HOSTNAME, DB_NAME, DB_USERNAME, DB_PASSWORD, DB_SCHEMA);
}
else {
return new PostgresFullPrunedBlockStore(params, blockCount, DB_HOSTNAME, DB_NAME, DB_USERNAME, DB_PASSWORD);
}
}
@Override
public void resetStore(FullPrunedBlockStore store) throws BlockStoreException {
((PostgresFullPrunedBlockStore)store).resetStore();
}
@Test
public void testFirst100kBlocksWithCustomSchema() throws Exception {
boolean oldSchema = useSchema;
useSchema = true;
try {
super.testFirst100KBlocks();
} finally {
useSchema = oldSchema;
}
}
}

View File

@ -28,9 +28,8 @@ import java.io.File;
/** Very thin wrapper around {@link BlockFileLoader} */
public class BlockImporter {
public static void main(String[] args) throws BlockStoreException, VerificationException, PrunedException {
System.out.println("USAGE: BlockImporter (prod|test) (H2|Disk|MemFull|Mem|SPV) [blockStore]");
System.out.println("USAGE: BlockImporter (prod|test) (Disk|MemFull|Mem|SPV) [blockStore]");
System.out.println(" blockStore is required unless type is Mem or MemFull");
System.out.println(" eg BlockImporter prod H2 /home/user/bitcoinj.h2store");
System.out.println(" Does full verification if the store supports it");
Preconditions.checkArgument(args.length == 2 || args.length == 3);
@ -41,10 +40,7 @@ public class BlockImporter {
params = MainNetParams.get();
BlockStore store;
if (args[1].equals("H2")) {
Preconditions.checkArgument(args.length == 3);
store = new H2FullPrunedBlockStore(params, args[2], 100);
} else if (args[1].equals("MemFull")) {
if (args[1].equals("MemFull")) {
Preconditions.checkArgument(args.length == 2);
store = new MemoryFullPrunedBlockStore(params, 100);
} else if (args[1].equals("Mem")) {

View File

@ -1001,7 +1001,7 @@ public class WalletTool implements Callable<Integer> {
}
chain = new BlockChain(params, wallet, store);
} else if (mode == ValidationMode.FULL) {
store = new H2FullPrunedBlockStore(params, chainFile.getAbsolutePath(), 5000);
store = new MemoryFullPrunedBlockStore(params, 5000);
chain = new FullPrunedBlockChain(params, wallet, (FullPrunedBlockStore) store);
}
// This will ensure the wallet is saved when it changes.