mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-31 20:11:23 +00:00
1) Fixed the height field to store the height for both coinbase/non coinbase open outputs.
2) Thanks to Ed Lee - Fixed Issue 447 : H2 store: block header hash code is off by 4 bytes. This fix also applies to Postgres and MySQL stores. 3) Added a coinbase field to the openoutputs table and updated code to use this value. 4) Updated field type of ‘value’ in the openoutputs table from bytes to long. 5) Updated the stores (DatabaseFullPrunedBlockStore) with a compatibility check. 6) Updated field type of ‘addresstargetable’ in the openoutputs table from int to tinyint/smallint.
This commit is contained in:
committed by
Mike Hearn
parent
fbf62614b4
commit
9004166122
@@ -442,6 +442,7 @@
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
<!-- Note this is an optional dependency: Postgres blockstore -->
|
||||
<!-- To Test remove optional -->
|
||||
<dependency>
|
||||
<groupId>postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
@@ -449,6 +450,7 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- Note this is an optional dependency: MySQL blockstore -->
|
||||
<!-- To Test remove optional -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
|
@@ -206,8 +206,11 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||
// Coinbases can't be spent until they mature, to avoid re-orgs destroying entire transaction
|
||||
// chains. The assumption is there will ~never be re-orgs deeper than the spendable coinbase
|
||||
// chain depth.
|
||||
if (height - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
|
||||
throw new VerificationException("Tried to spend coinbase at depth " + (height - prevOut.getHeight()));
|
||||
if (prevOut.isCoinbase()) {
|
||||
if (height - prevOut.getHeight() < params.getSpendableCoinbaseDepth()) {
|
||||
throw new VerificationException("Tried to spend coinbase at depth " + (height - prevOut.getHeight()));
|
||||
}
|
||||
}
|
||||
// TODO: Check we're not spending the genesis transaction here. Satoshis code won't allow it.
|
||||
valueIn = valueIn.add(prevOut.getValue());
|
||||
if (verifyFlags.contains(VerifyFlag.P2SH)) {
|
||||
@@ -332,7 +335,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||
in.getOutpoint().getIndex());
|
||||
if (prevOut == null)
|
||||
throw new VerificationException("Attempted spend of a non-existent or already spent output!");
|
||||
if (newBlock.getHeight() - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
|
||||
if (prevOut.isCoinbase() && newBlock.getHeight() - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
|
||||
throw new VerificationException("Tried to spend coinbase at depth " + (newBlock.getHeight() - prevOut.getHeight()));
|
||||
valueIn = valueIn.add(prevOut.getValue());
|
||||
if (verifyFlags.contains(VerifyFlag.P2SH)) {
|
||||
|
@@ -38,35 +38,36 @@ public class StoredTransactionOutput implements Serializable {
|
||||
private Sha256Hash hash;
|
||||
/** Which output of that transaction we are talking about. */
|
||||
private long index;
|
||||
|
||||
/** arbitrary value lower than -{@link NetworkParameters#spendableCoinbaseDepth}
|
||||
* (not too low to get overflows when we do blockHeight - NONCOINBASE_HEIGHT, though) */
|
||||
private static final int NONCOINBASE_HEIGHT = -200;
|
||||
/** The height of the creating block (for coinbases, NONCOINBASE_HEIGHT otherwise) */
|
||||
/** The height of the tx of this output */
|
||||
private int height;
|
||||
/** If this output is from a coinbase tx */
|
||||
private boolean coinbase;
|
||||
|
||||
/**
|
||||
* Creates a stored transaction output
|
||||
* @param hash the hash of the containing transaction
|
||||
* @param index the outpoint
|
||||
* @param value the value available
|
||||
* @param height the height this output was created in
|
||||
* @param scriptBytes
|
||||
* Creates a stored transaction output.
|
||||
* @param hash The hash of the containing transaction
|
||||
* @param index The outpoint.
|
||||
* @param value The value available.
|
||||
* @param height The height this output was created in.
|
||||
* @param coinbase The coinbase flag.
|
||||
* @param scriptBytes The script bytes.
|
||||
*/
|
||||
public StoredTransactionOutput(Sha256Hash hash, long index, Coin value, int height, boolean isCoinbase, byte[] scriptBytes) {
|
||||
public StoredTransactionOutput(Sha256Hash hash, long index, Coin value, int height, boolean coinbase, byte[] scriptBytes) {
|
||||
this.hash = hash;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.height = isCoinbase ? height : NONCOINBASE_HEIGHT;
|
||||
this.height = height;
|
||||
this.scriptBytes = scriptBytes;
|
||||
this.coinbase = coinbase;
|
||||
}
|
||||
|
||||
public StoredTransactionOutput(Sha256Hash hash, TransactionOutput out, int height, boolean isCoinbase) {
|
||||
public StoredTransactionOutput(Sha256Hash hash, TransactionOutput out, int height, boolean coinbase) {
|
||||
this.hash = hash;
|
||||
this.index = out.getIndex();
|
||||
this.value = out.getValue();
|
||||
this.height = isCoinbase ? height : NONCOINBASE_HEIGHT;
|
||||
this.height = height;
|
||||
this.scriptBytes = out.getScriptBytes();
|
||||
this.coinbase = coinbase;
|
||||
}
|
||||
|
||||
public StoredTransactionOutput(InputStream in) throws IOException {
|
||||
@@ -97,11 +98,19 @@ public class StoredTransactionOutput implements Serializable {
|
||||
((in.read() & 0xFF) << 8) |
|
||||
((in.read() & 0xFF) << 16) |
|
||||
((in.read() & 0xFF) << 24);
|
||||
|
||||
byte[] coinbaseByte = new byte[1];
|
||||
in.read(coinbaseByte);
|
||||
if (coinbaseByte[0] == 1) {
|
||||
coinbase = true;
|
||||
} else {
|
||||
coinbase = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value which this Transaction output holds
|
||||
* @return the value
|
||||
* The value which this Transaction output holds.
|
||||
* @return the value.
|
||||
*/
|
||||
public Coin getValue() {
|
||||
return value;
|
||||
@@ -109,35 +118,44 @@ public class StoredTransactionOutput implements Serializable {
|
||||
|
||||
/**
|
||||
* The backing script bytes which can be turned into a Script object.
|
||||
* @return the scriptBytes
|
||||
* @return the scriptBytes.
|
||||
*/
|
||||
public byte[] getScriptBytes() {
|
||||
return scriptBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash of the transaction which holds this output
|
||||
* @return the hash
|
||||
* The hash of the transaction which holds this output.
|
||||
* @return the hash.
|
||||
*/
|
||||
public Sha256Hash getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* The index of this output in the transaction which holds it
|
||||
* @return the index
|
||||
* The index of this output in the transaction which holds it.
|
||||
* @return the index.
|
||||
*/
|
||||
public long getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the block that created this output (or -1 if this output was not created by a coinbase)
|
||||
* Gets the height of the block that created this output.
|
||||
* @return The height.
|
||||
*/
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flag of whether this was created by a coinbase tx.
|
||||
* @return The coinbase flag.
|
||||
*/
|
||||
public boolean isCoinbase() {
|
||||
return coinbase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Stored TxOut of %s (%s:%d)", value.toFriendlyString(), hash.toString(), index);
|
||||
@@ -173,5 +191,13 @@ public class StoredTransactionOutput implements Serializable {
|
||||
bos.write(0xFF & (height >> 8));
|
||||
bos.write(0xFF & (height >> 16));
|
||||
bos.write(0xFF & (height >> 24));
|
||||
|
||||
byte[] coinbaseByte = new byte[1];
|
||||
if(coinbase) {
|
||||
coinbaseByte[0] = 1;
|
||||
} else {
|
||||
coinbaseByte[0] = 0;
|
||||
}
|
||||
bos.write(coinbaseByte);
|
||||
}
|
||||
}
|
||||
|
@@ -29,10 +29,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <p>A generic full pruned block store for a relational database. This generic class requires
|
||||
@@ -79,10 +76,11 @@ import java.util.Properties;
|
||||
* <tr><td>hash</td><td>binary</td></tr>
|
||||
* <tr><td>index</td><td>integer</td></tr>
|
||||
* <tr><td>height</td><td>integer</td></tr>
|
||||
* <tr><td>value</td><td>binary</td></tr>
|
||||
* <tr><td>value</td><td>integer</td></tr>
|
||||
* <tr><td>scriptbytes</td><td>binary</td></tr>
|
||||
* <tr><td>toaddress</td><td>string</td></tr>
|
||||
* <tr><td>addresstargetable</td><td>integer</td></tr>
|
||||
* <tr><td>coinbase</td><td>boolean</td></tr>
|
||||
* </table>
|
||||
* </p>
|
||||
*
|
||||
@@ -90,43 +88,52 @@ import java.util.Properties;
|
||||
public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockStore {
|
||||
private static final Logger log = LoggerFactory.getLogger(DatabaseFullPrunedBlockStore.class);
|
||||
|
||||
private static final String CHAIN_HEAD_SETTING = "chainhead";
|
||||
private static final String VERIFIED_CHAIN_HEAD_SETTING = "verifiedchainhead";
|
||||
private static final String VERSION_SETTING = "version";
|
||||
private static final String CHAIN_HEAD_SETTING = "chainhead";
|
||||
private static final String VERIFIED_CHAIN_HEAD_SETTING = "verifiedchainhead";
|
||||
private static final String VERSION_SETTING = "version";
|
||||
|
||||
// drop table SQL
|
||||
private static final String DROP_SETTINGS_TABLE = "DROP TABLE settings";
|
||||
private static final String DROP_HEADERS_TABLE = "DROP TABLE headers";
|
||||
private static final String DROP_UNDOABLE_TABLE = "DROP TABLE undoableblocks";
|
||||
private static final String DROP_OPEN_OUTPUT_TABLE = "DROP TABLE openoutputs";
|
||||
// Drop table SQL.
|
||||
private static final String DROP_SETTINGS_TABLE = "DROP TABLE settings";
|
||||
private static final String DROP_HEADERS_TABLE = "DROP TABLE headers";
|
||||
private static final String DROP_UNDOABLE_TABLE = "DROP TABLE undoableblocks";
|
||||
private static final String DROP_OPEN_OUTPUT_TABLE = "DROP TABLE openoutputs";
|
||||
|
||||
// SQL Queries
|
||||
private static final String SELECT_SETTINGS_SQL = "SELECT value FROM settings WHERE name = ?";
|
||||
private static final String INSERT_SETTINGS_SQL = "INSERT INTO settings(name, value) VALUES(?, ?)";
|
||||
private static final String UPDATE_SETTINGS_SQL = "UPDATE settings SET value = ? WHERE name = ?";
|
||||
// Queries SQL.
|
||||
private static final String SELECT_SETTINGS_SQL = "SELECT value FROM settings WHERE name = ?";
|
||||
private static final String INSERT_SETTINGS_SQL = "INSERT INTO settings(name, value) VALUES(?, ?)";
|
||||
private static final String UPDATE_SETTINGS_SQL = "UPDATE settings SET value = ? WHERE name = ?";
|
||||
|
||||
private static final String SELECT_HEADERS_SQL = "SELECT chainWork, height, header, wasUndoable FROM headers WHERE hash = ?";
|
||||
private static final String INSERT_HEADERS_SQL = "INSERT INTO headers(hash, chainWork, height, header, wasUndoable) VALUES(?, ?, ?, ?, ?)";
|
||||
private static final String UPDATE_HEADERS_SQL = "UPDATE headers SET wasUndoable=? WHERE hash=?";
|
||||
private static final String SELECT_HEADERS_SQL = "SELECT chainWork, height, header, wasUndoable FROM headers WHERE hash = ?";
|
||||
private static final String INSERT_HEADERS_SQL = "INSERT INTO headers(hash, chainWork, height, header, wasUndoable) VALUES(?, ?, ?, ?, ?)";
|
||||
private static final String UPDATE_HEADERS_SQL = "UPDATE headers SET wasUndoable=? WHERE hash=?";
|
||||
|
||||
private static final String SELECT_UNDOABLEBLOCKS_SQL = "SELECT txOutChanges, transactions FROM undoableBlocks WHERE hash = ?";
|
||||
private static final String INSERT_UNDOABLEBLOCKS_SQL = "INSERT INTO undoableBlocks(hash, height, txOutChanges, transactions) VALUES(?, ?, ?, ?)";
|
||||
private static final String UPDATE_UNDOABLEBLOCKS_SQL = "UPDATE undoableBlocks SET txOutChanges=?, transactions=? WHERE hash = ?";
|
||||
private static final String DELETE_UNDOABLEBLOCKS_SQL = "DELETE FROM undoableBlocks WHERE height <= ?";
|
||||
private static final String SELECT_UNDOABLEBLOCKS_SQL = "SELECT txOutChanges, transactions FROM undoableBlocks WHERE hash = ?";
|
||||
private static final String INSERT_UNDOABLEBLOCKS_SQL = "INSERT INTO undoableBlocks(hash, height, txOutChanges, transactions) VALUES(?, ?, ?, ?)";
|
||||
private static final String UPDATE_UNDOABLEBLOCKS_SQL = "UPDATE undoableBlocks SET txOutChanges=?, transactions=? WHERE hash = ?";
|
||||
private static final String DELETE_UNDOABLEBLOCKS_SQL = "DELETE FROM undoableBlocks WHERE height <= ?";
|
||||
|
||||
private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptBytes FROM openOutputs WHERE hash = ? AND index = ?";
|
||||
private static final String SELECT_OPENOUTPUTS_COUNT_SQL = "SELECT COUNT(*) FROM openOutputs WHERE hash = ?";
|
||||
private static final String INSERT_OPENOUTPUTS_SQL = "INSERT INTO openOutputs (hash, index, height, value, scriptBytes, toAddress, addressTargetable) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
private static final String DELETE_OPENOUTPUTS_SQL = "DELETE FROM openOutputs WHERE hash = ? AND index = ?";
|
||||
private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptBytes, coinbase FROM openOutputs WHERE hash = ? AND index = ?";
|
||||
private static final String SELECT_OPENOUTPUTS_COUNT_SQL = "SELECT COUNT(*) FROM openOutputs WHERE hash = ?";
|
||||
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 = ?";
|
||||
|
||||
// dump table SQL (this is just for data sizing statistics).
|
||||
private static final String SELECT_DUMP_SETTINGS_SQL = "SELECT name, value FROM settings";
|
||||
private static final String SELECT_DUMP_HEADERS_SQL = "SELECT chainWork, header FROM headers";
|
||||
private static final String SELECT_DUMP_UNDOABLEBLOCKS_SQL = "SELECT txOutChanges, transactions FROM undoableBlocks";
|
||||
private static final String SELECT_DUMP_OPENOUTPUTS_SQL = "SELECT value, scriptBytes FROM openOutputs";
|
||||
// Dump table SQL (this is just for data sizing statistics).
|
||||
private static final String SELECT_DUMP_SETTINGS_SQL = "SELECT name, value FROM settings";
|
||||
private static final String SELECT_DUMP_HEADERS_SQL = "SELECT chainWork, header FROM headers";
|
||||
private static final String SELECT_DUMP_UNDOABLEBLOCKS_SQL = "SELECT txOutChanges, transactions FROM undoableBlocks";
|
||||
private static final String SELECT_DUMP_OPENOUTPUTS_SQL = "SELECT value, scriptBytes FROM openOutputs";
|
||||
|
||||
private static final String SELECT_TRANSACTION_OUTPUTS_SQL = "SELECT value, scriptBytes FROM openOutputs where toaddress = ?";
|
||||
private static final String SELECT_TRANSACTION_OUTPUTS_WITH_HEIGHT_SQL = "SELECT value, scriptBytes FROM openOutputs where toaddress = ? AND height <= ?";
|
||||
private static final String SELECT_TRANSACTION_OUTPUTS_SQL = "SELECT value, scriptBytes, height FROM openOutputs where toaddress = ?";
|
||||
private static final String SELECT_TRANSACTION_OUTPUTS_WITH_HEIGHT_SQL = "SELECT value, scriptBytes, height FROM openOutputs where toaddress = ? AND height <= ?";
|
||||
|
||||
// Select the balance of an address SQL.
|
||||
private static final String SELECT_BALANCE_SQL = "select sum(value) from openoutputs where toaddress = ?";
|
||||
|
||||
// Tables exist SQL.
|
||||
private static final String SELECT_CHECK_TABLES_EXIST_SQL = "SELECT * FROM settings WHERE 1 = 2";
|
||||
|
||||
// Compatibility SQL.
|
||||
private static final String SELECT_COMPATIBILITY_COINBASE_SQL = "SELECT coinbase FROM openOutputs WHERE 1 = 2";
|
||||
|
||||
protected Sha256Hash chainHeadHash;
|
||||
protected StoredBlock chainHeadBlock;
|
||||
@@ -175,8 +182,10 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
|
||||
try {
|
||||
// Create tables if needed
|
||||
if (!tableExists("settings")) {
|
||||
if (!tablesExists()) {
|
||||
createTables();
|
||||
} else {
|
||||
checkCompatibility();
|
||||
}
|
||||
initFromDatabase();
|
||||
} catch (SQLException e) {
|
||||
@@ -191,13 +200,6 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
*/
|
||||
protected abstract String getDatabaseDriverClass();
|
||||
|
||||
/**
|
||||
* Get the SQL statement that checks is a particular table exists.
|
||||
* @param tableName The name of the table.
|
||||
* @return The SQL select statement.
|
||||
*/
|
||||
protected abstract String getTableExistSQL(String tableName);
|
||||
|
||||
/**
|
||||
* Get the SQL statements that create the schema (DDL).
|
||||
* @return The list of SQL statements.
|
||||
@@ -224,12 +226,30 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
protected abstract String getDuplicateKeyErrorCode();
|
||||
|
||||
/**
|
||||
* Get the SQL to select the total balance for a given address. This has been abstracted as
|
||||
* difference databases will handle casting of return values differently (since values/balances
|
||||
* are stored as bytes within the database for space reasons).
|
||||
* Get the SQL to select the total balance for a given address.
|
||||
* @return The SQL prepared statement.
|
||||
*/
|
||||
protected abstract String getBalanceSelectSQL();
|
||||
protected String getBalanceSelectSQL() {
|
||||
return SELECT_BALANCE_SQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL statement that checks if tables exist.
|
||||
* @return The SQL prepared statement.
|
||||
*/
|
||||
protected String getTablesExistSQL() {
|
||||
return SELECT_CHECK_TABLES_EXIST_SQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL statements to check if the database is compatible.
|
||||
* @return The SQL prepared statements.
|
||||
*/
|
||||
protected List<String> getCompatibilitySQL() {
|
||||
List<String> sqlStatements = new ArrayList<String>();
|
||||
sqlStatements.add(SELECT_COMPATIBILITY_COINBASE_SQL);
|
||||
return sqlStatements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL to select the transaction outputs for a given address.
|
||||
@@ -456,21 +476,51 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table exists within the database.
|
||||
* @param table The name of the table.
|
||||
* @return If the table exists.
|
||||
* <p>Check if a tables exists within the database.</p>
|
||||
*
|
||||
* <p>This specifically checks for the 'settings' table and
|
||||
* if it exists makes an assumption that the rest of the data
|
||||
* structures are present.</p>
|
||||
*
|
||||
* @return If the tables exists.
|
||||
* @throws java.sql.SQLException
|
||||
*/
|
||||
private boolean tableExists(String table) throws SQLException {
|
||||
Statement s = conn.get().createStatement();
|
||||
private boolean tablesExists() throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
ResultSet results = s.executeQuery(getTableExistSQL(table));
|
||||
ps = conn.get().prepareStatement(getTablesExistSQL());
|
||||
ResultSet results = ps.executeQuery();
|
||||
results.close();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
return false;
|
||||
} finally {
|
||||
s.close();
|
||||
if(ps != null && !ps.isClosed()) {
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the database is compatible with this version of the {@link DatabaseFullPrunedBlockStore}.
|
||||
* @throws BlockStoreException If the database is not compatible.
|
||||
*/
|
||||
private void checkCompatibility() throws SQLException, BlockStoreException {
|
||||
for(String sql : getCompatibilitySQL()) {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
ps = conn.get().prepareStatement(sql);
|
||||
ResultSet results = ps.executeQuery();
|
||||
results.close();
|
||||
} catch (SQLException ex) {
|
||||
String message = "Database block store is not compatible with the current release. " +
|
||||
"See bitcoinj release notes for further information.";
|
||||
throw new BlockStoreException(message);
|
||||
} finally {
|
||||
if (ps != null && !ps.isClosed()) {
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +597,6 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
if (!rs.next()) {
|
||||
throw new BlockStoreException("corrupt database block store - no chain head pointer");
|
||||
}
|
||||
//Sha256Hash hash = new Sha256Hash(rs.getBytes(1));
|
||||
Sha256Hash hash = new Sha256Hash(rs.getBytes(1));
|
||||
rs.close();
|
||||
this.chainHeadBlock = get(hash);
|
||||
@@ -576,7 +625,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
conn.get().prepareStatement(getInsertHeadersSQL());
|
||||
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
|
||||
byte[] hashBytes = new byte[28];
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 3, hashBytes, 0, 28);
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 4, hashBytes, 0, 28);
|
||||
s.setBytes(1, hashBytes);
|
||||
s.setBytes(2, storedBlock.getChainWork().toByteArray());
|
||||
s.setInt(3, storedBlock.getHeight());
|
||||
@@ -594,7 +643,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
s.setBoolean(1, true);
|
||||
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
|
||||
byte[] hashBytes = new byte[28];
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 3, hashBytes, 0, 28);
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 4, hashBytes, 0, 28);
|
||||
s.setBytes(2, hashBytes);
|
||||
s.executeUpdate();
|
||||
s.close();
|
||||
@@ -617,7 +666,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
maybeConnect();
|
||||
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
|
||||
byte[] hashBytes = new byte[28];
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 3, hashBytes, 0, 28);
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 4, hashBytes, 0, 28);
|
||||
int height = storedBlock.getHeight();
|
||||
byte[] transactions = null;
|
||||
byte[] txOutChanges = null;
|
||||
@@ -697,7 +746,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
.prepareStatement(getSelectHeadersSQL());
|
||||
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
|
||||
byte[] hashBytes = new byte[28];
|
||||
System.arraycopy(hash.getBytes(), 3, hashBytes, 0, 28);
|
||||
System.arraycopy(hash.getBytes(), 4, hashBytes, 0, 28);
|
||||
s.setBytes(1, hashBytes);
|
||||
ResultSet results = s.executeQuery();
|
||||
if (!results.next()) {
|
||||
@@ -754,7 +803,7 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
|
||||
|
||||
byte[] hashBytes = new byte[28];
|
||||
System.arraycopy(hash.getBytes(), 3, hashBytes, 0, 28);
|
||||
System.arraycopy(hash.getBytes(), 4, hashBytes, 0, 28);
|
||||
s.setBytes(1, hashBytes);
|
||||
ResultSet results = s.executeQuery();
|
||||
if (!results.next()) {
|
||||
@@ -887,9 +936,10 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
}
|
||||
// Parse it.
|
||||
int height = results.getInt(1);
|
||||
Coin value = Coin.valueOf(new BigInteger(results.getBytes(2)).longValue());
|
||||
// Tell the StoredTransactionOutput that we are a coinbase, as that is encoded in height
|
||||
StoredTransactionOutput txout = new StoredTransactionOutput(hash, index, value, height, true, results.getBytes(3));
|
||||
Coin value = Coin.valueOf(results.getLong(2));
|
||||
byte[] scriptBytes = results.getBytes(3);
|
||||
boolean coinbase = results.getBoolean(4);
|
||||
StoredTransactionOutput txout = new StoredTransactionOutput(hash, index, value, height, coinbase, scriptBytes);
|
||||
return txout;
|
||||
} catch (SQLException ex) {
|
||||
throw new BlockStoreException(ex);
|
||||
@@ -947,10 +997,11 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
// index is actually an unsigned int
|
||||
s.setInt(2, (int) out.getIndex());
|
||||
s.setInt(3, out.getHeight());
|
||||
s.setBytes(4, BigInteger.valueOf(out.getValue().value).toByteArray());
|
||||
s.setLong(4, out.getValue().value);
|
||||
s.setBytes(5, out.getScriptBytes());
|
||||
s.setString(6, dbAddress);
|
||||
s.setInt(7, type);
|
||||
s.setBoolean(8, out.isCoinbase());
|
||||
s.executeUpdate();
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
@@ -1166,9 +1217,35 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
* @throws BlockStoreException If there is an error getting the list of outputs.
|
||||
*/
|
||||
public List<TransactionOutput> getOpenTransactionOutputs(Address address, @Nullable Integer maxHeight) throws BlockStoreException {
|
||||
List<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
|
||||
for(List<TransactionOutput> outputsSubList : getOpenTransactionOutputsHeightMap(address, maxHeight).values()) {
|
||||
outputs.addAll(outputsSubList);
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map of the {@link org.bitcoinj.core.TransactionOutput}'s keyed on their height for a given address.
|
||||
* @param address The address.
|
||||
* @return The map of transaction outputs (list) keyed by height
|
||||
* @throws BlockStoreException If there is an error getting the list out outputs.
|
||||
*/
|
||||
public Map<Integer, List<TransactionOutput>> getOpenTransactionOutputsHeightMap(Address address) throws BlockStoreException {
|
||||
return getOpenTransactionOutputsHeightMap(address, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map of the {@link org.bitcoinj.core.TransactionOutput}'s keyed on their height for a given address
|
||||
* and a specified height.
|
||||
* @param address The address.
|
||||
* @param maxHeight The minimum block height of this tx output (inclusive).
|
||||
* @return The map of transaction outputs (list) keyed by height
|
||||
* @throws BlockStoreException If there is an error getting the list out outputs.
|
||||
*/
|
||||
public Map<Integer, List<TransactionOutput>> getOpenTransactionOutputsHeightMap(Address address, @Nullable Integer maxHeight) throws BlockStoreException {
|
||||
maybeConnect();
|
||||
PreparedStatement s = null;
|
||||
List<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
|
||||
HashMap<Integer, List<TransactionOutput>> outputsMap = new HashMap<Integer, List<TransactionOutput>>();
|
||||
try {
|
||||
if(maxHeight != null) {
|
||||
s = conn.get().prepareStatement(getTrasactionOutputWithHeightSelectSQL());
|
||||
@@ -1181,19 +1258,25 @@ public abstract class DatabaseFullPrunedBlockStore implements FullPrunedBlockSto
|
||||
while (rs.next()) {
|
||||
Coin amount = Coin.valueOf((new BigInteger(rs.getBytes(1)).longValue()));
|
||||
byte[] scriptBytes = rs.getBytes(2);
|
||||
int height = rs.getInt(3);
|
||||
TransactionOutput output = new TransactionOutput(params, null, amount, scriptBytes);
|
||||
outputs.add(output);
|
||||
if (outputsMap.containsKey(height)) {
|
||||
outputsMap.get(height).add(output);
|
||||
} else {
|
||||
List outputs = new ArrayList<TransactionOutput>();
|
||||
outputs.add(output);
|
||||
outputsMap.put(height, outputs);
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
return outputsMap;
|
||||
} catch (SQLException ex) {
|
||||
throw new BlockStoreException(ex);
|
||||
} finally {
|
||||
if (s != null) {
|
||||
if (s != null)
|
||||
try {
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
throw new BlockStoreException("Could not close statement");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -68,10 +68,11 @@ public class H2FullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
+ "hash BINARY(32) NOT NULL,"
|
||||
+ "index INT NOT NULL,"
|
||||
+ "height INT NOT NULL,"
|
||||
+ "value BLOB NOT NULL,"
|
||||
+ "value BIGINT NOT NULL,"
|
||||
+ "scriptBytes BLOB NOT NULL,"
|
||||
+ "toaddress VARCHAR(35),"
|
||||
+ "addresstargetable INT,"
|
||||
+ "addresstargetable TINYINT,"
|
||||
+ "coinbase BOOLEAN,"
|
||||
+ "PRIMARY KEY (hash, index),"
|
||||
+ ")";
|
||||
|
||||
@@ -82,8 +83,6 @@ public class H2FullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
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)";
|
||||
|
||||
private static final String SELECT_BALANCE_SQL = "SELECT value from openoutputs where toaddress = ?";
|
||||
|
||||
/**
|
||||
* Creates a new H2FullPrunedBlockStore
|
||||
* @param params A copy of the NetworkParameters used
|
||||
@@ -116,11 +115,6 @@ public class H2FullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger calculateBalanceForAddress(Address address) throws BlockStoreException {
|
||||
return calculateBalanceForAddress(address, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDuplicateKeyErrorCode() {
|
||||
return H2_DUPLICATE_KEY_ERROR_CODE;
|
||||
@@ -153,19 +147,8 @@ public class H2FullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableExistSQL(String tableName) {
|
||||
return "SELECT * FROM " + tableName + " WHERE 1 = 2";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDatabaseDriverClass() {
|
||||
return DATABASE_DRIVER_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBalanceSelectSQL() {
|
||||
return SELECT_BALANCE_SQL;
|
||||
}
|
||||
|
||||
}
|
@@ -63,10 +63,11 @@ public class MySQLFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
" hash blob NOT NULL,\n" +
|
||||
" `index` integer NOT NULL,\n" +
|
||||
" height integer NOT NULL,\n" +
|
||||
" value blob NOT NULL,\n" +
|
||||
" value bigint NOT NULL,\n" +
|
||||
" scriptbytes mediumblob NOT NULL,\n" +
|
||||
" toaddress varchar(35),\n" +
|
||||
" addresstargetable integer,\n" +
|
||||
" addresstargetable tinyint(1),\n" +
|
||||
" coinbase boolean,\n" +
|
||||
" CONSTRAINT `openoutputs_pk` PRIMARY KEY (hash(32),`index`) USING btree\n" +
|
||||
")\n";
|
||||
|
||||
@@ -78,12 +79,10 @@ public class MySQLFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
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 FROM openOutputs WHERE hash = ? AND `index` = ?";
|
||||
private static final String INSERT_OPENOUTPUTS_SQL = "INSERT INTO openOutputs (hash, `index`, height, value, scriptBytes, toAddress, addressTargetable) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
private static final String SELECT_OPENOUTPUTS_SQL = "SELECT height, value, scriptBytes, coinbase 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_BALANCE_SQL = "SELECT sum(conv(hex(value),16,10)) from openoutputs where toaddress = ?";
|
||||
|
||||
/**
|
||||
* Creates a new MySQLFullPrunedBlockStore.
|
||||
*
|
||||
@@ -147,18 +146,8 @@ public class MySQLFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableExistSQL(String tableName) {
|
||||
return "SELECT * FROM " + tableName + " WHERE 1 = 2";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDatabaseDriverClass() {
|
||||
return DATABASE_DRIVER_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBalanceSelectSQL() {
|
||||
return SELECT_BALANCE_SQL;
|
||||
}
|
||||
}
|
||||
|
@@ -71,10 +71,11 @@ public class PostgresFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
" hash bytea NOT NULL,\n" +
|
||||
" index integer NOT NULL,\n" +
|
||||
" height integer NOT NULL,\n" +
|
||||
" value bytea NOT NULL,\n" +
|
||||
" value bigint NOT NULL,\n" +
|
||||
" scriptbytes bytea NOT NULL,\n" +
|
||||
" toaddress character varying(35),\n" +
|
||||
" addresstargetable integer,\n" +
|
||||
" addresstargetable smallint,\n" +
|
||||
" coinbase boolean,\n" +
|
||||
" CONSTRAINT openoutputs_pk PRIMARY KEY (hash,index)\n" +
|
||||
")\n";
|
||||
|
||||
@@ -87,8 +88,6 @@ public class PostgresFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
|
||||
private static final String SELECT_UNDOABLEBLOCKS_EXISTS_SQL = "select 1 from undoableBlocks where hash = ?";
|
||||
|
||||
private static final String SELECT_BALANCE_SQL = "select sum(('x'||lpad(substr(value::text, 3, 50),16,'0'))::bit(64)::bigint) from openoutputs where toaddress = ?";
|
||||
|
||||
/**
|
||||
* Creates a new PostgresFullPrunedBlockStore.
|
||||
*
|
||||
@@ -160,27 +159,17 @@ public class PostgresFullPrunedBlockStore extends DatabaseFullPrunedBlockStore {
|
||||
return sqlStatements;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableExistSQL(String tableName) {
|
||||
return "SELECT * FROM " + tableName + " WHERE 1 = 2";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDatabaseDriverClass() {
|
||||
return DATABASE_DRIVER_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBalanceSelectSQL() {
|
||||
return SELECT_BALANCE_SQL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(StoredBlock storedBlock, StoredUndoableBlock undoableBlock) throws BlockStoreException {
|
||||
maybeConnect();
|
||||
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
|
||||
byte[] hashBytes = new byte[28];
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 3, hashBytes, 0, 28);
|
||||
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 4, hashBytes, 0, 28);
|
||||
int height = storedBlock.getHeight();
|
||||
byte[] transactions = null;
|
||||
byte[] txOutChanges = null;
|
||||
|
Reference in New Issue
Block a user