/* * Copyright 2014 http://Bither.net * * 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 net.bither.db; import net.bither.ApplicationInstanceManager; import net.bither.bitherj.BitherjSettings; import net.bither.bitherj.core.Block; import net.bither.bitherj.db.AbstractDb; import net.bither.bitherj.db.IBlockProvider; import net.bither.bitherj.exception.AddressFormatException; import net.bither.bitherj.utils.Base58; import net.bither.utils.LogUtil; import net.bither.utils.StringUtil; import net.bither.utils.SystemUtil; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class BlockProvider implements IBlockProvider { private static final String insertBlockSql = "insert into blocks " + "(block_no,block_hash,block_root,block_ver,block_bits,block_nonce,block_time,block_prev,is_main)" + " values (?,?,?,?,?,?,?,?,?) "; private static BlockProvider blockProvider = new BlockProvider(ApplicationInstanceManager.txDBHelper); public static BlockProvider getInstance() { return blockProvider; } private TxDBHelper mDb; private BlockProvider(TxDBHelper db) { this.mDb = db; } public List<Block> getAllBlocks() { List<Block> blockItems = new ArrayList<Block>(); String sql = "select * from blocks order by block_no desc"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); while (c.next()) { blockItems.add(applyCursor(c)); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return blockItems; } @Override public List<Block> getLimitBlocks(int limit) { List<Block> blockItems = new ArrayList<Block>(); String sql = "select * from blocks order by block_no desc limit ?"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Integer.toString(limit)}); ResultSet c = statement.executeQuery(); while (c.next()) { blockItems.add(applyCursor(c)); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return blockItems; } public List<Block> getBlocksFrom(int blockNo) { List<Block> blockItems = new ArrayList<Block>(); String sql = "select * from blocks where block_no>? order by block_no desc"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Integer.toString(blockNo)}); ResultSet c = statement.executeQuery(); while (c.next()) { blockItems.add(applyCursor(c)); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } System.gc(); return blockItems; } public int getBlockCount() { String sql = "select count(*) cnt from blocks "; int count = 0; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); if (c.next()) { int idColumn = c.findColumn("cnt"); if (idColumn != -1) { count = c.getInt(idColumn); } } c.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return count; } public Block getLastBlock() { Block item = null; String sql = "select * from blocks where is_main=1 order by block_no desc limit 1"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); if (c.next()) { item = applyCursor(c); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { } return item; } public Block getLastOrphanBlock() { Block item = null; String sql = "select * from blocks where is_main=0 order by block_no desc limit 1"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); if (c.next()) { item = applyCursor(c); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return item; } public Block getBlock(byte[] blockHash) { Block item = null; String sql = "select * from blocks where block_hash=?"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Base58.encode(blockHash)}); ResultSet c = statement.executeQuery(); if (c.next()) { item = applyCursor(c); } c.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } catch (AddressFormatException e) { e.printStackTrace(); } return item; } public Block getOrphanBlockByPrevHash(byte[] prevHash) { Block item = null; String sql = "select * from blocks where block_prev=? and is_main=0"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Base58.encode(prevHash)}); ResultSet c = statement.executeQuery(); if (c.next()) { item = applyCursor(c); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return item; } public Block getMainChainBlock(byte[] blockHash) { Block item = null; String sql = "select * from blocks where block_hash=? and is_main=1"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Base58.encode(blockHash)}); ResultSet c = statement.executeQuery(); if (c.next()) { item = applyCursor(c); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return item; } public List<byte[]> exists(List<byte[]> blockHashes) { List<byte[]> exists = new ArrayList<byte[]>(); List<Block> blockItems = getAllBlocks(); for (Block blockItm : blockItems) { for (byte[] bytes : exists) { if (Arrays.equals(bytes, blockItm.getBlockHash())) { exists.add(bytes); break; } } } return exists; } public boolean isExist(byte[] blockHash) { boolean result = false; String sql = "select count(0) cnt from blocks where block_hash=?"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Base58.encode(blockHash)}); ResultSet c = statement.executeQuery(); if (c.next()) { int idColumn = c.findColumn("cnt"); if (idColumn != -1) { result = (c.getInt(idColumn) == 1); } } c.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return result; } public void addBlocks(List<Block> blockItemList) { final List<Block> addBlockList = new ArrayList<Block>(); List<Block> allBlockList = getAllBlocks(); for (Block item : blockItemList) { if (!allBlockList.contains(item)) { addBlockList.add(item); } } allBlockList.clear(); try { this.mDb.getConn().setAutoCommit(false); for (Block item : addBlockList) { PreparedStatement preparedStatement = this.mDb.getConn().prepareStatement(insertBlockSql); preparedStatement.setInt(1, item.getBlockNo()); preparedStatement.setString(2, Base58.encode(item.getBlockHash())); preparedStatement.setString(3, Base58.encode(item.getBlockRoot())); preparedStatement.setLong(4, item.getBlockVer()); preparedStatement.setLong(5, item.getBlockBits()); preparedStatement.setLong(6, item.getBlockNonce()); preparedStatement.setInt(7, item.getBlockTime()); preparedStatement.setString(8, Base58.encode(item.getBlockPrev())); preparedStatement.setInt(9, item.isMain() ? 1 : 0); preparedStatement.executeUpdate(); preparedStatement.close(); } this.mDb.getConn().commit(); } catch (SQLException e) { e.printStackTrace(); } LogUtil.printlnOut("addBlocks"); SystemUtil.callSystemGC(); } public void addBlock(Block item) { boolean blockExists = blockExists(item.getBlockHash()); if (!blockExists) { this.mDb.executeUpdate(insertBlockSql, new String[]{Integer.toString(item.getBlockNo()), Base58.encode(item.getBlockHash()), Base58.encode(item.getBlockRoot()), Long.toString(item.getBlockVer()) , Long.toString(item.getBlockBits()), Long.toString(item.getBlockNonce()), Integer.toString(item.getBlockTime()), Base58.encode(item.getBlockPrev()), Integer.toString(item.isMain() ? 1 : 0)}); } LogUtil.printlnOut("addBlock"); } public boolean blockExists(byte[] blockHash) { String sql = "select count(0) cnt from blocks where block_hash=?"; int cnt = 0; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Base58.encode(blockHash)}); ResultSet c = statement.executeQuery(); if (c.next()) { int idColumn = c.findColumn("cnt"); if (idColumn != -1) { cnt = c.getInt(idColumn); } } c.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return cnt > 0; } public void updateBlock(byte[] blockHash, boolean isMain) { this.mDb.executeUpdate("update blocks set is_main=? where block_hash=?", new String[]{Integer.toString(isMain ? 1 : 0), Base58.encode(blockHash)}); } public void removeBlock(byte[] blockHash) { this.mDb.executeUpdate("delete from blocks where block_hash=?", new String[]{Base58.encode(blockHash)}); } public void cleanOldBlock() { try { String sql = "select count(0) cnt from blocks"; PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); int cnt = 0; if (c.next()) { int idColumn = c.findColumn("cnt"); if (idColumn != -1) { cnt = c.getInt(idColumn); } } c.close(); statement.close(); if (cnt > 5000) { sql = "select max(block_no) max_block_no from blocks where is_main=1"; statement = this.mDb.getPreparedStatement(sql, null); c = statement.executeQuery(); int maxBlockNo = 0; if (c.next()) { int idColumn = c.findColumn("max_block_no"); if (idColumn != -1) { maxBlockNo = c.getInt(idColumn); } } c.close(); statement.close(); int blockNo = (maxBlockNo - BitherjSettings.BLOCK_DIFFICULTY_INTERVAL) - maxBlockNo % BitherjSettings.BLOCK_DIFFICULTY_INTERVAL; this.mDb.executeUpdate("delete from blocks where block_no<?", new String[]{Integer.toString(blockNo)}); } } catch (SQLException e) { e.printStackTrace(); } } private Block applyCursor(ResultSet rs) throws AddressFormatException, SQLException { byte[] blockHash = null; long version = 1; byte[] prevBlock = null; byte[] merkleRoot = null; int timestamp = 0; long target = 0; long nonce = 0; int blockNo = 0; boolean isMain = false; int idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_BITS); if (idColumn != -1) { target = rs.getLong(idColumn); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_HASH); if (idColumn != -1) { blockHash = Base58.decode(rs.getString(idColumn)); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_NO); if (idColumn != -1) { blockNo = rs.getInt(idColumn); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_NONCE); if (idColumn != -1) { nonce = rs.getLong(idColumn); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_PREV); if (idColumn != -1) { prevBlock = Base58.decode(rs.getString(idColumn)); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_ROOT); if (idColumn != -1) { merkleRoot = Base58.decode(rs.getString(idColumn)); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_TIME); if (idColumn != -1) { timestamp = rs.getInt(idColumn); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.BLOCK_VER); if (idColumn != -1) { version = rs.getLong(idColumn); } idColumn = rs.findColumn(AbstractDb.BlocksColumns.IS_MAIN); if (idColumn != -1) { isMain = rs.getInt(idColumn) == 1; } return new Block(blockHash, version, prevBlock, merkleRoot, timestamp, target, nonce, blockNo, isMain); } }