November 17, 2014

NODECOIN SOURCE CODE

NODECOIN SOURCE CODE GITHUB
EARLIER VERSION

function run(appDir) {
    var http = require("http"),
        querystring = require("querystring"),
        events = require("events"),
        util = require("util"),
        Step = require("step"),
        async = require("async"),
        long = require("long"),
        Map = require("collections/map"),
        SortedArrayMap = require("collections/sorted-array-map"),
        crypto = require("crypto"),
        extend = require("node.extend"),
        curve = require("node-curve25519"),
        Datastore = require("nedb"),
        db = new Datastore(),
        net = require("net"),
        swig = require("swig"),
        path = require("path"),
        fs = require("fs"),
        mime = require("mime"),
        url = require("url"),
        longInt = require("long"),
        request = require("request"),
        bigint = require("biginteger").BigInteger;
    var logger = function() {
        var winston = require("winston");
        var loggingLevels = {
            levels: {
                netdbg: 1,
                bchdbg: 0,
                rpcdbg: 0,
                scrdbg: 0,
                DBdbg: 1,
                transdbg: 1,
                debug: 1,
                info: 10,
                notice: 20,
                warn: 30,
                error: 40,
                crit: 50,
                alert: 60,
                emerg: 70
            },
            colors: {
                netdbg: "blue",
                bchdbg: "blue",
                rpcdbg: "blue",
                scrdbg: "blue",
                transdbg: "blue",
                debug: "blue",
                info: "green",
                notice: "cyan",
                warn: "yellow",
                error: "red",
                crit: "red",
                alert: "yellow",
                emerg: "red",
                DBdbg: "blue"
            }
        };
        var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
        var pad = function(n) {
            return n < 10 ? "0" + n.toString(10) : n.toString(10)
        };
        var pad3 = function(n) {
            var num = n.toString(10);
            while (num.length < 3) num = "0" + num;
            return num
        };
        var timestamp = function() {
            var d = new Date();
            var time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(":");
            time += "." + pad3(d.getMilliseconds());
            return [d.getDate(), months[d.getMonth()], time].join(" ")
        };
        var exports = {};
        var logger = exports.logger = new winston.Logger({
            transports: [new winston.transports.Console({
                colorize: true,
                level: "debug",
                timestamp: timestamp
            }), new winston.transports.File({
                level: "debug",
                timestamp: timestamp,
                filename: process.cwd() + "/logs/logs.log",
                maxsize: 1048576,
                maxFiles: 2
            })],
            levels: loggingLevels.levels,
            level: "debug"
        });
        winston.addColors(loggingLevels.colors);
        exports.loggerOn = true;
        exports.disable = function() {
            if (this.logger.loggerOn === false) {
                return
            }
            this.logger.loggerOn = false;
            this.logger.remove(winston.transports.Console);
            this.logger.remove(winston.transports.File)
        };
        return logger.extend(exports)
    }();
    bigint.fromBuffer = function(buf) {
        var strHex = buf.toString("hex");
        return bigint.parse(strHex.toLocaleUpperCase(), 16)
    };
    bigint.prototype.toBuffer = function() {
        var hexStr = this.toString(16);
        if (hexStr.length % 2 === 0) {
            return new Buffer(hexStr, "hex")
        }
        return new Buffer("0" + hexStr, "hex")
    };
    var Crypto = function() {};
    Crypto.getPublicKey = function(secretWord) {
        try {
            var secretWordHash = curve.sha256(secretWord);
            return curve.derivePublicKeyWithClamp(secretWordHash)
        } catch (e) {
            logger.error(e.stack ? e.stack : e.toString());
            return false
        }
    };
    var Utils = function() {
        var clearBuffer = function(buf) {
            for (var i = 0; i < buf.length; i++) {
                buf[i] = 0
            }
            return buf
        };
        var two64 = new bigint("18446744073709551616");
        var getDateTime = function() {
            var date = new Date();
            var hour = date.getHours();
            hour = (hour < 10 ? "0" : "") + hour;
            var min = date.getMinutes();
            min = (min < 10 ? "0" : "") + min;
            var sec = date.getSeconds();
            sec = (sec < 10 ? "0" : "") + sec;
            var year = date.getFullYear();
            var month = date.getMonth() + 1;
            month = (month < 10 ? "0" : "") + month;
            var day = date.getDate();
            day = (day < 10 ? "0" : "") + day;
            return year + "-" + month + "-" + day + " " + hour + ":" + min + ":" + sec
        };
        var decodeHex = function(hex) {
            return new Buffer(hex, "hex").slice(0)
        };
        var longToBigInt = function(long) {
            return bigint(long.toString())
        };
        var bigIntToLong = function(bigIntVal) {
            var buf = bigIntVal.toBuffer();
            return bufferToLongLE(buf)
        };
        var bigIntToLongBE = function(bigIntVal) {
            var buf = bigIntVal.toBuffer();
            return bufferToLongBE(buf)
        };
        var stringToLong = function(str) {
            var bi = bigint(str);
            return bigIntToLongBE(bi)
        };
        var bufferToLongLE = function(buf) {
            if (buf.length < 8) {
                var addLength = 8 - buf.length;
                var addBuffer = new Buffer(addLength);
                addBuffer = clearBuffer(addBuffer);
                buf = Buffer.concat([addBuffer, buf], 8)
            }
            var lowBits = buf.slice(buf.length - 4, buf.length);
            var higthBits = buf.slice(buf.length - 8, buf.length - 4);
            return new longInt(lowBits.readInt32LE(0), higthBits.readInt32LE(0), true)
        };
        var bufferToLongBE = function(buf) {
            if (buf.length < 8) {
                var addLength = 8 - buf.length;
                var addBuffer = new Buffer(addLength);
                addBuffer = clearBuffer(addBuffer);
                buf = Buffer.concat([addBuffer, buf], 8)
            }
            var lowBits = buf.slice(buf.length - 4, buf.length);
            var higthBits = buf.slice(buf.length - 8, buf.length - 4);
            return new longInt(lowBits.readInt32BE(0), higthBits.readInt32BE(0), true)
        };
        var getBEBigIntFromLENumber = function(num) {
            if (typeof num !== "string") {
                num = mun.toString()
            }
            var bi = bigint(num);
            var buf = bi.toBuffer();
            var buffArr = [];
            for (var i = 0; i < buf.length; i++) {
                buffArr.push(buf[buf.length - i - 1])
            }
            buf = new Buffer(buffArr);
            return bigint.fromBuffer(buf)
        };
        var sha256 = function(message) {
            var shasum = crypto.createHash("sha256");
            shasum.update(message);
            return shasum.digest()
        };
        var roundTo5Float = function(num) {
            var numF = parseFloat(nullToNumber(num));
            return Math.round(numF * 1e5) / 1e5
        };
        var isEmptyObj = function(obj) {
            if (obj == null) return true;
            if (obj.length && obj.length > 0) return false;
            if (obj.length === 0) return true;
            for (var key in obj) {
                if (hasOwnProperty.call(obj, key)) return false
            }
            return true
        };
        var nullToNumber = function(val) {
            if (val === null) {
                return 0
            }
            return val
        };
        return {
            clearBuffer: clearBuffer,
            two64: two64,
            getDateTime: getDateTime,
            decodeHex: decodeHex,
            longToBigInt: longToBigInt,
            bigIntToLong: bigIntToLong,
            bigIntToLongBE: bigIntToLongBE,
            stringToLong: stringToLong,
            bufferToLongLE: bufferToLongLE,
            bufferToLongBE: bufferToLongBE,
            getBEBigIntFromLENumber: getBEBigIntFromLENumber,
            sha256: sha256,
            roundTo5Float: roundTo5Float,
            isEmptyObj: isEmptyObj,
            nullToNumber: nullToNumber
        }
    }();
    var Config = function() {
        var Config = function() {
            return {
                port: 19775,
                host: "127.0.0.1",
                starPeers: ["5.45.124.65:19776", "5.45.114.108:19776"],
                peerPort: 19776,
                serverKey: "qwerty123456",
                devMod: false
            }
        }();
        try {
            Config.GEN_BLOCK_WAIT_MS = 6e4;
            Config.two64 = new bigint("18446744073709551616");
            Config.NULL_HASH = Utils.clearBuffer(new Buffer(32));
            Config.EMPTY_BUFFER = new Buffer(0);
            Config.ZERO_VALUE = Utils.clearBuffer(new Buffer(8));
            Config.INT64_MAX = Utils.decodeHex("ffffffffffffffff");
            Config.MAX_BALANCE = 1e9;
            Config.COIN = 1e9;
            Config.INITIAL_BASE_TARGET = 153722867;
            Config.TRANSPARENT_FORGING_BLOCK = 3e4;
            Config.MAX_BASE_TARGET = Config.MAX_BALANCE * Config.INITIAL_BASE_TARGET
        } catch (e) {
            logger.error("Error while generating utility constants:\n" + (e.stack ? e.stack : e.toString()));
            process.exit(1)
        }
        return Config
    }();

    function NodeServer(createNew) {
        events.EventEmitter.call(this);
        this.state = null;
        this.running = false;
        this.synced = false;
        this.intervalCheckSynced = null;
        this.netStatus = Peer.prototype.statuses.DISABLE;
        try {
            this.blockGenerator = new BlockGenerator(this);
            this.peerProcessor = new PeerProcessor(this);
            this.addListener("stateChange", this.handleStateChange.bind(this));
            this.setupStateTransitions()
        } catch (err) {
            logger.error("Initialization " + (err.stack ? err.stack : "error: " + err.toString()))
        }
        if (typeof createNew !== "undefined" && createNew) {
            return this
        }
        if (typeof NodeServer.instance === "undefined") {
            NodeServer.instance = this
        }
        return NodeServer.instance
    }
    util.inherits(NodeServer, events.EventEmitter);
    NodeServer.prototype.start = function() {
        this.setState("init")
    };
    NodeServer.prototype.setState = function(newState) {
        var oldState = this.state;
        if (newState == "init" && oldState !== null) {
            return
        }
        this.state = newState;
        this.emit("stateChange", {
            oldState: oldState,
            newState: newState
        })
    };
    NodeServer.prototype.setupStateTransitions = function() {
        var self = this;
        this.addListener("initComplete", function() {
            if (self.state == "init") {
                self.setState("netConnect")
            }
        });
        this.peerProcessor.addListener("netConnected", function() {
            if (self.state == "netConnect") {
                self.setState("blockDownload")
            }
        })
    };
    NodeServer.prototype.handleStateChange = function(e) {
        var self = this;
        this.running = !!~["netConnect", "blockDownload", "default"].indexOf(e.newState);
        switch (e.oldState) {}
        switch (e.newState) {
            case "init":
                BlockchainProcessor.run(function() {
                    setInterval(function() {
                        if (Account.currentAccount) {
                            logger.info("Account.currentAccount.accountSecret", Account.currentAccount.accountSecret)
                        } else {
                            logger.info("You not logged to generate block.")
                        }
                    }, 6e4);
                    self.emit("initComplete")
                });
                break;
            case "netConnect":
                this.peerProcessor.run();
                this.startFrontendServer(Router.route, handle);
                break;
            case "blockDownload":
                this.broadcastPeerStatus(Peer.prototype.statuses.CHECK);
                this.startCheckSynced();
                this.peerProcessor.syncTransaction();
                break;
            case "synced":
                break
        }
    };
    NodeServer.prototype.startCheckSynced = function() {
        this.intervalCheckSynced = setInterval(this.checkSynced.bind(this), 1e4)
    };
    NodeServer.prototype.checkSynced = function() {
        if (this.peerProcessor.synced && Account.currentAccount) {
            clearInterval(this.intervalCheckSynced);
            this.broadcastPeerStatus(Peer.prototype.statuses.ACTIVE)
        }
    };
    NodeServer.prototype.addConnection = function(conn) {
        this.blockGenerator.addConnection(conn)
    };
    NodeServer.prototype.broadcast = function(data, command, options) {
        var conns = this.peerProcessor.getActiveConnections();
        var _options = options || {};
        var f = null;
        if (typeof command === "string") {
            f = function(conn, data, command) {
                return conn.sendMessage(command, data)
            }
        } else {
            f = function(conn, data) {
                return conn.broadcast(data)
            }
        }
        var checkOptions;
        if (_options["peerKey"]) {
            checkOptions = function(data, key) {
                if (key == _options["peerKey"]) {
                    data["broadcastSelectPeer"] = true
                }
                return data
            }
        } else {
            checkOptions = function(data, key) {
                return data
            }
        }
        conns.forEach(function(conn, key) {
            f(conn, checkOptions(data, key), command)
        })
    };
    NodeServer.prototype.broadcastPeerStatus = function(status) {
        this.netStatus = status;
        this.broadcast({
            status: status
        }, Connection.prototype.commands.PEER_STATUS)
    };
    NodeServer.prototype.broadcastNewTransaction = function(transaction) {
        var data = transaction.getData();
        this.broadcast(data, Connection.prototype.commands.NEW_TRANSACTION)
    };
    NodeServer.prototype.broadcastNewBlock = function(block) {
        var data = block.getDataWithTransactions();
        data.broadcasted = true;
        this.broadcast(data, Connection.prototype.commands.BLOCK)
    };
    NodeServer.prototype.startFrontendServer = function(route, handle) {
        function onRequest(request, response) {
            route(handle, request, response)
        }
        http.createServer(onRequest).listen(Config.port);
        logger.info("UI Server has started at http://" + Config.host + ":" + Config.port)
    };

    function string_to_array(str) {
        var len = str.length;
        var res = new Array(len);
        for (var i = 0; i < len; i++) res[i] = str.charCodeAt(i);
        return res
    }

    function array_to_hex_string(ary) {
        var res = "";
        for (var i = 0; i < ary.length; i++) res += SHA256_hexchars[ary[i] >> 4] + SHA256_hexchars[ary[i] & 15];
        return res
    }

    function SHA256_init() {
        SHA256_H = new Array(1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225);
        SHA256_buf = new Array();
        SHA256_len = 0
    }

    function SHA256_write(msg) {
        if (typeof msg == "string") SHA256_buf = SHA256_buf.concat(string_to_array(msg));
        else SHA256_buf = SHA256_buf.concat(msg);
        for (var i = 0; i + 64 <= SHA256_buf.length; i += 64) SHA256_Hash_Byte_Block(SHA256_H, SHA256_buf.slice(i, i + 64));
        SHA256_buf = SHA256_buf.slice(i);
        SHA256_len += msg.length
    }

    function SHA256_finalize() {
        SHA256_buf[SHA256_buf.length] = 128;
        if (SHA256_buf.length > 64 - 8) {
            for (var i = SHA256_buf.length; i < 64; i++) SHA256_buf[i] = 0;
            SHA256_Hash_Byte_Block(SHA256_H, SHA256_buf);
            SHA256_buf.length = 0
        }
        for (var i = SHA256_buf.length; i < 64 - 5; i++) SHA256_buf[i] = 0;
        SHA256_buf[59] = SHA256_len >>> 29 & 255;
        SHA256_buf[60] = SHA256_len >>> 21 & 255;
        SHA256_buf[61] = SHA256_len >>> 13 & 255;
        SHA256_buf[62] = SHA256_len >>> 5 & 255;
        SHA256_buf[63] = SHA256_len << 3 & 255;
        SHA256_Hash_Byte_Block(SHA256_H, SHA256_buf);
        var res = new Array(32);
        for (var i = 0; i < 8; i++) {
            res[4 * i + 0] = SHA256_H[i] >>> 24;
            res[4 * i + 1] = SHA256_H[i] >> 16 & 255;
            res[4 * i + 2] = SHA256_H[i] >> 8 & 255;
            res[4 * i + 3] = SHA256_H[i] & 255
        }
        delete SHA256_H;
        delete SHA256_buf;
        delete SHA256_len;
        return res
    }

    function SHA256_hash(msg) {
        var res;
        SHA256_init();
        SHA256_write(msg);
        res = SHA256_finalize();
        return array_to_hex_string(res)
    }

    function HMAC_SHA256_init(key) {
        if (typeof key == "string") HMAC_SHA256_key = string_to_array(key);
        else HMAC_SHA256_key = new Array().concat(key); if (HMAC_SHA256_key.length > 64) {
            SHA256_init();
            SHA256_write(HMAC_SHA256_key);
            HMAC_SHA256_key = SHA256_finalize()
        }
        for (var i = HMAC_SHA256_key.length; i < 64; i++) HMAC_SHA256_key[i] = 0;
        for (var i = 0; i < 64; i++) HMAC_SHA256_key[i] ^= 54;
        SHA256_init();
        SHA256_write(HMAC_SHA256_key)
    }

    function HMAC_SHA256_write(msg) {
        SHA256_write(msg)
    }

    function HMAC_SHA256_finalize() {
        var md = SHA256_finalize();
        for (var i = 0; i < 64; i++) HMAC_SHA256_key[i] ^= 54 ^ 92;
        SHA256_init();
        SHA256_write(HMAC_SHA256_key);
        SHA256_write(md);
        for (var i = 0; i < 64; i++) HMAC_SHA256_key[i] = 0;
        delete HMAC_SHA256_key;
        return SHA256_finalize()
    }

    function HMAC_SHA256_MAC(key, msg) {
        var res;
        HMAC_SHA256_init(key);
        HMAC_SHA256_write(msg);
        res = HMAC_SHA256_finalize();
        return array_to_hex_string(res)
    }
    SHA256_hexchars = new Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f");
    SHA256_K = new Array(1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298);

    function SHA256_sigma0(x) {
        return (x >>> 7 | x << 25) ^ (x >>> 18 | x << 14) ^ x >>> 3
    }

    function SHA256_sigma1(x) {
        return (x >>> 17 | x << 15) ^ (x >>> 19 | x << 13) ^ x >>> 10
    }

    function SHA256_Sigma0(x) {
        return (x >>> 2 | x << 30) ^ (x >>> 13 | x << 19) ^ (x >>> 22 | x << 10)
    }

    function SHA256_Sigma1(x) {
        return (x >>> 6 | x << 26) ^ (x >>> 11 | x << 21) ^ (x >>> 25 | x << 7)
    }

    function SHA256_Ch(x, y, z) {
        return z ^ x & (y ^ z)
    }

    function SHA256_Maj(x, y, z) {
        return x & y ^ z & (x ^ y)
    }

    function SHA256_Hash_Word_Block(H, W) {
        for (var i = 16; i < 64; i++) W[i] = SHA256_sigma1(W[i - 2]) + W[i - 7] + SHA256_sigma0(W[i - 15]) + W[i - 16] & 4294967295;
        var state = new Array().concat(H);
        for (var i = 0; i < 64; i++) {
            var T1 = state[7] + SHA256_Sigma1(state[4]) + SHA256_Ch(state[4], state[5], state[6]) + SHA256_K[i] + W[i];
            var T2 = SHA256_Sigma0(state[0]) + SHA256_Maj(state[0], state[1], state[2]);
            state.pop();
            state.unshift(T1 + T2 & 4294967295);
            state[4] = state[4] + T1 & 4294967295
        }
        for (var i = 0; i < 8; i++) H[i] = H[i] + state[i] & 4294967295
    }

    function SHA256_Hash_Byte_Block(H, w) {
        var W = new Array(16);
        for (var i = 0; i < 16; i++) W[i] = w[4 * i + 0] << 24 | w[4 * i + 1] << 16 | w[4 * i + 2] << 8 | w[4 * i + 3];
        SHA256_Hash_Word_Block(H, W)
    }
    var converters = function() {
        var charToNibble = {};
        var nibbleToChar = [];
        var i;
        for (i = 0; i <= 9; ++i) {
            var char = i.toString();
            charToNibble[char] = i;
            nibbleToChar.push(char)
        }
        for (i = 10; i <= 15; ++i) {
            var lowerChar = String.fromCharCode("a".charCodeAt(0) + i - 10);
            var upperChar = String.fromCharCode("A".charCodeAt(0) + i - 10);
            charToNibble[lowerChar] = i;
            charToNibble[upperChar] = i;
            nibbleToChar.push(lowerChar)
        }
        return {
            byteArrayToHexString: function(bytes) {
                var str = "";
                for (var i = 0; i < bytes.length; ++i) str += nibbleToChar[bytes[i] >> 4] + nibbleToChar[bytes[i] & 15];
                return str
            },
            stringToByteArray: function(str) {
                var bytes = new Array(str.length);
                for (var i = 0; i < str.length; ++i) bytes[i] = str.charCodeAt(i);
                return bytes
            },
            hexStringToByteArray: function(str) {
                var bytes = [];
                var i = 0;
                if (0 !== str.length % 2) {
                    bytes.push(charToNibble[str.charAt(0)]);
                    ++i
                }
                for (; i < str.length - 1; i += 2) bytes.push((charToNibble[str.charAt(i)] << 4) + charToNibble[str.charAt(i + 1)]);
                return bytes
            },
            stringToHexString: function(str) {
                return this.byteArrayToHexString(this.stringToByteArray(str))
            }
        }
    }();
    var curve25519 = function() {
        var KEY_SIZE = 32;
        var UNPACKED_SIZE = 16;
        var ORDER = [237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16];
        var ORDER_TIMES_8 = [104, 159, 174, 231, 210, 24, 147, 192, 178, 230, 188, 23, 245, 206, 247, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128];
        var BASE_2Y = [22587, 610, 29883, 44076, 15515, 9479, 25859, 56197, 23910, 4462, 17831, 16322, 62102, 36542, 52412, 16035];
        var BASE_R2Y = [5744, 16384, 61977, 54121, 8776, 18501, 26522, 34893, 23833, 5823, 55924, 58749, 24147, 14085, 13606, 6080];
        var C1 = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        var C9 = [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        var C486671 = [27919, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        var C39420360 = [33224, 601, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        var P25 = 33554431;
        var P26 = 67108863;

        function clamp(k) {
            k[31] &= 127;
            k[31] |= 64;
            k[0] &= 248
        }

        function cpy32(d, s) {
            for (var i = 0; i < 32; i++) d[i] = s[i]
        }

        function mula_small(p, q, m, x, n, z) {
            m = m | 0;
            n = n | 0;
            z = z | 0;
            var v = 0;
            for (var i = 0; i < n; ++i) {
                v += (q[i + m] & 255) + z * (x[i] & 255);
                p[i + m] = v & 255;
                v >>= 8
            }
            return v
        }

        function mula32(p, x, y, t, z) {
            t = t | 0;
            z = z | 0;
            var n = 31;
            var w = 0;
            var i = 0;
            for (; i < t; i++) {
                var zy = z * (y[i] & 255);
                w += mula_small(p, p, i, x, n, zy) + (p[i + n] & 255) + zy * (x[n] & 255);
                p[i + n] = w & 255;
                w >>= 8
            }
            p[i + n] = w + (p[i + n] & 255) & 255;
            return w >> 8
        }

        function divmod(q, r, n, d, t) {
            n = n | 0;
            t = t | 0;
            var rn = 0;
            var dt = (d[t - 1] & 255) << 8;
            if (t > 1) dt |= d[t - 2] & 255;
            while (n-- >= t) {
                var z = rn << 16 | (r[n] & 255) << 8;
                if (n > 0) z |= r[n - 1] & 255;
                var i = n - t + 1;
                z /= dt;
                rn += mula_small(r, r, i, d, t, -z);
                q[i] = z + rn & 255;
                mula_small(r, r, i, d, t, -rn);
                rn = r[n] & 255;
                r[n] = 0
            }
            r[t - 1] = rn & 255
        }

        function numsize(x, n) {
            while (n-- !== 0 && x[n] === 0) {}
            return n + 1
        }

        function egcd32(x, y, a, b) {
            var an, bn = 32,
                qn, i;
            for (i = 0; i < 32; i++) x[i] = y[i] = 0;
            x[0] = 1;
            an = numsize(a, 32);
            if (an === 0) return y;
            var temp = new Array(32);
            while (true) {
                qn = bn - an + 1;
                divmod(temp, b, bn, a, an);
                bn = numsize(b, bn);
                if (bn === 0) return x;
                mula32(y, x, temp, qn, -1);
                qn = an - bn + 1;
                divmod(temp, a, an, b, bn);
                an = numsize(a, an);
                if (an === 0) return y;
                mula32(x, y, temp, qn, -1)
            }
        }

        function unpack(x, m) {
            for (var i = 0; i < KEY_SIZE; i += 2) x[i / 2] = m[i] & 255 | (m[i + 1] & 255) << 8
        }

        function is_overflow(x) {
            return x[0] > P26 - 19 && (x[1] & x[3] & x[5] & x[7] & x[9]) === P25 && (x[2] & x[4] & x[6] & x[8]) === P26 || x[9] > P25
        }

        function pack(x, m) {
            for (var i = 0; i < UNPACKED_SIZE; ++i) {
                m[2 * i] = x[i] & 255;
                m[2 * i + 1] = (x[i] & 65280) >> 8
            }
        }

        function createUnpackedArray() {
            return new Uint16Array(UNPACKED_SIZE)
        }

        function cpy(d, s) {
            for (var i = 0; i < UNPACKED_SIZE; ++i) d[i] = s[i]
        }

        function set(d, s) {
            d[0] = s;
            for (var i = 1; i < UNPACKED_SIZE; ++i) d[i] = 0
        }
        var add = c255laddmodp;
        var sub = c255lsubmodp;
        var mul_small = c255lmulasmall;
        var mul = c255lmulmodp;
        var sqr = c255lsqrmodp;

        function recip(y, x, sqrtassist) {
            var t0 = createUnpackedArray();
            var t1 = createUnpackedArray();
            var t2 = createUnpackedArray();
            var t3 = createUnpackedArray();
            var t4 = createUnpackedArray();
            var i;
            sqr(t1, x);
            sqr(t2, t1);
            sqr(t0, t2);
            mul(t2, t0, x);
            mul(t0, t2, t1);
            sqr(t1, t0);
            mul(t3, t1, t2);
            sqr(t1, t3);
            sqr(t2, t1);
            sqr(t1, t2);
            sqr(t2, t1);
            sqr(t1, t2);
            mul(t2, t1, t3);
            sqr(t1, t2);
            sqr(t3, t1);
            for (i = 1; i < 5; i++) {
                sqr(t1, t3);
                sqr(t3, t1)
            }
            mul(t1, t3, t2);
            sqr(t3, t1);
            sqr(t4, t3);
            for (i = 1; i < 10; i++) {
                sqr(t3, t4);
                sqr(t4, t3)
            }
            mul(t3, t4, t1);
            for (i = 0; i < 5; i++) {
                sqr(t1, t3);
                sqr(t3, t1)
            }
            mul(t1, t3, t2);
            sqr(t2, t1);
            sqr(t3, t2);
            for (i = 1; i < 25; i++) {
                sqr(t2, t3);
                sqr(t3, t2)
            }
            mul(t2, t3, t1);
            sqr(t3, t2);
            sqr(t4, t3);
            for (i = 1; i < 50; i++) {
                sqr(t3, t4);
                sqr(t4, t3)
            }
            mul(t3, t4, t2);
            for (i = 0; i < 25; i++) {
                sqr(t4, t3);
                sqr(t3, t4)
            }
            mul(t2, t3, t1);
            sqr(t1, t2);
            sqr(t2, t1);
            if (sqrtassist !== 0) {
                mul(y, x, t2)
            } else {
                sqr(t1, t2);
                sqr(t2, t1);
                sqr(t1, t2);
                mul(y, t1, t0)
            }
        }

        function is_negative(x) {
            var isOverflowOrNegative = is_overflow(x) || x[9] < 0;
            var leastSignificantBit = x[0] & 1;
            return ((isOverflowOrNegative ? 1 : 0) ^ leastSignificantBit) & 4294967295
        }

        function sqrt(x, u) {
            var v = createUnpackedArray();
            var t1 = createUnpackedArray();
            var t2 = createUnpackedArray();
            add(t1, u, u);
            recip(v, t1, 1);
            sqr(x, v);
            mul(t2, t1, x);
            sub(t2, t2, C1);
            mul(t1, v, t2);
            mul(x, u, t1)
        }

        function c255lsqr8h(a7, a6, a5, a4, a3, a2, a1, a0) {
            var r = [];
            var v;
            r[0] = (v = a0 * a0) & 65535;
            r[1] = (v = (v / 65536 | 0) + 2 * a0 * a1) & 65535;
            r[2] = (v = (v / 65536 | 0) + 2 * a0 * a2 + a1 * a1) & 65535;
            r[3] = (v = (v / 65536 | 0) + 2 * a0 * a3 + 2 * a1 * a2) & 65535;
            r[4] = (v = (v / 65536 | 0) + 2 * a0 * a4 + 2 * a1 * a3 + a2 * a2) & 65535;
            r[5] = (v = (v / 65536 | 0) + 2 * a0 * a5 + 2 * a1 * a4 + 2 * a2 * a3) & 65535;
            r[6] = (v = (v / 65536 | 0) + 2 * a0 * a6 + 2 * a1 * a5 + 2 * a2 * a4 + a3 * a3) & 65535;
            r[7] = (v = (v / 65536 | 0) + 2 * a0 * a7 + 2 * a1 * a6 + 2 * a2 * a5 + 2 * a3 * a4) & 65535;
            r[8] = (v = (v / 65536 | 0) + 2 * a1 * a7 + 2 * a2 * a6 + 2 * a3 * a5 + a4 * a4) & 65535;
            r[9] = (v = (v / 65536 | 0) + 2 * a2 * a7 + 2 * a3 * a6 + 2 * a4 * a5) & 65535;
            r[10] = (v = (v / 65536 | 0) + 2 * a3 * a7 + 2 * a4 * a6 + a5 * a5) & 65535;
            r[11] = (v = (v / 65536 | 0) + 2 * a4 * a7 + 2 * a5 * a6) & 65535;
            r[12] = (v = (v / 65536 | 0) + 2 * a5 * a7 + a6 * a6) & 65535;
            r[13] = (v = (v / 65536 | 0) + 2 * a6 * a7) & 65535;
            r[14] = (v = (v / 65536 | 0) + a7 * a7) & 65535;
            r[15] = v / 65536 | 0;
            return r
        }

        function c255lsqrmodp(r, a) {
            var x = c255lsqr8h(a[15], a[14], a[13], a[12], a[11], a[10], a[9], a[8]);
            var z = c255lsqr8h(a[7], a[6], a[5], a[4], a[3], a[2], a[1], a[0]);
            var y = c255lsqr8h(a[15] + a[7], a[14] + a[6], a[13] + a[5], a[12] + a[4], a[11] + a[3], a[10] + a[2], a[9] + a[1], a[8] + a[0]);
            var v;
            r[0] = (v = 8388608 + z[0] + (y[8] - x[8] - z[8] + x[0] - 128) * 38) & 65535;
            r[1] = (v = 8388480 + (v / 65536 | 0) + z[1] + (y[9] - x[9] - z[9] + x[1]) * 38) & 65535;
            r[2] = (v = 8388480 + (v / 65536 | 0) + z[2] + (y[10] - x[10] - z[10] + x[2]) * 38) & 65535;
            r[3] = (v = 8388480 + (v / 65536 | 0) + z[3] + (y[11] - x[11] - z[11] + x[3]) * 38) & 65535;
            r[4] = (v = 8388480 + (v / 65536 | 0) + z[4] + (y[12] - x[12] - z[12] + x[4]) * 38) & 65535;
            r[5] = (v = 8388480 + (v / 65536 | 0) + z[5] + (y[13] - x[13] - z[13] + x[5]) * 38) & 65535;
            r[6] = (v = 8388480 + (v / 65536 | 0) + z[6] + (y[14] - x[14] - z[14] + x[6]) * 38) & 65535;
            r[7] = (v = 8388480 + (v / 65536 | 0) + z[7] + (y[15] - x[15] - z[15] + x[7]) * 38) & 65535;
            r[8] = (v = 8388480 + (v / 65536 | 0) + z[8] + y[0] - x[0] - z[0] + x[8] * 38) & 65535;
            r[9] = (v = 8388480 + (v / 65536 | 0) + z[9] + y[1] - x[1] - z[1] + x[9] * 38) & 65535;
            r[10] = (v = 8388480 + (v / 65536 | 0) + z[10] + y[2] - x[2] - z[2] + x[10] * 38) & 65535;
            r[11] = (v = 8388480 + (v / 65536 | 0) + z[11] + y[3] - x[3] - z[3] + x[11] * 38) & 65535;
            r[12] = (v = 8388480 + (v / 65536 | 0) + z[12] + y[4] - x[4] - z[4] + x[12] * 38) & 65535;
            r[13] = (v = 8388480 + (v / 65536 | 0) + z[13] + y[5] - x[5] - z[5] + x[13] * 38) & 65535;
            r[14] = (v = 8388480 + (v / 65536 | 0) + z[14] + y[6] - x[6] - z[6] + x[14] * 38) & 65535;
            var r15 = 8388480 + (v / 65536 | 0) + z[15] + y[7] - x[7] - z[7] + x[15] * 38;
            c255lreduce(r, r15)
        }

        function c255lmul8h(a7, a6, a5, a4, a3, a2, a1, a0, b7, b6, b5, b4, b3, b2, b1, b0) {
            var r = [];
            var v;
            r[0] = (v = a0 * b0) & 65535;
            r[1] = (v = (v / 65536 | 0) + a0 * b1 + a1 * b0) & 65535;
            r[2] = (v = (v / 65536 | 0) + a0 * b2 + a1 * b1 + a2 * b0) & 65535;
            r[3] = (v = (v / 65536 | 0) + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0) & 65535;
            r[4] = (v = (v / 65536 | 0) + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0) & 65535;
            r[5] = (v = (v / 65536 | 0) + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0) & 65535;
            r[6] = (v = (v / 65536 | 0) + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0) & 65535;
            r[7] = (v = (v / 65536 | 0) + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0) & 65535;
            r[8] = (v = (v / 65536 | 0) + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1) & 65535;
            r[9] = (v = (v / 65536 | 0) + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2) & 65535;
            r[10] = (v = (v / 65536 | 0) + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3) & 65535;
            r[11] = (v = (v / 65536 | 0) + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4) & 65535;
            r[12] = (v = (v / 65536 | 0) + a5 * b7 + a6 * b6 + a7 * b5) & 65535;
            r[13] = (v = (v / 65536 | 0) + a6 * b7 + a7 * b6) & 65535;
            r[14] = (v = (v / 65536 | 0) + a7 * b7) & 65535;
            r[15] = v / 65536 | 0;
            return r
        }

        function c255lmulmodp(r, a, b) {
            var x = c255lmul8h(a[15], a[14], a[13], a[12], a[11], a[10], a[9], a[8], b[15], b[14], b[13], b[12], b[11], b[10], b[9], b[8]);
            var z = c255lmul8h(a[7], a[6], a[5], a[4], a[3], a[2], a[1], a[0], b[7], b[6], b[5], b[4], b[3], b[2], b[1], b[0]);
            var y = c255lmul8h(a[15] + a[7], a[14] + a[6], a[13] + a[5], a[12] + a[4], a[11] + a[3], a[10] + a[2], a[9] + a[1], a[8] + a[0], b[15] + b[7], b[14] + b[6], b[13] + b[5], b[12] + b[4], b[11] + b[3], b[10] + b[2], b[9] + b[1], b[8] + b[0]);
            var v;
            r[0] = (v = 8388608 + z[0] + (y[8] - x[8] - z[8] + x[0] - 128) * 38) & 65535;
            r[1] = (v = 8388480 + (v / 65536 | 0) + z[1] + (y[9] - x[9] - z[9] + x[1]) * 38) & 65535;
            r[2] = (v = 8388480 + (v / 65536 | 0) + z[2] + (y[10] - x[10] - z[10] + x[2]) * 38) & 65535;
            r[3] = (v = 8388480 + (v / 65536 | 0) + z[3] + (y[11] - x[11] - z[11] + x[3]) * 38) & 65535;
            r[4] = (v = 8388480 + (v / 65536 | 0) + z[4] + (y[12] - x[12] - z[12] + x[4]) * 38) & 65535;
            r[5] = (v = 8388480 + (v / 65536 | 0) + z[5] + (y[13] - x[13] - z[13] + x[5]) * 38) & 65535;
            r[6] = (v = 8388480 + (v / 65536 | 0) + z[6] + (y[14] - x[14] - z[14] + x[6]) * 38) & 65535;
            r[7] = (v = 8388480 + (v / 65536 | 0) + z[7] + (y[15] - x[15] - z[15] + x[7]) * 38) & 65535;
            r[8] = (v = 8388480 + (v / 65536 | 0) + z[8] + y[0] - x[0] - z[0] + x[8] * 38) & 65535;
            r[9] = (v = 8388480 + (v / 65536 | 0) + z[9] + y[1] - x[1] - z[1] + x[9] * 38) & 65535;
            r[10] = (v = 8388480 + (v / 65536 | 0) + z[10] + y[2] - x[2] - z[2] + x[10] * 38) & 65535;
            r[11] = (v = 8388480 + (v / 65536 | 0) + z[11] + y[3] - x[3] - z[3] + x[11] * 38) & 65535;
            r[12] = (v = 8388480 + (v / 65536 | 0) + z[12] + y[4] - x[4] - z[4] + x[12] * 38) & 65535;
            r[13] = (v = 8388480 + (v / 65536 | 0) + z[13] + y[5] - x[5] - z[5] + x[13] * 38) & 65535;
            r[14] = (v = 8388480 + (v / 65536 | 0) + z[14] + y[6] - x[6] - z[6] + x[14] * 38) & 65535;
            var r15 = 8388480 + (v / 65536 | 0) + z[15] + y[7] - x[7] - z[7] + x[15] * 38;
            c255lreduce(r, r15)
        }

        function c255lreduce(a, a15) {
            var v = a15;
            a[15] = v & 32767;
            v = (v / 32768 | 0) * 19;
            for (var i = 0; i <= 14; ++i) {
                a[i] = (v += a[i]) & 65535;
                v = v / 65536 | 0
            }
            a[15] += v
        }

        function c255laddmodp(r, a, b) {
            var v;
            r[0] = (v = ((a[15] / 32768 | 0) + (b[15] / 32768 | 0)) * 19 + a[0] + b[0]) & 65535;
            for (var i = 1; i <= 14; ++i) r[i] = (v = (v / 65536 | 0) + a[i] + b[i]) & 65535;
            r[15] = (v / 65536 | 0) + (a[15] & 32767) + (b[15] & 32767)
        }

        function c255lsubmodp(r, a, b) {
            var v;
            r[0] = (v = 524288 + ((a[15] / 32768 | 0) - (b[15] / 32768 | 0) - 1) * 19 + a[0] - b[0]) & 65535;
            for (var i = 1; i <= 14; ++i) r[i] = (v = (v / 65536 | 0) + 524280 + a[i] - b[i]) & 65535;
            r[15] = (v / 65536 | 0) + 32760 + (a[15] & 32767) - (b[15] & 32767)
        }

        function c255lmulasmall(r, a, m) {
            var v;
            r[0] = (v = a[0] * m) & 65535;
            for (var i = 1; i <= 14; ++i) r[i] = (v = (v / 65536 | 0) + a[i] * m) & 65535;
            var r15 = (v / 65536 | 0) + a[15] * m;
            c255lreduce(r, r15)
        }

        function mont_prep(t1, t2, ax, az) {
            add(t1, ax, az);
            sub(t2, ax, az)
        }

        function mont_add(t1, t2, t3, t4, ax, az, dx) {
            mul(ax, t2, t3);
            mul(az, t1, t4);
            add(t1, ax, az);
            sub(t2, ax, az);
            sqr(ax, t1);
            sqr(t1, t2);
            mul(az, t1, dx)
        }

        function mont_dbl(t1, t2, t3, t4, bx, bz) {
            sqr(t1, t3);
            sqr(t2, t4);
            mul(bx, t1, t2);
            sub(t2, t1, t2);
            mul_small(bz, t2, 121665);
            add(t1, t1, bz);
            mul(bz, t1, t2)
        }

        function x_to_y2(t, y2, x) {
            sqr(t, x);
            mul_small(y2, x, 486662);
            add(t, t, y2);
            add(t, t, C1);
            mul(y2, t, x)
        }

        function core(Px, s, k, Gx) {
            var dx = createUnpackedArray();
            var t1 = createUnpackedArray();
            var t2 = createUnpackedArray();
            var t3 = createUnpackedArray();
            var t4 = createUnpackedArray();
            var x = [createUnpackedArray(), createUnpackedArray()];
            var z = [createUnpackedArray(), createUnpackedArray()];
            var i, j;
            if (Gx !== null) unpack(dx, Gx);
            else set(dx, 9);
            set(x[0], 1);
            set(z[0], 0);
            cpy(x[1], dx);
            set(z[1], 1);
            for (i = 32; i-- !== 0;) {
                for (j = 8; j-- !== 0;) {
                    var bit1 = (k[i] & 255) >> j & 1;
                    var bit0 = ~(k[i] & 255) >> j & 1;
                    var ax = x[bit0];
                    var az = z[bit0];
                    var bx = x[bit1];
                    var bz = z[bit1];
                    mont_prep(t1, t2, ax, az);
                    mont_prep(t3, t4, bx, bz);
                    mont_add(t1, t2, t3, t4, ax, az, dx);
                    mont_dbl(t1, t2, t3, t4, bx, bz)
                }
            }
            recip(t1, z[0], 0);
            mul(dx, x[0], t1);
            pack(dx, Px);
            if (s !== null) {
                x_to_y2(t2, t1, dx);
                recip(t3, z[1], 0);
                mul(t2, x[1], t3);
                add(t2, t2, dx);
                add(t2, t2, C486671);
                sub(dx, dx, C9);
                sqr(t3, dx);
                mul(dx, t2, t3);
                sub(dx, dx, t1);
                sub(dx, dx, C39420360);
                mul(t1, dx, BASE_R2Y);
                if (is_negative(t1) !== 0) cpy32(s, k);
                else mula_small(s, ORDER_TIMES_8, 0, k, 32, -1);
                var temp1 = new Array(32);
                var temp2 = new Array(64);
                var temp3 = new Array(64);
                cpy32(temp1, ORDER);
                cpy32(s, egcd32(temp2, temp3, s, temp1));
                if ((s[31] & 128) !== 0) mula_small(s, s, 0, ORDER, 32, 1)
            }
        }

        function sign(h, x, s) {
            var w, i;
            var h1 = new Array(32);
            var x1 = new Array(32);
            var tmp1 = new Array(64);
            var tmp2 = new Array(64);
            cpy32(h1, h);
            cpy32(x1, x);
            var tmp3 = new Array(32);
            divmod(tmp3, h1, 32, ORDER, 32);
            divmod(tmp3, x1, 32, ORDER, 32);
            var v = new Array(32);
            mula_small(v, x1, 0, h1, 32, -1);
            mula_small(v, v, 0, ORDER, 32, 1);
            mula32(tmp1, v, s, 32, 1);
            divmod(tmp2, tmp1, 64, ORDER, 32);
            for (w = 0, i = 0; i < 32; i++) w |= v[i] = tmp1[i];
            return w !== 0 ? v : undefined
        }

        function verify(v, h, P) {
            var d = new Array(32);
            var p = [createUnpackedArray(), createUnpackedArray()];
            var s = [createUnpackedArray(), createUnpackedArray()];
            var yx = [createUnpackedArray(), createUnpackedArray(), createUnpackedArray()];
            var yz = [createUnpackedArray(), createUnpackedArray(), createUnpackedArray()];
            var t1 = [createUnpackedArray(), createUnpackedArray(), createUnpackedArray()];
            var t2 = [createUnpackedArray(), createUnpackedArray(), createUnpackedArray()];
            var vi = 0,
                hi = 0,
                di = 0,
                nvh = 0,
                i, j, k;
            set(p[0], 9);
            unpack(p[1], P);
            x_to_y2(t1[0], t2[0], p[1]);
            sqrt(t1[0], t2[0]);
            j = is_negative(t1[0]);
            add(t2[0], t2[0], C39420360);
            mul(t2[1], BASE_2Y, t1[0]);
            sub(t1[j], t2[0], t2[1]);
            add(t1[1 - j], t2[0], t2[1]);
            cpy(t2[0], p[1]);
            sub(t2[0], t2[0], C9);
            sqr(t2[1], t2[0]);
            recip(t2[0], t2[1], 0);
            mul(s[0], t1[0], t2[0]);
            sub(s[0], s[0], p[1]);
            sub(s[0], s[0], C486671);
            mul(s[1], t1[1], t2[0]);
            sub(s[1], s[1], p[1]);
            sub(s[1], s[1], C486671);
            mul_small(s[0], s[0], 1);
            mul_small(s[1], s[1], 1);
            for (i = 0; i < 32; i++) {
                vi = vi >> 8 ^ v[i] & 255 ^ (v[i] & 255) << 1;
                hi = hi >> 8 ^ h[i] & 255 ^ (h[i] & 255) << 1;
                nvh = ~(vi ^ hi);
                di = nvh & (di & 128) >> 7 ^ vi;
                di ^= nvh & (di & 1) << 1;
                di ^= nvh & (di & 2) << 1;
                di ^= nvh & (di & 4) << 1;
                di ^= nvh & (di & 8) << 1;
                di ^= nvh & (di & 16) << 1;
                di ^= nvh & (di & 32) << 1;
                di ^= nvh & (di & 64) << 1;
                d[i] = di & 255
            }
            di = (nvh & (di & 128) << 1 ^ vi) >> 8;
            set(yx[0], 1);
            cpy(yx[1], p[di]);
            cpy(yx[2], s[0]);
            set(yz[0], 0);
            set(yz[1], 1);
            set(yz[2], 1);
            vi = 0;
            hi = 0;
            for (i = 32; i-- !== 0;) {
                vi = vi << 8 | v[i] & 255;
                hi = hi << 8 | h[i] & 255;
                di = di << 8 | d[i] & 255;
                for (j = 8; j-- !== 0;) {
                    mont_prep(t1[0], t2[0], yx[0], yz[0]);
                    mont_prep(t1[1], t2[1], yx[1], yz[1]);
                    mont_prep(t1[2], t2[2], yx[2], yz[2]);
                    k = ((vi ^ vi >> 1) >> j & 1) + ((hi ^ hi >> 1) >> j & 1);
                    mont_dbl(yx[2], yz[2], t1[k], t2[k], yx[0], yz[0]);
                    k = di >> j & 2 ^ (di >> j & 1) << 1;
                    mont_add(t1[1], t2[1], t1[k], t2[k], yx[1], yz[1], p[di >> j & 1]);
                    mont_add(t1[2], t2[2], t1[0], t2[0], yx[2], yz[2], s[((vi ^ hi) >> j & 2) >> 1])
                }
            }
            k = (vi & 1) + (hi & 1);
            recip(t1[0], yz[k], 0);
            mul(t1[1], yx[k], t1[0]);
            var Y = [];
            pack(t1[1], Y);
            return Y
        }

        function keygen(k) {
            var P = [];
            var s = [];
            k = k || [];
            clamp(k);
            core(P, s, k, null);
            return {
                p: P,
                s: s,
                k: k
            }
        }
        return {
            sign: sign,
            verify: verify,
            keygen: keygen
        }
    }();
    var hash = {
        init: SHA256_init,
        update: SHA256_write,
        getBytes: SHA256_finalize
    };
    var nxtCrypto = function(curve25519, hash, converters) {
        function simpleHash(message) {
            hash.init();
            hash.update(message);
            return hash.getBytes()
        }

        function areByteArraysEqual(bytes1, bytes2) {
            if (bytes1.length !== bytes2.length) return false;
            for (var i = 0; i < bytes1.length; ++i) {
                if (bytes1[i] !== bytes2[i]) return false
            }
            return true
        }

        function getPublicKey(secretPhrase) {
            var secretPhraseBytes = converters.hexStringToByteArray(secretPhrase);
            var digest = simpleHash(secretPhraseBytes);
            return converters.byteArrayToHexString(curve25519.keygen(digest).p)
        }

        function sign(message, secretPhrase) {
            var messageBytes = converters.hexStringToByteArray(message);
            var secretPhraseBytes = converters.hexStringToByteArray(secretPhrase);
            var digest = simpleHash(secretPhraseBytes);
            var s = curve25519.keygen(digest).s;
            var m = simpleHash(messageBytes);
            hash.init();
            hash.update(m);
            hash.update(s);
            var x = hash.getBytes();
            var y = curve25519.keygen(x).p;
            hash.init();
            hash.update(m);
            hash.update(y);
            var h = hash.getBytes();
            var v = curve25519.sign(h, x, s);
            return converters.byteArrayToHexString(v.concat(h))
        }

        function verify(signature, message, publicKey) {
            var signatureBytes = converters.hexStringToByteArray(signature);
            var messageBytes = converters.hexStringToByteArray(message);
            var publicKeyBytes = converters.hexStringToByteArray(publicKey);
            var v = signatureBytes.slice(0, 32);
            var h = signatureBytes.slice(32);
            var y = curve25519.verify(v, h, publicKeyBytes);
            var m = simpleHash(messageBytes);
            hash.init();
            hash.update(m);
            hash.update(y);
            var h2 = hash.getBytes();
            return areByteArraysEqual(h, h2)
        }
        return {
            getPublicKey: getPublicKey,
            sign: sign,
            verify: verify
        }
    }(curve25519, hash, converters);
    var BlockGenerator = function BlockGenerator(node) {
        events.EventEmitter.call(this);
        this.node = node;
        this.timestamp = null;
        this.running = false;
        this.whoSayGenBlock = null;
        this.selectedPeer = null;
        this.collectedPeerResponse = new Map();
        this.verifiedPeerResponse = null
    };
    util.inherits(BlockGenerator, events.EventEmitter);
    BlockGenerator.prototype.flag = function(action) {
        switch (action) {
            case "start":
                this.timestamp = new Date().getTime();
                this.running = true;
                break;
            case "done":
            case "error":
                this.whoSayGenBlock = null;
                this.selectedPeer = null;
            case "stop":
                this.timestamp = null;
                this.running = false;
                break
        }
    };
    BlockGenerator.prototype.broadcastGenerateBlock = function() {
        if (this.whoSayGenBlock !== null) {
            logger.notice("Already generate block: " + this.whoSayGenBlock);
            return
        }
        if (this.running) {
            logger.notice("BlockGenerator already running.");
            return
        }
        this.flag("start");
        this.node.broadcast({
            timestamp: this.timestamp
        }, Connection.prototype.commands.BROADCAST_GENERATE_BLOCK);
        this.initCheckPeers()
    };
    BlockGenerator.prototype.initCheckPeers = function() {
        setTimeout(this.selectPeer.bind(this), 3e4)
    };
    BlockGenerator.prototype.stopNotice = function(message) {
        logger.notice("Generating unit is stopped due to: " + message)
    };
    BlockGenerator.prototype.addConnection = function(conn) {
        conn.addListener(Connection.prototype.commands.BROADCAST_GENERATE_BLOCK, this.handleBroadcastGenerateBlock.bind(this));
        conn.addListener(Connection.prototype.commands.STOP_GENERATE_BLOCK, this.handleStopGenerateBlock.bind(this));
        conn.addListener(Connection.prototype.commands.NEW_BLOCK, this.handleNewBlock.bind(this));
        conn.addListener(Connection.prototype.commands.ANSWER_ON_GENERATE_BLOCK, this.handleAnswerOnGenerateBlock.bind(this));
        conn.addListener(Connection.prototype.commands.VERIFIED_PEER, this.handleVerifiedPeer.bind(this));
        conn.addListener(Connection.prototype.commands.VERIFIED_PEER_RESPONSE, this.handleVerifiedPeerResponse.bind(this));
        conn.addListener(Connection.prototype.commands.PEER_NOT_VERIFIED, this.handlePeerNotVerified.bind(this))
    };
    BlockGenerator.prototype.handleBroadcastGenerateBlock = function(e) {
        logger.netdbg("Handle Broadcast Generate Block\n" + e.conn.toString());
        if (this.running) {
            if (this.timestamp < e.message.data.timestamp) {
                e.conn.sendMessage(Connection.prototype.commands.STOP_GENERATE_BLOCK, {
                    message: "My query generation was earlier in time."
                });
                return
            } else {
                this.stopNotice("Execution time is more than requesting " + e.peer.toString());
                this.flag("stop")
            }
        }
        if (this.whoSayGenBlock !== null) {
            e.conn.sendMessage(Connection.prototype.commands.STOP_GENERATE_BLOCK, {
                message: "Already generate by " + this.whoSayGenBlock
            });
            return
        }
        this.whoSayGenBlock = e.peer.toString();
        this.createResponse(e)
    };
    BlockGenerator.prototype.createResponse = function(e) {
        var accountId = Account.currentAccount.accountId,
            data = {
                accountId: accountId
            };
        Step(function getMyTransactions() {
            TransactionDb.getMyTransactions(accountId, this)
        }, function receiveTransactions(transactions) {
            data["transactionsCount"] = transactions.length;
            this(null)
        }, function sendMessage() {
            e.conn.sendMessage(Connection.prototype.commands.ANSWER_ON_GENERATE_BLOCK, data)
        })
    };
    BlockGenerator.prototype.handleStopGenerateBlock = function(e) {
        logger.netdbg("Handle Stop Generate Block\n" + e.conn.toString());
        this.stopNotice(e.message.data.message);
        this.flag(e.message.data.fullStop ? "done" : "stop")
    };
    BlockGenerator.prototype.handleAnswerOnGenerateBlock = function(e) {
        logger.netdbg("Handle Answer On Generate Block\n" + e.conn.toString());
        if (!this.running) {
            logger.notice("Whe are not running");
            return
        }
        this.collectedPeerResponse.set(e.peer.toString(), {
            peer: e.peer,
            account: e.message.data
        })
    };
    BlockGenerator.prototype.handleNewBlock = function(e) {
        logger.netdbg("Handle New Block\n" + e.conn.toString());
        var conn = this.node.peerProcessor.connections.get(this.whoSayGenBlock);
        if (conn) {
            conn.sendMessage(Connection.prototype.commands.STOP_GENERATE_BLOCK, {
                message: "Work done!!!",
                fullStop: true
            })
        } else {
            this.node.broadcast({
                message: "Work done!!!",
                fullStop: true
            }, Connection.prototype.commands.STOP_GENERATE_BLOCK)
        }
    };
    BlockGenerator.prototype.handlePeerNotVerified = function(e) {
        logger.netdbg("Handle NPeerNotVerified\n" + e.conn.toString());
        logger.error(e.message.data.message);
        this.flag("error")
    };
    BlockGenerator.prototype.handleVerifiedPeer = function(e) {
        logger.netdbg("Handle Verified Peer\n" + e.conn.toString());
        var data = e.message.data;
        if (typeof data.broadcastSelectPeer !== "undefined" && data.broadcastSelectPeer) {
            this.verifiedPeerResponse = new Map();
            setTimeout(this.verifiedPeer.bind(this), 3e4)
        } else {
            var peer = new Peer(data.peer);
            var connection = this.node.peerProcessor.connections.get(peer.toString());
            if (!connection) {
                this.node.broadcast({
                    message: "Work done!!!",
                    fullStop: true
                }, Connection.prototype.commands.STOP_GENERATE_BLOCK);
                this.flag("done");
                return
            }
            this.selectedPeer = peer.toString();
            this.validatePeer({
                peer: peer,
                account: data.account
            }, function(result) {
                connection.sendMessage(Connection.prototype.commands.VERIFIED_PEER_RESPONSE, {
                    valid: result.valid
                })
            })
        }
    };
    BlockGenerator.prototype.handleVerifiedPeerResponse = function(e) {
        logger.netdbg("Handle Verified Peer Response\n" + e.conn.toString());
        this.verifiedPeerResponse.set(e.peer.toString(), e.message.data)
    };
    BlockGenerator.prototype.verifiedPeer = function() {
        var approved = 0,
            notApproved = 0,
            countPeerVerified = this.verifiedPeerResponse.length;
        this.verifiedPeerResponse.forEach(function(data, key) {
            if (data.valid) {
                approved++
            } else {
                notApproved++
            }
        });
        if (approved * 100 / countPeerVerified > 50) {
            this.broadcastSendBlock(this.generateBlock())
        } else {
            this.node.broadcast({
                message: "Approved less then 50%",
                notValid: true
            }, Connection.prototype.commands.PEER_NOT_VERIFIED)
        }
    };
    BlockGenerator.prototype.generateBlock = function() {
        return new Block({
            id: 1
        })
    };
    BlockGenerator.prototype.validatePeer = function(data, callback) {
        var peer = data.peer,
            account = data.account;
        var _data = {};
        Step(function getMyTransactions() {
            TransactionDb.getMyTransactions(account.accountId, this)
        }, function receiveTransactions(transactions) {
            _data = data;
            _data["valid"] = account.transactionsCount === transactions.length;
            callback(_data)
        })
    };
    BlockGenerator.prototype.comparePeers = function(oldPeer, newPeer) {
        if (oldPeer === null) {
            return true
        }
        var now = new Date().getTime(),
            oldDiff = now - oldPeer.timestamp,
            newDiff = now - newPeer.timestamp;
        if (oldDiff > newDiff) {
            return false
        }
        var oldPeerResponse = this.collectedPeerResponse.get(oldPeer.toString()),
            newPeerResponse = this.collectedPeerResponse.get(newPeer.toString());
        return oldPeerResponse.account.transactionsCount < newPeerResponse.account.transactionsCount
    };
    BlockGenerator.prototype.selectPeer = function() {
        if (!this.running) {
            logger.notice("Whe are not running");
            return
        }
        var peerProc = this.node.peerProcessor;
        var filtered = peerProc.connections.filter(function(conn, key) {
            return conn.peer.status === Peer.prototype.statuses.ACTIVE && this.collectedPeerResponse.has(key)
        }.bind(this));
        var self = this;
        async.eachSeries(filtered.toArray(), function(conn, _callback) {
            var key = conn.peer.toString(),
                peerResponse = self.collectedPeerResponse.get(key);
            self.validatePeer(peerResponse, function(data) {
                self.collectedPeerResponse.set(key, data);
                _callback()
            })
        }, function(err) {
            if (err) {
                logger.error("No select peer!!!");
                self.node.broadcast({
                    message: "No select peer!!!",
                    fullStop: true
                }, Connection.prototype.commands.STOP_GENERATE_BLOCK);
                return
            }
            var curPeer = null,
                currPeerAccount = null;
            filtered.forEach(function(conn, key) {
                var peerResponse = self.collectedPeerResponse.get(key);
                if (curPeer === null || peerResponse.valid && self.comparePeers(curPeer, peerResponse.peer)) {
                    curPeer = peerResponse.peer;
                    currPeerAccount = peerResponse.account
                }
            });
            if (curPeer) {
                self.selectedPeer = curPeer;
                self.node.broadcast({
                    peer: curPeer.getData(),
                    account: currPeerAccount
                }, Connection.prototype.commands.VERIFIED_PEER, {
                    peerKey: curPeer.toString()
                })
            } else {
                logger.error("No select peer!!!");
                self.node.broadcast({
                    message: "No select peer!!!",
                    fullStop: true
                }, Connection.prototype.commands.STOP_GENERATE_BLOCK)
            }
        })
    };
    BlockGenerator.prototype.broadcastSendBlock = function(block) {
        if (block instanceof Block) {
            this.node.broadcast({
                block: block.getData()
            }, Connection.prototype.commands.NEW_BLOCK)
        } else {
            logger.error("'block' must be instance of 'models/Block'")
        }
    };
    var DB = function() {
        return {
            db: new Datastore({
                filename: appDir + "/db/nxtl.db",
                autoload: true
            }),
            dbTx: new Datastore({
                filename: appDir + "/db/nxtlTx.db",
                autoload: true
            }),
            dbPeers: new Datastore({
                filename: appDir + "/db/peers.db",
                autoload: true
            })
        }
    }();
    var Genesis = {
        genesisBlockId: "9971962888809300594",
        creatorId: "7684489094182123925",
        creatorPublicKey: new Buffer("03362fbbe6611243d853507a82dbe59844d169157fcda08deb171ed238fa3e19", "hex"),
        genesisRecipients: ["15007997926042764084", "14302536936654097014", "4768052738330486459", "3216421205779526334", "17591617551191551229", "17629803831991308677", "17982531019448340817", "1092564458935116408", "1797803473310418787"],
        genesisAmounts: [6e8, 15e7, 5e7, 5e7, 1e7, 4e7, 5e7, 3e7, 2e7],
        genesisSignatures: ["d10e619bf8a9e31cd3c0ef1234d1efa40e7ab19f8a66e1ce63d065b8a992ae0f3ab0bbe032584229b9b64bbf2a1a19368a139cf52b76544fc9f7c4cfa6524475", "c687b6d33fbe1a8f4175d107e4d6bdbe3cffb5bd89aca5496c3fe0ae2e3d1b0a63278702131d59404eb6c5ca642f27a5eb8a8c68f5f70f64afe869f9fd81da2a", "d7d7b05dea2fc4371029d3d2e7a4250dd29f4a99d9eeb624c0df5155bd147e0c8597148beb1aa370c5b2dfeeaa28c9abfe0762ece014a24bb7c09370690ef963", "7520150c9d1741212d7748cc6841e554f1c89372d488026c1887b62b70a3860f5293f72274b57a64d77aad7eac220e322a0fa36f11b4f6ef58764d8b6e88229c", "2e456ec235841c8ac301e288f20b53ec2dc91ee24b94c2316c1b143a605550096d4192d7bea120a45509f23ae9aa92b16de094ddadf39d9411072e1f0045c75c", "fb0b87dbcf493a226f438ae27725237d7d828638d0a259c2e64996b8140f610f8d7cf644f51e97bc558a45adac66d57954abf7ad5eaf3e490ec6ea2da6e15800", "38af290d4a8cf743ebe14174173658cb08905827b0b68fc38aae257836d58b06b09a1bbd856c2d377f282e065951c886f607f1d3e01d970ef2c2dd9023c2f0ea", "eede7ced4d51b186bdb447e02d14e83fa1cf6ccf8e0752c2064b6d76cdca0f0b64edf1cdeedd14bcd9d4d59cfb70034cb1f28601f8ae0ef30ecc5b44aa1f9bd0", "5c4caafcca29e86b7659bcb56c93dfabb28d682d4ca1bff23e3ec25cc9d6230f86c71a36f4f74617b0beec5a5cb274e1f6577ac52b9e6d8fb7daceef3bd0dba3"],
        genesisBlockSignature: "e0580c1f3feb66a9aa09ba2649f6726dca87b6db14f2dbcda368f7a2269c5604d16866925c706318ff0fdc79e1455c9b4ee88a969eb96899f54b6603a68f319f"
    };
    var TransactionProcessor = function() {
        events.EventEmitter.call(this)
    };
    util.inherits(TransactionProcessor, events.EventEmitter);
    TransactionProcessor.prototype.verifiyTransaction = function(transaction, unconfTransArr, callback) {
        if (typeof unconfTransArr === "function") {
            callback = unconfTransArr;
            unconfTransArr = []
        }
        TransactionDb.hasTransaction(transaction.getId(), function(res) {
            if (res) {
                callback("Transaction " + transaction.getStringId() + " is already in the blockchain")
            }

            function validContinue() {
                if (!transaction.verify()) {
                    callback("Signature verification failed for transaction " + transaction.getStringId())
                }
                if (transaction.getId().equals(new long(0))) {
                    callback("Invalid transaction id")
                }
                try {
                    transaction.validateAttachment()
                } catch (e) {
                    callback(e)
                }
                Account.getAccounts(transaction.senderId.toString(), function(accounts) {
                    if (accounts == null || transaction.amount + transaction.fee > parseFloat(accounts.nxtlAccount.unconfirmedAmount)) {
                        callback("Not Enough money accId:" + transaction.senderId.toString())
                    }
                    callback(null)
                })
            }
            TransactionDb.hasTransaction(transaction.referencedTransactionId.toString(), function(res) {
                if (!res && transaction.referencedTransactionId != null) {
                    var isInUnconfTx = false;
                    for (var index = 0; index < unconfTransArr.length; ++index) {
                        var tx = unconfTransArr[index];
                        if (tx.id.toString() == transaction.referencedTransactionId.toString()) {
                            isInUnconfTx = true;
                            break
                        }
                    }
                    UnconfirmedTransactions.hasTransaction(transaction.referencedTransactionId.toString(), function(res) {
                        if (!res && !isInUnconfTx) {
                            callback("Missing referenced transaction " + transaction.referencedTransactionId.toString() + " for transaction " + transaction.getStringId())
                        } else {
                            validContinue()
                        }
                    })
                } else {
                    validContinue()
                }
            })
        })
    };
    TransactionProcessor.prototype.addTransactionOrConfirmation = function(transaction, withConfirmation) {
        if (typeof withConfirmation === "undefined") {
            withConfirmation = false
        }
        UnconfirmedTransactions.findTransaction(transaction.getId().toString(), function(resTx) {
            if (resTx) {
                if (resTx.confirmations < 1440) {
                    logger.info("Add TX confirmation txID: " + transaction.getId().toString());
                    UnconfirmedTransactions.addConfirmation(transaction.getId().toString())
                }
            } else {
                logger.info("Add new TX from broadcast txID: " + transaction.getId().toString());
                if (withConfirmation === true) {
                    transaction.confirmations += 1
                } else {
                    transaction.confirmations = 1
                }
                UnconfirmedTransactions.addTransactions([transaction]);
                var senderAccount = Account.addOrGetAccount(transaction.getSenderId().toString());
                senderAccount.addToUnconfirmedBalance(-Utils.roundTo5Float(transaction.amount) - Utils.roundTo5Float(transaction.fee));
                var recipientAccount = Account.addOrGetAccount(transaction.recipientId.toString());
                recipientAccount.addToUnconfirmedBalance(Utils.roundTo5Float(transaction.amount));
                var blockchain = new Blockchain();
                blockchain.setLastTransaction(transaction);
                logger.info("Added transaction", transaction.getId().toString());
                NodeServer.broadcastNewTransaction(transaction)
            }
        })
    };
    var TransactionType = function() {};
    TransactionType.TYPE_PAYMENT = 0;
    TransactionType.TYPE_MESSAGING = 1;
    TransactionType.TYPE_COLORED_COINS = 2;
    TransactionType.SUBTYPE_PAYMENT_ORDINARY_PAYMENT = 0;
    TransactionType.SUBTYPE_MESSAGING_ARBITRARY_MESSAGE = 0;
    TransactionType.SUBTYPE_MESSAGING_ALIAS_ASSIGNMENT = 1;
    TransactionType.SUBTYPE_MESSAGING_POLL_CREATION = 2;
    TransactionType.SUBTYPE_MESSAGING_VOTE_CASTING = 3;
    TransactionType.SUBTYPE_COLORED_COINS_ASSET_ISSUANCE = 0;
    TransactionType.SUBTYPE_COLORED_COINS_ASSET_TRANSFER = 1;
    TransactionType.SUBTYPE_COLORED_COINS_ASK_ORDER_PLACEMENT = 2;
    TransactionType.SUBTYPE_COLORED_COINS_BID_ORDER_PLACEMENT = 3;
    TransactionType.SUBTYPE_COLORED_COINS_ASK_ORDER_CANCELLATION = 4;
    TransactionType.SUBTYPE_COLORED_COINS_BID_ORDER_CANCELLATION = 5;
    TransactionType.findTransactionType = function(type, subtype) {
        switch (type) {
            case TransactionType.TYPE_PAYMENT:
                switch (subtype) {
                    case TransactionType.SUBTYPE_PAYMENT_ORDINARY_PAYMENT:
                        return TransactionType.Payment;
                    default:
                        return null
                }
            default:
                return null
        }
    };
    TransactionType.Payment = function() {};
    TransactionType.Payment.getType = function() {
        return TransactionType.TYPE_PAYMENT
    };
    TransactionType.Payment.getSubtype = function() {
        return TransactionType.SUBTYPE_PAYMENT_ORDINARY_PAYMENT
    };
    TransactionType.Payment.loadAttachment = function(transaction, buffer) {
        TransactionType.Payment.validateAttachment(transaction)
    };
    TransactionType.Payment.loadAttachment = function(transaction, attachmentData) {
        TransactionType.Payment.validateAttachment(transaction)
    };
    TransactionType.Payment.applyAttachmentUnconfirmed = function(transaction, senderAccount) {
        return true
    };
    TransactionType.Payment.applyAttachment = function(transaction, senderAccount, recipientAccount) {
        recipientAccount.addToBalanceAndUnconfirmedBalance(transaction.amount)
    };
    TransactionType.Payment.undoAttachment = function(transaction, senderAccount, recipientAccount) {
        recipientAccount.addToBalanceAndUnconfirmedBalance(-transaction.amount)
    };
    TransactionType.Payment.undoAttachmentUnconfirmed = function(transaction, senderAccount) {};
    TransactionType.Payment.isDuplicate = function(transaction, duplicates) {
        return false
    };
    TransactionType.Payment.updateTotals = function(transaction, accumulatedAmounts, accumulatedAssetQuantities, accumulatedAmount) {};
    TransactionType.Payment.validateAttachment = function(transaction) {
        if (transaction.amount <= 0 || transaction.amount >= Config.MAX_BALANCE) {
            throw new Error("Invalid ordinary payment: " + transaction.attachment.getJSON())
        }
    };
    var Blockchain = function() {
        var lastBlock = null;
        var lastTransaction = null;
        this.getBlock = function(blockId) {};
        this.setLastTransaction = function(transaction) {
            lastTransaction = transaction
        };
        this.getLastTransaction = function() {
            return lastTransaction
        };
        this.setLastBlock = function(block) {
            lastBlock = block
        };
        this.getLastBlock = function() {
            return lastBlock
        };
        this.getAllBlocks = function(callback) {};
        this.getAllTransactions = function(callback) {};
        if (Blockchain.instance) {
            return Blockchain.instance
        }
        Blockchain.instance = this
    };
    var BlockchainProcessor = function() {
        events.EventEmitter.call(this)
    };
    util.inherits(BlockchainProcessor, events.EventEmitter);
    BlockchainProcessor.run = function(callback) {
        Account.addOrGetAccount("7684489094182123925");
        BlockchainProcessor.addGenesisBlock(function() {
            scan(function() {
                logger.info("blockchain run end");
                if (typeof callback == "function") {
                    callback()
                }
            })
        })
    };
    BlockchainProcessor.addGenesisBlock = function(callback) {
        BlockDb.hasBlock(Genesis.genesisBlockId, function(has) {
            if (has) {
                logger.info("Genesis block already in database");
                if (typeof callback === "function") {
                    callback()
                }
            } else {
                logger.info("Genesis block not in database, starting from scratch");
                try {
                    var transactionsMap = {
                        count: 0
                    };
                    for (var i = 0; i < Genesis.genesisRecipients.length; i++) {
                        var transaction = new Transaction({
                            type: TransactionType.Payment,
                            timestamp: 0,
                            deadline: 0,
                            senderPublicKey: Genesis.creatorPublicKey,
                            recipientId: Genesis.genesisRecipients[i],
                            amount: Genesis.genesisAmounts[i],
                            fee: 0,
                            referencedTransactionId: 0,
                            signature: Genesis.genesisSignatures[i]
                        });
                        transactionsMap[transaction.getId().toString()] = transaction;
                        transactionsMap.count++
                    }
                    var digest = crypto.createHash("sha256");
                    for (var transactionId in transactionsMap) {
                        if (transactionsMap.hasOwnProperty(transactionId) && transactionId != "count") {
                            transaction = transactionsMap[transactionId];
                            digest.update(transaction.getBytes())
                        }
                    }
                    var genesisBlock = new Block({
                        version: -1,
                        timestamp: 0,
                        previousBlockId: null,
                        totalAmount: 1e9,
                        totalFee: 0,
                        payloadLength: transactionsMap.count * 128,
                        payloadHash: digest.digest(),
                        generatorPublicKey: Genesis.creatorPublicKey,
                        generationSignature: Config.NULL_HASH,
                        blockSignature: Genesis.genesisBlockSignature,
                        previousBlockHash: null,
                        blockTransactions: transactionsMap
                    });
                    genesisBlock.setPrevious(null);
                    logger.info("genesisBlock.verifyBlockSignature()", genesisBlock.verifyBlockSignature());
                    BlockchainProcessor.addBlock(genesisBlock, true);
                    if (typeof callback === "function") {
                        callback()
                    }
                } catch (e) {
                    logger.error(e.stack ? e.stack : e.toString());
                    throw new Error(e)
                }
            }
        })
    };
    BlockchainProcessor.generateBlock = function(secretPhrase) {
        try {
            UnconfirmedTransactions.getAll(function(transactionsArr) {
                if (transactionsArr.count > 0) {
                    var totalFee = 0;
                    var totalAmount = 0;
                    var digest = crypto.createHash("sha256");
                    for (var transactionId in transactionsArr) {
                        if (transactionsArr.hasOwnProperty(transactionId) && transactionId != "count") {
                            var transaction = transactionsArr[transactionId];
                            digest.update(transaction.getBytes());
                            totalAmount += Utils.roundTo5Float(transaction.amount);
                            totalFee += Utils.roundTo5Float(transaction.fee)
                        }
                    }
                    var blockchain = new Blockchain();
                    var previousBlock = blockchain.getLastBlock();
                    var previousBlockHash = Utils.sha256(previousBlock.getBytes());
                    var generatorPublicKey = nxtCrypto.getPublicKey(secretPhrase);
                    var generationSignature = null;
                    if (previousBlock.height < Config.TRANSPARENT_FORGING_BLOCK) {
                        generationSignature = nxtCrypto.sign(previousBlock.generationSignature.toString("hex"), secretPhrase)
                    } else {
                        digest.update(previousBlock.generationSignature);
                        digest.update(generatorPublicKey);
                        generationSignature = digest.digest()
                    }
                    var block = new Block({
                        version: 1,
                        timestamp: new Date().getTime(),
                        previousBlockId: previousBlock.id,
                        totalAmount: Utils.roundTo5Float(totalAmount),
                        totalFee: Utils.roundTo5Float(totalFee),
                        payloadLength: transactionsArr.length * 128,
                        payloadHash: digest.digest(),
                        generatorPublicKey: generatorPublicKey,
                        generationSignature: generationSignature,
                        blockSignature: null,
                        previousBlockHash: previousBlockHash,
                        blockTransactions: transactionsArr
                    });
                    block.sign(secretPhrase);
                    block.setPrevious(previousBlock);
                    logger.info("Generating block", block.getId().toString());
                    try {
                        logger.info(block.verifyBlockSignature());
                        block.verifyGenerationSignature(function(res) {
                            logger.info(res)
                        });
                        if (block.verifyBlockSignature()) {
                            block.verifyGenerationSignature(function(res) {
                                if (!res) {
                                    logger.error("Account " + block.getGeneratorId() + " generated an incorrect block.")
                                }
                                BlockchainProcessor.pushBlock(block, function() {
                                    BlockDb.setNextBlockId(previousBlock.getId().toString(), block.getId().toString(), function() {
                                        BlockchainProcessor.addBlock(block, false)
                                    })
                                });
                                logger.info("Account " + block.getGeneratorId() + " generated block " + block.getStringId())
                            })
                        } else {
                            logger.error("Account " + block.getGeneratorId() + " generated an incorrect block.")
                        }
                    } catch (err) {
                        logger.error("BlockchainProcessor.generateBlock error");
                        logger.error(err.toString())
                    }
                } else {
                    logger.info("No new transactions to generate block.")
                }
            })
        } catch (e) {
            logger.error("Create block ERROR:", e);
            throw new Error(e)
        }
    };
    BlockchainProcessor.pushBlock = function(block, _callback) {
        var curTime = new Date().getTime();
        var blockchain = new Blockchain();
        var previousLastBlock = blockchain.getLastBlock();
        if (previousLastBlock.getId().toString() != block.previousBlockId.toString()) {
            throw new Error("Previous block id doesn't match")
        }
        if (Utils.sha256(previousLastBlock.getBytes()).toString("hex") != block.previousBlockHash.toString("hex")) {
            throw new Error("Previous block hash doesn't match")
        }
        if (parseInt(block.timestamp) > curTime + 15 || parseInt(block.timestamp) <= parseInt(previousLastBlock.timestamp)) {
            throw new Error("Invalid timestamp: " + block.timestamp + " current time is " + curTime + ", previous block timestamp is " + previousLastBlock.timestamp)
        }
        if (block.getId().equals(long.fromInt(0))) {
            throw new Error("Invalid id")
        }
        BlockDb.hasBlock(block.getId(), function(res) {
            if (res) {
                throw new Error("Duplicate ID")
            }
            if (!block.verifyBlockSignature()) {
                throw new Error("Signature verifyBlockSignature verification failed")
            }
            block.verifyGenerationSignature(function(res) {
                if (!res) {
                    throw new Error("Signature verifyGenerationSignature verification failed")
                }
                var calculatedTotalAmount = 0,
                    calculatedTotalFee = 0,
                    duplicates = null,
                    accumulatedAmounts = {},
                    accumulatedAssetQuantities = null,
                    digest = crypto.createHash("sha256");
                var transactionsArr = block.getTransactionsAsArray();
                async.eachSeries(transactionsArr, function(transaction, callback) {
                    if (transaction.getExpiration() < block.timestamp) {
                        callback("Invalid transaction timestamp " + transaction.timestamp + " for transaction " + transaction.getStringId() + ", current time is " + curTime + ", block timestamp is " + block.timestamp)
                    }
                    TransactionDb.hasTransaction(transaction.getId(), function(res) {
                        if (res) {
                            callback("Transaction " + transaction.getStringId() + " is already in the blockchain")
                        }
                        TransactionDb.hasTransaction(transaction.referencedTransactionId.toString(), function(res) {
                            if (!res && transaction.referencedTransactionId != null && block.getTransactionIds().indexOf(transaction.referencedTransactionId.toString()) === -1) {
                                callback("Missing referenced transaction " + transaction.referencedTransactionId.toString() + " for transaction " + transaction.getStringId())
                            }
                            if (!transaction.verify()) {
                                callback("Signature verification failed for transaction " + transaction.getStringId())
                            }
                            if (transaction.getId().equals(new long(0))) {
                                callback("Invalid transaction id")
                            }
                            if (transaction.isDuplicate(duplicates)) {
                                callback("Transaction is a duplicate: " + transaction.getStringId())
                            }
                            try {
                                transaction.validateAttachment()
                            } catch (e) {
                                callback(e)
                            }
                            calculatedTotalAmount += Utils.roundTo5Float(transaction.amount);
                            transaction.updateTotals(accumulatedAmounts, accumulatedAssetQuantities);
                            calculatedTotalFee += Utils.roundTo5Float(transaction.fee);
                            digest.update(transaction.getBytes());
                            callback()
                        })
                    })
                }, function(err) {
                    if (err) {
                        logger.error(err);
                        throw new Error(err)
                    } else {
                        logger.info("All transactions passed async verification");
                        if (Utils.roundTo5Float(calculatedTotalAmount) != Utils.roundTo5Float(block.totalAmount) || Utils.roundTo5Float(calculatedTotalFee) != Utils.roundTo5Float(block.totalFee)) {
                            throw new Error("Total amount or fee don't match transaction totals")
                        }
                        var digestStr = digest.digest().toString("hex");
                        if (digestStr != block.payloadHash.toString("hex")) {
                            logger.error("Payload hash doesn't match block ID: " + block.id.toString());
                            throw new Error("Payload hash doesn't match")
                        }
                        var accumulatedAmountsArr = [];
                        for (var accumulatedAmountEntryId in accumulatedAmounts) {
                            if (accumulatedAmounts.hasOwnProperty(accumulatedAmountEntryId)) {
                                accumulatedAmountsArr.push({
                                    key: accumulatedAmountEntryId,
                                    amount: accumulatedAmounts[accumulatedAmountEntryId]
                                })
                            }
                        }
                        async.each(accumulatedAmountsArr, function(accumulatedAmountEntry, callback) {
                            var senderAccount = Account.getAccounts(accumulatedAmountEntry.key, function(senderAccauntNums) {
                                if (senderAccauntNums.nxtlAccount.amount < accumulatedAmountEntry.amount) {
                                    callback("Not enough funds in sender account: " + Convert.toUnsignedLong(senderAccount.getId()))
                                } else {
                                    callback()
                                }
                            })
                        }, function(err) {
                            if (err) {
                                throw new Error(err)
                            }
                            if (typeof _callback === "function") {
                                _callback()
                            }
                        })
                    }
                })
            })
        })
    };
    BlockchainProcessor.checkExistBlock = function(block, _callback) {
        function blockTransactionsCheck() {
            var txs = block.getTransactionsAsArray();
            async.each(txs, function(transaction, callback) {
                BlockchainProcessor.checkExistTransaction(transaction, callback)
            }, function(err) {
                if (err) {
                    _callback(err)
                } else {
                    _callback(null)
                }
            })
        }
        if (!block.verifyBlockSignature()) {
            _callback("Signature verifyBlockSignature verification failed: block height " + block.height);
            return
        }
        if (parseInt(block.version) > 0) {
            var prevHeight = block.height - 1;
            BlockDb.findBlockIdAtHeight(prevHeight, function(prevBlock) {
                if (!prevBlock) {
                    _callback("Error no prev block find: block height " + block.height)
                } else {
                    if (prevBlock.getId().toString() != block.previousBlockId.toString()) {
                        _callback("Previous block id doesn't match: block height " + block.height);
                        return
                    }
                    if (Utils.sha256(prevBlock.getBytes()).toString("hex") != block.previousBlockHash.toString("hex")) {
                        _callback("Previous block hash doesn't match: block height " + block.height);
                        return
                    }
                    block.verifyGenerationSignature(function(res) {
                        if (!res) {
                            _callback("Signature verifyGenerationSignature verification failed: block height " + block.height)
                        } else {
                            blockTransactionsCheck()
                        }
                    })
                }
            })
        } else {
            blockTransactionsCheck()
        }
    };
    BlockchainProcessor.checkExistTransaction = function(tx, _callback) {
        function txVerify(tx, _callback) {
            if (!tx.verify()) {
                _callback("Signature verification failed for transaction " + transaction.getStringId());
                return
            }
            if (tx.getId().equals(new long(0))) {
                _callback("Invalid transaction id");
                return
            }
            _callback()
        }
        if (tx.blockId.toString() == Genesis.genesisBlockId) {
            txVerify(tx, _callback);
            return
        }
        TransactionDb.hasTransaction(tx.referencedTransactionId.toString(), function(res) {
            if (!res) {
                _callback("Wrong referenced transaction ID");
                return
            } else {
                txVerify(tx, _callback)
            }
        })
    };

    function scan(_callback) {
        logger.info("Scanning blockchain...");
        var blockchain = new Blockchain();
        async.waterfall([
            function(callback) {
                BlockDb.getLastBlock(function(res) {
                    blockchain.setLastBlock(res);
                    logger.info("Last block set on height " + res.height);
                    callback()
                })
            },
            function(callback) {
                TransactionDb.getLastTransaction(function(res) {
                    blockchain.setLastTransaction(res);
                    logger.info("Last Transaction id is " + res.id)
                });
                callback()
            },
            function(callback) {
                var curHeight = 0;
                var lastHeight = blockchain.getLastBlock().height;
                BlockDb.getAllBlockList(function(blocks) {
                    if (!blocks) {
                        throw Error("No block finded at height " + curHeight)
                    }
                    async.eachSeries(blocks, function(blockData, _callback) {
                        var block = new Block(blockData);
                        BlockDb.findRelatedTransactions(block, function(block) {
                            BlockchainProcessor.checkExistBlock(block, function(err) {
                                if (err) {
                                    _callback(err)
                                } else {
                                    block.addConfirmedAndUnconfirmedAmounts();
                                    setImmediate(function() {
                                        _callback()
                                    })
                                }
                            })
                        })
                    }, function(err) {
                        if (err) {
                            callback(err)
                        } else {
                            callback(null)
                        }
                    })
                })
            }
        ], function(err, result) {
            if (err) {
                logger.error("Scan blockchain ERROR", err);
                throw new Error(err)
            }
            if (typeof _callback == "function") {
                _callback()
            }
        });
        logger.info("...Scanning blockchain done")
    }
    BlockchainProcessor.addBlock = function(block, withoutBalanceChange, callback) {
        BlockDb.saveBlock(block, function() {
            if (typeof withoutBalanceChange === "undefined" || !withoutBalanceChange) {
                block.addConfirmedAmounts();
                block.addUnconfirmedFee()
            }
            var blockchain = new Blockchain();
            blockchain.setLastBlock(block);
            block.removeUnconfirmedTxs();
            NodeServer.broadcastNewBlock(block);
            if (typeof callback === "function") {
                callback()
            }
        })
    };
    var Nxtl = function() {};
    Nxtl.getBlockchain = function() {
        return new Blockchain()
    };
    var BlockDb = function() {};
    BlockDb.getLastBlock = function(callback) {
        var q = {
            tbl: "block"
        };
        DB.dbTx.find(q).limit(1).sort({
            height: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    if (docs.length > 0) {
                        var block = new Block(docs[0]);
                        BlockDb.findRelatedTransactions(block, callback)
                    } else {
                        callback(null)
                    }
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    BlockDb.getAllBlockList = function(callback) {
        var q = {
            tbl: "block"
        };
        DB.dbTx.find(q).sort({
            height: 1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err);
                callback(false)
            }
        })
    };
    BlockDb.getLastBlocksList = function(n, callback) {
        var q = {
            tbl: "block"
        };
        DB.dbTx.find(q).limit(n).sort({
            height: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err);
                callback(false)
            }
        })
    };
    BlockDb.findRelatedTransactions = function(block, callback) {
        TransactionDb.findBlockTransactions(block.id.toString(), function(txs) {
            if (txs === null) {
                txs = {
                    count: 0
                }
            }
            block.blockTransactions = txs;
            callback(block)
        })
    };
    BlockDb.findBlock = function(blockId, callback) {
        var q = {
            id: blockId,
            tbl: "block"
        };
        DB.dbTx.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var block = false;
                    if (docs.length > 0) {
                        block = new Block(docs[0]);
                        BlockDb.findRelatedTransactions(block, callback)
                    } else {
                        callback(block)
                    }
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    BlockDb.hasBlock = function(blockId, callback) {
        var q = {
            id: blockId,
            tbl: "block"
        };
        DB.dbTx.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    if (docs.length > 0) {
                        callback(true)
                    } else {
                        callback(false)
                    }
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    BlockDb.findBlockIdAtHeight = function(height, callback) {
        var q = {
            height: height,
            tbl: "block"
        };
        DB.dbTx.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var block = false;
                    if (docs.length > 0) {
                        block = new Block(docs[0]);
                        BlockDb.findRelatedTransactions(block, callback)
                    } else {
                        callback(block)
                    }
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err);
                callback(false)
            }
        })
    };
    BlockDb.findBlockByRs = function(rs, callback) {
        rs.tbl = "block";
        DB.dbTx.find(rs, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var block = false;
                    if (docs.length > 0) {
                        block = new Block(docs[0]);
                        BlockDb.findRelatedTransactions(block, callback)
                    } else {
                        callback(block)
                    }
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    BlockDb.saveBlock = function(block, callback) {
        if (block instanceof Block) {
            var tmpBlock = block.getData();
            tmpBlock.tbl = "block";
            if (block.blockTransactions.count > 0 || block.blockTransactions.length > 0) {
                TransactionDb.deleteTransactions(block.blockTransactions);
                UnconfirmedTransactions.deleteTransactions(block.blockTransactions);
                TransactionDb.saveTransactions(block.blockTransactions)
            }
            DB.dbTx.insert(tmpBlock, function(err, newDoc) {
                if (err) {
                    logger.info("Transaction insert ERROR", err)
                } else {
                    if (typeof callback === "function") {
                        callback()
                    }
                }
            })
        }
    };
    BlockDb.setNextBlockId = function(blockId, nextBlockId, callback) {
        if (blockId === 0) {
            callback()
        } else {
            DB.dbTx.update({
                id: blockId,
                tbl: "block"
            }, {
                $set: {
                    nextBlockId: nextBlockId
                }
            }, {}, function(err, numReplaced) {
                if (err) {
                    logger.info("setNextBlockId error")
                } else {
                    if (typeof callback === "function") {
                        callback(numReplaced)
                    }
                }
            })
        }
    };
    BlockDb.deleteBlockAtHeight = function(height, callback) {
        height = parseInt(height);
        DB.dbTx.remove({
            height: height
        }, {
            multi: true
        }, function(err, numRemoved) {
            if (typeof callback === "function") {
                if (err) {
                    callback(err)
                } else {
                    callback(numRemoved)
                }
            }
        })
    };
    BlockDb.deleteAll = function() {
        DB.dbTx.remove({}, {}, function(err, numRemoved) {
            if (err) {
                logger.info("Error drop DB", err)
            }
        })
    };
    BlockDb.addConfirmation = function(blockId, callback) {
        var q = {
            id: blockId,
            tbl: "block"
        };
        DB.dbTx.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var block = false;
                    if (docs.length > 0) {
                        block = docs[0];
                        var confirmations = block.confirmations + 1;
                        DB.dbTx.update(q, {
                            $set: {
                                confirmations: confirmations
                            }
                        }, {}, function() {
                            callback()
                        })
                    } else {
                        callback(false)
                    }
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err);
                callback(err)
            }
        })
    };
    var PeersDb = function() {};
    PeersDb.addPeer = function(peer, callback) {
        var peerTmp = peer.getData();
        peerTmp.id = peer.host + ":" + peer.port;
        DB.dbPeers.insert(peerTmp, function(err, newDoc) {
            if (err) {
                logger.error("Peer insert ERROR", err)
            } else {
                logger.DBdbg("PeersDb.addPeer ok: " + peerTmp.id);
                if (typeof callback === "function") {
                    callback(newDoc)
                }
            }
        })
    };
    PeersDb.addReplacePeer = function(peer, callback) {
        var peerTmp = peer.getData();
        peerTmp.id = peer.host + ":" + peer.port;
        DB.dbPeers.update({
            id: peerTmp.id
        }, peerTmp, {}, function(err, numReplaced) {
            if (err) {
                logger.error("Peer insert ERROR", err)
            } else {
                logger.DBdbg("PeersDb.addReplacePeer " + peerTmp.id + " numReplaced " + numReplaced);
                if (numReplaced > 0) {
                    if (typeof callback === "function") {
                        callback(numReplaced)
                    }
                } else {
                    PeersDb.addPeer(peer, callback)
                }
            }
        })
    };
    PeersDb.getAllPeersList = function(callback) {
        var q = {};
        PeersDb.getPeersListByRs(q, callback)
    };
    PeersDb.getPeersListByRs = function(q, callback) {
        DB.dbPeers.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.error("Find Peer ERROR!!!", err)
            }
        })
    };
    var TransactionDb = function() {};
    TransactionDb.getLastTransaction = function(callback) {
        var q = {
            tbl: "transaction"
        };
        DB.dbTx.find(q).limit(1).sort({
            timestamp: -1,
            id: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transaction = false;
                    if (docs.length > 0) {
                        transaction = new Transaction(docs[0])
                    }
                    callback(transaction)
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    TransactionDb.getLastTransactions = function(n, callback) {
        var q = {
            tbl: "transaction"
        };
        DB.dbTx.find(q).limit(n).sort({
            timestamp: -1,
            id: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    if (docs.length > 0) {
                        callback(docs)
                    } else {
                        callback(null)
                    }
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    TransactionDb.getUnconfirmedTransactions = function(callback) {
        var q = {
            blockId: null,
            tbl: "transaction"
        };
        DB.dbTx.find(q).sort({
            timestamp: 1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transactionsMap = false;
                    if (docs.length > 0) {
                        transactionsMap = {
                            count: 0
                        };
                        for (var i in docs) {
                            transactionsMap[docs[i].id] = new Transaction(docs[i]);
                            transactionsMap.count++
                        }
                    }
                    callback(transactionsMap)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    TransactionDb.getMyTransactions = function(_accountId, callback) {
        var q = {
            $or: [{
                recipientId: _accountId
            }, {
                senderId: _accountId
            }],
            $not: {
                blockId: null
            },
            type: TransactionType.TYPE_PAYMENT,
            tbl: "transaction"
        };
        TransactionDb.getTransactionsListByRs(q, callback)
    };
    TransactionDb.getMyAllTransactions = function(_accountId, callback) {
        var q = {
            $or: [{
                recipientId: _accountId
            }, {
                senderId: _accountId
            }],
            type: TransactionType.TYPE_PAYMENT,
            tbl: "transaction"
        };
        DB.dbTx.find(q).sort({
            timestamp: -1,
            id: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    TransactionDb.getAllTransactionsList = function(callback) {
        var q = {
            tbl: "transaction"
        };
        TransactionDb.getTransactionsListByRs(q, callback)
    };
    TransactionDb.getTransactionsListByRs = function(q, callback) {
        DB.dbTx.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    TransactionDb.findTransaction = function(transactionId, callback) {
        var q = {
            id: transactionId,
            tbl: "transaction"
        };
        DB.dbTx.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transaction = false;
                    if (docs.length > 0) {
                        transaction = new Transaction(docs[0])
                    }
                    callback(transaction)
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    TransactionDb.hasTransaction = function(transactionId, callback) {
        var q = {
            id: transactionId,
            tbl: "transaction"
        };
        DB.dbTx.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    if (docs.length > 0) {
                        callback(true)
                    } else {
                        callback(false)
                    }
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    TransactionDb.findTransactionByRs = function(rs, callback) {
        rs.tbl = "transaction";
        DB.dbTx.find(rs, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transaction = false;
                    if (docs.length > 0) {
                        transaction = new Transaction(docs[0])
                    }
                    callback(transaction)
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    TransactionDb.findBlockTransactions = function(blockId, callback) {
        var q = {
            blockId: blockId,
            tbl: "transaction"
        };
        DB.dbTx.find(q).sort({
            timestamp: 1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transactionsMap = {
                        count: 0
                    };
                    if (docs.length > 0) {
                        for (var i in docs) {
                            transactionsMap[docs[i].id] = new Transaction(docs[i]);
                            transactionsMap.count++
                        }
                    }
                    callback(transactionsMap)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err);
                callback(null)
            }
        })
    };
    TransactionDb.saveTransactions = function(transactions) {
        for (var i in transactions) {
            if (!transactions.hasOwnProperty(i) || i == "count") {
                continue
            }
            var transaction = transactions[i];
            var transactionTmp = transaction.getData();
            transactionTmp.tbl = "transaction";
            DB.dbTx.insert(transactionTmp, function(err, newDoc) {
                if (err) {
                    logger.info("Transaction insert ERROR", err)
                }
            })
        }
    };
    TransactionDb.deleteTransactions = function(transactions) {
        for (var i in transactions) {
            if (!transactions.hasOwnProperty(i) || i == "count") {
                continue
            }
            var transaction = transactions[i];
            var transactionTmp = transaction.getData();
            transactionTmp.tbl = "transaction";
            DB.dbTx.remove({
                id: transactionTmp.id,
                tbl: "transaction"
            }, {}, function(err, numRemoved) {})
        }
    };
    var Block = function(data) {
        if ("object" !== typeof data) {
            data = {}
        }
        this.version = data.version || null;
        this.timestamp = data.timestamp || null;
        this.previousBlockId = data.previousBlockId || 0;
        this.generatorPublicKey = data.generatorPublicKey || Config.NULL_HASH;
        this.previousBlockHash = data.previousBlockHash || Config.NULL_HASH;
        this.totalAmount = data.totalAmount || 0;
        this.totalFee = data.totalFee || 0;
        this.payloadLength = data.payloadLength || 0;
        this.generationSignature = data.generationSignature || Config.NULL_HASH;
        this.payloadHash = data.payloadHash || Config.NULL_HASH;
        this.transactionIds = data.transactionIds || [];
        this.blockTransactions = data.blockTransactions || [];
        this.blockSignature = data.blockSignature || null;
        this.cumulativeDifficulty = data.cumulativeDifficulty || new bigint("0");
        this.baseTarget = data.baseTarget || Config.INITIAL_BASE_TARGET;
        this.nextBlockId = data.nextBlockId || null;
        this.height = typeof data.height !== "undefined" ? data.height : -1;
        this.id = data.id || null;
        this.stringId = data.stringId || null;
        this.generatorId = data.generatorId || null;
        this.confirmations = data.confirmations || 0;
        if (typeof this.previousBlockId == "string") {
            this.previousBlockId = Utils.stringToLong(this.previousBlockId)
        }
        if (typeof this.generatorPublicKey == "string") {
            this.generatorPublicKey = new Buffer(this.generatorPublicKey, "hex")
        }
        if (typeof this.previousBlockHash == "string") {
            this.previousBlockHash = new Buffer(this.previousBlockHash, "hex")
        }
        if (typeof this.generationSignature == "string") {
            this.generationSignature = new Buffer(this.generationSignature, "hex")
        }
        if (typeof this.payloadHash == "string") {
            this.payloadHash = new Buffer(this.payloadHash, "hex")
        }
        if (typeof this.blockSignature == "string") {
            this.blockSignature = new Buffer(this.blockSignature, "hex")
        }
        if (typeof this.nextBlockId == "string") {
            this.nextBlockId = Utils.stringToLong(this.nextBlockId)
        }
        if (typeof this.id == "string") {
            this.id = Utils.stringToLong(this.id)
        }
        if (typeof this.cumulativeDifficulty == "string") {
            this.cumulativeDifficulty = new bigint(this.cumulativeDifficulty)
        }
    };
    Block.prototype.getData = function() {
        return {
            version: this.version,
            timestamp: this.timestamp,
            previousBlockId: this.previousBlockId.toString(),
            generatorPublicKey: this.generatorPublicKey.toString("hex"),
            previousBlockHash: this.previousBlockHash.toString("hex"),
            totalAmount: this.totalAmount,
            totalFee: this.totalFee,
            payloadLength: this.payloadLength,
            generationSignature: this.generationSignature.toString("hex"),
            payloadHash: this.payloadHash.toString("hex"),
            blockSignature: this.blockSignature ? this.blockSignature.toString("hex") : null,
            cumulativeDifficulty: this.cumulativeDifficulty.toString(),
            baseTarget: this.baseTarget,
            nextBlockId: this.nextBlockId ? this.nextBlockId.toString() : null,
            height: this.height,
            id: this.id.toString(),
            stringId: this.stringId,
            generatorId: this.generatorId ? this.generatorId.toString() : null,
            confirmations: this.confirmations
        }
    };
    Block.prototype.getDataWithTransactions = function() {
        var data = this.getData();
        data.blockTransactions = this.getTransactionsDataAsArray().slice(0);
        return data
    };
    Block.prototype.getId = function() {
        if (this.id == null) {
            if (this.blockSignature == null) {
                throw new Error("Block is not signed yet")
            }
            var hash = curve.sha256(this.getBytes());
            this.id = Utils.bufferToLongBE(hash);
            this.stringId = this.id.toString()
        }
        return this.id
    };
    Block.prototype.getStringId = function() {
        if (this.stringId == null) {
            this.getId();
            if (this.stringId == null) {
                this.stringId = this.id.toString()
            }
        }
        return this.stringId
    };
    Block.prototype.getGeneratorId = function() {
        if (this.generatorId == null) {
            this.generatorId = Account.getId(this.generatorPublicKey)
        }
        return this.generatorId
    };
    Block.prototype.hashCode = function() {
        var id = this.getId();
        id.toString("16")
    };
    Block.prototype.getBytes = function() {
        var self = this;
        var obj = {
            version: this.version,
            timestamp: this.timestamp,
            previousBlockId: this.previousBlockId.toString(),
            blockTransactions: this.blockTransactions.count,
            totalAmount: this.totalAmount,
            totalFee: this.totalFee,
            payloadLength: this.payloadLength,
            payloadHash: this.payloadHash,
            generatorPublicKey: this.generatorPublicKey,
            generationSignature: this.generationSignature
        };
        if (this.version > 1) {
            obj.previousBlockHash = this.previousBlockHash
        }
        return JSON.stringify(obj)
    };
    Block.prototype.sign = function(secretPhrase) {
        if (this.blockSignature != null) {
            return this.blockSignature
        }
        if (!secretPhrase instanceof Buffer) {
            secretPhrase = curve.sha256(secretPhrase)
        }
        this.blockSignature = nxtCrypto.sign(curve.sha256(this.getBytes()).toString("hex"), secretPhrase.toString("hex"));
        return this.blockSignature
    };
    Block.prototype.verifyBlockSignature = function() {
        var account = Account.addOrGetAccount(this.getGeneratorId());
        if (account == null) {
            return false
        }
        var data = curve.sha256(this.getBytes());
        var isSignVerified = nxtCrypto.verify(this.blockSignature.toString("hex"), data.toString("hex"), this.generatorPublicKey.toString("hex"));
        return isSignVerified && account.setOrVerify(this.generatorPublicKey, this.height)
    };
    Block.prototype.verifyGenerationSignature = function(__callback) {
        var self = this;
        BlockDb.findBlock(this.previousBlockId.toString(), function(previousBlock) {
            try {
                if (previousBlock == null && self.height != 0) {
                    __callback(false)
                }
                var isSignVerified = nxtCrypto.verify(self.generationSignature.toString("hex"), previousBlock.generationSignature.toString("hex"), self.generatorPublicKey.toString("hex"));
                if (self.version == 1 && !isSignVerified) {
                    __callback(false)
                }
                var account = Account.getAccount(self.getGeneratorId());
                __callback(true)
            } catch (e) {
                logger.error("Error verifying block generation signature", e);
                __callback(false)
            }
        })
    };
    Block.prototype.apply = function() {
        var generatorAccount = Account.addOrGetAccount(this.getGeneratorId());
        generatorAccount.apply(this.generatorPublicKey, this.height);
        generatorAccount.addToBalanceAndUnconfirmedBalance(Utils.roundTo5Float(this.totalFee))
    };
    Block.prototype.setPrevious = function(previousBlock) {
        if (previousBlock != null) {
            if (!previousBlock.getId() == this.previousBlockId) {
                throw new Error("Previous block id doesn't match")
            }
            this.height = previousBlock.height + 1;
            this.calculateBaseTarget(previousBlock)
        } else {
            this.height = 0
        }
        for (var transactionId in this.blockTransactions) {
            var transaction;
            if (this.blockTransactions.hasOwnProperty(transactionId) && transactionId != "count") {
                transaction = this.blockTransactions[transactionId];
                transaction.setBlock(this)
            }
        }
    };
    Block.prototype.calculateBaseTarget = function(previousBlock) {
        if (this.getId() == Genesis.genesisBlockId && this.previousBlockId == null) {
            this.baseTarget = Config.INITIAL_BASE_TARGET;
            this.cumulativeDifficulty = 0
        } else {
            var curBaseTarget = previousBlock.baseTarget;
            var newBaseTarget = new bigint(curBaseTarget).multiply(this.timestamp - previousBlock.timestamp).divide(60);
            newBaseTarget = Utils.bigIntToLongBE(newBaseTarget);
            if (newBaseTarget < 0 || newBaseTarget > Config.MAX_BASE_TARGET) {
                newBaseTarget = Config.MAX_BASE_TARGET
            }
            if (newBaseTarget < curBaseTarget / 2) {
                newBaseTarget = curBaseTarget / 2
            }
            if (newBaseTarget == 0) {
                newBaseTarget = 1
            }
            var twofoldCurBaseTarget = curBaseTarget * 2;
            if (twofoldCurBaseTarget < 0) {
                twofoldCurBaseTarget = Config.MAX_BASE_TARGET
            }
            if (newBaseTarget > twofoldCurBaseTarget) {
                newBaseTarget = twofoldCurBaseTarget
            }
            this.baseTarget = newBaseTarget;
            this.cumulativeDifficulty = previousBlock.cumulativeDifficulty.add(Config.two64.divide(this.baseTarget.toString()))
        }
    };
    Block.prototype.getTransactionIds = function() {
        if (!this.transactionIds || this.transactionIds.length == 0) {
            this.transactionIds = [];
            for (var transactionId in this.blockTransactions) {
                var transaction;
                if (this.blockTransactions.hasOwnProperty(transactionId) && transactionId != "count") {
                    transaction = this.blockTransactions[transactionId];
                    this.transactionIds.push(transaction.id.toString())
                }
            }
        }
        return this.transactionIds
    };
    Block.prototype.getTransactionsAsArray = function() {
        var transactionsArr = [];
        for (var transactionId in this.blockTransactions) {
            if (this.blockTransactions.hasOwnProperty(transactionId) && transactionId != "count") {
                transactionsArr.push(this.blockTransactions[transactionId])
            }
        }
        return transactionsArr
    };
    Block.prototype.getTransactionsDataAsArray = function() {
        var transactionsArr = [];
        for (var transactionId in this.blockTransactions) {
            if (this.blockTransactions.hasOwnProperty(transactionId) && transactionId != "count") {
                transactionsArr.push(this.blockTransactions[transactionId].getData())
            }
        }
        return transactionsArr
    };
    Block.prototype.addConfirmedAndUnconfirmedAmounts = function() {
        for (var transactionId in this.blockTransactions) {
            if (this.blockTransactions.hasOwnProperty(transactionId) && transactionId != "count") {
                var tx = this.blockTransactions[transactionId];
                var recipientAccount = Account.addOrGetAccount(tx.recipientId.toString());
                recipientAccount.addToBalanceAndUnconfirmedBalance(Utils.roundTo5Float(Utils.nullToNumber(tx.amount)));
                var senderAccount = Account.addOrGetAccount(tx.getSenderId().toString());
                senderAccount.addToBalanceAndUnconfirmedBalance(-Utils.roundTo5Float(Utils.nullToNumber(tx.amount)) - Utils.roundTo5Float(tx.fee))
            }
        }
        var _genereatorAccount = Account.addOrGetAccount(this.generatorId.toString());
        _genereatorAccount.addToBalanceAndUnconfirmedBalance(Utils.roundTo5Float(this.totalFee))
    };
    Block.prototype.addConfirmedAmounts = function() {
        for (var transactionId in this.blockTransactions) {
            if (this.blockTransactions.hasOwnProperty(transactionId) && transactionId != "count") {
                var tx = this.blockTransactions[transactionId];
                var recipientAccount = Account.addOrGetAccount(tx.recipientId.toString());
                recipientAccount.addToBalance(Utils.roundTo5Float(Utils.nullToNumber(tx.amount)));
                var senderAccount = Account.addOrGetAccount(tx.senderId.toString());
                senderAccount.addToBalance(-Utils.roundTo5Float(Utils.nullToNumber(tx.amount)) - Utils.roundTo5Float(tx.fee))
            }
        }
        var _genereatorAccount = Account.addOrGetAccount(this.generatorId.toString());
        _genereatorAccount.addToBalance(Utils.roundTo5Float(this.totalFee))
    };
    Block.prototype.addUnconfirmedFee = function() {
        var _genereatorAccount = Account.addOrGetAccount(this.generatorId.toString());
        _genereatorAccount.addToUnconfirmedBalance(Utils.roundTo5Float(this.totalFee))
    };
    Block.prototype.addUnconfirmedAmounts = function() {
        for (var transactionId in this.blockTransactions) {
            if (this.blockTransactions.hasOwnProperty(transactionId) && transactionId != "count") {
                var tx = this.blockTransactions[transactionId];
                var recipientAccount = Account.addOrGetAccount(tx.recipientId.toString());
                recipientAccount.addToUnconfirmedBalance(Utils.roundTo5Float(Utils.nullToNumber(tx.amount)));
                var senderAccount = Account.addOrGetAccount(tx.senderId.toString());
                senderAccount.addToUnconfirmedBalance(-Utils.roundTo5Float(Utils.nullToNumber(tx.amount)) - Utils.roundTo5Float(tx.fee))
            }
        }
        var _genereatorAccount = Account.addOrGetAccount(this.generatorId.toString());
        _genereatorAccount.addToUnconfirmedBalance(Utils.roundTo5Float(this.totalFee))
    };
    Block.prototype.removeUnconfirmedTxs = function(callback) {
        var txArr = this.getTransactionsAsArray();
        UnconfirmedTransactions.deleteTransactions(txArr, callback)
    };
    var Transaction = function(data) {
        if ("object" !== typeof data) {
            data = {}
        }
        this.deadline = data.deadline || null;
        this.senderPublicKey = data.senderPublicKey || null;
        this.recipientId = data.recipientId || null;
        this.amount = data.amount || 0;
        this.fee = data.fee || null;
        this.referencedTransactionId = data.referencedTransactionId || null;
        this.type = typeof data.type !== "undefined" ? data.type : null;
        this.height = data.height || null;
        this.blockId = data.blockId || null;
        this.block = data.block || null;
        this.signature = data.signature || null;
        this.timestamp = data.timestamp || null;
        this.attachment = data.attachment || null;
        this.id = data.id || null;
        this.null = null;
        this.senderId = data.senderId || null;
        this.hash = data.hash || null;
        this.confirmations = data.confirmations || 0;
        if (typeof this.senderPublicKey == "string") {
            this.senderPublicKey = new Buffer(this.senderPublicKey, "hex")
        }
        if (typeof this.recipientId == "string") {
            this.recipientId = Utils.stringToLong(this.recipientId)
        }
        if (typeof this.referencedTransactionId == "string") {
            this.referencedTransactionId = Utils.stringToLong(this.referencedTransactionId)
        }
        if (typeof this.type == "string" || typeof this.type == "number") {
            this.type = TransactionType.findTransactionType(this.type, 0)
        }
        if (typeof this.blockId == "string") {
            this.blockId = Utils.stringToLong(this.blockId)
        }
        if (typeof this.signature == "string") {
            this.signature = new Buffer(this.signature, "hex")
        }
        if (typeof this.id == "string") {
            this.id = Utils.stringToLong(this.id)
        }
        if (typeof this.senderId == "string") {
            this.senderId = Utils.stringToLong(this.senderId)
        }
        if (typeof this.hash == "string") {
            this.hash = new Buffer(this.hash, "hex")
        }
    };
    Transaction.prototype.getData = function() {
        var type = null;
        if (this.type) {
            type = this.type.getType()
        }
        return {
            deadline: this.deadline,
            senderPublicKey: this.senderPublicKey.toString("hex"),
            recipientId: this.recipientId.toString(),
            amount: this.amount,
            fee: this.fee,
            referencedTransactionId: this.referencedTransactionId ? this.referencedTransactionId.toString() : this.referencedTransactionId,
            type: type,
            height: this.height,
            blockId: this.blockId ? this.blockId.toString() : null,
            signature: this.signature ? this.signature.toString("hex") : null,
            timestamp: this.timestamp,
            attachment: this.attachment,
            id: this.getId().toString(),
            "null": null,
            senderId: this.getSenderId().toString(),
            hash: this.hash.toString("hex"),
            confirmations: this.confirmations
        }
    };
    Transaction.prototype.getBlock = function() {
        if (this.block == null) {
            var self = this;
            BlockDb.findBlock(self.blockId, function(block) {
                self.block = block
            })
        }
        return this.block
    };
    Transaction.prototype.setBlock = function(block) {
        this.block = block;
        this.blockId = block.getId();
        this.height = block.height
    };
    Transaction.prototype.getExpiration = function() {
        return this.timestamp + this.deadline * 60 * 60 * 1e3
    };
    Transaction.prototype.getId = function() {
        if (this.id == null) {
            if (this.signature == null) {
                return false
            }
            this.hash = curve.sha256(this.getBytes());
            this.id = Utils.bufferToLongBE(this.hash);
            this.stringId = this.id.toString()
        }
        return this.id
    };
    Transaction.prototype.getStringId = function() {
        if (this.stringId == null) {
            this.getId();
            if (this.stringId == null) {
                this.stringId = this.id.toString()
            }
        }
        return this.stringId
    };
    Transaction.prototype.getSenderId = function() {
        if (this.senderId == null) {
            this.senderId = Account.getId(this.senderPublicKey)
        }
        return this.senderId
    };
    Transaction.prototype.getStringId = function() {
        if (this.stringId == null) {
            this.getId();
            if (this.stringId == null) {
                this.stringId = this.id.toString()
            }
        }
        return this.stringId
    };
    Transaction.prototype.compareTo = function(o) {
        if (this.height < o.height) {
            return -1
        }
        if (this.height > o.height) {
            return 1
        }
        if (Utils.nullToNumber(this.fee) * o.getSize() > Utils.nullToNumber(o.fee) * this.getSize() || this.fee === null && o.fee !== null) {
            return -1
        }
        if (Utils.nullToNumber(this.fee) * o.getSize() < Utils.nullToNumber(o.fee) * this.getSize()) {
            return 1
        }
        if (this.timestamp < o.timestamp) {
            return -1
        }
        if (this.timestamp > o.timestamp) {
            return 1
        }
        return 0
    };
    Transaction.prototype.TRANSACTION_BYTES_LENGTH = 1 + 1 + 4 + 2 + 32 + 8 + 4 + 4 + 8 + 64;
    Transaction.prototype.getSize = function() {
        return this.TRANSACTION_BYTES_LENGTH + (this.attachment == null ? 0 : this.attachment.getSize())
    };
    Transaction.prototype.getBytes = function() {
        var self = this;
        var obj = {
            type: self.type.getType(),
            subtype: self.type.getSubtype(),
            timestamp: self.timestamp,
            deadline: self.deadline,
            senderPublicKey: self.senderPublicKey,
            recipientId: self.recipientId,
            amount: self.amount,
            fee: self.fee,
            referencedTransactionId: self.referencedTransactionId
        };
        if (self.attachment != null) {
            obj.attachment = self.attachment.getBytes()
        }
        return JSON.stringify(obj)
    };
    Transaction.prototype.sign = function(secretPhrase) {
        if (this.signature != null) {
            return this.signature
        }
        console.log("Transaction sign", curve.sha256(this.getBytes()));
        this.signature = nxtCrypto.sign(curve.sha256(this.getBytes()).toString("hex"), secretPhrase.toString("hex"));
        try {
            while (!this.verify()) {
                this.timestamp++;
                this.signature = nxtCrypto.sign(curve.sha256(this.getBytes()).toString("hex"), secretPhrase.toString("hex"))
            }
            return this.signature
        } catch (e) {
            console.log("Error signing transaction", e);
            return false
        }
    };
    Transaction.prototype.getHash = function() {
        if (this.hash == null) {
            var data = this.getBytes();
            var hash = curve.sha256(data);
            this.hash = hash.toString("hex")
        }
        return this.hash
    };
    Transaction.prototype.hashCode = function() {
        var id = this.getId();
        id.toString("16")
    };
    Transaction.prototype.verify = function() {
        var account = Account.addOrGetAccount(this.getSenderId().toString());
        if (account == null) {
            return false
        }
        var data = curve.sha256(this.getBytes());
        console.log("Transaction data virify", data);
        var isSignVerified = nxtCrypto.verify(this.signature.toString("hex"), data.toString("hex"), this.senderPublicKey.toString("hex"));
        return isSignVerified && account.setOrVerify(this.senderPublicKey, this.height)
    };
    Transaction.prototype.applyUnconfirmed = function() {
        var senderAccount = Account.getAccount(this.getSenderId());
        if (senderAccount == null) {
            return false
        }
        return this.type.applyUnconfirmed(this, senderAccount)
    };
    Transaction.prototype.apply = function() {
        var senderAccount = Account.getAccount(this.getSenderId());
        senderAccount.apply(this.senderPublicKey, this.height);
        var recipientAccount = Account.getAccount(this.recipientId);
        if (recipientAccount == null) {
            recipientAccount = Account.addOrGetAccount(recipientId)
        }
        this.type.apply(this, senderAccount, recipientAccount)
    };
    Transaction.prototype.undoUnconfirmed = function() {
        var senderAccount = Account.getAccount(this.getSenderId());
        this.type.undoUnconfirmed(this, senderAccount)
    };
    Transaction.prototype.undo = function() {
        var senderAccount = Account.getAccount(this.senderId);
        senderAccount.undo(this.height);
        var recipientAccount = Account.getAccount(this.recipientId);
        this.type.undo(this, senderAccount, recipientAccount)
    };
    Transaction.prototype.updateTotals = function(accumulatedAmounts, accumulatedAssetQuantities) {
        var senderId = this.getSenderId();
        var accumulatedAmount = accumulatedAmounts === null || typeof accumulatedAmounts[senderId.toString()] === "undefined" ? null : accumulatedAmounts[senderId.toString()];
        if (accumulatedAmount == null) {
            accumulatedAmount = 0
        }
        accumulatedAmounts[senderId.toString()] = Utils.roundTo5Float(accumulatedAmount) + (Utils.roundTo5Float(this.amount) + Utils.roundTo5Float(this.fee));
        this.type.updateTotals(this, accumulatedAmounts, accumulatedAssetQuantities, accumulatedAmount)
    };
    Transaction.prototype.isDuplicate = function(duplicates) {
        return this.type.isDuplicate(this, duplicates)
    };
    Transaction.prototype.validateAttachment = function() {
        this.type.validateAttachment(this)
    };
    var UnconfirmedTransactions = function() {};
    UnconfirmedTransactions.addConfirmation = function(id, callback) {
        var q = {
            id: id,
            tbl: "transaction"
        };
        db.update(q, {
            $inc: {
                confirmations: 1
            }
        }, {}, function() {
            if (typeof callback === "function") {
                callback()
            }
        })
    };
    UnconfirmedTransactions.getLastTransaction = function(callback) {
        var q = {
            tbl: "transaction"
        };
        db.find(q).limit(1).sort({
            timestamp: -1,
            id: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transaction = false;
                    if (docs.length > 0) {
                        transaction = new Transaction(docs[0])
                    }
                    callback(transaction)
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.getLastTransactions = function(n, callback) {
        var q = {
            tbl: "transaction"
        };
        db.find(q).limit(n).sort({
            timestamp: -1,
            id: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    if (docs.length > 0) {
                        callback(docs)
                    } else {
                        callback(null)
                    }
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.getAll = function(callback) {
        var q = {
            blockId: null,
            tbl: "transaction"
        };
        db.find(q).sort({
            timestamp: 1,
            id: 1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transactionsMap = false;
                    if (docs.length > 0) {
                        transactionsMap = {
                            count: 0
                        };
                        for (var i in docs) {
                            transactionsMap[docs[i].id] = new Transaction(docs[i]);
                            transactionsMap.count++
                        }
                    }
                    callback(transactionsMap)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.getMyTransactions = function(_accountId, callback) {
        var q = {
            $or: [{
                recipientId: _accountId
            }, {
                senderId: _accountId
            }],
            $not: {
                blockId: null
            },
            type: TransactionType.TYPE_PAYMENT,
            tbl: "transaction"
        };
        UnconfirmedTransactions.getTransactionsListByRs(q, callback)
    };
    UnconfirmedTransactions.getMyAllTransactions = function(_accountId, callback) {
        var q = {
            $or: [{
                recipientId: _accountId
            }, {
                senderId: _accountId
            }],
            type: TransactionType.TYPE_PAYMENT,
            tbl: "transaction"
        };
        db.find(q).sort({
            timestamp: -1,
            id: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.getAllTransactionsList = function(callback) {
        var q = {
            tbl: "transaction"
        };
        db.find(q).sort({
            timestamp: -1,
            id: -1
        }).exec(function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.getTransactionsListByRs = function(q, callback) {
        db.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(docs)
                }
            } else {
                logger.info("Find BlockTransactions ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.findTransaction = function(transactionId, callback) {
        var q = {
            id: transactionId,
            tbl: "transaction"
        };
        db.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transaction = false;
                    if (docs.length > 0) {
                        transaction = new Transaction(docs[0])
                    }
                    callback(transaction)
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.hasTransaction = function(transactionId, callback) {
        var q = {
            id: transactionId,
            tbl: "transaction"
        };
        db.find(q, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    if (docs.length > 0) {
                        callback(true)
                    } else {
                        callback(false)
                    }
                }
            } else {
                logger.error("Find transaction ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.findTransactionByRs = function(rs, callback) {
        rs.tbl = "transaction";
        db.find(rs, function(err, docs) {
            if (!err) {
                if (typeof callback === "function") {
                    var transaction = false;
                    if (docs.length > 0) {
                        transaction = new Transaction(docs[0])
                    }
                    callback(transaction)
                }
            } else {
                logger.info("Find transaction ERROR!!!", err)
            }
        })
    };
    UnconfirmedTransactions.addTransactions = function(transactions) {
        for (var i in transactions) {
            if (!transactions.hasOwnProperty(i) || i == "count") {
                continue
            }
            var transaction = transactions[i];
            var transactionTmp = transaction.getData();
            transactionTmp.tbl = "transaction";
            db.insert(transactionTmp, function(err, newDoc) {
                if (err) {
                    logger.info("Transaction insert ERROR", err)
                }
            })
        }
    };
    UnconfirmedTransactions.addTransactionsData = function(transactionsData, callback) {
        for (var i in transactionsData) {
            if (!transactionsData.hasOwnProperty(i) || i == "count") {
                continue
            }
            var transactionTmp = transactionsData[i];
            transactionTmp.tbl = "transaction";
            db.insert(transactionTmp, function(err, newDoc) {
                if (err) {
                    logger.info("Transaction insert ERROR", err);
                    callback(false)
                } else {
                    callback(true)
                }
            })
        }
    };
    UnconfirmedTransactions.addTransactionsData = function(transactionsData, callback) {
        for (var i in transactionsData) {
            if (!transactionsData.hasOwnProperty(i) || i == "count") {
                continue
            }
            var transactionTmp = transactionsData[i];
            transactionTmp.tbl = "transaction";
            db.insert(transactionTmp, function(err, newDoc) {
                if (err) {
                    logger.info("Transaction insert ERROR", err);
                    callback(false)
                } else {
                    callback(true)
                }
            })
        }
    };
    UnconfirmedTransactions.deleteTransactions = function(transactions, callback) {
        for (var i in transactions) {
            if (!transactions.hasOwnProperty(i) || i == "count") {
                continue
            }
            var transaction = transactions[i];
            var transactionTmp = transaction.getData();
            transactionTmp.tbl = "transaction";
            db.remove({
                id: transactionTmp.id,
                tbl: "transaction"
            }, {}, function(err, numRemoved) {
                if (typeof callback === "function") {
                    callback()
                }
            })
        }
    };

    function Connection(socket, peer) {
        events.EventEmitter.call(this);
        this.socket = socket;
        this.peer = peer;
        this.buffer = "";
        this.active = false;
        this.inbound = !!socket.server;
        this.setupHandlers()
    }
    util.inherits(Connection, events.EventEmitter);
    Connection.prototype.commands = {
        VERSION: "version",
        READY: "verack",
        ADDRESSES: "addr",
        GET_ADDRESSES: "getaddr",
        GET_TRANSACTIONS: "gettrans",
        GET_LAST_TRANSACTION: "getlasttrans",
        LAST_TRANSACTION: "lasttrans",
        GET_NEXT_BLOCK: "getnextblock",
        NEW_TRANSACTION: "newtrans",
        BLOCK: "block",
        GET_PREV_TRANSACTION: "getprevtrans",
        BROADCAST: "broadcast",
        PEER_STATUS: "peerstatus",
        BROADCAST_GENERATE_BLOCK: "bcgenblk",
        STOP_GENERATE_BLOCK: "stopgenblk",
        ANSWER_ON_GENERATE_BLOCK: "answergenblk",
        NEW_BLOCK: "newblk",
        VERIFIED_PEER: "verifpeer",
        VERIFIED_PEER_RESPONSE: "verifpeerresp",
        PEER_NOT_VERIFIED: "peernotverif",
        UNCONFIRMED_TRANSACTIONS: "unconftrans"
    };
    Connection.prototype.toString = function() {
        return "Connection: '" + this.peer.toString() + "',\n" + "inbound(isServer): " + this.inbound
    };
    Connection.prototype.setupHandlers = function() {
        this.socket.addListener("connect", this.handleConnect.bind(this));
        this.socket.addListener("error", this.handleError.bind(this));
        this.socket.addListener("end", this.handleDisconnect.bind(this));
        this.socket.addListener("data", function(data) {
            var _data = data.toString(),
                DATA_LENGTH_SHOW = 31,
                dataMsg = _data.length > DATA_LENGTH_SHOW ? _data.substring(0, DATA_LENGTH_SHOW) + "..." : _data;
            logger.netdbg("[" + this.peer + "] " + "Received " + dataMsg + " bytes of data: " + Buffer.byteLength(_data, "utf8"));
            logger.netdbg(this.toString())
        }.bind(this));
        this.socket.addListener("data", this.handleData.bind(this))
    };
    Connection.prototype.handleConnect = function() {
        if (!this.inbound) {
            this.sendVersion()
        }
        this.emit("connect", {
            conn: this,
            socket: this.socket,
            peer: this.peer
        })
    };
    Connection.prototype.handleError = function(err) {
        if (err.errno == 110 || err.errno == "ETIMEDOUT") {
            logger.netdbg("Connection timed out for " + this.peer)
        } else if (err.errno == 111 || err.errno == "ECONNREFUSED") {
            logger.netdbg("Connection refused for " + this.peer)
        } else {
            logger.warn("Connection with " + this.peer + " " + err.toString())
        }
        this.emit("error", {
            conn: this,
            socket: this.socket,
            peer: this.peer,
            err: err
        })
    };
    Connection.prototype.handleDisconnect = function() {
        this.emit("disconnect", {
            conn: this,
            socket: this.socket,
            peer: this.peer
        })
    };
    Connection.prototype.handleData = function(data) {
        this.buffer += data.toString();
        if (this.buffer.indexOf("___|||___") == -1) {
            return
        }
        var datas = this.buffer.split("___|||___");
        this.buffer = datas[datas.length - 1];
        delete datas[datas.length - 1];
        var self = this;
        datas.forEach(function(_data) {
            var message;
            try {
                message = self.parseMessage(_data)
            } catch (e) {
                logger.error("Error while parsing message " + _data + "\n from [" + self.peer + "]\nmessage: " + e.toString())
            }
            if (message) {
                self.peer.uploadBytes += Buffer.byteLength(_data, "utf8");
                self.handleMessage(message)
            }
        })
    };
    Connection.prototype.handleMessage = function(message) {
        logger.netdbg("Handle Message (message command: '" + message.command + "')\n" + this.toString());
        switch (message.command) {
            case Connection.prototype.commands.VERSION:
                if (this.inbound) {
                    this.sendVersion()
                }
                this.sendMessage(Connection.prototype.commands.READY);
                break;
            case Connection.prototype.commands.READY:
                this.active = true;
                break
        }
        this.emit(message.command, {
            conn: this,
            socket: this.socket,
            peer: this.peer,
            message: message
        })
    };
    Connection.prototype.parseMessage = function(message) {
        message = JSON.parse(message);
        var notExist = true;
        for (var command in Connection.prototype.commands) {
            if (Connection.prototype.commands[command] == message.command) {
                notExist = false;
                break
            }
        }
        if (notExist) {
            logger.error("Connection.parseMessage(): Command not implemented", {
                cmd: message.command
            });
            return null
        }
        switch (message.command) {
            case Connection.prototype.commands.ADDRESSES:
                message.addrs = message.data.addrs;
                message.netStatus = message.data.netStatus;
                break
        }
        return message
    };
    Connection.prototype.sendVersion = function() {
        var data = {
            version: 1,
            timestamp: new Date().getTime()
        };
        this.sendMessage(Connection.prototype.commands.VERSION, data)
    };
    Connection.prototype.sendGetAddr = function() {
        this.sendMessage(Connection.prototype.commands.GET_ADDRESSES)
    };
    Connection.prototype.sendLastTransaction = function(data) {
        this.sendMessage(Connection.prototype.commands.LAST_TRANSACTION, data)
    };
    Connection.prototype.sendGetLastTransaction = function() {
        this.sendMessage(Connection.prototype.commands.GET_LAST_TRANSACTION)
    };
    Connection.prototype.sendGetNextBlock = function(data) {
        this.sendMessage(Connection.prototype.commands.GET_NEXT_BLOCK, data)
    };
    Connection.prototype.sendNewTransaction = function(data) {
        this.sendMessage(Connection.prototype.commands.NEW_TRANSACTION, data)
    };
    Connection.prototype.sendBlock = function(data) {
        this.sendMessage(Connection.prototype.commands.BLOCK, data)
    };
    Connection.prototype.sendUnconfirmedTransactions = function(data) {
        this.sendMessage(Connection.prototype.commands.UNCONFIRMED_TRANSACTIONS, data)
    };
    Connection.prototype.sendMessage = function(command, data) {
        try {
            if (typeof data === "undefined") {
                data = null
            }
            var message = {
                command: command,
                data: data
            };
            logger.netdbg("[" + this.peer + "] Sending message " + message.command);
            message = JSON.stringify(message) + "___|||___";
            this.peer.downloadBytes += Buffer.byteLength(message, "utf8");
            this.socket.write(message)
        } catch (err) {
            logger.error("Error while sending message to peer " + this.peer + ": " + (err.stack ? err.stack : err.toString()))
        }
    };
    Connection.prototype.broadcast = function(data) {
        this.sendMessage(Connection.prototype.commands.broadcast, data)
    };

    function Peer(host, port, services) {
        if ("string" === typeof host) {
            if (host.indexOf(":") && !port) {
                var parts = host.split(":");
                host = parts[0];
                port = parts[1]
            }
            this.host = host;
            this.port = +port || 8333
        } else if (host instanceof Peer) {
            this.host = host.host;
            this.port = host.port
        } else if (Buffer.isBuffer(host)) {
            this.host = host.toString("hex").match(/(.{1,4})/g).join(":");
            this.port = +port || 8333
        } else if (host instanceof Object) {
            return this.fromData(host)
        } else {
            throw new Error("Could not instantiate peer, invalid parameter type: " + typeof host)
        }
        this.services = services ? services : null;
        this.MAX_LOST_CONNECTION = 10;
        this.lostConnection = 0;
        this.connection = null;
        this.uploadBytes = 0;
        this.downloadBytes = 0;
        this.timestamp = null;
        this.lastSeen = null;
        this.status = Peer.prototype.statuses.DISABLE;
        this.oldStatus = null
    }
    Peer.prototype.statuses = {
        ACTIVE: "active",
        CHECK: "check",
        PENDING: "pending",
        SYNCED: "synced",
        DISABLE: "disable"
    };
    Peer.prototype.getData = function() {
        return {
            port: this.port,
            host: this.host,
            uploadBytes: this.uploadBytes,
            downloadBytes: this.downloadBytes,
            services: this.services,
            MAX_LOST_CONNECTION: this.MAX_LOST_CONNECTION,
            lostConnection: this.lostConnection,
            status: this.status
        }
    };
    Peer.prototype.fromData = function(data) {
        this.port = data.port || null;
        this.host = data.host || null;
        this.uploadBytes = data.uploadBytes || 0;
        this.downloadBytes = data.downloadBytes || 0;
        this.services = data.services || null;
        this.MAX_LOST_CONNECTION = data.MAX_LOST_CONNECTION || 0;
        this.lostConnection = data.lostConnection || 0;
        this.status = data.status || Peer.prototype.statuses.DISABLE;
        return this
    };
    Peer.prototype.isLost = function() {
        if (this.lostConnection >= this.MAX_LOST_CONNECTION) {
            this.status = Peer.prototype.statuses.DISABLE;
            return true
        }
        return false
    };
    Peer.prototype.connectionLost = function() {
        if (this.lostConnection === 0) {
            this.updateLastSeen()
        }
        this.lostConnection++;
        this.connection = null;
        this.timestamp = null;
        this.oldStatus = this.status;
        this.status = Peer.prototype.statuses.PENDING
    };
    Peer.prototype.updateLastSeen = function() {
        this.lastSeen = new Date().getTime()
    };
    Peer.prototype.updateUpTime = function() {
        this.timestamp = new Date().getTime()
    };
    Peer.prototype.getConnection = function() {
        if (this.connection === null) {
            this.updateUpTime();
            this.connection = net.createConnection(this.port, this.host);
            this.status = this.oldStatus === null ? Peer.prototype.statuses.DISABLE : this.oldStatus
        }
        return this.connection
    };
    Peer.prototype.getHostAsBuffer = function() {
        return new Buffer(this.host.split("."))
    };
    Peer.prototype.toString = function() {
        return this.host + ":" + this.port
    };
    Peer.prototype.toBuffer = function() {
        return ""
    };

    function PeerProcessor(node) {
        events.EventEmitter.call(this);
        var self = this;
        var client, server;
        this.node = node;
        this.synced = false;
        this.enabled = false;
        this.timer = null;
        this.peers = new Map();
        this.forcePeers = new Map();
        this.connections = new Map();
        this.isConnected = false;
        this.peerDiscovery = true;
        this.interval = 5e3;
        this.minConnections = 8;
        this.MAX_ADDR_COUNT = 1e3
    }
    util.inherits(PeerProcessor, events.EventEmitter);
    PeerProcessor.prototype.getActivePeersArr = function() {
        var activePeers = [];
        this.connections.map(function(connection, key) {
            activePeers.push(connection.peer)
        }.bind(this));
        return activePeers
    };
    PeerProcessor.prototype.run = function() {
        this.enabled = true;
        var initialPeers = Config.starPeers;
        var forcePeers = Config.starPeers;
        initialPeers.forEach(function(peer) {
            if ("string" !== typeof peer) {
                throw new Error("PeerManager.enable(): Invalid Configuration for initial" + "peers.")
            }
            this.addPeer(peer)
        }.bind(this));
        forcePeers.forEach(function(peer) {
            if ("string" !== typeof peer) {
                throw new Error("PeerManager.enable(): Invalid Configuration for initial" + "peers.")
            }
            var _peer = new Peer(peer);
            this.forcePeers.set(_peer.toString(), _peer)
        }.bind(this));
        this.server = net.createServer(function(socketConn) {
            try {
                var peer = new Peer(socketConn.remoteAddress, Config.peerPort);
                this.addConnection(socketConn, peer)
            } catch (e) {
                logger.error("Add peer errror: " + JSON.stringify(e))
            }
        }.bind(this));
        logger.netdbg("this.server.listen on port: " + Config.peerPort);
        this.server.listen(Config.peerPort);
        if (!this.timer) {
            this.pingStatus()
        }
    };
    PeerProcessor.prototype.addPeer = function(peer, port) {
        if (peer instanceof Peer) {
            logger.netdbg("Add peer: " + peer.toString());
            var defStatus = Peer.prototype.statuses.DISABLE;
            if (!this.peers.has(peer.toString())) {
                PeersDb.addReplacePeer(peer);
                this.peers.set(peer.toString(), peer)
            } else if (peer.status != defStatus) {
                var _peer;
                if (this.connections.has(peer.toString()) && (_peer = this.connections.get(peer.toString()).peer).status == defStatus) {
                    _peer.status = peer.status
                }
                if ((_peer = this.peers.get(peer.toString())).status == defStatus) {
                    _peer.status = peer.status
                }
            }
        } else if ("string" == typeof peer) {
            this.addPeer(new Peer(peer, port))
        } else {
            logger.log("error", "Node.addPeer(): Invalid value provided for peer", {
                val: peer
            });
            throw "Node.addPeer(): Invalid value provided for peer."
        }
    };
    PeerProcessor.prototype.connectTo = function(peer) {
        logger.netdbg("Connecting to peer " + peer);
        try {
            return this.addConnection(peer.getConnection(), peer)
        } catch (e) {
            logger.error("Error creating connection", e);
            return null
        }
    };
    PeerProcessor.prototype.addConnection = function(socketConn, peer) {
        var conn = new Connection(socketConn, peer);
        this.connections.set(conn.peer.toString(), conn);
        this.node.addConnection(conn);
        conn.addListener("connect", this.handleConnect.bind(this));
        conn.addListener("error", this.handleError.bind(this));
        conn.addListener("disconnect", this.handleDisconnect.bind(this));
        conn.addListener(Connection.prototype.commands.VERSION, this.handleVersion.bind(this));
        conn.addListener(Connection.prototype.commands.READY, this.handleReady.bind(this));
        conn.addListener(Connection.prototype.commands.ADDRESSES, this.handleAddr.bind(this));
        conn.addListener(Connection.prototype.commands.GET_ADDRESSES, this.handleGetAddr.bind(this));
        conn.addListener(Connection.prototype.commands.GET_LAST_TRANSACTION, this.handleGetLastTransaction.bind(this));
        conn.addListener(Connection.prototype.commands.LAST_TRANSACTION, this.handleLastTransaction.bind(this));
        conn.addListener(Connection.prototype.commands.BLOCK, this.handleBlock.bind(this));
        conn.addListener(Connection.prototype.commands.GET_NEXT_BLOCK, this.handleGetNextBlock.bind(this));
        conn.addListener(Connection.prototype.commands.NEW_TRANSACTION, this.handleNewTransaction.bind(this));
        conn.addListener(Connection.prototype.commands.BROADCAST, this.handleBroadcast.bind(this));
        conn.addListener(Connection.prototype.commands.PEER_STATUS, this.handlePeerStatus.bind(this));
        conn.addListener(Connection.prototype.commands.UNCONFIRMED_TRANSACTIONS, this.handleUnconfirmedTransactions.bind(this));
        return conn
    };
    PeerProcessor.prototype.getActiveConnections = function() {
        return this.connections
    };
    PeerProcessor.prototype.handleConnect = function(e) {
        logger.netdbg("Handle Connect\n" + e.conn.toString());
        this.addPeer(e.peer)
    };
    PeerProcessor.prototype.handleVersion = function(e) {
        logger.netdbg("Handle Version\n" + e.conn.toString());
        if (!e.conn.inbound) {
            this.addPeer(e.peer)
        }
        if (this.peerDiscovery) {
            e.conn.sendGetAddr();
            e.conn.getaddr = true
        }
    };
    PeerProcessor.prototype.handleReady = function(e) {
        logger.netdbg("Handle Ready\n" + e.conn.toString());
        this.addPeer(e.peer);
        this.emit("connect", {
            pm: this,
            conn: e.conn,
            socket: e.socket,
            peer: e.peer
        });
        if (this.isConnected == false) {
            this.emit("netConnected");
            this.isConnected = true
        }
    };
    PeerProcessor.prototype.handleAddr = function(e) {
        if (!this.peerDiscovery) {
            return
        }
        logger.netdbg("Handle Addr\n" + e.conn.toString());
        if (typeof e.message.addrs !== "undefined") {
            var peer, defStatus = Peer.prototype.statuses.DISABLE;
            e.peer.status = e.message.netStatus || defStatus;
            e.message.addrs.forEach(function(addr) {
                try {
                    peer = new Peer(addr.ip, addr.port, addr.services);
                    peer.status = addr.status || defStatus;
                    this.addPeer(peer)
                } catch (e) {
                    logger.warn("Invalid addr received: " + e.message)
                }
            }.bind(this));
            if (e.message.addrs.length < 1e3) {
                e.conn.getaddr = false
            }
        }
    };
    PeerProcessor.prototype.handleGetAddr = function(e) {
        logger.netdbg("Handle GetAddr\n", e.conn.toString());
        var addressesCount = this.peers.length;
        if (addressesCount > this.MAX_ADDR_COUNT) {
            addressesCount = this.MAX_ADDR_COUNT
        }
        var peers = this.peers.values();
        var addrs = [],
            connection = null,
            status, defStatus = Peer.prototype.statuses.DISABLE;
        for (var i = 0; i < addressesCount; i++) {
            if (e.peer.host === peers[i].host) {
                continue
            }
            connection = this.connections.get(peers[i].toString());
            status = defStatus;
            if (typeof connection !== "undefined") {
                status = connection.peer.status
            }
            addrs.push({
                services: peers[i].services,
                ip: peers[i].host,
                port: peers[i].port,
                status: status
            })
        }
        e.conn.sendMessage(Connection.prototype.commands.ADDRESSES, {
            addrs: addrs,
            netStatus: this.node.netStatus
        })
    };
    PeerProcessor.prototype.handleError = function(e) {
        logger.netdbg("Handle Error\n" + e.conn.toString());
        this.handleDisconnect.apply(this, [].slice.call(arguments))
    };
    PeerProcessor.prototype.handleDisconnect = function(e) {
        logger.netdbg("Handle Disconnect\n" + e.conn.toString());
        logger.netdbg("Disconnected from peer " + e.peer);
        var key = e.peer.toString();
        if (this.connections.has(key)) {
            this.connections.delete(key)
        }
        if (this.peers.has(key)) {
            this.peers.get(key).connectionLost()
        }
        e.peer.connectionLost();
        if (!this.connections.length) {
            this.emit("netDisconnected");
            this.isConnected = false
        }
    };
    PeerProcessor.prototype.pingStatus = function pingStatus() {
        if (!this.enabled) {
            return
        }
        this.checkStatus();
        this.clearLostPeers();
        this.timer = setTimeout(this.pingStatus.bind(this), this.interval)
    };
    PeerProcessor.prototype.checkStatus = function checkStatus() {
        if (this.forcePeers.length) {
            this.forcePeers.map(function(peer, key) {
                if (!this.connections.has(key)) {
                    this.connectTo(peer)
                }
            }.bind(this))
        }
        var connectablePeers = [];
        this.peers.map(function(peer, key) {
            if (!this.connections.has(key)) {
                connectablePeers.push(peer)
            }
        }.bind(this));
        while (this.connections.length < this.minConnections && connectablePeers.length) {
            var peer = connectablePeers.splice(Math.random() * connectablePeers.length, 1);
            this.connectTo(peer[0])
        }
    };
    PeerProcessor.prototype.clearLostPeers = function() {
        var lostPeers = this.peers.filter(function(peer, key) {
            if (!this.connections.has(key)) {
                peer.connectionLost()
            }
            if (peer.isLost()) {
                logger.netdbg("Removed peer " + key);
                return true
            }
            return false
        }.bind(this));
        if (lostPeers.length) {
            this.peers.deleteEach(lostPeers.keys())
        }
    };
    PeerProcessor.prototype.sendTransaction = function(trans) {
        if (this.forcePeers.length) {
            var connectionCount = 0;
            var connection = null;
            this.forcePeers.map(function(peer, key) {
                if (this.connections.has(key)) {
                    connection = this.connections.get(key);
                    connection.sendNewTransaction();
                    connectionCount++
                }
            }.bind(this));
            if (connectionCount === 0) {
                logger.netdbg("Error: no connection to force peers.")
            }
        } else {
            logger.netdbg("Error: forcePeers is empty.")
        }
    };
    PeerProcessor.prototype.syncTransaction = function() {
        if (this.forcePeers.length) {
            var connectionCount = 0;
            var connection = null;
            this.forcePeers.some(function(peer, key) {
                if (this.connections.has(key)) {
                    connection = this.connections.get(key);
                    connection.sendGetLastTransaction();
                    connectionCount++
                }
            }.bind(this));
            if (connectionCount === 0) {
                logger.netdbg("Error: no connection to force peers.")
            }
        } else {
            logger.netdbg("Error: forcePeers is empty.")
        }
    };
    PeerProcessor.prototype.handleLastTransaction = function(e) {
        logger.netdbg("Handle Last Transaction \n", e.conn.toString());
        if (!e.message.data) {
            logger.error('Error: no data for "handleLastTransaction"');
            return
        }
        var blockchain = new Blockchain();
        var transaction = new Transaction(e.message.data);
        var result = blockchain.getLastTransaction().compareTo(transaction);
        var nextHeight = blockchain.getLastBlock().height + 1;
        e.conn.sendGetNextBlock(nextHeight)
    };
    PeerProcessor.prototype.handleGetLastTransaction = function(e) {
        logger.netdbg("Handle Get Last Transaction \n", e.conn.toString());
        var blockchain = new Blockchain();
        e.conn.sendLastTransaction(blockchain.getLastTransaction().getData())
    };
    var blockSyncBuffer = new Map();
    var syncBlockRunning = false;
    var unconfTxsSyncBuffer = new Map();
    var syncUnconfTxsRunning = false;
    PeerProcessor.prototype.handleBlock = function(e) {
        var self = this;
        logger.netdbg("Handle Block\n", e.conn.toString());
        var blockData = e.message.data;
        var tyransactionsArr = e.message.data.blockTransactions;
        txArr = {
            count: 0
        };
        tyransactionsArr.forEach(function(transactionData) {
            var tx = new Transaction(transactionData);
            txArr[tx.getId().toString()] = tx;
            txArr.count += 1
        });
        blockData.blockTransactions = txArr;
        var block = new Block(blockData);
        var broadcasted = false;
        if (typeof e.message.data.broadcasted !== "undefined" && e.message.data.broadcasted) {
            broadcasted = true
        }
        blockSyncBuffer.set(blockData.id.toString(), {
            block: block,
            conn: e.conn,
            broadcasted: broadcasted
        });
        this.processSyncBlock()
    };
    PeerProcessor.prototype.processSyncBlock = function() {
        var self = this;
        if (syncBlockRunning) {
            return
        }
        syncBlockRunning = true;
        var blockSyncBufferArr = blockSyncBuffer.toArray();
        async.eachSeries(blockSyncBufferArr, function(data, callback) {
            var conn = data.conn;
            var block = data.block;
            var broadcasted = data.broadcasted;
            blockSyncBuffer.delete(block.id.toString());
            BlockDb.hasBlock(block.id.toString(), function(exist) {
                if (!exist) {
                    BlockchainProcessor.pushBlock(block, function() {
                        logger.warn("block pushed OK id:" + block.id.toString());
                        BlockDb.setNextBlockId(block.previousBlockId.toString(), block.getId().toString(), function() {
                            block.confirmations = block.confirmations + 1;
                            BlockchainProcessor.addBlock(block, false, function() {
                                logger.warn("Block saved OK id:" + block.id.toString());
                                if (!broadcasted) {
                                    block.addUnconfirmedAmounts();
                                    conn.sendGetLastTransaction()
                                }
                                callback()
                            })
                        })
                    })
                } else {
                    logger.info("Block exist, continue on height: " + block.height);
                    BlockDb.addConfirmation(block.id.toString(), function() {
                        callback()
                    })
                }
            })
        }, function(err, callback) {
            if (err) {
                logger.error(err)
            }
            syncBlockRunning = false
        })
    };
    PeerProcessor.prototype.handleGetNextBlock = function(e) {
        logger.netdbg("Handle Get Next Block \n", e.conn.toString());
        var height = e.message.data || null;
        var blockchain = new Blockchain();
        if (parseInt(height) > blockchain.getLastBlock().height) {
            UnconfirmedTransactions.getAllTransactionsList(function(unconfTxsData) {
                e.conn.sendUnconfirmedTransactions(unconfTxsData)
            })
        } else if (height) {
            BlockDb.findBlockIdAtHeight(height, function(block) {
                if (block) {
                    logger.transdbg("Send block at heifght - " + e.height);
                    e.conn.sendBlock(block.getDataWithTransactions())
                } else {
                    logger.warn("No block at heifght - " + e.height)
                }
            })
        } else {
            e.conn.sendBlock({})
        }
    };
    var newTxsSyncBuffer = [];
    var newTxsRunning = false;
    PeerProcessor.prototype.handleNewTransaction = function(e) {
        var transactionData = e.message.data || null;
        logger.netdbg("Handle New Transaction \n", e.conn.toString());
        if (!transactionData) {
            return
        }
        newTxsSyncBuffer.push(transactionData);
        this.processNewTransaction()
    };
    PeerProcessor.prototype.processNewTransaction = function() {
        if (newTxsRunning) {
            return
        }
        newTxsRunning = true;
        async.eachSeries(newTxsSyncBuffer, function(transactionData, callback) {
            var transaction = new Transaction(transactionData);
            var transactionProcessor = new TransactionProcessor();
            transactionProcessor.verifiyTransaction(transaction, function(err) {
                if (err) {
                    logger.warn(err);
                    callback()
                } else {
                    transactionProcessor.addTransactionOrConfirmation(transaction, false);
                    callback()
                }
            })
        }, function(err) {
            newTxsRunning = false;
            newTxsSyncBuffer = []
        })
    };
    PeerProcessor.prototype.handleUnconfirmedTransactions = function(e) {
        var self = this;
        logger.netdbg("Handle Unconfirmed Transactions\n", e.conn.toString());
        var unconfTxsData = e.message.data || null;
        if (syncUnconfTxsRunning) {
            return
        }
        syncUnconfTxsRunning = true;
        if (unconfTxsData.length) {
            async.eachSeries(unconfTxsData, function(txData, callback) {
                UnconfirmedTransactions.hasTransaction(txData.id, function(exist) {
                    if (!exist) {
                        var transaction = new Transaction(txData);
                        var transactionProcessor = new TransactionProcessor();
                        transactionProcessor.verifiyTransaction(transaction, unconfTxsData, function(err) {
                            if (err) {
                                logger.warn(err);
                                callback(err)
                            } else {
                                transactionProcessor.addTransactionOrConfirmation(transaction, true);
                                callback()
                            }
                        })
                    }
                })
            }, function(err) {
                if (err) {
                    logger.error("ERROR PeerProcessor.prototype.handleUnconfirmedTransactions " + err);
                    syncUnconfTxsRunning = false
                } else {
                    syncUnconfTxsRunning = false;
                    self.synced = true;
                    self.node.setState("synced");
                    self.node.broadcastPeerStatus(Peer.prototype.statuses.SYNCED)
                }
            })
        } else {
            this.synced = true;
            self.node.setState("synced");
            this.node.broadcastPeerStatus(Peer.prototype.statuses.SYNCED)
        }
    };
    PeerProcessor.prototype.handleBroadcast = function(e) {
        logger.netdbg("Handle Broadcast\n", e.conn.toString())
    };
    PeerProcessor.prototype.handlePeerStatus = function(e) {
        logger.netdbg("Handle Peer Status\n", e.conn.toString());
        e.peer.status = e.message.data.status || Peer.prototype.statuses.DISABLE
    };
    var AccountHandlers = function() {};
    AccountHandlers.loginAccount = function loginAccount(response, params, request) {
        if (!NodeServer.peerProcessor.synced) {
            ResponseHelper.end500(response, "Not synced yet!!")
        }
        var user = new User(params.userId);
        var accountId = user.loginAccount(params.walletKey);
        var publicKey = user.getPublicKey().toString("hex");
        logger.info("user.getPublicKey()", publicKey);
        Account.insertNewAccount({
            accountId: accountId,
            publicKey: publicKey
        }, request);
        Account.addOrGetAccount(accountId.toString());
        Account.setCurrentAccount({
            accountId: accountId,
            accountSecret: user.getSecretWord().toString("hex")
        });
        Account.getAccounts(accountId, function(accounts) {
            TransactionDb.getLastTransactions(10, function(recentTransactions) {
                TransactionDb.getMyAllTransactions(accountId, function(myTransactions) {
                    UnconfirmedTransactions.getMyAllTransactions(accountId, function(unconfTrans) {
                        myTransactions = unconfTrans.concat(myTransactions);
                        ResponseHelper.end200Text(response, JSON.stringify({
                            accountId: accountId,
                            accountSecret: user.getSecretWord().toString("hex"),
                            accountTypes: accounts,
                            recentTransactions: recentTransactions || [],
                            myTransactions: myTransactions
                        }))
                    })
                })
            })
        })
    };
    AccountHandlers.getAccountTransactions = function getAccountTransactions(response, params, request) {
        TransactionDb.getLastTransactions(10, function(recentTransactions) {
            TransactionDb.getMyAllTransactions(params.accountId, function(myTransactions) {
                UnconfirmedTransactions.getMyAllTransactions(params.accountId, function(unconfTrans) {
                    UnconfirmedTransactions.getAllTransactionsList(function(unconfTransAll) {
                        myTransactions = unconfTrans.concat(myTransactions);
                        recentTransactions = unconfTransAll.concat(recentTransactions);
                        ResponseHelper.end200Text(response, JSON.stringify({
                            recentTransactions: recentTransactions || [],
                            myTransactions: myTransactions
                        }))
                    })
                })
            })
        })
    };
    AccountHandlers.getMyTransactions = function getMyTransactions(response, params, request) {
        TransactionDb.getMyAllTransactions(params.accountId, function(myTransactions) {
            ResponseHelper.end200Text(response, JSON.stringify({
                myTransactions: myTransactions
            }))
        })
    };
    AccountHandlers.logoutAccount = function logoutAccount(response) {
        Account.currentAccount = null;
        NodeServer.broadcastPeerStatus(Peer.prototype.statuses.DISABLE);
        NodeServer.startCheckSynced();
        ResponseHelper.end200Text(response, JSON.stringify({
            logout: true
        }))
    };
    AccountHandlers.getAccountByHash = function getAccountByHash(response, params, request) {
        if (typeof params.secret === "undefined") {
            ResponseHelper.end403(response);
            return
        }
        var account = Account.createFromSecretWord(params.secret);
        Account.getAccounts(account.id, function(accountNums) {
            account.accountTypes = accountNums;
            TransactionDb.getLastTransactions(10, function(recentTransactions) {
                account.recentTransactions = recentTransactions;
                ResponseHelper.end200Text(response, JSON.stringify(account))
            })
        })
    };
    AccountHandlers.showAccounts = function showAccounts(response, params) {
        if (params.key === Config.serverKey) {
            Account.getRegisteredAccounts(function(err, docs) {
                response.writeHead(200, {
                    "Content-Type": "text/html"
                });
                var tplStr = swig.renderFile(appDir + "/frontend/showAcc.html", {
                    accounts: docs
                });
                response.end(tplStr)
            })
        } else {
            ResponseHelper.end403(response)
        }
    };
    AccountHandlers.getAccounts = function getAccounts(response, params, request) {
        ResponseHelper.end200Text(response, JSON.stringify(Account.accounts))
    };
    var BlockHandlers = function() {};
    BlockHandlers.showAllBlocks = function(response, params, request) {
        BlockDb.getLastBlocksList(25, function(res) {
            var data = [];
            async.eachSeries(res, function(blockData, _callback) {
                var block = new Block(blockData);
                BlockDb.findRelatedTransactions(block, function(block) {
                    data.push(block.getDataWithTransactions());
                    _callback()
                })
            }, function(err) {
                if (err) {
                    ResponseHelper.end500(response, err)
                } else {
                    response.writeHead(200, {
                        "Content-Type": "text/plain"
                    });
                    response.write(JSON.stringify(data));
                    response.end()
                }
            })
        })
    };
    var BlockchainexplHandlers = function() {};
    BlockchainexplHandlers.index = function(response, params, request) {
        response.writeHead(200, {
            "Content-Type": "text/html"
        });
        var tplStr = swig.renderFile(appDir + "/frontend/blockchainExpl.html", {});
        response.end(tplStr)
    };
    var PeersHandlers = function() {};
    PeersHandlers.getAllPeersText = function(response, params, request) {
        var peersObj = NodeServer.peerProcessor;
        var peers = peersObj.getActivePeersArr();
        var activePeersStr = "";
        for (var id in peers) {
            if (peers.hasOwnProperty(id)) {
                activePeersStr += JSON.stringify(peers[id].getData()) + "\n<br/>"
            }
        }
        PeersDb.getAllPeersList(function(archivedPeers) {
            ResponseHelper.end200Text(response, "Active peers:\n" + JSON.stringify(activePeersStr + "\n<br/><br/><br/>\n\nArchived Peers:\n" + JSON.stringify(archivedPeers)))
        })
    };
    PeersHandlers.getAllPeersJSON = function(response, params, request) {
        var peersObj = NodeServer.peerProcessor;
        var peersData = [];
        var peers = peersObj.getActivePeersArr();
        for (var id in peers) {
            if (peers.hasOwnProperty(id)) {
                peersData.push(peers[id].getData())
            }
        }
        ResponseHelper.end200Text(response, JSON.stringify(peersData))
    };
    RequestHandlers = function() {};
    swig.setDefaults({
        locals: {
            host: Config.host,
            port: Config.port
        }
    });
    RequestHandlers.start = function(response) {
        response.writeHead(200, {
            "Content-Type": "text/html"
        });
        var tplStr = swig.renderFile(appDir + "/frontend/index.html", {});
        response.end(tplStr)
    };
    RequestHandlers.broadcast = function(response, params) {
        response.writeHead(200, {
            "Content-Type": "text/plain"
        });
        NodeServer.broadcast({
            message: params.message
        });
        response.write("sending");
        response.end()
    };
    RequestHandlers.checkLoading = function(response, prams) {
        ResponseHelper.end200Text(response, JSON.stringify({
            loading: !NodeServer.peerProcessor.synced
        }))
    };
    var TransactionHandlers = function() {};
    TransactionHandlers.showAllTransactions = function(response, params, request) {
        UnconfirmedTransactions.getAllTransactionsList(function(res) {
            response.writeHead(200, {
                "Content-Type": "text/plain"
            });
            response.write(JSON.stringify(res));
            response.end()
        })
    };
    TransactionHandlers.validateTransactionParams = function(response, params) {
        var result = {
            err: false,
            result: null
        };
        logger.info(params);
        var errs = {};
        if (typeof params.recipientId == "undefined" || params.recipientId == "") {
            errs.recipientId = true
        }
        var account = Account.createFromSecretWord(params.accountSecret);
        if (account.id.toString() == params.recipientId) {
            errs.recipientId = true
        }
        logger.info("parseFloat(params.amount)", parseFloat(params.amount));
        var regEx = /^([0-9])*\.{0,1}([0-9])*$/g;
        if (typeof params.amount == "undefined" || params.amount == 0 || parseFloat(params.amount) < 1e-5 || !params.amount.match(regEx)) {
            errs.amount = true
        }
        if (typeof params.accountSecret == "undefined" || params.accountSecret == "") {
            errs.accountSecret = true
        }
        if (!Utils.isEmptyObj(errs)) {
            result.err = errs;
            response.writeHead(200, {
                "Content-Type": "text/plain"
            });
            response.write(JSON.stringify(result));
            response.end();
            return false
        }
        return result
    };
    TransactionHandlers.validateTransactionAmount = function(response, params, result, account, accounts) {
        if (accounts == null || params.amount + params.fee > parseFloat(accounts.nxtlAccount.unconfirmedAmount)) {
            logger.info("Not Enough money accId:", account.accountId);
            result.err = {
                amount: true
            };
            response.writeHead(200, {
                "Content-Type": "text/plain"
            });
            response.write(JSON.stringify(result));
            response.end();
            return false
        }
        return true
    };
    TransactionHandlers.validateTransaction = function(response, params, request) {
        var result = TransactionHandlers.validateTransactionParams(response, params);
        if (result === false) {
            return false
        }
        params.amount = Utils.roundTo5Float(params.amount);
        params.fee = Utils.roundTo5Float(params.amount * .0035) > 1e-5 ? Utils.roundTo5Float(params.amount * .0035) : 1e-5;
        var account = Account.createFromSecretWord(params.accountSecret);
        Account.getAccounts(account.id, function(accounts) {
            if (TransactionHandlers.validateTransactionAmount(response, params, result, account, accounts) === false) {
                return false
            } else {
                result.result = true;
                response.writeHead(200, {
                    "Content-Type": "text/plain"
                });
                response.write(JSON.stringify(result));
                response.end();
                return true
            }
        });
        return true
    };
    TransactionHandlers.addTransaction = function(response, params, request) {
        var result = TransactionHandlers.validateTransactionParams(response, params);
        if (result === false) {
            return false
        }
        var blockchain = new Blockchain();
        params.amount = Utils.roundTo5Float(params.amount);
        params.fee = Utils.roundTo5Float(params.amount * .0035) > 1e-5 ? Utils.roundTo5Float(params.amount * .0035) : 1e-5;
        var referencedTransactionId = blockchain.getLastTransaction();
        var account = Account.createFromSecretWord(params.accountSecret);
        Account.getAccounts(account.id, function(accounts) {
            if (TransactionHandlers.validateTransactionAmount(response, params, result, account, accounts) === false) {
                return false
            }
            var transaction = new Transaction({
                type: TransactionType.Payment,
                timestamp: new Date().getTime(),
                deadline: params.deadline,
                senderPublicKey: account.publicKey,
                recipientId: params.recipientId,
                amount: params.amount,
                fee: params.fee,
                referencedTransactionId: referencedTransactionId.id,
                signature: null
            });
            transaction.sign(params.accountSecret);
            var transactionProcessor = new TransactionProcessor();
            transactionProcessor.addTransactionOrConfirmation(transaction);
            result.result = {
                transactionId: transaction.getId().toString()
            };
            response.writeHead(200, {
                "Content-Type": "text/plain"
            });
            response.write(JSON.stringify(result));
            response.end();
            return true
        });
        return true
    };
    TransactionHandlers.getLastTransaction = function(response, params, request) {
        TransactionDb.getLastTransaction(function(res) {
            response.writeHead(200, {
                "Content-Type": "text/plain"
            });
            response.write(JSON.stringify(res));
            response.end()
        })
    };
    TransactionHandlers.getTransactionByUser = function(response, params, request) {
        TransactionDb.getMyAllTransactions(params.userId, function(res) {
            response.writeHead(200, {
                "Content-Type": "text/plain"
            });
            response.write(JSON.stringify(res));
            response.end()
        })
    };
    var handle = {};
    handle["/"] = RequestHandlers.start;
    handle["/app/checkLoading"] = RequestHandlers.checkLoading;
    handle["/account/login"] = AccountHandlers.loginAccount;
    handle["/account/getAccountTransactions"] = AccountHandlers.getAccountTransactions;
    handle["/account/getMyTransactions"] = AccountHandlers.getMyTransactions;
    handle["/account/getAccounts"] = AccountHandlers.getAccounts;
    handle["/account/getAccountByHash"] = AccountHandlers.getAccountByHash;
    handle["/account/logoutAccount"] = AccountHandlers.logoutAccount;
    handle["/showAccounts"] = AccountHandlers.showAccounts;
    handle["/block/showAllBlocks"] = BlockHandlers.showAllBlocks;
    handle["/transaction/showAllTransactions"] = TransactionHandlers.showAllTransactions;
    handle["/transaction/addTransaction"] = TransactionHandlers.addTransaction;
    handle["/transaction/validateTransaction"] = TransactionHandlers.validateTransaction;
    handle["/transaction/getLastTransaction"] = TransactionHandlers.getLastTransaction;
    handle["/transaction/getTransactionByUser"] = TransactionHandlers.getTransactionByUser;
    handle["/blockchainexpl/"] = BlockchainexplHandlers.index;
    handle["/getAllPeersText"] = PeersHandlers.getAllPeersText;
    handle["/getAllPeersJSON"] = PeersHandlers.getAllPeersJSON;
    handle["/api/broadcast"] = RequestHandlers.broadcast;
    var PostRequestProcess = function() {};
    PostRequestProcess.processPost = function processPost(request, response, callback) {
        var queryData = "";
        if (typeof callback !== "function") return null;
        if (request.method == "POST") {
            request.on("data", function(data) {
                queryData += data;
                if (queryData.length > 1e6) {
                    queryData = "";
                    response.writeHead(413, {
                        "Content-Type": "text/plain"
                    }).end();
                    request.connection.destroy()
                }
            });
            request.on("end", function() {
                response.post = querystring.parse(queryData);
                callback()
            })
        } else {
            response.writeHead(405, {
                "Content-Type": "text/plain"
            });
            response.end()
        }
    };
    var ResponseHelper = function() {};
    ResponseHelper.end404 = function(response) {
        response.writeHead(404, {
            "Content-Type": "text/plain"
        });
        response.write("404 Not found");
        response.end()
    };
    ResponseHelper.end403 = function(response) {
        response.writeHead(403, {
            "Content-Type": "text/plain"
        });
        response.write("You can't access this area.");
        response.end()
    };
    ResponseHelper.end500 = function(response, err) {
        response.writeHead(500, {
            "Content-Type": "text/plain"
        });
        response.write(err + "\n");
        response.end()
    };
    ResponseHelper.end200Text = function(response, data) {
        response.writeHead(200, {
            "Content-Type": "text/plain"
        });
        response.write(data);
        response.end()
    };
    var Router = function() {};
    Router.route = function route(handle, request, response) {
        var urlParts = url.parse(request.url, true);
        var pathname = urlParts.pathname;
        if (typeof handle[pathname] === "function") {
            if (request.method === "POST") {
                PostRequestProcess.processPost(request, response, function() {
                    handle[pathname](response, response.post, request)
                })
            } else {
                handle[pathname](response, urlParts.query, request)
            }
        } else {
            var staticFilesPath = appDir + "/frontend/";
            var filename = path.join(staticFilesPath, pathname);
            logger.info("filename", filename);
            fs.exists(filename, function(exists) {
                if (!exists) {
                    logger.info("No request handler found for " + pathname);
                    ResponseHelper.end404(response);
                    return
                }
                if (fs.statSync(filename).isDirectory()) {
                    ResponseHelper.end403(response);
                    return
                }
                fs.readFile(filename, "binary", function(err, file) {
                    if (err) {
                        ResponseHelper.end500(response, err);
                        return
                    }
                    var type = mime.lookup(filename);
                    response.writeHead(200, {
                        "Content-Type": type
                    });
                    response.write(file, "binary");
                    response.end()
                })
            })
        }
    };
    var User = function(_userId) {
        var userId;
        var secretWord;
        var publicKey;
        var isInactive = false;
        userId = _userId;
        this.getId = function() {
            return userId
        };
        this.getPublicKey = function() {
            return publicKey
        };
        this.getSecretWord = function() {
            return secretWord
        };
        this.getIsInactive = function() {
            return isInactive
        };
        this.setIsInactive = function(_isInactive) {
            if (typeof _isInactive === "boolean") isInactive = _isInactive
        };
        this.loginAccount = function(_secretWord) {
            secretWord = curve.sha256(_secretWord);
            var nxtlPublicKeyHex = nxtCrypto.getPublicKey(secretWord.toString("hex"));
            console.log("nxtCrypto", nxtlPublicKeyHex);
            publicKey = new Buffer(nxtlPublicKeyHex, "hex");
            return Account.getId(publicKey)
        };
        this.logoutAccount = function() {
            secretWord = null
        }
    };
    var Account = function(id) {
        this.id = id;
        this.height = 0;
        this.publicKey = null;
        this.keyHeight = null;
        this.balance = 0;
        this.unconfirmedBalance = 0;
        this.guaranteedBalances = {};
        this.assetBalances = {};
        this.unconfirmedAssetBalances = {};
        this.accountTypes = {}
    };
    Account.accounts = {};
    Account.currentAccount = null;
    Account.setCurrentAccount = function(account) {
        if (Account.currentAccount !== null) {
            logger.warn("Current account already set");
            return false
        }
        Account.currentAccount = account
    };
    Account.createFromSecretWord = function(secret) {
        if (secret instanceof Buffer) {
            secret = secret.toString("hex")
        }
        var publicKey = nxtCrypto.getPublicKey(secret);
        var accountId = Account.getId(new Buffer(publicKey, "hex"));
        var account = Account.addOrGetAccount(accountId.toString());
        account.publicKey = new Buffer(publicKey, "hex");
        return account
    };
    Account.getId = function(publicKey) {
        var publicKeyHash = curve.sha256(publicKey);
        var longVal = Utils.bufferToLongLE(publicKeyHash);
        return longVal.toString()
    };
    Account.getAccounts = function(accountId, _callback) {
        var nxtlAccount = {};
        var dollarAccount = {};
        var euroAccount = {};
        var btcAccount = {};
        var accountNums;
        async.waterfall([
            function(callback) {
                NxtlAccount.init(accountId, function(res) {
                    nxtlAccount = res;
                    callback(null)
                })
            },
            function(callback) {
                DollarAccount.init(accountId, function(res) {
                    dollarAccount = res;
                    callback(null)
                })
            },
            function(callback) {
                EuroAccount.init(accountId, function(res) {
                    euroAccount = res;
                    callback(null)
                })
            },
            function(callback) {
                BtcAccount.init(accountId, function(res) {
                    btcAccount = res;
                    callback(null)
                })
            }
        ], function(err, result) {
            if (err) {
                console.log("Account.getAccounts Error", err)
            }
            accountNums = {
                nxtlAccount: nxtlAccount.getSettings(),
                dollarAccount: dollarAccount.getSettings(),
                euroAccount: euroAccount.getSettings(),
                btcAccount: btcAccount.getSettings()
            };
            if (typeof _callback === "function") {
                _callback(accountNums)
            }
        })
    };
    Account.getAccount = function(_id) {
        return Account.accounts[_id]
    };
    Account.addOrGetAccount = function(id) {
        var account = new Account(id);
        if (typeof Account.accounts[id] === "undefined") {
            Account.accounts[id] = account
        }
        return Account.accounts[id]
    };
    Account.insertNewAccount = function(params, request, callback) {
        if (typeof params === "undefined") {
            console.log("empty params for new account insert.");
            return false
        }
        var doc = {
            type: "account",
            accountId: params.accountId,
            publicKeyStr: params.publicKey,
            ip: request.connection.remoteAddress,
            time: Utils.getDateTime()
        };
        logger.info("user doc", doc);
        DB.db.insert(doc, function(err, newDocs) {
            if (!err) {
                if (typeof callback === "function") {
                    callback(newDocs)
                }
            } else {
                console.log("insert_callback error", err)
            }
        });
        return true
    };
    Account.getRegisteredAccounts = function(callback) {
        if (typeof callback !== "function") {
            callback = function(err, docs) {}
        }
        DB.db.find({
            type: "account"
        }).sort({
            time: 1
        }).exec(callback)
    };
    Account.prototype.setOrVerify = function(key, height) {
        if (this.publicKey == null) {
            this.publicKey = key;
            this.keyHeight = -1;
            return true
        } else if (this.publicKey.toString("hex") == key.toString("hex")) {
            return true
        } else if (this.keyHeight == -1) {
            console.log("DUPLICATE KEY!!!");
            console.log("Account key for " + this.id + " was already set to a different one at the same height " + ", current height is " + height + ", rejecting new key");
            return false
        } else if (this.keyHeight >= height) {
            console.log("DUPLICATE KEY!!!");
            console.log("Changing key for account " + this.id + " at height " + height + ", was previously set to a different one at height " + this.keyHeight);
            this.publicKey = key;
            this.keyHeight = height;
            return true
        }
        console.log("DUPLICATE KEY!!!");
        console.log("Invalid key for account " + this.id + " at height " + height + ", was already set to a different one at height " + this.keyHeight);
        return false
    };
    Account.prototype.apply = function(key, height) {};
    Account.prototype.addToBalanceAndUnconfirmedBalance = function(amount) {
        this.balance += amount;
        this.unconfirmedBalance += amount
    };
    Account.prototype.addToBalance = function(amount) {
        this.balance += amount
    };
    Account.prototype.addToUnconfirmedBalance = function(amount) {
        this.unconfirmedBalance += amount
    };
    Account.isLogginedForForge = function() {
        return !!Account.currentAccount
    };
    var AccountClass = function() {
        var settings = {
            name: "",
            id: "",
            postfix: "",
            amount: 0,
            unconfirmedAmount: 0
        };
        this.setId = function(_id) {
            settings.id = _id
        };
        this.setName = function(_name) {
            settings.name = _name
        };
        this.setAmount = function(_ammount) {
            settings.amount = _ammount
        };
        this.setUnconfirmedAmount = function(_ammount) {
            settings.unconfirmedAmount = _ammount
        };
        this.setPostfix = function(_postfix) {
            settings.postfix = _postfix
        };
        this.getPostfix = function() {
            return settings.postfix
        };
        this.getSettings = function() {
            return settings
        }
    };
    var BtcAccount = function() {
        AccountClass.apply(this, Array.prototype.slice.call(arguments))
    };
    BtcAccount.prototype = new AccountClass();
    BtcAccount.init = function(_id, _callback) {
        var self = new BtcAccount();
        self.setName("btc");
        self.setPostfix("BTC");
        self.setId("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W");
        _callback(self)
    };
    var DollarAccount = function() {
        AccountClass.apply(this, Array.prototype.slice.call(arguments))
    };
    DollarAccount.prototype = new AccountClass();
    DollarAccount.init = function(_id, _callback) {
        var self = new DollarAccount();
        self.setName("dollar");
        self.setPostfix("USD");
        self.setId(_id + self.getPostfix());
        _callback(self)
    };
    var EuroAccount = function() {
        AccountClass.apply(this, Array.prototype.slice.call(arguments))
    };
    EuroAccount.prototype = new AccountClass();
    EuroAccount.init = function(_id, _callback) {
        var self = new EuroAccount();
        self.setName("euro");
        self.setPostfix("EUR");
        self.setId(_id + self.getPostfix());
        _callback(self)
    };
    var NxtlAccount = function() {
        AccountClass.apply(this, Array.prototype.slice.call(arguments))
    };
    NxtlAccount.prototype = new AccountClass();
    NxtlAccount.init = function(_id, _callback) {
        var self = new NxtlAccount(_id);
        async.waterfall([
            function(callback) {
                self.setName("nxtl");
                self.setPostfix("NODE");
                self.setId(_id + self.getPostfix());
                callback(null)
            },
            function(callback) {
                var account = Account.getAccount(_id.toString());
                self.setAmount(Utils.roundTo5Float(account.balance));
                self.setUnconfirmedAmount(Utils.roundTo5Float(account.unconfirmedBalance));
                callback(null, "ok")
            }
        ], function(err, result) {
            if (typeof _callback === "function") {
                _callback(self)
            }
        })
    };
    NodeServer = new NodeServer();
    NodeServer.start()
}