/* * * 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.*; import net.bither.bitherj.db.AbstractDb; import net.bither.bitherj.db.IHDAccountProvider; import net.bither.bitherj.exception.AddressFormatException; import net.bither.bitherj.utils.Base58; import net.bither.bitherj.utils.Sha256Hash; import net.bither.bitherj.utils.Utils; import net.bither.utils.StringUtil; import net.bither.utils.SystemUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; public class HDAccountProvider implements IHDAccountProvider { private final static String queryTxHashOfHDAccount = " select distinct txs.tx_hash from addresses_txs txs ,hd_account_addresses hd where txs.address=hd.address"; private final static String inQueryTxHashOfHDAccount = " (" + queryTxHashOfHDAccount + ")"; private static HDAccountProvider txProvider = new HDAccountProvider(ApplicationInstanceManager.txDBHelper); public static HDAccountProvider getInstance() { return txProvider; } private TxDBHelper mDb; public HDAccountProvider(TxDBHelper db) { this.mDb = db; } @Override public void addAddress(List<HDAccount.HDAccountAddress> hdAccountAddresses) { try { this.mDb.getConn().setAutoCommit(false); Connection conn = this.mDb.getConn(); for (HDAccount.HDAccountAddress hdAccountAddress : hdAccountAddresses) { addAddress(conn, hdAccountAddress); } conn.commit(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } @Override public int issuedIndex(AbstractHD.PathType pathType) { int issuedIndex = -1; try { PreparedStatement statement = this.mDb.getPreparedStatement("select ifnull(max(address_index),-1) address_index from " + AbstractDb.Tables.HD_ACCOUNT_ADDRESS + " where path_type=? and is_issued=? ", new String[]{Integer.toString(pathType.getValue()), "1"}); ResultSet cursor = statement.executeQuery(); if (cursor.next()) { int idColumn = cursor.findColumn(AbstractDb.HDAccountAddressesColumns.ADDRESS_INDEX); if (idColumn != -1) { issuedIndex = cursor.getInt(idColumn); } } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return issuedIndex; } @Override public int allGeneratedAddressCount(AbstractHD.PathType pathType) { int count = 0; try { PreparedStatement statement = this.mDb.getPreparedStatement("select ifnull(count(address),0) count from " + AbstractDb.Tables.HD_ACCOUNT_ADDRESS + " where path_type=? ", new String[]{Integer.toString(pathType.getValue())}); ResultSet cursor = statement.executeQuery(); if (cursor.next()) { int idColumn = cursor.findColumn("count"); if (idColumn != -1) { count = cursor.getInt(idColumn); } } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return count; } @Override public void updateIssuedIndex(AbstractHD.PathType pathType, int index) { String sql = "update hd_account_addresses set is_issued=? where path_type=? and address_index<=? "; Connection conn = this.mDb.getConn(); try { String[] params = new String[]{ "1", Integer.toString(pathType.getValue()), Integer.toString(index) }; PreparedStatement stmt = conn.prepareStatement(sql); if (params != null) { for (int i = 0; i < params.length; i++) { stmt.setString(i + 1, params[i]); } } stmt.executeUpdate(); conn.commit(); stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } @Override public String externalAddress() { String address = null; try { PreparedStatement statement = this.mDb.getPreparedStatement("select address from " + AbstractDb.Tables.HD_ACCOUNT_ADDRESS + " where path_type=? and is_issued=? order by address_index asc limit 1 ", new String[]{Integer.toString(AbstractHD.PathType.EXTERNAL_ROOT_PATH.getValue()), "0"}); ResultSet cursor = statement.executeQuery(); if (cursor.next()) { int idColumn = cursor.findColumn(AbstractDb.HDAccountAddressesColumns.ADDRESS); if (idColumn != -1) { address = cursor.getString(idColumn); } } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return address; } @Override public HashSet<String> getBelongAccountAddresses(List<String> addressList) { HashSet<String> addressSet = new HashSet<String>(); List<String> temp = new ArrayList<String>(); if (addressList != null) { for (String str : addressList) { temp.add(Utils.format("'%s'", str)); } } try { String sql = Utils.format("select address from hd_account_addresses where address in (%s) " , Utils.joinString(temp, ",")); PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet cursor = statement.executeQuery(); while (cursor.next()) { int idColumn = cursor.findColumn(AbstractDb.HDAccountAddressesColumns.ADDRESS); if (idColumn != -1) { addressSet.add(cursor.getString(idColumn)); } } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } SystemUtil.maxUsedSize(); return addressSet; } @Override public HDAccount.HDAccountAddress addressForPath(AbstractHD.PathType type, int index) { HDAccount.HDAccountAddress accountAddress = null; try { PreparedStatement statement = this.mDb.getPreparedStatement("select address,pub,path_type,address_index,is_issued,is_synced from " + AbstractDb.Tables.HD_ACCOUNT_ADDRESS + " where path_type=? and address_index=? ", new String[]{Integer.toString(type.getValue()), Integer.toString(index)}); ResultSet cursor = statement.executeQuery(); if (cursor.next()) { accountAddress = formatAddress(cursor); } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return accountAddress; } @Override public List<byte[]> getPubs(AbstractHD.PathType pathType) { List<byte[]> adressPubList = new ArrayList<byte[]>(); try { PreparedStatement statement = this.mDb.getPreparedStatement("select pub from hd_account_addresses where path_type=? ", new String[]{Integer.toString(pathType.getValue())}); ResultSet cursor = statement.executeQuery(); while (cursor.next()) { try { int idColumn = cursor.findColumn(AbstractDb.HDAccountAddressesColumns.PUB); if (idColumn != -1) { adressPubList.add(Base58.decode(cursor.getString(idColumn))); } } catch (AddressFormatException e) { e.printStackTrace(); } } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return adressPubList; } @Override public List<HDAccount.HDAccountAddress> belongAccount(List<String> addresses) { List<HDAccount.HDAccountAddress> hdAccountAddressList = new ArrayList<HDAccount.HDAccountAddress>(); List<String> temp = new ArrayList<String>(); for (String str : addresses) { temp.add(Utils.format("'%s'", str)); } String sql = "select address,pub,path_type,address_index,is_issued,is_synced from " + AbstractDb.Tables.HD_ACCOUNT_ADDRESS + " where address in (" + Utils.joinString(temp, ",") + ")"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet cursor = statement.executeQuery(); while (cursor.next()) { hdAccountAddressList.add(formatAddress(cursor)); } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return hdAccountAddressList; } @Override public void updateSyncdComplete(HDAccount.HDAccountAddress address) { String sql = "update hd_account_addresses set is_synced=? where address=? "; Connection conn = this.mDb.getConn(); try { String[] params = new String[]{ Integer.toString(address.isSyncedComplete() ? 1 : 0), address.getAddress() }; PreparedStatement stmt = conn.prepareStatement(sql); if (params != null) { for (int i = 0; i < params.length; i++) { stmt.setString(i + 1, params[i]); } } stmt.executeUpdate(); conn.commit(); stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } @Override public void setSyncdNotComplete() { this.mDb.executeUpdate("update hd_account_addresses set is_synced=?", new String[]{"0"}); } @Override public int unSyncedAddressCount() { int cnt = 0; try { String sql = "select count(address) cnt from hd_account_addresses where is_synced=? "; PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{"0"}); ResultSet cursor = statement.executeQuery(); if (cursor.next()) { int idColumn = cursor.findColumn("cnt"); if (idColumn != -1) { cnt = cursor.getInt(idColumn); } } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return cnt; } @Override public void updateSyncdForIndex(AbstractHD.PathType pathType, int index) { this.mDb.executeUpdate("update hd_account_addresses set is_synced=? where path_type=? and address_index>? " , new String[]{"1", Integer.toString(pathType.getValue()), Integer.toString(index)}); } @Override public List<HDAccount.HDAccountAddress> getSigningAddressesForInputs(List<In> inList) { List<HDAccount.HDAccountAddress> hdAccountAddressList = new ArrayList<HDAccount.HDAccountAddress>(); ResultSet c; try { for (In in : inList) { String sql = "select a.*" + " from hd_account_addresses a ,outs b" + " where a.address=b.out_address" + " and b.tx_hash=? and b.out_sn=? "; OutPoint outPoint = in.getOutpoint(); PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Base58.encode(in.getPrevTxHash()), Integer.toString(outPoint.getOutSn())}); c = statement.executeQuery(); if (c.next()) { hdAccountAddressList.add(formatAddress(c)); } c.close(); statement.close(); } } catch (SQLException e) { e.printStackTrace(); } return hdAccountAddressList; } @Override public int hdAccountTxCount() { int result = 0; try { String sql = "select count( distinct a.tx_hash) cnt from addresses_txs a ,hd_account_addresses b where a.address=b.address "; PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); if (c.next()) { int idColumn = c.findColumn("cnt"); if (idColumn != -1) { result = c.getInt(idColumn); } } c.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return result; } @Override public long getHDAccountConfirmedBanlance(int hdAccountId) { long sum = 0; String unspendOutSql = "select ifnull(sum(a.out_value),0) sum from outs a,txs b where a.tx_hash=b.tx_hash " + " and a.out_status=? and a.hd_account_id=? and b.block_no is not null"; try { PreparedStatement statement = this.mDb.getPreparedStatement(unspendOutSql, new String[]{Integer.toString(Out.OutStatus.unspent.getValue()), Integer.toString(hdAccountId)}); ResultSet c = statement.executeQuery(); if (c.next()) { int idColumn = c.findColumn("sum"); if (idColumn != -1) { sum = c.getLong(idColumn); } } c.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return sum; } @Override public List<Tx> getHDAccountUnconfirmedTx() { List<Tx> txList = new ArrayList<Tx>(); HashMap<Sha256Hash, Tx> txDict = new HashMap<Sha256Hash, Tx>(); try { String sql = "select * from txs where tx_hash in" + inQueryTxHashOfHDAccount + " and block_no is null " + " order by block_no desc"; PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); while (c.next()) { Tx txItem = TxHelper.applyCursor(c); txItem.setIns(new ArrayList<In>()); txItem.setOuts(new ArrayList<Out>()); txList.add(txItem); txDict.put(new Sha256Hash(txItem.getTxHash()), txItem); } c.close(); statement.close(); sql = "select b.* " + " from ins b, txs c " + " where c.tx_hash in " + inQueryTxHashOfHDAccount + " and b.tx_hash=c.tx_hash and c.block_no is null " + " order by b.tx_hash ,b.in_sn"; statement = this.mDb.getPreparedStatement(sql, null); c = statement.executeQuery(); while (c.next()) { In inItem = TxHelper.applyCursorIn(c); Tx tx = txDict.get(new Sha256Hash(inItem.getTxHash())); if (tx != null) { tx.getIns().add(inItem); } } c.close(); statement.close(); sql = "select b.* " + " from outs b, txs c " + " where c.tx_hash in" + inQueryTxHashOfHDAccount + " and b.tx_hash=c.tx_hash and c.block_no is null " + " order by b.tx_hash,b.out_sn"; statement = this.mDb.getPreparedStatement(sql, null); c = statement.executeQuery(); while (c.next()) { Out out = TxHelper.applyCursorOut(c); Tx tx = txDict.get(new Sha256Hash(out.getTxHash())); if (tx != null) { tx.getOuts().add(out); } } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return txList; } @Override public long sentFromAccount(int hdAccountId, byte[] txHash) { String sql = "select sum(o.out_value) out_value from ins i,outs o where" + " i.tx_hash=? and o.tx_hash=i.prev_tx_hash and i.prev_out_sn=o.out_sn and o.hd_account_id=?"; long sum = 0; ResultSet cursor; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Base58.encode(txHash), Integer.toString(hdAccountId)}); cursor = statement.executeQuery(); if (cursor.next()) { int idColumn = cursor.findColumn(AbstractDb.OutsColumns.OUT_VALUE); if (idColumn != -1) { sum = cursor.getLong(idColumn); } } cursor.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return sum; } @Override public List<Tx> getTxAndDetailByHDAccount() { List<Tx> txItemList = new ArrayList<Tx>(); HashMap<Sha256Hash, Tx> txDict = new HashMap<Sha256Hash, Tx>(); try { String sql = "select * from txs where tx_hash in " + inQueryTxHashOfHDAccount + " order by" + " ifnull(block_no,4294967295) desc "; PreparedStatement statement = this.mDb.getPreparedStatement(sql, null); ResultSet c = statement.executeQuery(); StringBuilder txsStrBuilder = new StringBuilder(); while (c.next()) { Tx txItem = TxHelper.applyCursor(c); txItem.setIns(new ArrayList<In>()); txItem.setOuts(new ArrayList<Out>()); txItemList.add(txItem); txDict.put(new Sha256Hash(txItem.getTxHash()), txItem); txsStrBuilder.append("'").append(Base58.encode(txItem.getTxHash())).append("'").append(","); } c.close(); statement.close(); if (txsStrBuilder.length() > 1) { String txs = txsStrBuilder.substring(0, txsStrBuilder.length() - 1); sql = Utils.format("select b.* from ins b where b.tx_hash in (%s)" + " order by b.tx_hash ,b.in_sn", txs); statement = this.mDb.getPreparedStatement(sql, null); c = statement.executeQuery(); while (c.next()) { In inItem = TxHelper.applyCursorIn(c); Tx tx = txDict.get(new Sha256Hash(inItem.getTxHash())); if (tx != null) { tx.getIns().add(inItem); } } c.close(); statement.close(); sql = Utils.format("select b.* from outs b where b.tx_hash in (%s)" + " order by b.tx_hash,b.out_sn", txs); statement = this.mDb.getPreparedStatement(sql, null); c = statement.executeQuery(); while (c.next()) { Out out = TxHelper.applyCursorOut(c); Tx tx = txDict.get(new Sha256Hash(out.getTxHash())); if (tx != null) { tx.getOuts().add(out); } } c.close(); statement.close(); } } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return txItemList; } @Override public List<Tx> getTxAndDetailByHDAccount(int page) { List<Tx> txItemList = new ArrayList<Tx>(); HashMap<Sha256Hash, Tx> txDict = new HashMap<Sha256Hash, Tx>(); try { String sql = "select * from txs where tx_hash in " + inQueryTxHashOfHDAccount + " order by" + " ifnull(block_no,4294967295) desc limit ?,? "; PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{ Integer.toString((page - 1) * BitherjSettings.TX_PAGE_SIZE), Integer.toString(BitherjSettings.TX_PAGE_SIZE) }); ResultSet c = statement.executeQuery(); StringBuilder txsStrBuilder = new StringBuilder(); while (c.next()) { Tx txItem = TxHelper.applyCursor(c); txItem.setIns(new ArrayList<In>()); txItem.setOuts(new ArrayList<Out>()); txItemList.add(txItem); txDict.put(new Sha256Hash(txItem.getTxHash()), txItem); txsStrBuilder.append("'").append(Base58.encode(txItem.getTxHash())).append("'").append(","); } c.close(); statement.close(); if (txsStrBuilder.length() > 1) { String txs = txsStrBuilder.substring(0, txsStrBuilder.length() - 1); sql = Utils.format("select b.* from ins b where b.tx_hash in (%s)" + " order by b.tx_hash ,b.in_sn", txs); statement = this.mDb.getPreparedStatement(sql, null); c = statement.executeQuery(); while (c.next()) { In inItem = TxHelper.applyCursorIn(c); Tx tx = txDict.get(new Sha256Hash(inItem.getTxHash())); if (tx != null) { tx.getIns().add(inItem); } } c.close(); statement.close(); sql = Utils.format("select b.* from outs b where b.tx_hash in (%s)" + " order by b.tx_hash,b.out_sn", txs); statement = this.mDb.getPreparedStatement(sql, null); c = statement.executeQuery(); while (c.next()) { Out out = TxHelper.applyCursorOut(c); Tx tx = txDict.get(new Sha256Hash(out.getTxHash())); if (tx != null) { tx.getOuts().add(out); } } c.close(); statement.close(); } } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return txItemList; } @Override public List<Out> getUnspendOutByHDAccount(int hdAccountId) { List<Out> outItems = new ArrayList<Out>(); String unspendOutSql = "select a.* from outs a,txs b where a.tx_hash=b.tx_hash " + " and a.out_status=? and a.hd_account_id=?"; try { PreparedStatement statement = this.mDb.getPreparedStatement(unspendOutSql, new String[]{Integer.toString(Out.OutStatus.unspent.getValue()), Integer.toString(hdAccountId)}); ResultSet c = statement.executeQuery(); while (c.next()) { outItems.add(TxHelper.applyCursorOut(c)); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return outItems; } @Override public List<Tx> getRecentlyTxsByAccount(int greateThanBlockNo, int limit) { List<Tx> txItemList = new ArrayList<Tx>(); String sql = "select * from txs where tx_hash in " + inQueryTxHashOfHDAccount + " and ((block_no is null) or (block_no is not null and block_no>?)) " + " order by ifnull(block_no,4294967295) desc, tx_time desc " + " limit ? "; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Integer.toString(greateThanBlockNo), Integer.toString(limit)}); ResultSet c = statement.executeQuery(); while (c.next()) { Tx txItem = TxHelper.applyCursor(c); txItemList.add(txItem); } for (Tx item : txItemList) { TxHelper.addInsAndOuts(mDb, item); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return txItemList; } @Override public int getUnspendOutCountByHDAccountWithPath(int hdAccountId, AbstractHD.PathType pathType) { int result = 0; String sql = "select count(tx_hash) cnt from outs where out_address in " + "(select address from hd_account_addresses where path_type =? and out_status=?) " + "and hd_account_id=?"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Integer.toString(pathType.getValue()) , Integer.toString(Out.OutStatus.unspent.getValue()) , Integer.toString(hdAccountId) }); ResultSet c = statement.executeQuery(); if (c.next()) { int idColumn = c.findColumn("cnt"); if (idColumn != -1) { result = c.getInt(idColumn); } } c.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } return result; } @Override public List<Out> getUnspendOutByHDAccountWithPath(int hdAccountId, AbstractHD.PathType pathType) { List<Out> outList = new ArrayList<Out>(); String sql = "select * from outs where out_address in " + "(select address from hd_account_addresses where path_type =? and out_status=?) " + "and hd_account_id=?"; try { PreparedStatement statement = this.mDb.getPreparedStatement(sql, new String[]{Integer.toString(pathType.getValue()) , Integer.toString(Out.OutStatus.unspent.getValue()) , Integer.toString(hdAccountId) }); ResultSet c = statement.executeQuery(); while (c.next()) { outList.add(TxHelper.applyCursorOut(c)); } c.close(); statement.close(); } catch (AddressFormatException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return outList; } private HDAccount.HDAccountAddress formatAddress(ResultSet c) throws SQLException { String address = null; byte[] pubs = null; AbstractHD.PathType ternalRootType = AbstractHD.PathType.EXTERNAL_ROOT_PATH; int index = 0; boolean isIssued = false; boolean isSynced = true; HDAccount.HDAccountAddress hdAccountAddress = null; try { int idColumn = c.findColumn(AbstractDb.HDAccountAddressesColumns.ADDRESS); if (idColumn != -1) { address = c.getString(idColumn); } idColumn = c.findColumn(AbstractDb.HDAccountAddressesColumns.PUB); if (idColumn != -1) { pubs = Base58.decode(c.getString(idColumn)); } idColumn = c.findColumn(AbstractDb.HDAccountAddressesColumns.PATH_TYPE); if (idColumn != -1) { ternalRootType = AbstractHD.getTernalRootType(c.getInt(idColumn)); } idColumn = c.findColumn(AbstractDb.HDAccountAddressesColumns.ADDRESS_INDEX); if (idColumn != -1) { index = c.getInt(idColumn); } idColumn = c.findColumn(AbstractDb.HDAccountAddressesColumns.IS_ISSUED); if (idColumn != -1) { isIssued = c.getInt(idColumn) == 1; } idColumn = c.findColumn(AbstractDb.HDAccountAddressesColumns.IS_SYNCED); if (idColumn != -1) { isSynced = c.getInt(idColumn) == 1; } hdAccountAddress = new HDAccount.HDAccountAddress(address, pubs, ternalRootType, index, isIssued, isSynced); } catch (AddressFormatException e) { e.printStackTrace(); } return hdAccountAddress; } private void addAddress(Connection conn, HDAccount.HDAccountAddress accountAddress) throws SQLException { String sql = "insert into hd_account_addresses(path_type,address_index" + ",is_issued,address,pub,is_synced) " + " values(?,?,?,?,?,?)"; String[] params = new String[]{Integer.toString(accountAddress.getPathType().getValue()) , Integer.toString(accountAddress.getIndex()) , Integer.toString(accountAddress.isIssued() ? 1 : 0) , accountAddress.getAddress() , Base58.encode(accountAddress.getPub()) , Integer.toString(accountAddress.isSyncedComplete() ? 1 : 0) }; PreparedStatement stmt = conn.prepareStatement(sql); if (params != null) { for (int i = 0; i < params.length; i++) { stmt.setString(i + 1, params[i]); } } stmt.executeUpdate(); stmt.close(); } }