org.coinspark.wallet.CSBalanceDatabase.java Source code

Java tutorial

Introduction

Here is the source code for org.coinspark.wallet.CSBalanceDatabase.java

Source

/* 
 * SparkBit's Bitcoinj
 *
 * Copyright 2014 Coin Sciences Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.coinspark.wallet;

import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionInput;
import com.google.bitcoin.core.TransactionOutPoint;
import com.google.bitcoin.core.TransactionOutput;
import com.google.bitcoin.utils.Threading;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import org.coinspark.core.CSLogger;
import org.coinspark.core.CSUtils;
import org.coinspark.protocol.CoinSparkBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.Arrays;

public class CSBalanceDatabase {

    protected final ReentrantLock lock = Threading.lock("balancedb");

    private static final Logger log = LoggerFactory.getLogger(CSBalanceDatabase.class);
    private CSLogger csLog;

    private class CSBalanceEntry {
        public int offsetInDB;
        public BigInteger qty;
        public Date qtyChecked;
        public int qtyFailures;
        public CSBalance.CSBalanceState balanceState;
        public static final int serializedSize = 16;

        public CSBalanceEntry() {
            qty = null;
            qtyChecked = null;
            qtyFailures = 0;
            balanceState = CSBalance.CSBalanceState.NEVERCHECKED;
        }

        public CSBalanceEntry(CSBalanceEntry Model) {
            qty = Model.qty;
            qtyChecked = Model.qtyChecked;
            qtyFailures = Model.qtyFailures;
            balanceState = Model.balanceState;
            offsetInDB = Model.offsetInDB;
        }

        public CSBalanceEntry(byte[] Serialized, int off, int OffsetInDB) {
            offsetInDB = OffsetInDB;
            qty = null;
            qtyChecked = null;
            qtyChecked = new Date((long) CSUtils.littleEndianToInt(Serialized, off + 8) * 1000);
            qtyFailures = CSUtils.littleEndianToInt(Serialized, off + 12) & 0xFFFFFF;
            switch (Serialized[off + 15]) {
            case 0:
                balanceState = CSBalance.CSBalanceState.ZERO;
                qty = new BigInteger("0");
                break;
            case 1:
                balanceState = CSBalance.CSBalanceState.VALID;
                qty = CSUtils.littleEndianToBigInteger(Serialized, off);
                break;
            case 2:
                balanceState = CSBalance.CSBalanceState.UNKNOWN;
                qty = CSUtils.littleEndianToBigInteger(Serialized, off);
                break;
            case 3:
                balanceState = CSBalance.CSBalanceState.SPENT;
                qty = CSUtils.littleEndianToBigInteger(Serialized, off);
                break;
            case 4:
                balanceState = CSBalance.CSBalanceState.NEVERCHECKED;
                qty = CSUtils.littleEndianToBigInteger(Serialized, off);
                qtyChecked = null;
                break;
            case 5:
                balanceState = CSBalance.CSBalanceState.REFRESH;
                qtyChecked = null;
                break;
            case 6:
                balanceState = CSBalance.CSBalanceState.REFRESH;
                qty = CSUtils.littleEndianToBigInteger(Serialized, off);
                qtyChecked = null;
                break;
            case 7:
                balanceState = CSBalance.CSBalanceState.DELETED;
                qtyChecked = null;
                break;
            case 8:
                balanceState = CSBalance.CSBalanceState.SELF;
                qty = CSUtils.littleEndianToBigInteger(Serialized, off);
                break;
            case 9:
                balanceState = CSBalance.CSBalanceState.CALCULATED;
                qty = CSUtils.littleEndianToBigInteger(Serialized, off);
                break;
            }
        }

        public CSBalanceDatabase.CSBalanceEntry setQty(BigInteger Qty, CSBalance.CSBalanceState State) {
            balanceState = State;
            switch (State) {
            case UNKNOWN:
            case SELF:
            case CALCULATED:
            case SPENT:
                qtyChecked = new Date();
                qtyFailures++;
                qty = Qty;
                break;
            case ZERO:
            case VALID:
                qtyChecked = new Date();
                qtyFailures = 0;
                if (Qty.longValue() == 0) {
                    balanceState = CSBalance.CSBalanceState.ZERO;
                }
                qty = Qty;
                break;
            case REFRESH:
            case DELETED:
                qtyChecked = null;
                qtyFailures = 0;
                break;
            case NEVERCHECKED:
                qtyChecked = null;
                qtyFailures = 0;
                qty = Qty;
                break;
            }
            return this;
        }

        public byte[] serialize() {
            int balanceCode = 0;
            BigInteger storedQty = new BigInteger("0");
            int ts = 0;
            if (qtyChecked != null) {
                ts = (int) (qtyChecked.getTime() / 1000);
            }
            switch (balanceState) {
            case NEVERCHECKED:
                balanceCode = 4;
                if (qty != null) {
                    storedQty = qty;
                }
                ;
                break;
            case REFRESH:
                balanceCode = 5;
                if (qty != null) {
                    balanceCode = 6;
                    storedQty = qty;
                }
                break;
            case SPENT:
                balanceCode = 3;
                storedQty = qty;
                break;
            case UNKNOWN:
                balanceCode = 2;
                if (qty != null) {
                    storedQty = qty;
                }
                ;
                break;
            case SELF:
                balanceCode = 8;
                if (qty != null) {
                    storedQty = qty;
                }
                ;
                break;
            case CALCULATED:
                balanceCode = 9;
                if (qty != null) {
                    storedQty = qty;
                }
                ;
                break;
            case VALID:
                balanceCode = 1;
                storedQty = qty;
                break;
            case ZERO:
                balanceCode = 0;
                break;
            case DELETED:
                balanceCode = 7;
                break;
            }

            byte[] s = new byte[16];
            CSUtils.littleEndianByteArray(storedQty, s, 0);
            CSUtils.littleEndianByteArray(ts, s, 8);
            CSUtils.littleEndianByteArray(CSUtils.codedSize(qtyFailures, balanceCode), s, 12);
            return s;
        }
    }

    private class CSTxOutEntry {
        public ConcurrentHashMap<Integer, CSBalanceDatabase.CSBalanceEntry> map;
        public int offsetInDB;
        public int mapSize;
        public int serializedSize = 0;
        public static final int serializedRowSize = 20;
        public static final int serializedHeaderSize = 40;

        CSTxOutEntry() {
        }

        CSTxOutEntry(byte[] Serialized, int OffsetInDB) {
            offsetInDB = OffsetInDB;
            mapSize = Serialized.length / serializedRowSize;
            map = new ConcurrentHashMap<Integer, CSBalanceDatabase.CSBalanceEntry>(mapSize, 0.9f, 1);
            int off = 0;
            int AssetID;
            for (int i = 0; i < mapSize; i++) {
                AssetID = CSUtils.littleEndianToInt(Serialized, off);
                off += 4;
                map.put(AssetID, new CSBalanceDatabase.CSBalanceEntry(Serialized, off,
                        OffsetInDB + CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize + off - 4));
                off += CSBalanceDatabase.CSBalanceEntry.serializedSize;
            }
        }

        CSTxOutEntry(int EstimatedSize, int OffsetInDB) {
            offsetInDB = OffsetInDB;
            mapSize = 0;
            map = new ConcurrentHashMap<Integer, CSBalanceDatabase.CSBalanceEntry>(EstimatedSize, 0.9f, 1);
        }

        CSTxOutEntry(int[] AssetIDs, Map<Integer, BigInteger> balances, int OffsetInDB) {
            offsetInDB = OffsetInDB;
            mapSize = AssetIDs.length + 1;

            map = new ConcurrentHashMap<Integer, CSBalanceDatabase.CSBalanceEntry>(mapSize, 0.9f, 1);
            if (balances.containsKey(0)) {
                map.put(0, new CSBalanceDatabase.CSBalanceEntry().setQty(balances.get(0),
                        CSBalance.CSBalanceState.VALID));
            } else {
                map.put(0, new CSBalanceDatabase.CSBalanceEntry());
            }
            for (int i = 0; i < AssetIDs.length; i++) {
                if (AssetIDs[i] > 0) {
                    if (map.get(AssetIDs[i]) == null) {
                        if (balances.containsKey(AssetIDs[i])) {
                            long value = balances.get(AssetIDs[i]).longValue();
                            CSBalance.CSBalanceState type = CSBalance.CSBalanceState.SELF;
                            if (value < 0) {
                                value = -value;
                                type = CSBalance.CSBalanceState.NEVERCHECKED;
                            }
                            csLog.info("Balance DB: AssetID: " + AssetIDs[i] + ", Quantity: " + value + " (" + type
                                    + ")");
                            map.put(AssetIDs[i],
                                    new CSBalanceDatabase.CSBalanceEntry().setQty(BigInteger.valueOf(value), type));
                        } else {
                            map.put(AssetIDs[i], new CSBalanceDatabase.CSBalanceEntry());
                        }
                    }
                }
            }
            mapSize = map.size();
            serializedSize = mapSize * 16;
        }

        public void add(int AssetID, CSBalanceDatabase.CSBalanceEntry Entry) {
            map.put(AssetID, Entry);
            mapSize++;
        }

        public byte[] serialize() {
            byte[] s = new byte[CSBalanceDatabase.CSTxOutEntry.serializedRowSize * mapSize];

            int off = 0;
            for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : map.entrySet()) {
                CSUtils.littleEndianByteArray(entry.getKey(), s, off);
                off += 4;
                CSUtils.copyArray(s, off, entry.getValue().serialize());
                off += CSBalanceDatabase.CSBalanceEntry.serializedSize;
            }
            return s;
        }

    }

    private CSAssetDatabase assetDB;
    public static final String BALANCE_DB_SUFFIX = ".csbalances";
    private static final int BALANCE_DB_CODE_TXOUT = 0;
    private static final int BALANCE_DB_CODE_DELETED = 1;
    private static final int BALANCE_DB_CODE_NEWASSET = 2;
    private static final int BALANCE_DB_CODE_DELETEDASSET = 3;
    private static final int BALANCE_DB_MAXIMAL_UNKNOWN_DEPTH = 5000;
    private String fileName;
    private int fileSize = 0;
    private int deadSize = 0;
    private HashMap<String, CSBalanceDatabase.CSTxOutEntry> map = new HashMap<String, CSBalanceDatabase.CSTxOutEntry>(
            16, 0.9f);
    private List<Integer> newAssets = new ArrayList<Integer>();
    private List<Integer> deletedAssets = new ArrayList<Integer>();
    private List<CSBalanceTransaction> trackedTransactions = new ArrayList<CSBalanceTransaction>();
    //    private List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates= new ArrayList<CSBalanceDatabase.CSBalanceUpdate>();        

    public CSBalanceDatabase() {
    }

    public CSBalanceDatabase(String FilePrefix, CSAssetDatabase AssetDB, CSLogger CSLog) {
        fileName = FilePrefix + BALANCE_DB_SUFFIX;
        assetDB = AssetDB;
        csLog = CSLog;
        load();
    }

    private void load() {

        RandomAccessFile aFile;
        try {
            aFile = new RandomAccessFile(fileName, "r");
        } catch (FileNotFoundException ex) {
            log.info("Balance DB: Cannot open file " + ex.getClass().getName() + " " + ex.getMessage());
            return;
        }

        fileSize = 0;
        try {
            fileSize = (int) aFile.length();
        } catch (IOException ex) {
            log.error("Balance DB: Cannot get file size " + ex.getClass().getName() + " " + ex.getMessage());
            return;
        }

        map.clear();
        newAssets.clear();
        deletedAssets.clear();

        int off = 0;
        deadSize = 0;
        int size, code;

        byte[] SerializedTxOut = new byte[CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize];
        while (off + CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize <= fileSize) {
            if (!CSUtils.readFromFileToBytes(aFile, SerializedTxOut)) {
                log.error("Balance DB: Cannot read file");
                return;
            }

            size = (CSUtils.littleEndianToInt(SerializedTxOut, 36) & 0xFFFFFF)
                    * CSBalanceDatabase.CSTxOutEntry.serializedRowSize;
            code = SerializedTxOut[39];
            if (off + size > fileSize) {
                log.error("Balance DB: Corrupted file, on " + off);
                fileSize = off;
                return;
            }

            Sha256Hash hash = new Sha256Hash(Arrays.copyOf(SerializedTxOut, 32));
            CSTransactionOutput txOut = new CSTransactionOutput(hash,
                    CSUtils.littleEndianToInt(SerializedTxOut, 32));

            byte[] Serialized = null;
            if (size > 0) {
                Serialized = new byte[size];
                if (!CSUtils.readFromFileToBytes(aFile, Serialized)) {
                    log.error("Balance DB: Cannot read file");
                    return;
                }
            }

            if (!hash.equals(Sha256Hash.ZERO_HASH)) {
                if (code == BALANCE_DB_CODE_TXOUT) {
                    map.put(txOut.getStrValue(), new CSBalanceDatabase.CSTxOutEntry(Serialized, off));
                } else {
                    deadSize += CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize + size;
                }
            } else {
                int AssetID = CSUtils.littleEndianToInt(SerializedTxOut, 32);
                if (code == BALANCE_DB_CODE_NEWASSET) {
                    int pos = deletedAssets.indexOf(AssetID);
                    if (pos >= 0) {
                        deletedAssets.remove(pos);
                    }
                    newAssets.add(AssetID);
                }
                if (code == BALANCE_DB_CODE_DELETEDASSET) {
                    int pos = newAssets.indexOf(AssetID);
                    if (pos >= 0) {
                        newAssets.remove(pos);
                    }
                    deletedAssets.add(AssetID);
                }
            }

            off += CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize + size;
        }

        if (off < fileSize) {
            log.error("Balance DB: Corrupted file, on " + off);
            fileSize = off;
            return;
        }

        try {
            aFile.close();
        } catch (IOException ex) {
            log.error("Balance DB: Cannot close file " + ex.getClass().getName() + " " + ex.getMessage());
        }

        log.info("Balance DB: File opened: " + fileName);
    }

    private boolean saveTxOut(CSTransactionOutput TxOut, CSBalanceDatabase.CSTxOutEntry TxOutEntry, int Offset,
            int CodedSize) {
        RandomAccessFile aFile;

        try {
            aFile = new RandomAccessFile(fileName, "rw");
        } catch (FileNotFoundException ex) {
            log.info("Balance DB: Cannot open file " + ex.getClass().getName() + " " + ex.getMessage());
            return false;
        }

        try {
            aFile.seek(Offset);
        } catch (IOException ex) {
            log.error("Balance DB: Cannot set file position " + ex.getClass().getName() + " " + ex.getMessage());
            return false;
        }

        byte[] s;
        if (TxOutEntry != null) {
            s = new byte[CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize
                    + TxOutEntry.mapSize * CSBalanceDatabase.CSTxOutEntry.serializedRowSize];
            CSUtils.copyArray(s, CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize, TxOutEntry.serialize());
        } else {
            s = new byte[CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize];
        }

        CSUtils.copyArray(s, 0, TxOut.getTxID().getBytes());
        CSUtils.littleEndianByteArray(TxOut.getIndex(), s, 32);
        CSUtils.littleEndianByteArray(CodedSize, s, 36);

        try {
            aFile.write(s);
        } catch (IOException ex) {
            log.error("Balance DB: Cannot write to file " + ex.getClass().getName() + " " + ex.getMessage());
            return false;
        }

        if (Offset >= fileSize) {
            fileSize = Offset + s.length;
        }

        try {
            aFile.close();
        } catch (IOException ex) {
            log.error("Balance DB: Cannot close file " + ex.getClass().getName() + " " + ex.getMessage());
            return false;
        }

        return true;
    }

    public boolean insertTxOut(CSTransactionOutput TxOut, int[] AssetIDs, Map<Integer, BigInteger> outputBalances) {
        if (TxOut == null) {
            return false;
        }

        if (map.get(TxOut.getStrValue()) != null) {
            log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex()
                    + " already in the database");
            return true;
        }

        int[] ids;
        ids = AssetIDs;
        if (ids == null) {
            csLog.info("Balance DB: Inserting new all-assets TxOut: " + TxOut.toString());
            if (assetDB != null) {
                ids = assetDB.getAssetIDs();
            } else {
                return false;
            }
        } else {
            csLog.info("Balance DB: Inserting new TxOut: " + TxOut.toString());
        }

        if (ids == null) {
            log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex()
                    + " has no relevant assets");
            return true;
        }

        boolean result = true;

        lock.lock();
        try {
            CSBalanceDatabase.CSTxOutEntry TxOutEntry = new CSBalanceDatabase.CSTxOutEntry(ids, outputBalances,
                    fileSize);

            map.put(TxOut.getStrValue(), TxOutEntry);

            if (!saveTxOut(TxOut, TxOutEntry, fileSize,
                    CSUtils.codedSize(TxOutEntry.mapSize, BALANCE_DB_CODE_TXOUT))) {
                map.remove(TxOut.getStrValue());
                result = false;
            } else {
                log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex() + " inserted");
            }
        } finally {
            lock.unlock();
        }

        csLog.info("Balance DB: Inserted new TxOut: " + TxOut.toString());
        return result;
    }

    public boolean deleteTxOut(CSTransactionOutput TxOut) {
        if (TxOut == null) {
            return false;
        }

        CSBalanceDatabase.CSTxOutEntry TxOutEntry = map.get(TxOut.getStrValue());

        if (TxOutEntry == null) {
            log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex()
                    + " not found in the database");
            return true;
        }

        boolean result = true;

        lock.lock();
        try {
            if (!saveTxOut(TxOut, null, TxOutEntry.offsetInDB,
                    CSUtils.codedSize(TxOutEntry.mapSize, BALANCE_DB_CODE_DELETED))) {
                result = false;
            } else {
                deadSize += CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize
                        + TxOutEntry.mapSize * CSBalanceDatabase.CSTxOutEntry.serializedRowSize;
                map.remove(TxOut.getStrValue());

                log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex() + " deleted");
            }
        } finally {
            lock.unlock();
        }
        return result;
    }

    public boolean insertAsset(int AssetID) {
        if (newAssets.indexOf(AssetID) >= 0) {
            log.info("Balance DB: Asset " + AssetID + " already in the database");
            return true;
        }

        csLog.info("Balance DB: Inserting new asset " + AssetID);

        boolean result = true;

        lock.lock();
        try {
            int pos = deletedAssets.indexOf(AssetID);
            if (pos >= 0) {
                deletedAssets.remove(pos);
            }

            newAssets.add(AssetID);

            CSTransactionOutput TxOut = new CSTransactionOutput(Sha256Hash.ZERO_HASH, AssetID);
            if (!saveTxOut(TxOut, null, fileSize, CSUtils.codedSize(0, BALANCE_DB_CODE_NEWASSET))) {
                pos = newAssets.indexOf(AssetID);
                if (pos >= 0) {
                    newAssets.remove(pos);
                }
                result = false;
            } else {
                csLog.info("Balance DB: Asset " + AssetID + " inserted");
            }
        } finally {
            lock.unlock();
        }
        return result;
    }

    public boolean deleteAsset(int AssetID) {
        if (deletedAssets.indexOf(AssetID) >= 0) {
            log.info("Balance DB: Asset " + AssetID + " already deleted from  the database");
            return true;
        }

        boolean result = true;

        lock.lock();
        try {
            int pos = newAssets.indexOf(AssetID);
            if (pos >= 0) {
                newAssets.remove(pos);
            }

            deletedAssets.add(AssetID);

            CSTransactionOutput TxOut = new CSTransactionOutput(Sha256Hash.ZERO_HASH, AssetID);
            if (!saveTxOut(TxOut, null, fileSize, CSUtils.codedSize(0, BALANCE_DB_CODE_DELETEDASSET))) {
                pos = deletedAssets.indexOf(AssetID);
                if (pos >= 0) {
                    deletedAssets.remove(pos);
                }
                result = false;
            } else {
                csLog.info("Balance DB: Asset " + AssetID + " deleted");
            }
        } finally {
            lock.unlock();
        }
        return result;
    }

    public class CSBalanceIterator {
        private List<CSBalance> list = new ArrayList<CSBalance>();
        private int pointer;

        public CSBalanceIterator() {
            pointer = -1;
        }

        public boolean add(CSBalance Balance) {
            return list.add(Balance);
        }

        public CSBalance rewind() {
            pointer = -1;
            return next();
        }

        public CSBalance next() {
            pointer++;
            if (pointer >= list.size()) {
                return null;
            }
            return list.get(pointer);
        }
    }

    public CSBalanceDatabase.CSBalanceIterator getTxOutBalances(CSTransactionOutput TxOut) {
        CSBalanceDatabase.CSBalanceIterator iter = new CSBalanceDatabase.CSBalanceIterator();

        CSBalanceDatabase.CSTxOutEntry TxOutEntry = map.get(TxOut.getStrValue());

        if (TxOutEntry == null) {
            log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex()
                    + " not found in the database");
            return iter;
        }

        lock.lock();
        try {
            for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : TxOutEntry.map.entrySet()) {
                CSBalanceDatabase.CSBalanceEntry be = entry.getValue();
                int AssetID = entry.getKey();
                if (deletedAssets.indexOf(AssetID) < 0) {
                    CSBalance balance = new CSBalance(TxOut, AssetID, be.qty, be.qtyChecked, be.qtyFailures,
                            be.balanceState);
                    iter.add(balance);
                }
            }

            for (Integer newAsset : newAssets) {
                int asset = newAsset;
                if (!TxOutEntry.map.containsKey(asset)) {
                    CSBalance balance = new CSBalance(TxOut, asset);
                    iter.add(balance);
                }
            }

        } finally {
            lock.unlock();
        }
        return iter;
    }

    public CSBalance getBalance(CSTransactionOutput TxOut, int AssetID) {
        if (TxOut == null) {
            return null;
        }

        CSBalanceDatabase.CSTxOutEntry TxOutEntry = map.get(TxOut.getStrValue());

        if (TxOutEntry == null) {
            log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex()
                    + " not found in the database");
            return null;
        }

        CSBalanceDatabase.CSBalanceEntry be;

        lock.lock();
        try {
            be = TxOutEntry.map.get(AssetID);
        } finally {
            lock.unlock();
        }

        if (be != null) {
            return new CSBalance(TxOut, AssetID, be.qty, be.qtyChecked, be.qtyFailures, be.balanceState);
        }

        return null;
    }

    public CSBalanceDatabase.CSBalanceIterator getAssetBalances(int AssetID) {
        CSBalanceDatabase.CSBalanceIterator iter = new CSBalanceDatabase.CSBalanceIterator();

        if (deletedAssets.indexOf(AssetID) >= 0) {
            if (newAssets.indexOf(AssetID) < 0) {
                return iter;
            }
        }

        lock.lock();
        try {
            for (Map.Entry<String, CSBalanceDatabase.CSTxOutEntry> entryTxOut : map.entrySet()) {
                CSTransactionOutput TxOut = new CSTransactionOutput(entryTxOut.getKey());
                CSBalanceDatabase.CSTxOutEntry TxOutEntry = entryTxOut.getValue();

                for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : TxOutEntry.map.entrySet()) {
                    if (entry.getKey() == AssetID) {
                        CSBalanceDatabase.CSBalanceEntry be = entry.getValue();
                        CSBalance balance = new CSBalance(TxOut, AssetID, be.qty, be.qtyChecked, be.qtyFailures,
                                be.balanceState);
                        iter.add(balance);
                    }
                }
            }

            if (newAssets.indexOf(AssetID) >= 0) {
                for (Map.Entry<String, CSBalanceDatabase.CSTxOutEntry> entryTxOut : map.entrySet()) {
                    CSTransactionOutput TxOut = new CSTransactionOutput(entryTxOut.getKey());
                    CSBalanceDatabase.CSTxOutEntry TxOutEntry = entryTxOut.getValue();

                    if (!TxOutEntry.map.containsKey(AssetID)) {
                        CSBalance balance = new CSBalance(TxOut, AssetID);
                        iter.add(balance);
                    }
                }
            }
        } finally {
            lock.unlock();
        }

        return iter;
    }

    private boolean defragment() {
        File bdbFile = new File(fileName + ".new");

        if (bdbFile.exists()) {
            if (!bdbFile.delete()) {
                log.error("Balance DB: Cannot deleted temporary file");
                return false;
            }
        }

        RandomAccessFile aFile;
        try {
            aFile = new RandomAccessFile(fileName + ".new", "rw");
        } catch (FileNotFoundException ex) {
            log.info("Balance DB: Cannot open temporary file " + ex.getClass().getName() + " " + ex.getMessage());
            return false;
        }

        int off = 0;

        for (Map.Entry<String, CSBalanceDatabase.CSTxOutEntry> entryTxOut : map.entrySet()) {
            CSTransactionOutput TxOut = new CSTransactionOutput(entryTxOut.getKey());
            CSBalanceDatabase.CSTxOutEntry TxOutEntry = entryTxOut.getValue();
            CSBalanceDatabase.CSTxOutEntry newTxOutEntry = new CSBalanceDatabase.CSTxOutEntry(
                    TxOutEntry.mapSize + newAssets.size(), off);

            for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : TxOutEntry.map.entrySet()) {
                int asset = entry.getKey();
                CSBalanceDatabase.CSBalanceEntry be = entry.getValue();

                switch (be.balanceState) {
                case DELETED:
                case ZERO:
                    break;
                default:
                    if (deletedAssets.indexOf(asset) < 0) {
                        newTxOutEntry.add(asset, be);
                    }
                    break;
                }
            }
            for (Integer newAsset : newAssets) {
                int asset = newAsset;
                if (!TxOutEntry.map.containsKey(asset)) {
                    newTxOutEntry.add(asset, new CSBalanceDatabase.CSBalanceEntry());
                }
            }

            if (newTxOutEntry.mapSize > 0) {
                byte[] s;
                s = new byte[CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize
                        + newTxOutEntry.mapSize * CSBalanceDatabase.CSTxOutEntry.serializedRowSize];
                CSUtils.copyArray(s, CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize,
                        newTxOutEntry.serialize());
                CSUtils.copyArray(s, 0, TxOut.getTxID().getBytes());
                CSUtils.littleEndianByteArray(TxOut.getIndex(), s, 32);
                CSUtils.littleEndianByteArray(CSUtils.codedSize(newTxOutEntry.mapSize, BALANCE_DB_CODE_TXOUT), s,
                        36);

                try {
                    aFile.write(s);
                } catch (IOException ex) {
                    log.error(
                            "Balance DB: Cannot write to file " + ex.getClass().getName() + " " + ex.getMessage());
                    return false;
                }
                off += s.length;
            }
        }

        try {
            aFile.close();
        } catch (IOException ex) {
            log.error("Balance DB: Cannot close db file " + ex.getClass().getName() + " " + ex.getMessage());
            return false;
        }

        bdbFile = new File(fileName + ".new");

        if (bdbFile.exists()) {
            try {
                Files.move(bdbFile.toPath(), new File(fileName).toPath(), StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException ex) {
                log.error("Balance DB: Cannot rename temporary file: " + ex.getMessage());
                return false;
            }
            /*            
                        if(!bdbFile.renameTo(new File(fileName)))
                        {
            log.error("Balance DB: Cannot rename temporary file");                
            return false;
                        }            
                        */
        }

        load();

        log.info("Balance DB: Defragmentation completed");
        return true;

    }

    private class CSBalanceTxTransfers {
        public CSBalanceUpdate[] inputs;
        public CSBalanceUpdate[] outputs;
        public boolean valid;

        public CSBalanceTxTransfers(int AssetID, Transaction Tx) {
            valid = true;
            inputs = new CSBalanceUpdate[Tx.getInputs().size()];
            int count = 0;
            for (TransactionInput input : Tx.getInputs()) {
                TransactionOutPoint outPoint = input.getOutpoint();
                if (outPoint != null) {
                    CSBalanceEntry be = new CSBalanceEntry();
                    inputs[count] = new CSBalanceUpdate(
                            new CSTransactionOutput(outPoint.getHash(), (int) outPoint.getIndex()), AssetID,
                            new CSBalanceEntry(), -1, null);
                } else {
                    valid = false;
                }
                count++;
            }

            outputs = new CSBalanceUpdate[Tx.getOutputs().size()];
            for (int i = 0; i < Tx.getOutputs().size(); i++) {
                outputs[i] = null;
            }
        }

    }

    protected void addTrackedTransaction(Transaction Tx) {
        boolean found = false;
        for (CSBalanceTransaction balanceTransaction : trackedTransactions) {
            if (balanceTransaction.tx.getHash().equals(Tx.getHash())) {
                found = true;
            }
        }
        if (!found) {
            trackedTransactions.add(new CSBalanceTransaction(Tx));
        }
    }

    private class CSBalanceTransaction {
        public Transaction tx;
        public Date entryTime;
        public boolean deleteFlag;
        Map<Integer, CSBalanceTxTransfers> assetsToTrack = null;

        public CSBalanceTransaction() {
        }

        public CSBalanceTransaction(Transaction Tx) {
            tx = Tx;
            deleteFlag = false;
            entryTime = new Date();
        }

        public void addInputBalanceUpdates(List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates) {
            assetsToTrack = new HashMap<Integer, CSBalanceTxTransfers>();

            for (int i = 0; i < balanceUpdates.size(); i++) {
                CSBalanceDatabase.CSBalanceUpdate bu = balanceUpdates.get(i);
                switch (bu.balance.balanceState) {
                case NEVERCHECKED:
                case UNKNOWN:
                    if (bu.txOut.getTxID().equals(tx.getHash())) {
                        CSAsset asset = assetDB.getAsset(bu.assetID);
                        if ((asset != null) && (asset.getAssetState() == CSAsset.CSAssetState.VALID)
                                && (asset.getAssetReference() != null) && (asset.getAssetReference().isValid())) {
                            if (!assetsToTrack.containsKey(bu.assetID)) {
                                assetsToTrack.put(bu.assetID, new CSBalanceTxTransfers(bu.assetID, tx));
                            }
                            assetsToTrack.get(bu.assetID).outputs[bu.txOut.getIndex()] = bu;
                        }
                    }
                    break;
                }
            }

            deleteFlag = true;

            for (Map.Entry<Integer, CSBalanceTxTransfers> entryTransfers : assetsToTrack.entrySet()) {
                CSBalanceTxTransfers transfers = entryTransfers.getValue();
                if (transfers.valid) {
                    deleteFlag = false;
                    for (int i = 0; i < transfers.inputs.length; i++) {
                        balanceUpdates.add(transfers.inputs[i]);
                    }
                }
            }
        }

        public void applyInputBalances(List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates) {
            for (Map.Entry<Integer, CSBalanceTxTransfers> entryTransfers : assetsToTrack.entrySet()) {
                CSAsset asset = assetDB.getAsset(entryTransfers.getKey());
                CSBalanceTxTransfers balanceTransfers = entryTransfers.getValue();
                if ((asset == null) || (asset.getAssetState() != CSAsset.CSAssetState.VALID)
                        || (asset.getAssetReference() == null) || (!asset.getAssetReference().isValid())) {
                    balanceTransfers.valid = false;
                }
                if (balanceTransfers.valid) {
                    int countInputs = balanceTransfers.inputs.length;
                    int countOutputs = balanceTransfers.outputs.length;
                    long[] inputBalances = new long[countInputs];
                    long[] outputBalances = new long[countOutputs];
                    boolean[] outputsRegular = new boolean[countOutputs];

                    long totalInput = 0;
                    long totalOutput = 0;
                    long[] outputsSatoshis = new long[countOutputs];

                    for (int input_id = 0; input_id < countInputs; input_id++) {
                        CSBalanceUpdate bu = balanceTransfers.inputs[input_id];
                        switch (bu.balance.balanceState) {
                        case VALID:
                        case ZERO:
                        case SPENT:
                            inputBalances[input_id] = bu.balance.qty.longValue();
                            if (bu.qtyBTC != null) {
                                totalInput += bu.qtyBTC.longValue();
                            } else {
                                balanceTransfers.valid = false;
                            }
                            break;
                        default:
                            balanceTransfers.valid = false;
                        }
                    }

                    int output_id = 0;
                    for (TransactionOutput output : tx.getOutputs()) {
                        outputBalances[output_id] = 0;
                        outputsRegular[output_id] = CoinSparkBase.scriptIsRegular(output.getScriptBytes());
                        long value = output.getValue().longValue();
                        outputsSatoshis[output_id] = value;
                        totalOutput += value;
                        output_id++;
                    }

                    long feeSatoshis = totalInput - totalOutput;

                    CSTransactionAssets txAssets = new CSTransactionAssets(tx);

                    if (balanceTransfers.valid) {
                        if (txAssets.getGenesis() != null) {
                            long validFeeSatoshis = txAssets.getGenesis().calcMinFee(outputsSatoshis,
                                    outputsRegular);
                            if (feeSatoshis >= validFeeSatoshis) {
                                outputBalances = txAssets.getGenesis().apply(outputsRegular);
                            }
                        }

                        if (txAssets.getTransfers() != null) {
                            long validFeeSatoshis = txAssets.getTransfers().calcMinFee(countInputs, outputsSatoshis,
                                    outputsRegular);
                            if (feeSatoshis >= validFeeSatoshis) {
                                outputBalances = txAssets.getTransfers().apply(asset.getAssetReference(),
                                        asset.getGenesis(), inputBalances, outputsRegular);
                            } else {
                                outputBalances = txAssets.getTransfers().applyNone(asset.getAssetReference(),
                                        asset.getGenesis(), inputBalances, outputsRegular);
                            }
                        }

                        for (output_id = 0; output_id < countOutputs; output_id++) {
                            CSBalanceUpdate bu = balanceTransfers.outputs[output_id];
                            if (bu != null) {
                                switch (bu.balance.balanceState) {
                                case UNKNOWN:
                                    if (outputBalances[output_id] > 0) {
                                        bu.balance.setQty(BigInteger.valueOf(outputBalances[output_id]),
                                                CSBalance.CSBalanceState.CALCULATED);
                                        CSEventBus.INSTANCE.postAsyncEvent(CSEventType.BALANCE_VALID,
                                                new CSBalance(bu.txOut, bu.assetID, bu.balance.qty,
                                                        bu.balance.qtyChecked, bu.balance.qtyFailures,
                                                        bu.balance.balanceState));
                                    } else {
                                        bu.balance.setQty(BigInteger.ZERO, CSBalance.CSBalanceState.CALCULATED);
                                    }
                                    csLog.info("Balance DB: Update: " + bu.txOut.getTxID().toString() + "-"
                                            + bu.txOut.getIndex() + "-" + bu.assetID + "-" + bu.balance.qty + "-"
                                            + bu.balance.qtyChecked + "-" + bu.balance.qtyFailures + "-"
                                            + bu.balance.balanceState);
                                    break;
                                }
                            }
                        }

                    }

                }
            }

            deleteFlag = true;
            if (new Date().getTime() - entryTime.getTime() < 60000) {
                for (int i = 0; i < balanceUpdates.size(); i++) {
                    CSBalanceDatabase.CSBalanceUpdate bu = balanceUpdates.get(i);
                    switch (bu.balance.balanceState) {
                    case NEVERCHECKED:
                    case UNKNOWN:
                        if (bu.txOut.getTxID().equals(tx.getHash())) {
                            deleteFlag = false;
                        }
                        break;
                    }
                }

            }
        }

    }

    private class CSBalanceUpdate {
        public CSTransactionOutput txOut;
        public int assetID;
        public CSBalanceDatabase.CSBalanceEntry balance;
        public CSBalanceDatabase.CSBalanceEntry oldBalance = null;
        public BigInteger qtyBTC;
        public int offset;
        public int assetIDInRequest = -1;
        public int serverIDInRequest = -1;

        public CSBalanceUpdate() {
        }

        public CSBalanceUpdate(CSTransactionOutput TxOut, int AssetID, CSBalanceDatabase.CSBalanceEntry Balance,
                int Offset, BigInteger QtyBTC) {
            txOut = TxOut;
            assetID = AssetID;
            balance = Balance;
            offset = Offset;
            oldBalance = null;
            qtyBTC = QtyBTC;
        }

        public void setBalance(CSBalanceDatabase.CSBalanceEntry Balance) {
            oldBalance = balance;
            balance = Balance;
        }
    }

    private boolean applyBalanceUpdates(List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates) {
        if (balanceUpdates.isEmpty()) {
            return true;
        }
        RandomAccessFile aFile;
        try {
            aFile = new RandomAccessFile(fileName, "rw");
        } catch (FileNotFoundException ex) {
            log.info("Balance DB: Cannot open file " + ex.getClass().getName() + " " + ex.getMessage());
            balanceUpdates.clear();
            return false;
        }

        boolean result = true;
        int rollback = -1;
        for (int i = 0; i < balanceUpdates.size(); i++) {
            CSBalanceDatabase.CSBalanceUpdate bu = balanceUpdates.get(i);
            if (bu.offset >= 0) {
                if ((bu.oldBalance != null) || ((bu.balance != null)
                        && (bu.balance.balanceState == CSBalance.CSBalanceState.REFRESH))) {
                    map.get(bu.txOut.getStrValue()).map.replace(bu.assetID, bu.balance);
                    try {
                        aFile.seek(bu.balance.offsetInDB + 4);
                    } catch (IOException ex) {
                        log.error("Balance DB: Cannot set file position " + ex.getClass().getName() + " "
                                + ex.getMessage());
                        rollback = i;
                        break;
                    }
                    try {
                        aFile.write(bu.balance.serialize());
                    } catch (IOException ex) {
                        log.error("Balance DB: Cannot write to file " + ex.getClass().getName() + " "
                                + ex.getMessage());
                        rollback = i;
                        break;
                    }
                }
            }
        }

        if (rollback >= 0) {
            result = false;
            for (int i = 0; i <= rollback; i++) {
                CSBalanceDatabase.CSBalanceUpdate bu = balanceUpdates.get(i);
                if ((bu.oldBalance != null) || ((bu.balance != null)
                        && (bu.balance.balanceState == CSBalance.CSBalanceState.REFRESH))) {
                    if (bu.oldBalance != null) {
                        map.get(bu.txOut.getStrValue()).map.replace(bu.assetID, bu.balance);
                        try {
                            aFile.seek(bu.offset);
                        } catch (IOException ex) {
                            log.error("Balance DB: Cannot set file position " + ex.getClass().getName() + " "
                                    + ex.getMessage());
                            break;
                        }
                        try {
                            aFile.write(bu.oldBalance.serialize());
                        } catch (IOException ex) {
                            log.error("Balance DB: Cannot write to file " + ex.getClass().getName() + " "
                                    + ex.getMessage());
                            break;
                        }
                    }
                }
            }
        }

        try {
            aFile.close();
        } catch (IOException ex) {
            log.error("Balance DB: Cannot close file " + ex.getClass().getName() + " " + ex.getMessage());
            result = false;
        }

        log.info("Balance DB: Changes commited ");
        balanceUpdates.clear();
        return result;
    }

    public boolean refreshTxOut(CSTransactionOutput TxOut) {
        if (TxOut == null) {
            return false;
        }
        boolean result = true;

        CSBalanceDatabase.CSTxOutEntry TxOutEntry = map.get(TxOut.getStrValue());

        if (TxOutEntry == null) {
            log.info("Balance DB: TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex()
                    + " not found in the database");
            return true;
        }

        lock.lock();
        try {
            List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates = new ArrayList<CSBalanceDatabase.CSBalanceUpdate>();
            BigInteger qtyBTC = null;
            if (TxOutEntry.map.containsKey(0)) {
                if (TxOutEntry.map.get(0).balanceState == CSBalance.CSBalanceState.VALID) {
                    qtyBTC = TxOutEntry.map.get(0).qty;
                }
            }
            balanceUpdates.clear();
            int off = TxOutEntry.offsetInDB + CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize;
            for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : TxOutEntry.map.entrySet()) {
                CSBalanceDatabase.CSBalanceEntry be = entry.getValue();
                int AssetID = entry.getKey();
                if (AssetID > 0) {
                    if (deletedAssets.indexOf(AssetID) < 0) {
                        be.setQty(be.qty, CSBalance.CSBalanceState.REFRESH);
                        balanceUpdates.add(new CSBalanceDatabase.CSBalanceUpdate(TxOut, AssetID, be, off, qtyBTC));
                    }
                }
                off += CSBalanceDatabase.CSBalanceEntry.serializedSize;
            }

            log.info("Balance DB: Refreshing TxOut " + TxOut.getTxID().toString() + "-" + TxOut.getIndex());
            result = applyBalanceUpdates(balanceUpdates);
        } finally {
            lock.unlock();
        }

        return result;
    }

    public boolean refreshAsset(int AssetID) {
        boolean result = true;

        if (AssetID <= 0) {
            return false;
        }
        if (deletedAssets.indexOf(AssetID) >= 0) {
            if (newAssets.indexOf(AssetID) < 0) {
                return true;
            }
        }

        lock.lock();
        try {
            List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates = new ArrayList<CSBalanceDatabase.CSBalanceUpdate>();
            balanceUpdates.clear();
            for (Map.Entry<String, CSBalanceDatabase.CSTxOutEntry> entryTxOut : map.entrySet()) {
                CSTransactionOutput TxOut = new CSTransactionOutput(entryTxOut.getKey());
                CSBalanceDatabase.CSTxOutEntry TxOutEntry = entryTxOut.getValue();
                BigInteger qtyBTC = null;
                if (TxOutEntry.map.containsKey(0)) {
                    if (TxOutEntry.map.get(0).balanceState == CSBalance.CSBalanceState.VALID) {
                        qtyBTC = TxOutEntry.map.get(0).qty;
                    }
                }
                int off = TxOutEntry.offsetInDB + CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize;

                for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : TxOutEntry.map.entrySet()) {
                    if (entry.getKey() == AssetID) {
                        CSBalanceDatabase.CSBalanceEntry be = entry.getValue();
                        be.setQty(be.qty, CSBalance.CSBalanceState.REFRESH);
                        balanceUpdates.add(new CSBalanceDatabase.CSBalanceUpdate(TxOut, AssetID, be, off, qtyBTC));
                    }
                    off += CSBalanceDatabase.CSBalanceEntry.serializedSize;
                }
            }

            log.info("Balance DB: Refreshing Asset " + AssetID);
            result = applyBalanceUpdates(balanceUpdates);
            if (result) {
                insertAsset(AssetID);
                defragment();
            }
        } finally {
            lock.unlock();
        }

        return result;
    }

    public boolean refreshAll() {

        boolean result = true;

        lock.lock();
        try {
            List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates = new ArrayList<CSBalanceDatabase.CSBalanceUpdate>();
            balanceUpdates.clear();
            for (Map.Entry<String, CSBalanceDatabase.CSTxOutEntry> entryTxOut : map.entrySet()) {
                CSTransactionOutput TxOut = new CSTransactionOutput(entryTxOut.getKey());
                CSBalanceDatabase.CSTxOutEntry TxOutEntry = entryTxOut.getValue();
                BigInteger qtyBTC = null;
                if (TxOutEntry.map.containsKey(0)) {
                    if (TxOutEntry.map.get(0).balanceState == CSBalance.CSBalanceState.VALID) {
                        qtyBTC = TxOutEntry.map.get(0).qty;
                    }
                }
                int off = TxOutEntry.offsetInDB + CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize;

                for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : TxOutEntry.map.entrySet()) {
                    int asset = entry.getKey();
                    if (asset > 0) {
                        if (deletedAssets.indexOf(asset) < 0) {
                            CSBalanceDatabase.CSBalanceEntry be = entry.getValue();
                            be.setQty(be.qty, CSBalance.CSBalanceState.REFRESH);
                            balanceUpdates
                                    .add(new CSBalanceDatabase.CSBalanceUpdate(TxOut, asset, be, off, qtyBTC));
                        }
                    }
                    off += CSBalanceDatabase.CSBalanceEntry.serializedSize;
                }
            }

            log.info("Balance DB: Refreshing All");
            result = applyBalanceUpdates(balanceUpdates);
        } finally {
            lock.unlock();
        }

        return result;
    }

    private List<CSBalanceDatabase.CSBalanceUpdate> createBalanceUpdateList() {
        boolean takeIt;
        Date timeNow = new Date();
        long interval;

        List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates = new ArrayList<CSBalanceDatabase.CSBalanceUpdate>();
        balanceUpdates.clear();
        for (Map.Entry<String, CSBalanceDatabase.CSTxOutEntry> entryTxOut : map.entrySet()) {
            CSTransactionOutput TxOut = new CSTransactionOutput(entryTxOut.getKey());
            CSBalanceDatabase.CSTxOutEntry TxOutEntry = entryTxOut.getValue();
            BigInteger qtyBTC = null;
            if (TxOutEntry.map.containsKey(0)) {
                if (TxOutEntry.map.get(0).balanceState == CSBalance.CSBalanceState.VALID) {
                    qtyBTC = TxOutEntry.map.get(0).qty;
                }
            }
            int off = TxOutEntry.offsetInDB + CSBalanceDatabase.CSTxOutEntry.serializedHeaderSize;

            for (Map.Entry<Integer, CSBalanceDatabase.CSBalanceEntry> entry : TxOutEntry.map.entrySet()) {
                int asset = entry.getKey();
                CSBalanceDatabase.CSBalanceEntry be = entry.getValue();

                //                if(qtyBTC != null)
                if (true) {
                    takeIt = false;
                    switch (be.balanceState) {
                    case NEVERCHECKED:
                    case REFRESH:
                        takeIt = true;
                        break;
                    case UNKNOWN:
                    case SELF:
                    case CALCULATED:
                        interval = (timeNow.getTime() - be.qtyChecked.getTime()) / 1000;
                        if (be.qtyFailures < 40) {
                            takeIt = true;
                        } else {
                            if (be.qtyFailures < 80) {
                                if (interval > 15) {
                                    takeIt = true;
                                }
                            } else {
                                if (be.qtyFailures < 120) {
                                    if (interval > 3600) {
                                        takeIt = true;
                                    }
                                } else {
                                    if (interval > 86400) {
                                        takeIt = true;
                                    }
                                }
                            }

                        }
                        break;
                    }
                } else {
                    takeIt = true;
                }
                if (takeIt) {
                    balanceUpdates.add(new CSBalanceDatabase.CSBalanceUpdate(TxOut, asset, be, off, qtyBTC));
                }
                off += CSBalanceDatabase.CSTxOutEntry.serializedRowSize;
            }
        }

        return balanceUpdates;
    }

    private class JRequestTxOut {
        public String txid;
        public int vout;

        JRequestTxOut(String TxID, int Index) {
            txid = TxID;
            vout = Index;
        }
    }

    private class JRequestParams {
        public CSBalanceDatabase.JRequestTxOut[] txouts;
        public String[] assets;
    }

    private class JRequest {
        public int id;
        public String jsonrpc = "2.0";
        public String method;
        public CSBalanceDatabase.JRequestParams params;
    }

    private void processBalanceUpdateList(List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates,
            Map<String, Integer> TxDepthMap) {
        if (assetDB == null) {
            return;
        }
        if (balanceUpdates.isEmpty()) {
            return;
        }

        List<Integer> assetIDs = new ArrayList<Integer>();
        List<CSAsset> assets = new ArrayList<CSAsset>();
        List<CSTransactionOutput> txOuts = new ArrayList<CSTransactionOutput>();
        List<String> servers = new ArrayList<String>();

        for (CSBalanceUpdate bu : balanceUpdates) {
            if (assetIDs.indexOf(bu.assetID) < 0) {
                assetIDs.add(bu.assetID);
            }
        }

        for (Integer assetID1 : assetIDs) {
            int assetID = assetID1;
            CSAsset asset = assetDB.getAsset(assetID);
            boolean takeit = false;
            if (asset != null) {
                asset.selectCoinsparkTrackerUrl();
                if (asset.getCoinsparkTrackerUrl() != null) {
                    if (servers.indexOf(asset.getCoinsparkTrackerUrl()) < 0) {
                        servers.add(asset.getCoinsparkTrackerUrl());
                    }
                    takeit = true;
                }
            }
            if (takeit) {
                assets.add(asset);
            }
        }

        if (assets.isEmpty()) {
            return;
        }

        assetIDs.clear();
        for (CSAsset asset : assets) {
            assetIDs.add(asset.getAssetID());
        }

        int[] idMap = new int[assets.size()];

        for (int s = 0; s < servers.size(); s++) {
            CSBalanceDatabase.JRequest request = new CSBalanceDatabase.JRequest();

            request.id = (int) (new Date().getTime() / 1000);
            request.jsonrpc = "2.0";
            request.method = "coinspark_assets_get_qty";
            request.params = new CSBalanceDatabase.JRequestParams();
            request.params.assets = new String[assets.size()];

            int c = 0;
            for (int i = 0; i < assets.size(); i++) {
                idMap[i] = -1;
                if (servers.get(s).equals(assets.get(i).getCoinsparkTrackerUrl())) {
                    CSAsset asset = assets.get(i);
                    request.params.assets[c] = asset.getGenTxID();
                    idMap[i] = c;
                    c++;
                }
            }

            CSTransactionOutput lastTxOut = null;

            txOuts.clear();
            for (int i = 0; i < balanceUpdates.size(); i++) {
                CSBalanceDatabase.CSBalanceUpdate bu = balanceUpdates.get(i);
                bu.assetIDInRequest = assetIDs.indexOf(bu.assetID);
                if (bu.assetIDInRequest >= 0) {
                    if (idMap[bu.assetIDInRequest] >= 0) {
                        bu.oldBalance = new CSBalanceEntry(bu.balance);
                        bu.serverIDInRequest = s;
                        balanceUpdates.set(i, bu);
                        if (!bu.txOut.equals(lastTxOut)) {
                            txOuts.add(bu.txOut);
                            lastTxOut = bu.txOut;
                        }
                    }
                }
            }

            if (!txOuts.isEmpty()) {
                request.params.txouts = new CSBalanceDatabase.JRequestTxOut[txOuts.size()];

                for (int i = 0; i < txOuts.size(); i++) {
                    CSTransactionOutput txout = txOuts.get(i);
                    request.params.txouts[i] = new CSBalanceDatabase.JRequestTxOut(txout.getTxID().toString(),
                            txout.getIndex());
                }

                JsonObject jresult = null;

                try {
                    // Use Google GSON library for Java Object <--> JSON conversions
                    Gson gson = new Gson();

                    // convert java object to JSON format,
                    String json = gson.toJson(request);
                    try {
                        JsonElement jelement;
                        JsonObject jobject = null;

                        Map<String, String> headers = new HashMap<String, String>();
                        headers.put("Content-Type", "application/json");

                        CSUtils.CSDownloadedURL downloaded = CSUtils.postURL(servers.get(s), 15, null, json,
                                headers);
                        if (downloaded.error != null) {
                            log.error(downloaded.error);
                        } else {
                            jelement = new JsonParser().parse(downloaded.contents);
                            jobject = jelement.getAsJsonObject();
                        }

                        if (jobject != null) {
                            if ((jobject.get("id") == null) || jobject.get("id").getAsInt() != request.id) {
                                log.error("Tracker: id doesn't match " + request.id);
                            } else {
                                if ((jobject.get("error") != null)) {
                                    if (jobject.get("error").isJsonObject()) {
                                        JsonObject jerror = jobject.get("error").getAsJsonObject();
                                        String errorMessage = "Query error: ";
                                        if (jerror.get("code") != null) {
                                            errorMessage += jerror.get("code").getAsInt() + " - ";
                                        }
                                        if (jerror.get("message") != null) {
                                            errorMessage += jerror.get("message").getAsString();
                                        }
                                        log.error("Tracker: " + errorMessage);
                                    } else {
                                        log.error("Tracker: Query error");
                                    }
                                } else {
                                    jresult = jobject.getAsJsonObject("result");
                                    if (jresult != null) {
                                        if (!jresult.isJsonObject()) {
                                            log.error("Tracker: result object is not array");
                                            jresult = null;
                                        }
                                    }
                                }
                            }
                        }
                    } catch (JsonSyntaxException ex) {
                        log.error("Tracker, JSON syntax " + ex.getClass().getName() + " " + ex.getMessage());
                    } catch (Exception ex) {
                        log.error("Tracker, exception " + ex.getClass().getName() + " " + ex.getMessage());
                    }
                }

                catch (Exception ex) {
                    log.error("Tracker: " + ex.getClass().getName() + " " + ex.getMessage());
                }

                if (jresult == null) {
                    for (int i = 0; i < balanceUpdates.size(); i++) {
                        CSBalanceDatabase.CSBalanceUpdate bu = balanceUpdates.get(i);

                        if (bu.serverIDInRequest == s) {
                            bu.oldBalance = null;
                            bu.serverIDInRequest = -1;
                            balanceUpdates.set(i, bu);
                        }
                    }
                } else {
                    for (int i = 0; i < balanceUpdates.size(); i++) {
                        CSBalanceDatabase.CSBalanceUpdate bu = balanceUpdates.get(i);

                        if (bu.serverIDInRequest == s) {
                            try {
                                if (bu.qtyBTC == null) {
                                    if ((jresult.get("BTC") != null) && (jresult.get("BTC").isJsonArray())) {
                                        JsonArray jarray = jresult.getAsJsonArray("BTC");
                                        for (int j = 0; j < jarray.size(); j++) {
                                            JsonObject jentry = jarray.get(j).getAsJsonObject();
                                            if (jentry.get("txid") != null) {
                                                String txID = jentry.get("txid").toString();
                                                txID = txID.substring(1, 65);
                                                if ((jentry.get("vout") != null)
                                                        && bu.txOut.equals(txID, jentry.get("vout").getAsInt())) {
                                                    if ((jentry.get("error") == null)
                                                            && (jentry.get("qty") != null)) {
                                                        bu.qtyBTC = new BigInteger(jentry.get("qty").getAsString());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }

                                String genTxID = assets.get(bu.assetIDInRequest).getGenTxID();

                                if ((jresult.get(genTxID) != null) && (jresult.get(genTxID).isJsonArray())) {
                                    JsonArray jarray = jresult.getAsJsonArray(genTxID);
                                    for (int j = 0; j < jarray.size(); j++) {
                                        JsonObject jentry = jarray.get(j).getAsJsonObject();
                                        if (jentry.get("txid") != null) {
                                            String txID = jentry.get("txid").toString();
                                            txID = txID.substring(1, 65);
                                            if ((jentry.get("vout") != null)
                                                    && bu.txOut.equals(txID, jentry.get("vout").getAsInt())) {
                                                if ((jentry.get("error") != null) || (jentry.get("qty") == null)) {
                                                    if ((bu.balance.balanceState != CSBalance.CSBalanceState.SELF)
                                                            && (bu.balance.balanceState != CSBalance.CSBalanceState.CALCULATED)) {
                                                        bu.balance.setQty(bu.balance.qty,
                                                                CSBalance.CSBalanceState.UNKNOWN);
                                                    } else {
                                                        bu.balance.setQty(bu.balance.qty, bu.balance.balanceState);
                                                    }
                                                    if (TxDepthMap != null) {
                                                        if (TxDepthMap.containsKey(txID)) {
                                                            if (TxDepthMap
                                                                    .get(txID) > BALANCE_DB_MAXIMAL_UNKNOWN_DEPTH) {
                                                                bu.balance.setQty(BigInteger.ZERO,
                                                                        CSBalance.CSBalanceState.ZERO);
                                                                csLog.info(
                                                                        "Balance DB: Tx is too deep in block chain (depth "
                                                                                + TxDepthMap.get(txID) + ": "
                                                                                + bu.txOut.getTxID().toString()
                                                                                + "-" + bu.txOut.getIndex() + "-"
                                                                                + bu.assetID
                                                                                + " - setting UNKNOWN state to ZERO");
                                                            }
                                                        }
                                                    }
                                                } else {
                                                    if ((jentry.get("spent") != null)
                                                            && (jentry.get("spent").getAsInt() > 0)) {
                                                        bu.balance.setQty(
                                                                new BigInteger(jentry.get("qty").getAsString()),
                                                                CSBalance.CSBalanceState.SPENT);
                                                    } else {
                                                        if (jentry.get("qty").getAsLong() > 0) {
                                                            bu.balance.setQty(
                                                                    new BigInteger(jentry.get("qty").getAsString()),
                                                                    CSBalance.CSBalanceState.VALID);
                                                            CSEventBus.INSTANCE.postAsyncEvent(
                                                                    CSEventType.BALANCE_VALID,
                                                                    new CSBalance(bu.txOut, bu.assetID,
                                                                            bu.balance.qty, bu.balance.qtyChecked,
                                                                            bu.balance.qtyFailures,
                                                                            bu.balance.balanceState));
                                                        } else {
                                                            bu.balance.setQty(
                                                                    new BigInteger(jentry.get("qty").getAsString()),
                                                                    CSBalance.CSBalanceState.ZERO);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                csLog.info("Balance DB: Update: " + bu.txOut.getTxID().toString() + "-"
                                        + bu.txOut.getIndex() + "-" + bu.assetID + "-" + bu.balance.qty + "-"
                                        + bu.balance.qtyChecked + "-" + bu.balance.qtyFailures + "-"
                                        + bu.balance.balanceState);

                            } catch (Exception ex) {
                                log.error("Tracker: " + ex.getClass().getName() + " " + ex.getMessage());
                                bu.oldBalance = null;
                            }
                            balanceUpdates.set(i, bu);
                        }
                    }
                }
            }
        }
    }

    private void checkDuplicates() {
        if (assetDB == null) {
            return;
        }

        int[] ids = assetDB.getAssetIDs();

        if (ids != null) {
            for (int assetID : ids) {
                CSAsset asset = assetDB.getAsset(assetID);
                if (asset.getAssetState() == CSAsset.CSAssetState.DUPLICATE) {
                    csLog.info("Balance DB: Duplicate asset, ID: " + assetID + ": " + asset.status());
                    CSAsset found = assetDB.findAsset(asset, true);
                    if (found != null) {
                        if (found.getAssetID() == asset.getAssetID()) {
                            csLog.error("Balance DB: Asset is mapped: " + asset.status());
                        } else {
                            csLog.info("Balance DB: Mapped to ID: " + found.getAssetID() + ": " + found.status());
                            insertAsset(found.getAssetID());
                            deleteAsset(assetID);
                            assetDB.deleteAsset(asset);
                        }
                    } else {
                        csLog.error("Cannot find copy of asset " + asset.status());
                    }
                }
            }
        }
    }

    private boolean calculationInProgress = false;

    private boolean calculateBalances(boolean Defragment, Map<String, Integer> TxDepthMap) {
        if (calculationInProgress) {
            return false;
        }

        calculationInProgress = true;

        List<CSBalanceDatabase.CSBalanceUpdate> balanceUpdates;//= new ArrayList<CSBalanceDatabase.CSBalanceUpdate>();        

        checkDuplicates();

        lock.lock();
        try {
            if (!newAssets.isEmpty() || !deletedAssets.isEmpty() || (deadSize > 0.5 * fileSize) || Defragment) {
                defragment();
            }

            balanceUpdates = createBalanceUpdateList();
            for (CSBalanceTransaction trackedTx : trackedTransactions) {
                trackedTx.addInputBalanceUpdates(balanceUpdates);
            }
        } finally {
            lock.unlock();
        }

        processBalanceUpdateList(balanceUpdates, TxDepthMap);

        CSBalanceTransaction[] trackedTxToRemove = new CSBalanceTransaction[trackedTransactions.size()];
        int count = 0;
        for (CSBalanceTransaction trackedTx : trackedTransactions) {
            trackedTx.applyInputBalances(balanceUpdates);
            if (trackedTx.deleteFlag) {
                trackedTxToRemove[count] = trackedTx;
                count++;
            }
        }

        for (int i = 0; i < count; i++) {
            trackedTransactions.remove(trackedTxToRemove[i]);
        }

        lock.lock();
        try {
            applyBalanceUpdates(balanceUpdates);
        } finally {
            lock.unlock();
        }

        log.info("Balance DB: Synchronization completed");
        calculationInProgress = false;
        return false;
    }

    public boolean calculateBalances() {
        return calculateBalances(true, null);
    }

    public boolean calculateBalances(Map<String, Integer> TxDepthMap) {
        return calculateBalances(true, TxDepthMap);
    }
}