/*
 * @project: TERA
 * @version: Development (beta)
 * @license: MIT (not for evil)
 * @copyright: Yuriy Ivanov 2017-2019 [progr76@gmail.com]
 * Web: https://terafoundation.org
 * Twitter: https://twitter.com/terafoundation
 * Telegram: https://web.telegram.org/#/im?p=@terafoundation
*/

"use strict";
const fs = require('fs');
const DBLib = require("./db");
global.BlockDB = new DBLib();
global.BLOCK_HEADER_SIZE = 150;
const FILE_NAME_HEADER = "block-header";
const FILE_NAME_BODY = "block-body";
module.exports = class CDB extends require("../code")
{
    constructor(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual)
    {
        super(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual)
        var bWriteMode = (global.PROCESS_NAME === "MAIN");
        BlockDB.OpenDBFile(FILE_NAME_HEADER, bWriteMode)
        BlockDB.OpenDBFile(FILE_NAME_BODY, bWriteMode)
        this.BlockNumDB = 0
        this.ClearBufMap()
    }
    LoadMemBlocksOnStart()
    {
        this.CurrentBlockNum = GetCurrentBlockNumByTime()
        for(var i = this.BlockNumDB - BLOCK_COUNT_IN_MEMORY; i <= this.BlockNumDB; i++)
            if(i >= 0)
            {
                if(i >= this.BlockNumDB - BLOCK_PROCESSING_LENGTH * 5)
                    this.GetBlock(i, true, true)
                else
                    this.GetBlock(i, true, false)
            }
    }
    GetMaxNumBlockDB()
    {
        var FI = BlockDB.OpenDBFile(FILE_NAME_HEADER);
        var BlockNum = (FI.size / BLOCK_HEADER_SIZE) - 1;
        return BlockNum;
    }
    FindStartBlockNum()
    {
        var BlockNum = this.GetMaxNumBlockDB();
        if(global.NO_CHECK_BLOCKNUM_ONSTART)
        {
            this.BlockNumDB = this.CheckBlocksOnStartFoward(BlockNum - 2, 0)
            ToLog("START_BLOCK_NUM:" + this.BlockNumDB)
            return ;
        }
        BlockNum = this.CheckBlocksOnStartReverse(BlockNum)
        this.BlockNumDB = this.CheckBlocksOnStartFoward(BlockNum - 2000, 0)
        this.BlockNumDB = this.CheckBlocksOnStartFoward(this.BlockNumDB - 100, 1)
        if(this.BlockNumDB >= BLOCK_PROCESSING_LENGTH2)
        {
            this.TruncateBlockDB(this.BlockNumDB)
        }
        ToLog("START_BLOCK_NUM:" + this.BlockNumDB)
        this.CheckOnStartComplete = 1
    }
    CheckBlocksOnStartReverse(StartNum)
    {
        var delta = 1;
        var Count = 0;
        var PrevBlock;
        for(var num = StartNum; num >= BLOCK_PROCESSING_LENGTH; num -= delta)
        {
            var Block = this.ReadBlockHeaderDB(num);
            if(!Block || IsZeroArr(Block.SumHash))
            {
                delta++
                Count = 0
                continue;
            }
            var PrevBlock = this.ReadBlockHeaderDB(num - 1);
            if(!PrevBlock || IsZeroArr(PrevBlock.SumHash))
            {
                Count = 0
                continue;
            }
            delta = 1
            var SumHash = shaarr2(PrevBlock.SumHash, Block.Hash);
            if(CompareArr(SumHash, Block.SumHash) === 0)
            {
                Count++
                if(Count > COUNT_BLOCKS_FOR_LOAD / 10)
                    return num;
            }
            else
            {
                Count = 0
            }
        }
        return 0;
    }
    CheckBlocksOnStartFoward(StartNum, bCheckBody)
    {
        var PrevBlock;
        if(StartNum < BLOCK_PROCESSING_LENGTH2)
            StartNum = BLOCK_PROCESSING_LENGTH2
        var MaxNum = DApps.Accounts.GetHashedMaxBlockNum();
        var BlockNumTime = GetCurrentBlockNumByTime();
        if(BlockNumTime < MaxNum)
            MaxNum = BlockNumTime
        var arr = [];
        for(var num = StartNum; num <= MaxNum; num++)
        {
            var Block;
            if(bCheckBody)
                Block = this.ReadBlockDB(num)
            else
                Block = this.ReadBlockHeaderDB(num)
            if(!Block)
                return num > 0 ? num - 1 : 0;
            if(num % 100000 === 0)
                ToLog("CheckBlocksOnStartFoward: " + num)
            if(bCheckBody)
            {
                var TreeHash = CalcTreeHashFromArrBody(Block.BlockNum, Block.arrContent);
                if(CompareArr(Block.TreeHash, TreeHash) !== 0)
                {
                    ToLog("BAD TreeHash block=" + Block.BlockNum)
                    return num > 0 ? num - 1 : 0;
                }
            }
            if(PrevBlock)
            {
                if(arr.length !== BLOCK_PROCESSING_LENGTH)
                {
                    var start = num - BLOCK_PROCESSING_LENGTH2;
                    for(var n = 0; n < BLOCK_PROCESSING_LENGTH; n++)
                    {
                        var Prev = this.ReadBlockHeaderDB(start + n);
                        arr.push(Prev.Hash)
                    }
                }
                else
                {
                    arr.shift()
                    var Prev = this.ReadBlockHeaderDB(num - BLOCK_PROCESSING_LENGTH - 1);
                    arr.push(Prev.Hash)
                }
                var PrevHash = CalcHashFromArray(arr, true);
                var SeqHash = this.GetSeqHash(Block.BlockNum, PrevHash, Block.TreeHash);
                var Value = GetHashFromSeqAddr(SeqHash, Block.AddrHash, Block.BlockNum, PrevHash);
                if(CompareArr(Value.Hash, Block.Hash) !== 0)
                {
                    ToLog("=================== FIND ERR Hash in " + Block.BlockNum + "  bCheckBody=" + bCheckBody)
                    return num > 0 ? num - 1 : 0;
                }
                var SumHash = shaarr2(PrevBlock.SumHash, Block.Hash);
                if(CompareArr(SumHash, Block.SumHash) !== 0)
                {
                    ToLog("=================== FIND ERR SumHash in " + Block.BlockNum)
                    return num > 0 ? num - 1 : 0;
                }
            }
            PrevBlock = Block
        }
        return num > 0 ? num - 1 : 0;
    }
    WriteBlockDB(Block)
    {
        var startTime = process.hrtime();
        if(Block.TrCount === 0 && !IsZeroArr(Block.TreeHash))
        {
            ToLogTrace("ERROR WRITE TrCount BLOCK:" + Block.BlockNum)
            throw "ERROR WRITE";
        }
        var Ret = this.WriteBodyDB(Block);
        if(Ret)
        {
            Ret = this.WriteBlockDBFinaly(Block)
        }
        ADD_TO_STAT_TIME("MAX:WriteBlockDB", startTime)
        ADD_TO_STAT_TIME("WriteBlockDB", startTime)
        return Ret;
    }
    WriteBlockDBFinaly(Block)
    {
        var Ret = this.WriteBlockHeaderDB(Block);
        if(Ret)
        {
            if(Block.TrDataLen === 0 && !IsZeroArr(Block.TreeHash))
            {
                ToLogTrace("ERROR WRITE FINAL TrDataLen BLOCK")
                throw "ERROR WRITE";
            }
            this.OnWriteBlock(Block)
            this.BlockNumDB = Block.BlockNum
            Block.bSave = true
        }
        return Ret;
    }
    SaveDataTreeToDB(Block)
    {
        var Ret = this.WriteBodyDB(Block);
        if(Ret)
        {
            var BufWrite = BufLib.GetNewBuffer(BLOCK_HEADER_SIZE);
            this.BlockHeaderToBuf(BufWrite, Block)
            Ret = this.WriteBufHeaderDB(BufWrite, Block.BlockNum)
        }
        return Ret;
    }
    WriteBodyResultDB(Block)
    {
        var arrTr = Block.arrContentResult;
        if(Block.TrDataPos && Block.TrDataLen && Block.VersionBody && arrTr && arrTr.length)
        {
            var FileItem = BlockDB.OpenDBFile(FILE_NAME_BODY, 1);
            var FD = FileItem.fd;
            var Size = arrTr.length * 6;
            var Position = Block.TrDataPos + Block.TrDataLen - Size;
            if(FileItem.size < Position + Size)
            {
                TO_ERROR_LOG("DB", 241, "Error Position in WriteBodyResultDB on block: " + Block.BlockNum)
                return false;
            }
            var BufWrite = BufLib.GetNewBuffer(Size);
            for(var i = 0; i < arrTr.length; i++)
            {
                BufWrite.Write(arrTr[i], "uint")
            }
            var written = fs.writeSync(FD, BufWrite, 0, BufWrite.length, Position);
            if(written !== BufWrite.length)
            {
                TO_ERROR_LOG("DB", 242, "Error write to file block-chain : " + written + " <> " + BufWrite.length)
                return false;
            }
        }
        return true;
    }
    WriteBodyDB(Block)
    {
        var FileItem = BlockDB.OpenDBFile(FILE_NAME_BODY, 1);
        var FD = FileItem.fd;
        var Position = FileItem.size;
        Block.TrDataPos = Position
        var arrTr = Block.arrContent;
        if(!arrTr || arrTr.length === 0)
        {
            Block.TrCount = 0
            Block.TrDataLen = 0
            return true;
        }
        var TrDataLen = 4;
        var arrSize = [];
        for(var i = 0; i < arrTr.length; i++)
        {
            var body = arrTr[i];
            arrSize[i] = 2 + body.length
            TrDataLen += arrSize[i]
        }
        Block.VersionBody = 1
        TrDataLen += arrTr.length * 6
        var BufWrite = BufLib.GetNewBuffer(TrDataLen);
        BufWrite.Write(arrTr.length, "uint16")
        BufWrite.Write(Block.VersionBody, "uint16")
        for(var i = 0; i < arrTr.length; i++)
        {
            var body = arrTr[i];
            BufWrite.Write(body, "tr")
        }
        var written = fs.writeSync(FD, BufWrite, 0, BufWrite.length, Position);
        if(written !== BufWrite.length)
        {
            TO_ERROR_LOG("DB", 240, "Error write to file block-chain : " + written + " <> " + BufWrite.length)
            return false;
        }
        FileItem.size += TrDataLen
        Block.TrCount = arrTr.length
        Block.TrDataLen = TrDataLen
        return true;
    }
    WriteBlockHeaderDB(Block)
    {
        if(Block.BlockNum > 0)
        {
            if(USE_CHECK_SAVE_DB)
                if(!this.CheckSeqHashDB(Block, "WriteBlockHeaderDB"))
                    return false;
            this.TruncateBlockDBInner(Block)
            this.BlockNumDB = Block.BlockNum - 1
            var PrevBlock = this.ReadBlockHeaderDB(Block.BlockNum - 1);
            if(!PrevBlock)
            {
                ToLogTrace("Cant write header block:" + Block.BlockNum + "  prev block not found")
                throw "ERR: PREV BLOCK NOT FOUND";
                return false;
            }
            Block.SumHash = shaarr2(PrevBlock.SumHash, Block.Hash)
            Block.SumPow = PrevBlock.SumPow + GetPowPower(Block.PowHash)
        }
        var BufWrite = BufLib.GetNewBuffer(BLOCK_HEADER_SIZE);
        this.BlockHeaderToBuf(BufWrite, Block)
        var Res = this.WriteBufHeaderDB(BufWrite, Block.BlockNum);
        return Res;
    }
    WriteBufHeaderDB(BufWrite, BlockNum)
    {
        BlockNum = Math.trunc(BlockNum)
        this.ClearBufMap()
        var Position = BlockNum * BLOCK_HEADER_SIZE;
        var FI = BlockDB.OpenDBFile(FILE_NAME_HEADER, 1);
        var written = fs.writeSync(FI.fd, BufWrite, 0, BufWrite.length, Position);
        if(Position >= FI.size)
        {
            FI.size = Position + BufWrite.length
        }
        if(written !== BufWrite.length)
        {
            TO_ERROR_LOG("DB", 260, "Error write to file block-header :" + written + " <> " + Info.key_width)
            return false;
        }
        else
        {
            return true;
        }
    }
    ReadBlockDB(Num)
    {
        if(!Num)
            Num = 0
        Num = Math.trunc(Num)
        var Block = this.ReadBlockHeaderDB(Num);
        if(Block && Block.TrDataLen)
        {
            var Ret = this.ReadBlockBodyDB(Block);
            if(!Ret)
                return undefined;
        }
        else
        {
            if(Block && !IsZeroArr(Block.TreeHash))
            {
                ToLogTrace("ERROR arrContent on BlockNum=" + Num)
                return undefined;
            }
        }
        return Block;
    }
    ReadBlockBodyDB(Block)
    {
        var FileItem = BlockDB.OpenDBFile(FILE_NAME_BODY);
        var FD = FileItem.fd;
        if(Block.TrDataLen > MAX_BLOCK_SIZE * 2)
        {
            ToLogTrace("Error value TrDataLen, BlockNum=" + Block.BlockNum)
            return false;
        }
        var Position = Block.TrDataPos;
        var BufRead = BufLib.GetNewBuffer(Block.TrDataLen);
        var bytesRead = fs.readSync(FD, BufRead, 0, BufRead.length, Position);
        if(bytesRead !== BufRead.length)
        {
            TO_ERROR_LOG("DB", 272, "Error read block-body file: " + FileItem.name + "  from POS:" + Position + "  bytesRead=" + bytesRead + " of " + BufRead.length + "  BlockNum=" + Block.BlockNum)
            return false;
        }
        Block.arrContent = []
        Block.arrContentResult = []
        var TrCount = BufRead.Read("uint16");
        Block.VersionBody = BufRead.Read("uint16")
        if(TrCount <= MAX_TRANSACTION_COUNT)
        {
            for(var i = 0; i < TrCount; i++)
            {
                var body = BufRead.Read("tr");
                if(!body)
                    break;
                Block.arrContent[i] = body
            }
            if(Block.VersionBody === 1)
            {
                var Size = TrCount * 6;
                BufRead.len = Block.TrDataLen - Size
                for(var i = 0; i < TrCount; i++)
                {
                    Block.arrContentResult[i] = BufRead.Read("uint")
                }
            }
        }
        Block.TrCount = Block.arrContent.length
        return true;
    }
    ReadBlockHeaderDB(Num)
    {
        if(Num < 0)
        {
            return undefined;
        }
        Num = Math.trunc(Num)
        var BufRead = BufLib.GetNewBuffer(BLOCK_HEADER_SIZE);
        var Position = Num * BLOCK_HEADER_SIZE;
        var FD = BlockDB.OpenDBFile(FILE_NAME_HEADER).fd;
        var bytesRead = fs.readSync(FD, BufRead, 0, BufRead.length, Position);
        if(bytesRead !== BufRead.length)
            return undefined;
        var Block = this.BufToBlockHeader(BufRead, Num);
        if(Block)
        {
            Block.bSave = true
            Block.Prepared = true
        }
        return Block;
    }
    ReadBlockHeaderFromMapDB(Num)
    {
        var Block = this.MapHeader[Num];
        if(!Block)
        {
            Block = this.ReadBlockHeaderDB(Num)
            this.MapHeader[Num] = Block
        }
        return Block;
    }
    SetTruncateBlockDB(Num)
    {
        Num = Math.trunc(Num)
        if(Num < BLOCK_PROCESSING_LENGTH2)
            Num = BLOCK_PROCESSING_LENGTH2
        if(this.UseTruncateBlockDB)
        {
            if(Num < this.UseTruncateBlockDB)
                this.UseTruncateBlockDB = Num
        }
        else
        {
            this.UseTruncateBlockDB = Num
        }
    }
    TruncateBlockDB(LastBlockNum)
    {
        this.UseTruncateBlockDB = undefined
        var Block = this.ReadBlockDB(LastBlockNum);
        if(!Block)
        {
            ToLog("************ ERROR TruncateBlockDB - not found block=" + LastBlockNum)
            return ;
        }
        this.WriteBlockDB(Block)
    }
    TruncateBlockDBInner(LastBlock)
    {
        var FItem1 = BlockDB.OpenDBFile(FILE_NAME_HEADER, 1);
        var size = (LastBlock.BlockNum + 1) * BLOCK_HEADER_SIZE;
        if(size < 0)
            size = 0
        if(FItem1.size > size)
        {
            FItem1.size = size
            fs.ftruncateSync(FItem1.fd, FItem1.size)
        }
        this.TruncateStat(LastBlock.BlockNum)
    }
    TruncateBlockBodyDBInner()
    {
        var FItem2 = BlockDB.OpenDBFile(FILE_NAME_BODY, 1);
        var size2 = 0;
        if(FItem2.size !== size2)
        {
            this.ClearBufMap()
            FItem2.size = size2
            fs.ftruncateSync(FItem2.fd, FItem2.size)
        }
    }
    TruncateFileBuf(Tree, size)
    {
        while(true)
        {
            var Item = Tree.max();
            if(Item === null)
                break;
            if(Item.Position >= size)
            {
                Tree.remove(Item)
            }
            else
            {
                break;
            }
        }
    }
    ClearDataBase()
    {
        this.RewriteAllTransactions()
        var FItem1 = BlockDB.OpenDBFile(FILE_NAME_HEADER, 1);
        FItem1.size = 0
        fs.ftruncateSync(FItem1.fd, FItem1.size)
        var FItem2 = BlockDB.OpenDBFile(FILE_NAME_BODY, 1);
        FItem2.size = 0
        fs.ftruncateSync(FItem2.fd, FItem2.size)
        this.BlockNumDB = 0
        this.ClearBufMap()
        this.ClearStat()
        this.CreateGenesisBlocks()
    }
    ClearBufMap()
    {
        this.MapHeader = {}
    }
    RewriteAllTransactions()
    {
        if(TX_PROCESS.Worker)
            TX_PROCESS.Worker.send({cmd:"RewriteAllTransactions"})
    }
    BlockHeaderToBuf(BufWrite, Block)
    {
        Block.Reserv500 = 0
        var len = BufWrite.len;
        BufWrite.Write(Block.TreeHash, "hash")
        BufWrite.Write(Block.AddrHash, "hash")
        BufWrite.Write(Block.PrevHash, "hash")
        BufWrite.Write(Block.SumHash, "hash")
        BufWrite.Write(Block.SumPow, "uint")
        BufWrite.Write(Block.Reserv500, "uint")
        BufWrite.Write(Block.TrDataPos, "uint")
        BufWrite.Write(Block.TrDataLen, "uint32")
        BufWrite.len = len + BLOCK_HEADER_SIZE
    }
    BufToBlockHeader(BufRead, Num)
    {
        var Block = {};
        Block.AddInfo = AddInfoBlock.bind(Block)
        Block.Info = ""
        var len = BufRead.len;
        Block.TreeHash = BufRead.Read("hash")
        Block.AddrHash = BufRead.Read("hash")
        Block.PrevHash = BufRead.Read("hash")
        Block.SumHash = BufRead.Read("hash")
        Block.SumPow = BufRead.Read("uint")
        Block.Reserv500 = BufRead.Read("uint")
        Block.TrDataPos = BufRead.Read("uint")
        Block.TrDataLen = BufRead.Read("uint32")
        Block.TrCount = 0
        BufRead.len = len + BLOCK_HEADER_SIZE
        Block.BlockNum = Num
        Block.SeqHash = this.GetSeqHash(Block.BlockNum, Block.PrevHash, Block.TreeHash)
        if(Block.BlockNum >= BLOCK_PROCESSING_LENGTH2)
        {
            CalcHashBlockFromSeqAddr(Block)
        }
        else
        {
            Block.Hash = this.GetHashGenesis(Block.BlockNum)
            Block.PowHash = Block.Hash
        }
        Block.Power = GetPowPower(Block.PowHash)
        if(IsZeroArr(Block.Hash))
            return undefined;
        return Block;
    }
    GetRows(start, count, Filter)
    {
        if(Filter)
        {
            Filter = Filter.trim()
            Filter = Filter.toUpperCase()
        }
        var MaxAccount = DApps.Accounts.GetMaxAccount();
        var WasError = 0;
        var arr = [];
        for(var num = start; true; num++)
        {
            var Block = this.ReadBlockHeaderDB(num);
            if(!Block)
                break;
            Block.Num = Block.BlockNum
            if(Block.AddrHash)
            {
                Block.Miner = ReadUintFromArr(Block.AddrHash, 0)
                if(Block.BlockNum < 16 || Block.Miner > MaxAccount)
                    Block.Miner = 0
                var Value = GetHashFromSeqAddr(Block.SeqHash, Block.AddrHash, Block.BlockNum, Block.PrevHash);
                Block.Hash1 = Value.Hash1
                Block.Hash2 = Value.Hash2
            }
            if(Filter)
            {
                var Num = Block.BlockNum;
                var Bytes = Block.TrDataLen;
                var Pow = Block.Power;
                var Miner = Block.Miner;
                var Date = DateFromBlock(Block.BlockNum);
                try
                {
                    if(!eval(Filter))
                        continue;
                }
                catch(e)
                {
                    if(!WasError)
                        ToLog(e)
                    WasError = 1
                }
            }
            arr.push(Block)
            count--
            if(count < 1)
                break;
        }
        return arr;
    }
    GetTrRows(BlockNum, start, count)
    {
        var arr = [];
        var Block = this.ReadBlockDB(BlockNum);
        if(Block && Block.arrContent)
            for(var num = start; num < start + count; num++)
            {
                if(num < 0)
                    continue;
                if(num >= Block.arrContent.length)
                    break;
                var Tr = {body:Block.arrContent[num]};
                this.CheckCreateTransactionObject(Tr)
                Tr.Num = num
                Tr.Type = Tr.body[0]
                Tr.Length = Tr.body.length
                Tr.Body = []
                for(var j = 0; j < Tr.body.length; j++)
                    Tr.Body[j] = Tr.body[j]
                var App = DAppByType[Tr.Type];
                if(App)
                {
                    Tr.Script = App.GetScriptTransaction(Tr.body)
                    Tr.Verify = App.GetVerifyTransaction(Block, BlockNum, Tr.Num, Tr.body)
                    if(Tr.Verify >= 1)
                    {
                        Tr.VerifyHTML = "<B style='color:green'>✔</B>"
                        if(Tr.Verify > 1)
                        {
                            Tr.VerifyHTML += "(" + Tr.Verify + ")"
                        }
                    }
                    else
                        if(Tr.Verify ==  - 1)
                            Tr.VerifyHTML = "<B style='color:red'>✘</B>"
                        else
                            Tr.VerifyHTML = ""
                }
                else
                {
                    Tr.Script = ""
                    Tr.VerifyHTML = ""
                }
                arr.push(Tr)
            }
        return arr;
    }
    ClearStat()
    {
        var MAX_ARR_PERIOD = MAX_STAT_PERIOD * 2 + 10;
        this.StatMap = {StartPos:0, StartBlockNum:0, Length:0, "ArrPower":new Float64Array(MAX_ARR_PERIOD), "ArrPowerMy":new Float64Array(MAX_ARR_PERIOD),
        }
    }
    TruncateStat(LastBlockNum)
    {
        if(this.StatMap)
        {
            var LastNumStat = this.StatMap.StartBlockNum + this.StatMap.Length;
            var Delta = LastNumStat - LastBlockNum;
            if(Delta > 0)
            {
                this.StatMap.Length -= Delta
                if(this.StatMap.Length < 0)
                    this.StatMap.Length = 0
            }
            this.StatMap.CaclBlockNum = 0
        }
    }
    GetStatBlockchainPeriod(Param)
    {
        var StartNum = Param.BlockNum;
        if(!Param.Count || Param.Count < 0)
            Param.Count = 1000
        if(!Param.Miner)
            Param.Miner = 0
        if(!Param.Adviser)
            Param.Adviser = 0
        var Map = {};
        var ArrList = new Array(Param.Count);
        var i = 0;
        for(var num = StartNum; num < StartNum + Param.Count; num++)
        {
            var Power = 0, PowerMy = 0, Nonce = 0;
            if(num <= this.BlockNumDB)
            {
                var Block = this.ReadBlockHeaderDB(num);
                if(Block)
                {
                    Power = GetPowPower(Block.PowHash)
                    var Miner = ReadUintFromArr(Block.AddrHash, 0);
                    var Nonce = ReadUintFromArr(Block.AddrHash, 6);
                    if(Param.Miner < 0)
                    {
                        if(Param.Adviser)
                        {
                            var Adviser = DApps.Accounts.GetAdviserByMiner(Map, Miner);
                            if(Adviser === Param.Adviser)
                                PowerMy = Power
                        }
                        else
                        {
                            PowerMy = Power
                        }
                    }
                    else
                        if(Miner === Param.Miner && !Param.bMinerLess)
                        {
                            PowerMy = Power
                        }
                        else
                            if(Miner <= Param.Miner && Param.bMinerLess)
                            {
                                PowerMy = Power
                            }
                }
            }
            ArrList[i] = PowerMy
            if(Param.bNonce && PowerMy)
                ArrList[i] = Nonce
            i++
        }
        var AvgValue = 0;
        for(var j = 0; j < ArrList.length; j++)
        {
            if(ArrList[j])
                AvgValue += ArrList[j]
        }
        if(ArrList.length > 0)
            AvgValue = AvgValue / ArrList.length
        const MaxSizeArr = 1000;
        var StepTime = 1;
        while(ArrList.length >= MaxSizeArr)
        {
            if(Param.bNonce)
                ArrList = ResizeArrMax(ArrList)
            else
                ArrList = ResizeArrAvg(ArrList)
            StepTime = StepTime * 2
        }
        return {ArrList:ArrList, AvgValue:AvgValue, steptime:StepTime};
    }
    GetStatBlockchain(name, MinLength)
    {
        if(!MinLength)
            return [];
        var MAX_ARR_PERIOD = MAX_STAT_PERIOD * 2 + 10;
        if(!this.StatMap)
        {
            this.ClearStat()
        }
        var MaxNumBlockDB = this.GetMaxNumBlockDB();
        if(this.StatMap.CaclBlockNum !== MaxNumBlockDB || this.StatMap.CalcMinLength !== MinLength)
        {
            this.StatMap.CaclBlockNum = MaxNumBlockDB
            this.StatMap.CalcMinLength = MinLength
            var start = MaxNumBlockDB - MinLength + 1;
            var finish = MaxNumBlockDB + 1;
            var StartPos = this.StatMap.StartPos;
            var ArrPower = this.StatMap.ArrPower;
            var ArrPowerMy = this.StatMap.ArrPowerMy;
            var StartNumStat = this.StatMap.StartBlockNum;
            var FinishNumStat = this.StatMap.StartBlockNum + this.StatMap.Length - 1;
            var CountReadDB = 0;
            var arr = new Array(MinLength);
            var arrmy = new Array(MinLength);
            for(var num = start; num < finish; num++)
            {
                var i = num - start;
                var i2 = (StartPos + i) % MAX_ARR_PERIOD;
                if(num >= StartNumStat && num <= FinishNumStat && (num < finish - 10))
                {
                    arr[i] = ArrPower[i2]
                    arrmy[i] = ArrPowerMy[i2]
                }
                else
                {
                    CountReadDB++
                    var Power = 0, PowerMy = 0;
                    if(num <= MaxNumBlockDB)
                    {
                        var Block = this.ReadBlockHeaderDB(num);
                        if(Block)
                        {
                            Power = GetPowPower(Block.PowHash)
                            var Miner = ReadUintFromArr(Block.AddrHash, 0);
                            if(Miner === GENERATE_BLOCK_ACCOUNT)
                            {
                                PowerMy = Power
                            }
                        }
                    }
                    arr[i] = Power
                    arrmy[i] = PowerMy
                    ArrPower[i2] = arr[i]
                    ArrPowerMy[i2] = arrmy[i]
                    if(num > FinishNumStat)
                    {
                        this.StatMap.StartBlockNum = num - this.StatMap.Length
                        this.StatMap.Length++
                        if(this.StatMap.Length > MAX_ARR_PERIOD)
                        {
                            this.StatMap.Length = MAX_ARR_PERIOD
                            this.StatMap.StartBlockNum++
                            this.StatMap.StartPos++
                        }
                    }
                }
            }
            this.StatMap["POWER_BLOCKCHAIN"] = arr
            this.StatMap["POWER_MY_WIN"] = arrmy
        }
        var arr = this.StatMap[name];
        if(!arr)
            arr = []
        var arrT = this.StatMap["POWER_BLOCKCHAIN"];
        for(var i = 0; i < arrT.length; i++)
            if(!arrT[i])
            {
                this.StatMap = undefined
                break;
            }
        return arr;
    }
    GetHashGenesis(Num)
    {
        return [Num + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Num + 1];
    }
    GetSeqHash(BlockNum, PrevHash, TreeHash)
    {
        var arr = [GetArrFromValue(BlockNum), PrevHash, TreeHash];
        var SeqHash = CalcHashFromArray(arr, true);
        return SeqHash;
    }
    CheckCreateTicketObject(Tr, BlockNum)
    {
        if(!Tr.HashPow && Tr.HashTicket)
        {
            Tr.num = BlockNum
            var FullHashTicket = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
            for(var i = 0; i < global.TR_TICKET_HASH_LENGTH; i++)
                FullHashTicket[i] = Tr.HashTicket[i]
            WriteUintToArrOnPos(FullHashTicket, Tr.num, global.TR_TICKET_HASH_LENGTH)
            Tr.HashPow = sha3(FullHashTicket)
            Tr.power = GetPowPower(Tr.HashPow)
            Tr.TimePow = Tr.power
        }
    }
    CheckCreateTransactionObject(Tr)
    {
        if(!Tr.HashPow)
        {
            Tr.IsTx = 1
            Tr.num = ReadUintFromArr(Tr.body, Tr.body.length - 12)
            if(Tr.num >= global.BLOCKNUM_TICKET_ALGO)
                Tr.HASH = sha3(Tr.body)
            else
                Tr.HASH = shaarr(Tr.body)
            Tr.HashTicket = Tr.HASH.slice(0, global.TR_TICKET_HASH_LENGTH)
            this.CheckCreateTicketObject(Tr, Tr.num)
        }
    }
    BlockChainToBuf(WriteNum, StartNum, EndBlockNum)
    {
        if(StartNum === undefined)
            return BufLib.GetNewBuffer(10);
        var GetLength = EndBlockNum - StartNum + 1;
        var PrevBlock;
        if(StartNum > 0)
            PrevBlock = this.ReadBlockHeaderDB(StartNum - 1)
        var arr = [];
        var arr0 = this.PrevBlockChainArr;
        if(arr0 && arr0.length === GetLength)
        {
            var StartNumArr =  - 1;
            var EndNumArr =  - 1;
            for(var i = 0; i < arr0.length; i++)
            {
                var BlockArr = arr0[i];
                if(BlockArr.BlockNum === StartNum)
                {
                    StartNumArr = i
                }
                if(BlockArr.BlockNum <= EndBlockNum)
                {
                    EndNumArr = i
                }
            }
            if(StartNumArr >= 0 && EndNumArr > StartNumArr)
            {
                var BlockArr = arr0[EndNumArr];
                var Block = this.ReadBlockHeaderDB(BlockArr.BlockNum);
                if(Block)
                    if(CompareArr(BlockArr.SumHash, Block.SumHash) == 0)
                    {
                        arr0.splice(EndNumArr + 1, arr.length)
                        if(StartNumArr > 0)
                            arr0.splice(StartNumArr - 1, StartNumArr)
                        arr = arr0
                        PrevBlock = Block
                        StartNum = Block.BlockNum + 1
                    }
            }
        }
        for(var num = StartNum; num <= EndBlockNum; num++)
        {
            var Block = this.ReadBlockHeaderDB(num);
            if(!Block || !Block.Prepared || !Block.Hash)
                break;
            if(PrevBlock)
            {
                if(!PrevBlock.SumHash)
                    break;
                var SumHash = shaarr2(PrevBlock.SumHash, Block.Hash);
                Block.SumHash = SumHash
            }
            arr.push(Block)
            PrevBlock = Block
        }
        this.PrevBlockChainArr = arr
        return this.ArrHeaderToBuf(WriteNum, arr);
    }
    ArrHeaderToBuf(StartNum, arr)
    {
        var CountSend = arr.length - BLOCK_PROCESSING_LENGTH2;
        var BufWrite;
        if(CountSend <= 0)
        {
            BufWrite = BufLib.GetNewBuffer(10)
        }
        else
        {
            var BufSize = 6 + 4 + BLOCK_PROCESSING_LENGTH2 * 32 + 32 + 6 + CountSend * 64;
            BufWrite = BufLib.GetNewBuffer(BufSize)
            BufWrite.Write(StartNum, "uint")
            BufWrite.Write(CountSend, "uint32")
            for(var i = 0; i < arr.length; i++)
            {
                var Block = arr[i];
                if(i < BLOCK_PROCESSING_LENGTH2)
                {
                    BufWrite.Write(Block.Hash, "hash")
                }
                else
                {
                    if(i === BLOCK_PROCESSING_LENGTH2)
                    {
                        BufWrite.Write(Block.SumHash, "hash")
                        BufWrite.Write(Block.SumPow, "uint")
                    }
                    BufWrite.Write(Block.TreeHash, "hash")
                    BufWrite.Write(Block.AddrHash, "hash")
                }
            }
        }
        return BufWrite;
    }
};

function AddInfo(Block,Str,BlockNumStart)
{
    if(!global.STAT_MODE)
        return ;
    if(!Block.Info)
        Block.Info = Str;
    else
        if(Block.Info.length < 2000)
        {
            var timesend = "" + SERVER.CurrentBlockNum - BlockNumStart;
            var now = GetCurrentTime();
            timesend += ".[" + now.getSeconds().toStringZ(2) + "." + now.getMilliseconds().toStringZ(3) + "]";
            Str = timesend + ": " + Str;
            Block.Info += "\n" + Str;
        }
};
global.AddInfoChain = function (Str)
{
    if(!global.STAT_MODE)
        return ;
    if(this.BlockNumStart > GetCurrentBlockNumByTime() - HISTORY_BLOCK_COUNT)
        AddInfo(this, Str, this.BlockNumStart);
};
global.AddInfoBlock = function (Block,Str)
{
    if(!global.STAT_MODE)
        return ;
    if(Block && Block.BlockNum && Block.BlockNum > GetCurrentBlockNumByTime() - HISTORY_BLOCK_COUNT)
        AddInfo(Block, Str, Block.BlockNum);
};
global.GetNodeStrPort = function (Node)
{
    if(!Node)
        return "";
    if(LOCAL_RUN)
        return "" + Node.port;
    else
    {
        if(!Node.ip)
            return "";
        var arr = Node.ip.split(".");
        return "" + arr[2] + "." + arr[3];
    }
};
