private void putUpdateStoredBlock(StoredBlock storedBlock, boolean wasUndoable) throws SQLException { try { PreparedStatement s = conn.get() .prepareStatement( "INSERT INTO headers(hash, chainWork, height, header, wasUndoable)" + " VALUES(?, ?, ?, ?, ?)"); // 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); s.setBytes(1, hashBytes); s.setBytes(2, storedBlock.getChainWork().toByteArray()); s.setInt(3, storedBlock.getHeight()); s.setBytes(4, storedBlock.getHeader().unsafeRimbitSerialize()); s.setBoolean(5, wasUndoable); s.executeUpdate(); s.close(); } catch (SQLException e) { // It is possible we try to add a duplicate StoredBlock if we upgraded // In that case, we just update the entry to mark it wasUndoable if (!(e.getSQLState().equals(POSTGRES_DUPLICATE_KEY_ERROR_CODE)) || !wasUndoable) throw e; PreparedStatement s = conn.get().prepareStatement("UPDATE headers SET wasUndoable=? WHERE hash=?"); 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); s.setBytes(2, hashBytes); s.executeUpdate(); s.close(); } }
public StoredBlock get(Sha256Hash hash, boolean wasUndoableOnly) throws BlockStoreException { // Optimize for chain head if (chainHeadHash != null && chainHeadHash.equals(hash)) return chainHeadBlock; if (verifiedChainHeadHash != null && verifiedChainHeadHash.equals(hash)) return verifiedChainHeadBlock; maybeConnect(); PreparedStatement s = null; try { s = conn.get() .prepareStatement( "SELECT chainWork, height, header, wasUndoable FROM headers WHERE hash = ?"); // 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); s.setBytes(1, hashBytes); ResultSet results = s.executeQuery(); if (!results.next()) { return null; } // Parse it. if (wasUndoableOnly && !results.getBoolean(4)) return null; BigInteger chainWork = new BigInteger(results.getBytes(1)); int height = results.getInt(2); Block b = new Block(params, results.getBytes(3)); b.verifyHeader(); StoredBlock stored = new StoredBlock(b, chainWork, height); return stored; } catch (SQLException ex) { throw new BlockStoreException(ex); } catch (ProtocolException e) { // Corrupted database. throw new BlockStoreException(e); } catch (VerificationException e) { // Should not be able to happen unless the database contains bad // blocks. throw new BlockStoreException(e); } finally { if (s != null) try { s.close(); } catch (SQLException e) { throw new BlockStoreException("Failed to close PreparedStatement"); } } }
public StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException { maybeConnect(); PreparedStatement s = null; try { s = conn.get() .prepareStatement( "SELECT txOutChanges, transactions FROM undoableBlocks WHERE hash = ?"); // 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); s.setBytes(1, hashBytes); ResultSet results = s.executeQuery(); if (!results.next()) { return null; } // Parse it. byte[] txOutChanges = results.getBytes(1); byte[] transactions = results.getBytes(2); StoredUndoableBlock block; if (txOutChanges == null) { int offset = 0; int numTxn = ((transactions[offset++] & 0xFF) << 0) | ((transactions[offset++] & 0xFF) << 8) | ((transactions[offset++] & 0xFF) << 16) | ((transactions[offset++] & 0xFF) << 24); List<Transaction> transactionList = new LinkedList<Transaction>(); for (int i = 0; i < numTxn; i++) { Transaction tx = new Transaction(params, transactions, offset); transactionList.add(tx); offset += tx.getMessageSize(); } block = new StoredUndoableBlock(hash, transactionList); } else { TransactionOutputChanges outChangesObject = new TransactionOutputChanges(new ByteArrayInputStream(txOutChanges)); block = new StoredUndoableBlock(hash, outChangesObject); } return block; } catch (SQLException ex) { throw new BlockStoreException(ex); } catch (NullPointerException e) { // Corrupted database. throw new BlockStoreException(e); } catch (ClassCastException e) { // Corrupted database. throw new BlockStoreException(e); } catch (ProtocolException e) { // Corrupted database. throw new BlockStoreException(e); } catch (IOException e) { // Corrupted database. throw new BlockStoreException(e); } finally { if (s != null) try { s.close(); } catch (SQLException e) { throw new BlockStoreException("Failed to close PreparedStatement"); } } }
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); 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(); bos.write((int) (0xFF & (numTxn >> 0))); bos.write((int) (0xFF & (numTxn >> 8))); bos.write((int) (0xFF & (numTxn >> 16))); bos.write((int) (0xFF & (numTxn >> 24))); for (Transaction tx : undoableBlock.getTransactions()) tx.rimbitSerialize(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: " + Utils.bytesToHexString(hashBytes)); PreparedStatement findS = conn.get().prepareStatement("select 1 from undoableBlocks where hash = ?"); 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 rimbitj PreparedStatement s = conn.get() .prepareStatement( "UPDATE undoableBlocks SET txOutChanges=?, transactions=?" + " WHERE hash = ?"); s.setBytes(3, hashBytes); if (log.isDebugEnabled()) log.debug("Updating undoable block with hash: " + Utils.bytesToHexString(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( "INSERT INTO undoableBlocks(hash, height, txOutChanges, transactions)" + " VALUES(?, ?, ?, ?)"); s.setBytes(1, hashBytes); s.setInt(2, height); if (log.isDebugEnabled()) log.debug( "Inserting undoable block with hash: " + Utils.bytesToHexString(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); } }