mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-19 05:33:44 +01:00
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:
parent
4e3bf65865
commit
410b127176
@ -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'
|
||||
}
|
||||
|
@ -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>
|
||||
*
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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")) {
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user