/*
 * @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";
require('./library');
require('./crypto-library');
const TX_PROCESS_TIME = 100;
const TX_DELTA_PROCESS_TIME = 300;
global.CAN_START = false;
global.StrWarn = "";
global.SUM_LIST_LENGTH = 2 * BLOCK_PROCESSING_LENGTH;
global.CONSENSUS_TIK_TIME = CONSENSUS_PERIOD_TIME / 10;
global.CONSENSUS_CHECK_TIME = CONSENSUS_PERIOD_TIME / 20;
const PERIOD_FOR_NEXT_SEND = CONSENSUS_TIK_TIME * 3;
global.BLOCK_DELTA_ACTIVATE = 0;
global.TIME_END_EXCHANGE =  - 3;
global.TIME_START_POW =  - 4;
global.TIME_START_SAVE =  - 4;
global.TIME_START_LOAD = global.TIME_START_SAVE - 4;
var FORMAT_DATA_TRANSFER = "{\
        Version:uint16,\
        BlockNum:uint,\
        Reserv1:uint32,\
        MaxPOW:[{BlockNum:uint,AddrHash:hash,SeqHash:hash}],\
        Reserv2:uint32,\
        BaseBlockNum:uint,\
        MaxSumID:[{BlockNum:uint,SumHash:hash,SumListID:[uint16]}],\
        BlockList:[{ID:uint16, AddrHash:hash,SeqHash:hash}],\
        TicketArray:[{HashTicket:arr10}],\
        TxArray:[{body:tr}],\
        NoSendTx:uint,\
        }";
const WorkStructSend = {};
module.exports = class CConsensus extends require("./block-loader")
{
    constructor(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual)
    {
        super(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual)
        this.CurrentBlockNum = 0
        this.SendBlockID = 0
        this.RelayMode = false
        this.TreeSendPacket = new RBTree(CompareItemHash)
        if(!global.ADDRLIST_MODE && !this.VirtualMode)
        {
            this.idBlockChainTimer = setInterval(this.StartBlockChain.bind(this), CONSENSUS_PERIOD_TIME - 5)
            setInterval(this.DoTransfer.bind(this), CONSENSUS_CHECK_TIME)
        }
    }
    StartBlockChain()
    {
        this.OnStartSecond()
        var CurTimeNum = GetCurrentTime() - CONSENSUS_PERIOD_TIME / 2;
        var StartTimeNum = Math.floor((CurTimeNum + CONSENSUS_PERIOD_TIME) / CONSENSUS_PERIOD_TIME) * CONSENSUS_PERIOD_TIME;
        var DeltaForStart = StartTimeNum - CurTimeNum;
        if(DeltaForStart < (CONSENSUS_PERIOD_TIME - 5))
        {
            var self = this;
            if(self.idBlockChainTimer)
                clearInterval(self.idBlockChainTimer)
            self.idBlockChainTimer = 0
            setTimeout(function ()
            {
                self.idBlockChainTimer = setInterval(self.StartBlockChain.bind(self), CONSENSUS_PERIOD_TIME)
                self.OnStartSecond()
            }, DeltaForStart)
        }
    }
    OnStartSecond()
    {
        PrepareStatEverySecond()
        this.AddStatOnTimer()
        this.DoBlockChain()
    }
    CreateBlockContext()
    {
        var Context = {};
        Context.AddInfo = AddInfoBlock.bind(Context)
        Context.Active = false
        Context.TransferFromAddr = {}
        Context.LevelsTransfer = []
        Context.ErrRun = ""
        Context.PowTxTree = new RBTree(CompareItemTimePow)
        Context.PowTicketTree = new RBTree(CompareItemTimePow)
        Context.bSave = false
        Context.PrevHash = undefined
        Context.TreeHash = undefined
        Context.MaxPOW = {}
        Context.MaxSum = {}
        Context.SumPow = 0
        Context.Power = 0
        Context.TrCount = 0
        Context.TrDataPos = 0
        Context.TrDataLen = 0
        Context.Info = "Create at:" + GetStrOnlyTimeUTC()
        var Transfer;
        var TransferM2;
        var LocalLevel = 0;
        var Levels = this.LevelNodes;
        for(let L = 0; L < Levels.length; L++)
        {
            var arr = Levels[L];
            if(arr && arr.length > 0)
            {
                Transfer = {LocalLevel:LocalLevel, TreeLevel:L, SendCount:0, GetCount:0, TransferNodes:{}, WasGet:false, WasSend:false, MustDeltaTime:CONSENSUS_TIK_TIME * (2 + MAX_LEVEL_SPECIALIZATION - L),
                }
                LocalLevel++
                Context.LevelsTransfer.push(Transfer)
                Context.StartLevel = Context.LevelsTransfer.length - 1
                for(let j = 0; j < arr.length; j++)
                {
                    var Node = arr[j];
                    var Addr = Node.addrStr;
                    if(!Transfer.TransferNodes[Addr])
                    {
                        let Item = {Node:Node, SendCount:0, GetCount:0, addrStr:Addr, TreeLevel:L, GetTiming:3 * CONSENSUS_PERIOD_TIME, };
                        Transfer.TransferNodes[Addr] = Item
                    }
                    Context.TransferFromAddr[Addr] = Transfer
                }
            }
        }
        Context.MLevelSend = Context.StartLevel
        return Context;
    }
    StartConsensus()
    {
        if(!CAN_START)
            return ;
        var StartBlockNum = GetCurrentBlockNumByTime();
        if(StartBlockNum < BLOCK_PROCESSING_LENGTH2)
            return ;
        this.CurrentBlockNum = StartBlockNum
        var Block0 = this.GetBlockContext(StartBlockNum - BLOCK_DELTA_ACTIVATE);
        if(!Block0.Active)
        {
            AddInfoBlock(Block0, "Activate")
            this.StartBlock(Block0)
        }
        else
        {
            AddInfoBlock(Block0, "Was Active")
        }
    }
    TrToInfo(Block, Array, StrInfo)
    {
        var Str = "";
        for(var i = 0; i < Array.length; i++)
        {
            var Item = Array[i];
            this.CheckCreateTransactionObject(Item)
            Str += this.GetStrFromHashShort(shaarr(Item.body)) + "(" + Item.body.length + "),"
        }
        AddInfoBlock(Block, "" + StrInfo + ": Arr=[" + Str + "]")
    }
    TRANSFER(Info, CurTime)
    {
        var startTime = process.hrtime();
        var Data = this.DataFromF(Info);
        var Node = Info.Node;
        Node.TransferBlockNum = Data.BlockNum
        Node.CurBlockNum = Data.BaseBlockNum + Data.BlockNum
        if(Data.Version !== 5)
            return ;
        var Block = this.GetBlockContext(Data.BlockNum);
        if(!Block || Block.StartLevel === undefined)
        {
            ADD_TO_STAT("TRANSFER_ERR_STARTLEVEL")
            this.AddCheckErrCount(Node, 1, "Err GetBlockContext")
            return ;
        }
        if(!Block.Active)
            this.StartBlock(Block)
        var Key = Node.addrStr;
        var Transfer = Block.TransferFromAddr[Key];
        if(!Transfer)
        {
            ADD_TO_STAT("NO_TRANSFER")
            this.AddCheckErrCount(Node, 1, "Err Transfer")
            return ;
        }
        Transfer.WasGet = true
        if(global.DoTxLog && Data.TicketArray.length)
            ToLog("TRANSFER BlockNum:" + Block.BlockNum + " TicketArray=" + Data.TicketArray.length + " from " + NodeName(Node))
        if(global.DoTxLog && Data.TxArray.length)
            ToLog("TRANSFER BlockNum:" + Block.BlockNum + " TxArray=" + Data.TxArray.length + " from " + NodeName(Node))
        this.ToMaxPOWList(Data.MaxPOW)
        this.ToMaxSumList(this.GetMaxSumListFromID(Node, Data.MaxSumID, Data.BlockList))
        var WasNewAdd = 0;
        if(Data.TxArray.length)
        {
            for(var i = 0; i < Data.TxArray.length; i++)
            {
                var Tr = Data.TxArray[i];
                var Res = this.AddTrToBlockQuote(Block, Tr);
                if(Res === 1)
                    WasNewAdd = 1
                if(global.USE_CHECK_SENDING && Res > 0)
                {
                    var Tt = Block.PowTxTree.find(Tr);
                    if(Tt)
                    {
                        if(!Tt.NodesList)
                            Tt.NodesList = []
                        Tt.NodesList.push(Node)
                        Tt.TreeLevel = Transfer.TreeLevel
                    }
                }
            }
        }
        else
        {
            for(var i = 0; i < Data.TicketArray.length; i++)
            {
                var Tr = this.AddTicketToBlockQuote(Block, Data.TicketArray[i]);
                if(Tr)
                {
                    if(!Tr.NodesList)
                        Tr.NodesList = []
                    Tr.NodesList.push(Node)
                }
            }
        }
        ADD_TO_STAT_TIME("TRANSFER_MS", startTime)
        var Delta = Date.now() - this.StartLoadBlockTime;
        if(Delta > 10 * 1000 && Node.TransferCount > 10)
        {
            Node.BlockProcessCount++
            Node.NextHotDelta = 10 * 1000
        }
        Node.TransferCount++
        Node.LastTimeTransfer = GetCurrentTime() - 0
        var Item = Transfer.TransferNodes[Key];
        Item.GetTiming = GetCurrentTime(Block.DELTA_CURRENT_TIME) - Block.StartTimeNum
        if(!Block.TransferNodesCount)
            Block.TransferNodesCount = 0
        Block.TransferNodesCount++
    }
    DoTransfer()
    {
        if(glStopNode)
            return ;
        if(!CAN_START)
            return ;
        var MaxPOWList;
        var MaxSumList;
        var start = this.CurrentBlockNum - BLOCK_PROCESSING_LENGTH;
        var finish = this.GetLastCorrectBlockNum();
        for(var b = start; b <= finish; b++)
        {
            var Block = this.GetBlock(b);
            if(!Block)
                continue;
            if(Block.StartLevel === undefined || Block.MLevelSend === undefined)
                continue;
            if(!Block.Active)
                continue;
            if(global.USE_TICKET)
                this.DoJobListTX(Block)
            if(Block.MLevelSend < 0)
            {
                this.CheckEndExchange(Block)
                continue;
            }
            if(Block.EndExchange)
                continue;
            var Transfer = Block.LevelsTransfer[Block.MLevelSend];
            if(!Transfer.WasSend)
            {
                if(!MaxPOWList)
                {
                    MaxPOWList = this.GetMaxPOWList()
                    MaxSumList = this.GetMaxSumList()
                }
                var ArrT;
                if(global.USE_TICKET)
                    ArrT = this.GetArrayFromTicketTree(Block)
                else
                    ArrT = this.GetArrayFromTxTree(Block)
                this.SendDataTransfer(Transfer, ArrT, MaxPOWList, MaxSumList, Block)
            }
            Transfer.WasSend = true
            var bNext = Transfer.WasGet;
            if(!bNext)
            {
                var CurTimeNum = GetCurrentTime(Block.DELTA_CURRENT_TIME) - 0;
                var DeltaTime = CurTimeNum - Block.StartTimeNum;
                if(DeltaTime > Transfer.MustDeltaTime)
                {
                    bNext = true
                    Block.ErrRun = "" + Transfer.LocalLevel + " " + Block.ErrRun
                    for(var Addr in Transfer.TransferNodes)
                    {
                        var Item = Transfer.TransferNodes[Addr];
                        ADD_TO_STAT("TRANSFER_TIME_OUT")
                        this.AddCheckErrCount(Item.Node, 1, "TRANSFER_TIME_OUT")
                    }
                    ADD_TO_STAT("TimeOutLevel")
                }
            }
            if(bNext)
            {
                if(Block.MLevelSend === 0)
                {
                    Block.EndExchangeTime = Date.now()
                    if(!global.USE_TICKET)
                        this.CheckEndExchange(Block)
                }
                Block.MLevelSend--
            }
        }
    }
    CheckEndExchange(Block)
    {
        if(Block.EndExchange)
            return ;
        if(!global.USE_TICKET)
        {
            this.CreateTreeHash(Block)
            return ;
        }
        if(!Block.JobListTX || !Block.EndExchangeTime)
            return ;
        var CurTime = Date.now();
        var Delta = CurTime - Block.EndExchangeTime;
        if(Delta >= TX_DELTA_PROCESS_TIME * 2)
        {
            if(global.DoTxLog)
                ToLog("END:" + Block.BlockNum + " -> CreateTreeHash")
            this.CreateTreeHash(Block)
        }
    }
    SendDataTransfer(Transfer, ArrT, MaxPOWList, MaxSumList, Block)
    {
        for(var Addr in Transfer.TransferNodes)
        {
            var Item = Transfer.TransferNodes[Addr];
            Transfer.SendCount++
            var arrPow = [];
            for(var i = 0; i < MaxPOWList.length; i++)
            {
                var elem = MaxPOWList[i];
                var Str = "POW:" + Item.Node.id + elem.BlockNum + "-" + GetHexFromArr(elem.AddrHash);
                var bWasSend = global.TreeBlockBuf.LoadValue(Str, 1);
                if(!bWasSend)
                {
                    global.TreeBlockBuf.SaveValue(Str, true)
                    arrPow.push(elem)
                }
            }
            var arrSum = [];
            for(var i = 0; i < MaxSumList.length; i++)
            {
                var elem = MaxSumList[i];
                var Str = "MAX:" + Item.Node.id + elem.BlockNum + "-" + GetHexFromArr(elem.SumHash);
                var bWasSend = global.TreeBlockBuf.LoadValue(Str, 1);
                if(!bWasSend)
                {
                    global.TreeBlockBuf.SaveValue(Str, true)
                    arrSum.push(elem)
                }
            }
            var Arr;
            if(global.USE_CHECK_SENDING)
                Arr = this.FilterArrForSendNode(Block, Item.Node, ArrT, global.USE_TICKET)
            else
                Arr = ArrT
            if(global.USE_LEVEL_WAY)
            {
                var Arr2 = [];
                for(var t = 0; t < Arr.length; t++)
                {
                    var Tr = Arr[t];
                    if(Tr.TreeLevel !== Transfer.TreeLevel)
                        Arr2.push(Tr)
                }
                Arr = Arr2
            }
            if(global.DoTxLog)
                ToLog("SEND TRANSFER BlockNum:" + Block.BlockNum + " Arr=" + Arr.length + " to " + NodeName(Item.Node))
            var BufData = this.CreateTransferBuffer(Arr, arrPow, arrSum, Block, Item.Node);
            this.Send(Item.Node, {"Method":"TRANSFER", "Context":{}, "Data":BufData}, 1)
            if(!Block.JobListTX)
                Block.JobListTX = []
            Block.JobListTX.push({Node:Item.Node, TreeLevel:Item.TreeLevel, Time:Date.now()})
        }
    }
    TRANSFERTX(Info, CurTime)
    {
        var Data = this.DataFromF(Info);
        var Node = Info.Node;
        var Block = this.GetBlockContext(Data.BlockNum);
        if(!Block)
        {
            return ;
        }
        if(global.DoTxLog)
            ToLog("TRANSFERTX BlockNum:" + Block.BlockNum + " Array=" + Data.Array.length + " from " + NodeName(Node))
        for(var i = 0; i < Data.Array.length; i++)
        {
            this.AddTrToBlockQuote(Block, Data.Array[i], 1)
        }
    }
    static
    TRANSFERTX_F()
    {
        return "{BlockNum:uint, Array:[{body:tr}]}";
    }
    static
    GETTRANSFERTX_F()
    {
        return "{BlockNum:uint, TicketArray:[{HashTicket:arr10}]}";
    }
    GETTRANSFERTX(Info, CurTime)
    {
        return ;
        var Data = this.DataFromF(Info);
        var Node = Info.Node;
        var Block = this.GetBlockContext(Data.BlockNum);
        if(!Block)
        {
            return ;
        }
        this.SendTrByTickets(Info, Node, Block, Data.TicketArray, 10)
    }
    CanSendTest()
    {
        return 1;
    }
    SendTrByTickets(Info, Node, Block, ArrTT, CountTrySend)
    {
        if(!Block.PowTxTree)
            return ;
        var Arr = [];
        var bFindTT = 0;
        var BufLength = 0;
        for(var i = 0; i < ArrTT.length; i++)
        {
            var Tr = ArrTT[i];
            this.CheckCreateTicketObject(Tr, Block.BlockNum)
            var Tr0 = Block.PowTxTree.find(Tr);
            if(Tr0)
            {
                if(Tr0.IsTx)
                {
                    if(this.CanSendTest())
                        Arr.push(Tr0)
                    BufLength += Tr0.body.length
                    if(BufLength > MAX_BLOCK_SIZE)
                        break;
                }
                else
                {
                    bFindTT = 1
                }
            }
        }
        if(bFindTT && CountTrySend)
        {
            let SELF = this;
            setTimeout(function ()
            {
                SELF.SendTrByTickets(Info, Node, Block, ArrTT, CountTrySend - 1)
            }, 100)
            return ;
        }
        if(!Arr.length)
            return ;
        var SendData = {"Method":"RETTRANSFERTX", "Context":Info.Context, "Data":{BlockNum:Block.BlockNum, Array:Arr, }};
        this.SendF(Node, SendData, global.MAX_BLOCK_SIZE + 1000)
    }
    static
    RETTRANSFERTX_F()
    {
        return "{BlockNum:uint,Array:[{body:tr}]}";
    }
    RETTRANSFERTX(Info, CurTime)
    {
        return ;
        var Data = this.DataFromF(Info);
        var Node = Info.Node;
        Node.TransferBlockNum = Data.BlockNum
        var Block = this.GetBlockContext(Data.BlockNum);
        if(!Block || !Block.PowTxTree)
        {
            return ;
        }
        if(global.DoTxLog)
            ToLog("RETTRANSFERTX BlockNum:" + Block.BlockNum + " Array=" + Data.Array.length + " from " + NodeName(Node))
        for(var i = 0; i < Data.Array.length; i++)
        {
            var Tr = Data.Array[i];
            this.CheckCreateTransactionObject(Tr)
            var Tr0 = Block.PowTxTree.find(Tr);
            if(Tr0)
            {
                if(!Tr0.IsTx)
                {
                    Tr0.IsTx = 1
                    Tr0.body = Tr.body
                    Tr0.HASH = Tr.HASH
                }
            }
        }
    }
    DoJobListTX(Block)
    {
        if(Block.EndExchange || !Block.JobListTX || !Block.PowTicketTree)
            return ;
        var ArrTx;
        var CurTime = Date.now();
        for(var i = 0; i < Block.JobListTX.length; i++)
        {
            var JobItem = Block.JobListTX[i];
            var Delta = CurTime - JobItem.Time;
            if(!JobItem.WasSend && Delta >= TX_DELTA_PROCESS_TIME)
            {
                JobItem.WasSend = 1
                if(!ArrTx)
                    ArrTx = this.GetArrayFromTxTree(Block)
                var Arr = this.FilterArrForSendNode(Block, JobItem.Node, ArrTx);
                if(!Arr.length)
                    return ;
                if(global.DoTxLog)
                    ToLog("DoJobListTX BlockNum:" + Block.BlockNum + "  Arr=" + Arr.length + " to " + NodeName(JobItem.Node))
                var SendData = {"Method":"TRANSFERTX", "Context":{}, "Data":{BlockNum:Block.BlockNum, Array:Arr, }};
                this.SendF(JobItem.Node, SendData, global.MAX_BLOCK_SIZE + 1000)
            }
        }
    }
    FindNodeTicket(Block, Tr, Node, bTt)
    {
        var Ticket;
        if(bTt)
            Ticket = Tr
        else
            Ticket = Block.PowTicketTree.find(Tr)
        if(Ticket && Ticket.NodesList)
        {
            for(var n = 0; n < Ticket.NodesList.length; n++)
            {
                var NodeItem = Ticket.NodesList[n];
                if(NodeItem === Node)
                {
                    return 1;
                }
            }
        }
        return 0;
    }
    FilterArrForSendNode(Block, Node, ArrTx, bTt)
    {
        var Arr = [];
        for(var t = 0; t < ArrTx.length; t++)
        {
            var Tr = ArrTx[t];
            if(this.FindNodeTicket(Block, Tr, Node, bTt))
                continue;
            Arr.push(Tr)
        }
        return Arr;
    }
    CheckEndExchange00(Block)
    {
        if(Block.EndExchange)
            return ;
        var CurTime = Date.now();
        var CanEnd = 1;
        var bSend = 0;
        var it = Block.PowTxTree.iterator(), Tr;
        while((Tr = it.next()) !== null)
        {
            if(!Tr.IsTx)
            {
                if(!Tr.LastProcessTime)
                    throw "!Tr.LastProcessTime";
                var Delta = CurTime - Tr.LastProcessTime;
                if(Delta < TX_PROCESS_TIME * 10)
                {
                    bSend = 1
                    CanEnd = 0
                    break;
                }
                else
                {
                    var Stop = 1;
                }
            }
        }
        if(CanEnd)
        {
            if(global.DoTxLog)
                ToLog("END:" + Block.BlockNum + " -> CreateTreeHash")
            this.CreateTreeHash(Block)
        }
        else
            if(bSend)
            {
                this.CheckTxExchange(Block, bSend)
            }
    }
    CheckTxExchange(Block)
    {
        return ;
        if(Block.EndExchange)
            return ;
        if(!Block.PowTxTree)
            return ;
        var CurTime = Date.now();
        var ArrNodesArr = [];
        var it = Block.PowTxTree.iterator(), Tr;
        while((Tr = it.next()) !== null)
        {
            if(!Tr.IsTx)
            {
                if(!Tr.LastProcessTime)
                    throw "!Tr.LastProcessTime";
                var Delta = CurTime - Tr.LastProcessTime;
                if(Delta >= TX_PROCESS_TIME)
                {
                    for(var i = 0; i < Tr.Nodes.length; i++)
                    {
                        var TrNode = Tr.Nodes[i];
                        var LocDelta = CurTime - TrNode.Time;
                        if(!TrNode.WasSend && LocDelta >= TX_PROCESS_TIME)
                        {
                            var FindArr = undefined;
                            for(var n = 0; n < ArrNodesArr.length; n++)
                            {
                                var ElArr = ArrNodesArr[n];
                                if(ElArr.Node === TrNode.Node)
                                {
                                    FindArr = ElArr
                                    break;
                                }
                            }
                            if(!FindArr)
                            {
                                FindArr = {Node:TrNode.Node, Arr:[]}
                                ArrNodesArr.push(FindArr)
                            }
                            Tr.LastProcessTime = CurTime
                            TrNode.WasSend = 1
                            FindArr.Arr.push(Tr)
                            break;
                        }
                    }
                }
            }
        }
        for(var n = 0; n < ArrNodesArr.length; n++)
        {
            var ElArr = ArrNodesArr[n];
            if(global.DoTxLog)
                ToLog("CheckTxExchange BlockNum:" + Block.BlockNum + " Array=" + ElArr.Arr.length + " to " + NodeName(ElArr.Node))
            var SendData = {"Method":"GETTRANSFERTX", "Context":{}, "Data":{BlockNum:Block.BlockNum, TicketArray:ElArr.Arr, }};
            this.SendF(ElArr.Node, SendData, ElArr.Arr.length * global.TR_TICKET_HASH_LENGTH + 1000)
        }
    }
    GetMaxSumListFromID(Node, MaxSumID, BlockList)
    {
        var Str0 = "GETBL:" + Node.id;
        for(var i = 0; i < BlockList.length; i++)
        {
            var elemBlockList = BlockList[i];
            global.TreeBlockBuf.SaveValue(Str0 + elemBlockList.ID, elemBlockList)
        }
        var MaxSum = [];
        MaxSum:
        for(var i = 0; i < MaxSumID.length; i++)
        {
            var elem = MaxSumID[i];
            var Arr = [];
            for(var n = 0; n < elem.SumListID.length; n++)
            {
                var elemBlockList = global.TreeBlockBuf.LoadValue(Str0 + elem.SumListID[n], 1);
                if(elemBlockList === undefined)
                {
                    continue MaxSum;
                }
                Arr.push(elemBlockList)
            }
            elem.SumList = Arr
            MaxSum.push(elem)
        }
        return MaxSum;
    }
    CreateTransferBuffer(ArrT, MaxPOWList, MaxSumList, Block, Node)
    {
        var Data;
        var MaxSumID = [];
        var BlockList = [];
        for(var i = 0; i < MaxSumList.length; i++)
        {
            var elem0 = MaxSumList[i];
            var ArrID = [];
            for(var n = 0; n < elem0.SumList.length; n++)
            {
                var elemBlockList = elem0.SumList[n];
                var Str = "BL:" + Node.id + GetHexFromArr(elemBlockList.Hash3);
                var ID = global.TreeBlockBuf.LoadValue(Str, 1);
                if(ID === undefined)
                {
                    this.SendBlockID++
                    ID = this.SendBlockID % 65535
                    global.TreeBlockBuf.SaveValue(Str, ID)
                    elemBlockList.ID = ID
                    BlockList.push(elemBlockList)
                }
                ArrID.push(ID)
            }
            MaxSumID.push({BlockNum:elem0.BlockNum, SumHash:elem0.SumHash, SumListID:ArrID})
        }
        var ArrTt, ArrTx;
        if(global.USE_TICKET)
        {
            ArrTt = ArrT
            ArrTx = []
        }
        else
        {
            ArrTt = []
            ArrTx = ArrT
        }
        Data = {"Version":5, "BlockNum":Block.BlockNum, "Reserv1":0, "MaxPOW":MaxPOWList, "Reserv2":0, "BaseBlockNum":this.CurrentBlockNum - Block.BlockNum,
            "MaxSumID":MaxSumID, "BlockList":BlockList, "TicketArray":ArrTt, "TxArray":ArrTx, "NoSendTx":Node.NoSendTx, }
        var BufWrite = BufLib.GetBufferFromObject(Data, FORMAT_DATA_TRANSFER, MAX_BLOCK_SIZE + 30000, WorkStructSend);
        return BufWrite;
    }
    static
    TRANSFER_F()
    {
        return FORMAT_DATA_TRANSFER;
    }
    CheckingMaxPowOther(Block)
    {
        var POW = Block.MaxPOW;
        if(POW && POW.Hash && CompareArr(POW.PowHash, Block.PowHash) < 0)
        {
            var LoadBlockNum = Block.BlockNum;
            var LoadHash = POW.Hash;
            var StrKey = this.GetStrFromHashShort(LoadHash);
            var StrHashWas = this.GetStrFromHashShort(Block.Hash);
            this.StartLoadBlockHeader(LoadHash, LoadBlockNum, "START OTHER:" + StrKey + " WAS:" + StrHashWas, false)
            AddInfoBlock(Block, "REQ H: " + StrKey)
        }
        Block.CheckMaxPow = true
    }
    AddToMaxPOW(Block, item, Node)
    {
        if(Block && item)
        {
            if(!Block.MaxPOW)
                Block.MaxPOW = {}
            var POW = Block.MaxPOW;
            if(!Block.PrevHash)
                return ;
            item.BlockNum = Block.BlockNum
            item.PrevHash = Block.PrevHash
            CalcHashBlockFromSeqAddr(item, Block.PrevHash, global.MINING_VERSION_NUM)
            if(POW.SeqHash === undefined || CompareArr(item.PowHash, POW.PowHash) < 0)
            {
                POW.AddrHash = item.AddrHash
                POW.Hash = item.Hash
                POW.PowHash = item.PowHash
                POW.PrevHash = item.PrevHash
                POW.TreeHash = item.TreeHash
                POW.SeqHash = item.SeqHash
            }
            if(Block.SeqHash && CompareArr(item.SeqHash, Block.SeqHash) === 0)
            {
                if(POW.LocalSeqHash === undefined || CompareArr(POW.LocalSeqHash, Block.SeqHash) !== 0 || CompareArr(item.PowHash, POW.PowLocalHash) < 0)
                {
                    POW.LocalAddrHash = item.AddrHash
                    POW.PowLocalHash = item.PowHash
                    POW.LocalSeqHash = Block.SeqHash
                }
            }
            var wasLider;
            if(POW.MaxTree)
                wasLider = POW.MaxTree.min()
            this.AddPOWToMaxTree(POW, item)
            if(wasLider)
            {
                var newLider = POW.MaxTree.min();
                if(newLider !== wasLider)
                {
                    var Power = GetPowPower(newLider.PowHash);
                    AddInfoBlock(Block, "MaxPOW: " + Power)
                }
            }
        }
    }
    AddPOWToMaxTree(POW, item)
    {
        if(!POW.MaxTree)
        {
            POW.MaxTree = new RBTree(function (a,b)
            {
                return CompareArr(a.PowHash, b.PowHash);
            })
        }
        if(!POW.MaxTree.find(item))
        {
            POW.MaxTree.insert(item)
            if(POW.MaxTree.size > 12)
            {
                var maxitem = POW.MaxTree.max();
                POW.MaxTree.remove(maxitem)
            }
        }
    }
    GetMaxPOWList()
    {
        var arr = [];
        var start, finish;
        start = this.CurrentBlockNum + TIME_START_SAVE - 2
        finish = this.CurrentBlockNum
        for(var b = start; b < finish; b++)
        {
            var Block = this.GetBlock(b);
            if(Block && Block.Prepared && Block.MaxPOW)
            {
                if(Block.MaxPOW && Block.MaxPOW.MaxTree)
                {
                    this.RecreateMaxPOW(Block)
                    var it = Block.MaxPOW.MaxTree.iterator(), Item;
                    while((Item = it.next()) !== null)
                    {
                        arr.push(Item)
                    }
                }
            }
        }
        return arr;
    }
    ToMaxPOWList(Arr)
    {
        for(var i = 0; i < Arr.length; i++)
        {
            var item = Arr[i];
            if(item && item.BlockNum >= this.CurrentBlockNum - BLOCK_PROCESSING_LENGTH && item.BlockNum < this.CurrentBlockNum)
            {
                var Block = this.GetBlock(item.BlockNum);
                this.AddToMaxPOW(Block, item)
            }
        }
    }
    RecreateMaxPOW(Block)
    {
        if(Block.MaxPOW && Block.MaxPOW.MaxTree)
        {
            var Tree = Block.MaxPOW.MaxTree;
            var it = Tree.iterator(), Item;
            while((Item = it.next()) !== null)
            {
                if(!Item.PrevHash)
                    ToLog("NO Item.PrevHash in " + Block.BlockNum)
                if(Item.PrevHash && CompareArr(Item.PrevHash, Block.PrevHash) !== 0)
                {
                    Tree.remove(Item)
                    it = Tree.iterator()
                }
            }
        }
        Block.CheckMaxSum = false
    }
    CheckMaxSum(Block)
    {
        var POW = Block.MaxSum;
        var List = this.GetBlockList(Block.BlockNum);
        var SumPow = this.GetSumFromList(List, Block.BlockNum);
        if(POW && POW.SumHash && POW.SumPow > SumPow)
        {
            var LoadBlockNum = Block.BlockNum;
            var LoadHash = POW.SumHash;
            var StrKey = this.GetStrFromHashShort(LoadHash);
            if(this.StartLoadBlockHeader(LoadHash, LoadBlockNum, "START POW:" + POW.SumPow + ">" + SumPow + " SH:" + StrKey, true))
                AddInfoBlock(Block, "REQ SH: " + StrKey)
        }
        Block.CheckMaxSum = true
    }
    AddToMaxSum(Block, item)
    {
        if(Block && item)
        {
            if(!Block.MaxSum)
                Block.MaxSum = {}
            var POW = Block.MaxSum;
            var SumPow = this.GetSumFromList(item.SumList, Block.BlockNum);
            if(POW.SumHash === undefined || SumPow > POW.SumPow)
            {
                POW.SumPow = SumPow
                POW.SumHash = item.SumHash
                POW.SumList = item.SumList
                AddInfoBlock(Block, "SumPow:" + POW.SumPow)
                Block.CheckMaxSum = false
            }
            return SumPow;
        }
        return 0;
    }
    GetMaxSumList()
    {
        var Arr = [];
        var start, finish;
        start = this.CurrentBlockNum + TIME_START_LOAD - 2
        finish = this.CurrentBlockNum
        for(var b = start; b <= finish; b++)
        {
            var Block = this.GetBlock(b);
            if(Block && Block.bSave && Block.MaxSum && Block.MaxSum.SumHash)
            {
                var POW = Block.MaxSum;
                var item = {BlockNum:Block.BlockNum, SumHash:POW.SumHash, SumList:POW.SumList, };
                Arr.push(item)
            }
        }
        return Arr;
    }
    ToMaxSumList(Arr)
    {
        var start, finish;
        start = this.CurrentBlockNum + TIME_START_LOAD - 2
        finish = this.CurrentBlockNum
        for(var i = 0; i < Arr.length; i++)
        {
            var item = Arr[i];
            if(item && item.BlockNum >= start && item.BlockNum <= finish)
            {
                var Block = this.GetBlock(item.BlockNum);
                if(Block)
                {
                    this.AddToMaxSum(Block, item)
                    this.CheckMaxSum(Block)
                }
            }
        }
    }
    GetBlockList(CurBlockNum)
    {
        var arr = [];
        for(var b = CurBlockNum - SUM_LIST_LENGTH + 1; b <= CurBlockNum; b++)
        {
            var Block = this.GetBlock(b);
            if(Block && Block.bSave)
            {
                var item = {AddrHash:Block.AddrHash, SeqHash:Block.SeqHash, };
                arr.push(item)
            }
            else
            {
                return [];
            }
        }
        return arr;
    }
    GetSumFromList(arr, CurBlockNum)
    {
        var SumPow = 0;
        if(arr.length !== SUM_LIST_LENGTH)
            return SumPow;
        var CountLoad = 0;
        var BlockNumStart = CurBlockNum - arr.length + 1;
        for(var i = 0; i < arr.length; i++)
        {
            var Item = arr[i];
            if(Item)
            {
                Item.BlockNum = BlockNumStart + i
                var Value = GetHashFromSeqAddr(Item.SeqHash, Item.AddrHash, Item.BlockNum, undefined, global.MINING_VERSION_NUM);
                SumPow += GetPowPower(Value.PowHash)
                Item.Hash3 = Value.Hash
            }
            else
            {
                break;
            }
        }
        return SumPow;
    }
    GetArrayFromTxTree(Block)
    {
        if(!Block.PowTxTree)
            return [];
        var BufLength = 0;
        var arr = [];
        var it = Block.PowTxTree.iterator(), Item;
        while((Item = it.next()) !== null)
        {
            arr.push(Item)
            BufLength += Item.body.length
            if(BufLength > MAX_BLOCK_SIZE)
                break;
        }
        return arr;
    }
    GetArrayFromTicketTree(Block)
    {
        if(!Block.PowTicketTree)
            return [];
        var arr = [];
        var it = Block.PowTicketTree.iterator(), Item;
        while((Item = it.next()) !== null)
        {
            arr.push(Item)
        }
        return arr;
    }
    AddToQuote(Tree, Tr)
    {
        var Tr0 = Tree.find(Tr);
        if(Tr0)
        {
            return 3;
        }
        else
        {
            Tree.insert(Tr)
            if(Tree.size > MAX_TRANSACTION_LIMIT)
            {
                var maxitem = Tree.max();
                Tree.remove(maxitem)
                if(CompareArr(maxitem.HashPow, Tr.HashPow) === 0)
                    return 0;
            }
            return 1;
        }
    }
    AddTicketToBlockQuote(Block, Tr)
    {
        if(Block.PowTicketTree)
        {
            var Res = this.IsValidTicket(Tr, Block.BlockNum);
            if(Res >= 1)
            {
                Res = this.AddToQuote(Block.PowTicketTree, Tr)
                if(Res)
                    return Block.PowTicketTree.find(Tr);
            }
            return null;
        }
    }
    AddTrToBlockQuote(Block, Tr, bTTAdd)
    {
        if(Block.PowTxTree)
        {
            var Res = this.IsValidTransaction(Tr, Block.BlockNum);
            if(Res >= 1)
            {
                if(bTTAdd)
                {
                    Res = this.AddToQuote(Block.PowTicketTree, Tr)
                    if(Res <= 0)
                        return Res;
                }
                Res = this.AddToQuote(Block.PowTxTree, Tr)
            }
            return Res;
        }
    }
    GetBlockContext(BlockNum)
    {
        if(BlockNum === undefined || !this.IsCorrectBlockNum(BlockNum))
            return undefined;
        var Context = this.GetBlock(BlockNum);
        if(!Context || !Context.StartTimeNum)
        {
            Context = this.CreateBlockContext()
            Context.BlockNum = BlockNum
            Context.DELTA_CURRENT_TIME = GetDeltaCurrentTime()
            Context.StartTimeNum = (BlockNum - 1 + BLOCK_DELTA_ACTIVATE) * CONSENSUS_PERIOD_TIME + START_NETWORK_DATE
            this.BlockChain[BlockNum] = Context
        }
        if(!Context.TransferFromAddr)
        {
            Context.TransferFromAddr = {}
            Context.LevelsTransfer = []
        }
        return Context;
    }
    StartBlock(Block)
    {
        Block.Active = true
    }
    IsCorrectBlockNum(BlockNum)
    {
        var start = this.CurrentBlockNum - BLOCK_PROCESSING_LENGTH;
        var finish = this.GetLastCorrectBlockNum();
        if(BlockNum < start || BlockNum > finish)
        {
            return false;
        }
        return true;
    }
    GetLastCorrectBlockNum()
    {
        return this.CurrentBlockNum + 4;
    }
    GetStrSendCount(Block)
    {
        if(!Block)
            return "";
        var Str = "";
        var Count = 0;
        for(var L = 0; L < Block.LevelsTransfer.length; L++)
        {
            var Transfer = Block.LevelsTransfer[L];
            Str = Str + "," + Transfer.SendCount
            if(typeof Transfer.SendCount === "number")
                Count = Count + Transfer.SendCount
        }
        return "" + Count + ":[" + Str.substr(1) + "]";
    }
    GetStrGetCount(Block)
    {
        if(!Block)
            return "";
        var Str = "";
        var Count = 0;
        for(var L = 0; L < Block.LevelsTransfer.length; L++)
        {
            var Transfer = Block.LevelsTransfer[L];
            Str = Str + "," + Transfer.GetCount
            Count = Count + Transfer.GetCount
        }
        return "" + Count + ":[" + Str.substr(1) + "]";
    }
    ToStrBlocks(DopStr)
    {
        var num = Math.floor(this.CurrentBlockNum / 3) * 3;
        var start = num - BLOCK_PROCESSING_LENGTH2 + 2;
        var finish = this.CurrentBlockNum;
        if(!DopStr)
            DopStr = ""
        var Str = "";
        for(var b = start; b <= finish; b++)
        {
            var hashStr = "";
            var Block = this.GetBlock(b);
            if(Block && Block.ErrRun)
            {
                if(Block.ErrRun)
                    hashStr = Block.ErrRun.substr(0, 5)
                else
                    if(Block && Block.TreeHash)
                        hashStr = "-" + GetHexFromAddres(Block.TreeHash).substr(0, 3) + "-"
            }
            else
                if(Block && Block.TreeHash)
                {
                    hashStr = GetHexFromAddres(Block.TreeHash).substr(0, 5)
                }
            Str = Str + "|" + (hashStr + "     ").substr(0, 5)
        }
        Str = Str.substr(1)
        ToInfo("" + finish + " -> " + Str + " " + DopStr)
    }
    PreparePOWHash(Block)
    {
        if(!Block.TreeHash)
            Block.TreeHash = [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]
        var PrevHash = this.GetPrevHash(Block);
        if(!PrevHash)
        {
            AddInfoBlock(Block, "-err prev hash-")
            return false;
        }
        Block.PrevHash = PrevHash
        Block.SeqHash = this.GetSeqHash(Block.BlockNum, Block.PrevHash, Block.TreeHash)
        this.CreatePOWNew(Block)
        Block.Prepared = true
        if(global.USE_MINING && !Block.StartMining)
        {
            Block.StartMining = true
            AddInfoBlock(Block, "-send mining-")
            global.SetCalcPOW(Block, "FastCalcBlock")
        }
        return true;
    }
    CalcTreeHashFromArrTr(BlockNum, arrTr)
    {
        var arrHASH = [];
        for(var i = 0; i < arrTr.length; i++)
        {
            var Tr = arrTr[i];
            arrHASH.push(Tr.HASH)
        }
        var Tree = CalcMerklFromArray(BlockNum, arrHASH);
        return Tree.Root;
    }
    CreateTreeHash(Block)
    {
        if(Block.EndExchange)
            return ;
        Block.EndExchange = true
        if(Block.bSave)
            return ;
        var PrevBlock = this.GetBlock(Block.BlockNum - 1);
        if(PrevBlock && !PrevBlock.EndExchange && !PrevBlock.bSave)
        {
            AddInfoBlock(Block, "Prev Not End Exchange")
            return ;
        }
        AddInfoBlock(Block, "End Exchange,N=" + Block.TransferNodesCount)
        var arrContent = [];
        var arrHASH = [];
        var arrTr = this.GetArrayFromTxTree(Block);
        this.AddDAppTransactions(Block.BlockNum, arrTr)
        for(var i = 0; i < arrTr.length; i++)
        {
            var Tr = arrTr[i];
            arrContent.push(Tr.body)
            arrHASH.push(Tr.HASH)
        }
        var Tree = CalcMerklFromArray(Block.BlockNum, arrHASH);
        Block.TreeHash = Tree.Root
        Block.arrContent = arrContent
        Block.TrCount = Block.arrContent.length
    }
    WatchdogSaved(BlockNum)
    {
        var Block = this.GetBlock(BlockNum);
        if(!Block)
        {
            ToLog("#1 WatchdogSaved: no BlockNum=" + BlockNum)
            return ;
        }
        if(Block.bSave)
        {
            var BlockDB = this.ReadBlockDB(BlockNum);
            if(!BlockDB)
            {
                Block.bSave = false
                return ;
            }
            if(CompareArr(BlockDB.Hash, Block.Hash) !== 0)
            {
                AddInfoBlock(Block, "=ERR:WATCHDOG=")
                ToLog("#3 WatchdogSaved: Error Hash on Num=" + BlockNum)
                return ;
            }
            if(CompareArr(BlockDB.SumHash, Block.SumHash) !== 0)
            {
                AddInfoBlock(Block, "=ERR:WATCHDOG=")
                ToLog("#4 WatchdogSaved: Error SumHash on Num=" + BlockNum)
                return ;
            }
            if(CompareArr(BlockDB.SeqHash, Block.SeqHash) !== 0)
            {
                AddInfoBlock(Block, "=ERR:WATCHDOG=")
                ToLog("#5 WatchdogSaved: Error SeqHash on Num=" + BlockNum)
                return ;
            }
            var PrevHash = this.GetPrevHash(Block);
            if(!PrevHash)
            {
                AddInfoBlock(Block, "=ERR:WATCHDOG=")
                ToLog("#6 WatchdogSaved: Error PrevHash on Num=" + BlockNum)
                return ;
            }
            var SeqHash = this.GetSeqHash(Block.BlockNum, PrevHash, Block.TreeHash);
            if(CompareArr(SeqHash, Block.SeqHash) !== 0)
            {
                AddInfoBlock(Block, "=ERR:WATCHDOG=")
                ToLog("#7 WatchdogSaved: Error SeqHash on Num=" + BlockNum)
                return ;
            }
            PrevHash = this.GetPrevHashDB(BlockDB)
            SeqHash = this.GetSeqHash(BlockDB.BlockNum, PrevHash, BlockDB.TreeHash)
            if(CompareArr(SeqHash, BlockDB.SeqHash) !== 0)
            {
                AddInfoBlock(Block, "=ERR:WATCHDOG=")
                ToLog("#8 WatchdogSaved: Error SeqHash on Num=" + BlockNum)
                return ;
            }
        }
    }
    DoBlockChain()
    {
        if(glStopNode)
            return ;
        if(!CAN_START)
            return ;
        this.StartConsensus()
        var CURRENTBLOCKNUM = this.CurrentBlockNum;
        if(GrayConnect())
        {
            if(!this.LoadHistoryMode)
                this.StartLoadHistory(undefined, 1)
            return ;
        }
        if(this.LoadHistoryMode)
            return ;
        var bWasSave = false;
        var LoadBlockNum;
        var LoadHash;
        var start_save = CURRENTBLOCKNUM + TIME_START_SAVE;
        for(var BlockNum = CURRENTBLOCKNUM - BLOCK_PROCESSING_LENGTH2; BlockNum > BLOCK_PROCESSING_LENGTH2 && BlockNum < CURRENTBLOCKNUM; BlockNum++)
        {
            var Block = this.GetBlock(BlockNum);
            if(!Block)
            {
                Block = this.GetBlockContext(BlockNum)
                if(!Block)
                {
                    continue;
                }
            }
            if(Block.bSave)
            {
                var BlockDB = this.ReadBlockDB(BlockNum);
                if(!BlockDB)
                {
                    Block.bSave = false
                }
            }
            if(global.WATCHDOG_DEV)
                this.WatchdogSaved(Block.BlockNum)
            if(Block.bSave)
            {
                bWasSave = true
                if(Block.MaxSum && !Block.CheckMaxSum)
                {
                    AddInfoBlock(Block, "CheckMaxSum")
                    this.CheckMaxSum(Block)
                }
                if(BlockNum <= CURRENTBLOCKNUM - BLOCK_PROCESSING_LENGTH * 4)
                {
                    Block.TransferFromAddr = undefined
                    Block.LevelsTransfer = undefined
                    Block.mapData = undefined
                    Block.MaxPOW = undefined
                    Block.MaxSum = undefined
                    Block.arrContent = undefined
                    if(Block.PowTxTree)
                    {
                        Block.PowTxTree.clear()
                        Block.PowTxTree = undefined
                    }
                }
                continue;
            }
            var PrevBlock = this.GetBlock(BlockNum - 1);
            if(!PrevBlock)
            {
                Block.HasErr = 1
                AddInfoBlock(Block, "!PrevBlock")
                continue;
            }
            if(BlockNum >= CURRENTBLOCKNUM + TIME_END_EXCHANGE)
            {
                if(!Block.Active)
                {
                    AddInfoBlock(Block, "WAIT ACTIVATE")
                    continue;
                }
                else
                    if(!Block.EndExchange)
                    {
                        AddInfoBlock(Block, "WAIT EXCHANGE")
                        continue;
                    }
            }
            if(BlockNum === CURRENTBLOCKNUM + TIME_START_POW || Block.EndExchange)
                if(!Block.Prepared)
                {
                    if(!Block.EndExchange)
                        this.CreateTreeHash(Block)
                    AddInfoBlock(Block, "Start POW")
                    this.PreparePOWHash(Block)
                    if(!Block.Prepared)
                        AddInfoBlock(Block, "!!Prepared")
                    continue;
                }
            if(!Block.EndExchange)
            {
                AddInfoBlock(Block, "Not EndExchange")
                Block.HasErr = 1
                Block.Prepared = 0
                this.CreateTreeHash(Block)
            }
            if(!Block.Prepared)
            {
                Block.HasErr = 1
                AddInfoBlock(Block, "Not was Prepared")
                this.PreparePOWHash(Block)
                if(!Block.Prepared)
                    continue;
            }
            {
                var PrevHash = this.GetPrevHash(Block);
                if(!PrevHash)
                {
                    Block.HasErr = 1
                    continue;
                }
                var SeqHash = this.GetSeqHash(Block.BlockNum, PrevHash, Block.TreeHash);
                if(CompareArr(SeqHash, Block.SeqHash) !== 0)
                {
                    Block.HasErr = 1
                    AddInfoBlock(Block, "New fast pow")
                    this.PreparePOWHash(Block)
                }
                if(Block.MaxPOW && Block.MaxPOW.SeqHash && Block.MaxPOW.AddrHash && Block.MaxPOW.LocalSeqHash && CompareArr(Block.SeqHash,
                Block.MaxPOW.LocalSeqHash) === 0)
                {
                    if(CompareArr(Block.SeqHash, Block.MaxPOW.LocalSeqHash) === 0 && CompareArr(Block.MaxPOW.PowLocalHash, Block.PowHash) < 0)
                    {
                        Block.AddrHash = Block.MaxPOW.LocalAddrHash
                        CalcHashBlockFromSeqAddr(Block, Block.PrevHash, global.MINING_VERSION_NUM)
                        AddInfoBlock(Block, "->Local lider:" + GetPowPower(Block.PowHash))
                    }
                    if(CompareArr(Block.SeqHash, Block.MaxPOW.SeqHash) === 0 && CompareArr(Block.MaxPOW.AddrHash, Block.AddrHash) !== 0 && CompareArr(Block.MaxPOW.PowHash,
                    Block.PowHash) < 0)
                    {
                        Block.AddrHash = Block.MaxPOW.AddrHash
                        CalcHashBlockFromSeqAddr(Block, Block.PrevHash, global.MINING_VERSION_NUM)
                        AddInfoBlock(Block, "->Max lider")
                    }
                }
                else
                {
                    Block.HasErr = 1
                    AddInfoBlock(Block, "ERROR MaxPOW")
                }
                if(Block.MaxPOW && Block.MaxPOW.SeqHash && !Block.CheckMaxPow && !Block.CheckMaxSum && CompareArr(Block.SeqHash, Block.MaxPOW.SeqHash) !== 0)
                {
                    AddInfoBlock(Block, "CheckMaxPow")
                    this.CheckingMaxPowOther(Block)
                }
                if(BlockNum > start_save)
                    continue;
                if(PrevBlock.bSave && this.BlockNumDB + 1 >= Block.BlockNum)
                {
                    this.AddToStatBlockConfirmation(Block)
                    if(this.WriteBlockDB(Block))
                    {
                        if(Block.arrContent && Block.arrContent.length)
                            ADD_TO_STAT("MAX:TRANSACTION_COUNT", Block.arrContent.length)
                        AddInfoBlock(Block, "SAVE TO DB: " + GetPowPower(Block.PowHash))
                    }
                    else
                    {
                        Block.HasErr = 1
                        AddInfoBlock(Block, "ERROR WRITE DB")
                    }
                    this.AddToMaxSum(Block, {SumHash:Block.SumHash, SumList:this.GetBlockList(Block.BlockNum), })
                }
                else
                {
                    Block.HasErr = 1
                    if(!PrevBlock.bSave)
                        AddInfoBlock(Block, "Prev block not saved")
                    else
                        AddInfoBlock(Block, "Low BlockNumDB")
                }
            }
        }
        for(var BlockNum = CURRENTBLOCKNUM - BLOCK_PROCESSING_LENGTH2; BlockNum > BLOCK_PROCESSING_LENGTH2 && BlockNum < start_save; BlockNum++)
        {
            var Block = this.GetBlock(BlockNum);
            if(Block && !Block.bSave && Block.TrCount && Block.TreeHash && !IsZeroArr(Block.TreeHash) && !Block.WasSaveDataTree)
            {
                this.SaveDataTreeToDB(Block)
                Block.WasSaveDataTree = 1
                AddInfoBlock(Block, "*SAVE DATA TREE*")
            }
        }
        this.RelayMode = !bWasSave
        this.FREE_MEM_BLOCKS(CURRENTBLOCKNUM - BLOCK_COUNT_IN_MEMORY)
    }
    CreatePOWNew(Block)
    {
        CreateHashMinimal(Block, GENERATE_BLOCK_ACCOUNT)
        this.AddToMaxPOW(Block, {SeqHash:Block.SeqHash, AddrHash:Block.AddrHash, PrevHash:Block.PrevHash, TreeHash:Block.TreeHash,
        })
    }
    SetNoPOW(BlockNumFrom, bReload, RefBlockNum)
    {
        var CurNum = BlockNumFrom;
        var finish = this.GetLastCorrectBlockNum();
        while(true)
        {
            var BlockMem = this.BlockChain[CurNum];
            if(BlockMem)
            {
                if(BlockMem.Prepared)
                {
                    AddInfoBlock(BlockMem, "-reset POW:" + RefBlockNum + "/" + bReload)
                    BlockMem.bSave = false
                    BlockMem.Prepared = false
                    BlockMem.StartMining = false
                    this.PreparePOWHash(BlockMem)
                }
                this.RecreateMaxPOW(BlockMem)
            }
            if(!BlockMem && CurNum > finish)
                break;
            CurNum++
        }
    }
    MiningProcess(msg)
    {
        var BlockMining = this.GetBlock(msg.BlockNum);
        if(!BlockMining)
        {
            return ;
        }
        if(!BlockMining.StartMining || BlockMining.bSave)
            return ;
        if(BlockMining && BlockMining.Hash && BlockMining.SeqHash && CompareArr(BlockMining.SeqHash, msg.SeqHash) === 0)
        {
            var ValueOld = GetHashFromSeqAddr(BlockMining.SeqHash, BlockMining.AddrHash, BlockMining.BlockNum);
            var ValueMsg = GetHashFromSeqAddr(msg.SeqHash, msg.AddrHash, BlockMining.BlockNum);
            var bWas = 0;
            if(CompareArr(ValueOld.Hash1, ValueMsg.Hash1) > 0)
            {
                var Nonce1 = ReadUintFromArr(msg.AddrHash, 12);
                var DeltaNum1 = ReadUint16FromArr(msg.AddrHash, 24);
                WriteUintToArrOnPos(BlockMining.AddrHash, Nonce1, 12)
                WriteUint16ToArrOnPos(BlockMining.AddrHash, DeltaNum1, 24)
                bWas += 1
            }
            if(CompareArr(ValueOld.Hash2, ValueMsg.Hash2) > 0)
            {
                var Nonce0 = ReadUintFromArr(msg.AddrHash, 6);
                var Nonce2 = ReadUintFromArr(msg.AddrHash, 18);
                var DeltaNum2 = ReadUint16FromArr(msg.AddrHash, 26);
                WriteUintToArrOnPos(BlockMining.AddrHash, Nonce0, 6)
                WriteUintToArrOnPos(BlockMining.AddrHash, Nonce2, 18)
                WriteUint16ToArrOnPos(BlockMining.AddrHash, DeltaNum2, 26)
                bWas += 2
            }
            if(!bWas)
                return ;
            var ValueNew = GetHashFromSeqAddr(BlockMining.SeqHash, BlockMining.AddrHash, BlockMining.BlockNum);
            BlockMining.Hash = ValueNew.Hash
            BlockMining.PowHash = ValueNew.PowHash
            BlockMining.Power = GetPowPower(BlockMining.PowHash)
            ADD_TO_STAT("MAX:POWER", BlockMining.Power)
            var Power = GetPowPower(BlockMining.PowHash);
            var HashCount = Math.pow(2, Power);
            ADD_HASH_RATE(HashCount)
            AddInfoBlock(BlockMining, "Set POW: " + Power)
            this.SetNoPOW(BlockMining.BlockNum + 8, 0, BlockMining.BlockNum)
            this.AddToMaxPOW(BlockMining, {SeqHash:BlockMining.SeqHash, AddrHash:BlockMining.AddrHash, PrevHash:BlockMining.PrevHash, TreeHash:BlockMining.TreeHash,
            })
        }
    }
};
global.TreeBlockBuf = new STreeBuffer(50 * 1000, CompareItemHashSimple, "string");
global.GetCurrentBlockNumByTime = function GetCurrentBlockNumByTime()
{
    var CurTimeNum = GetCurrentTime() - FIRST_TIME_BLOCK;
    var StartBlockNum = Math.trunc((CurTimeNum + CONSENSUS_PERIOD_TIME) / CONSENSUS_PERIOD_TIME);
    return StartBlockNum;
};
var PrevTimeIdle = 0;
OnTimeIdle();

function OnTimeIdle()
{
    var CurTime = Date.now();
    var Delta = CurTime - PrevTimeIdle;
    if(Delta <= 51)
    {
        ADD_TO_STAT("TIME_IDLE", 5);
    }
    setTimeout(OnTimeIdle, 49);
    PrevTimeIdle = CurTime;
};
