/* * @project: TERA * @version: Development (beta) * @license: MIT (not for evil) * @copyright: Yuriy Ivanov (Vtools) 2017-2019 [progr76@gmail.com] * Web: https://terafoundation.org * Twitter: https://twitter.com/terafoundation * Telegram: https://t.me/terafoundation */ "use strict"; import { RBTree } from './library'; import * as crypto from 'crypto' const MAX_MESSAGE_COUNT = 1000; import CSmartContract from './transaction-validator' import { TeraTr, SocketSendInfo } from '../interfaces/server'; import CNode from './node'; export default class CMessages extends CSmartContract { MemPoolMsg: RBTree[] constructor(SetKeyPair: crypto.ECDH, RunIP: string, RunPort: number, UseRNDHeader: boolean, bVirtual: boolean) { super(SetKeyPair, RunIP, RunPort, UseRNDHeader, bVirtual) this.MemPoolMsg = [] for (var i = 0; i <= global.MAX_LEVEL_SPECIALIZATION; i++) this.MemPoolMsg[i] = new RBTree(global.CompareItemTimePow) } AddMsgToQuote(Msg: TeraTr) { var Tree = this.MemPoolMsg[Msg.Level]; if (Tree) { if (Tree.insert(Msg)) { if (Tree.size > global.MEM_POOL_MSG_COUNT) { var maxitem = Tree.max(); Tree.remove(maxitem) if (maxitem === Msg) return 0; } return 1; } else { return 3; } } return 0; } IsValidMsg(Msg: TeraTr) { this.CheckCreateMsgHASH(Msg) if (Msg.power < global.MIN_POWER_POW_MSG) return - 1; if (Msg.time > this.CurrentBlockNum) return - 1; return 1; } CheckCreateMsgHASH(Msg: TeraTr) { if (!Msg.HashPow) { Msg.HASH = global.sha3(Msg.body) Msg.HashPow = global.GetHashWithValues(Msg.HASH, Msg.nonce, Msg.time) Msg.power = global.GetPowPower(Msg.HashPow) Msg.TimePow = Msg.time + Msg.power - Math.log2(Msg.body.length / 128) Msg.Level = global.AddrLevelArr(this.addrArr, Msg.addrArr) if (Msg.Level >= global.MAX_LEVEL_SPECIALIZATION) Msg.Level = global.MAX_LEVEL_SPECIALIZATION } } CreateMsgFromBody(Body: any, ToAddr: any) { var HASH = global.sha3(Body); var Msg = { HASH: HASH, body: Body, addrArr: ToAddr, nonce: global.CreateNoncePOWExtern(HASH, this.CurrentBlockNum, 3 * (1 << global.MIN_POWER_POW_MSG)), time: this.CurrentBlockNum, }; this.CheckCreateMsgHASH(Msg) return Msg; } SendMessage(Body: any, ToAddr: any) { var Msg = this.CreateMsgFromBody(Body, ToAddr); this.SendMessageNext(Msg) } SendMessageNext(Msg) { var CountNodes = 3; var LevelStart = Msg.Level; if (global.CompareArr(this.addrArr, Msg.addrArr) === 0) return false; for (var L = LevelStart; L >= 0; L--) if (this.LevelNodes[L] && this.LevelNodes[L].length) { var arr = this.LevelNodes[L]; for (var j = 0; arr && j < arr.length; j++) { var Node = arr[j]; this.SendF(Node, { "Method": "MESSAGE", "Data": { Arr: [Msg] } }) CountNodes-- if (CountNodes <= 0) break; } } return true; } static MESSAGE_F() { return "{Arr:[{addrArr:hash,body:tr,nonce:uint,time:uint}]}"; } MESSAGE(Info: SocketSendInfo, CurTime: number) { var Data = this.DataFromF(Info); var arr = Data.Arr; for (var i = 0; i < arr.length; i++) { var Msg = arr[i]; if (this.IsValidMsg(Msg)) { if (global.CompareArr(this.addrArr, Msg.addrArr) === 0) { var App = global.DAppByType[Msg.body[0]]; if (App) { //@ts-ignore App.OnMessage(Msg, BlockNum, i) } } else { if (this.AddMsgToQuote(Msg) === 1) { this.SendMessageNext(Msg) } } } } } SendGetMessage(Node: CNode) { var Context = { "SendGetMessage": 1 }; this.Send(Node, { "Method": "GETMESSAGE", "Context": Context, "Data": undefined }) } GETMESSAGE(Info: SocketSendInfo, CurTime: number) { var arr = []; var BufLength = 300; var Level = global.AddrLevelArr(this.addrArr, Info.Node.addrArr); var Tree = this.MemPoolMsg[Level]; if (Tree) { var it = Tree.iterator(), Item; while ((Item = it.next()) !== null) { if (arr.length >= MAX_MESSAGE_COUNT) break; arr.push(Item) BufLength += Item.body.length + 50 } } this.SendF(Info.Node, { "Method": "MESSAGE", "Context": Info.Context, "Data": { Arr: arr } }, BufLength) } AddTransaction(Tr: TeraTr, ToAll?: boolean) { Tr.ToAll = ToAll var Res = this.IsValidTransaction(Tr, this.CurrentBlockNum); if (Res <= 0 && Res !== - 3) return Res; if (Tr.num < this.CurrentBlockNum) return - 3; var delta = Tr.num - this.CurrentBlockNum; if (delta > 3) { if (delta < 15) { let TR = Tr; let SELF = this; setTimeout(function() { var Res = SELF.AddTransaction(TR, TR.ToAll); if (TR.ToAll) global.ToLogClient("#3 Added " + TrName(TR) + " for block: " + TR.num + " on timer Res=" + Res) }, (delta - 3) * 1000) if (Tr.ToAll) global.ToLogClient("#2 Added " + TrName(Tr) + " for block: " + Tr.num + " to timer. Send transaction after " + (delta - 3) + " sec") return 4; } return - 3; } var Block = this.GetBlockContext(Tr.num); if (!Block) return - 5; if (Block.Active) { Res = - 3 } else { Res = this.AddTrToBlockQuote(Block, Tr) if (Tr.ToAll) this.SendTransaction(Tr) } ToLogContext("#1 Add " + TrName(Tr) + " for Block: " + Tr.num + " Res=" + Res) return Res; } SendTransaction(Tr: TeraTr) { if (!Tr.ToAll) return; var CurTime = global.GetCurrentTime(0) - 0; var Count: number; if (global.GrayConnect()) Count = Math.trunc(global.MAX_GRAY_CONNECTIONS_TO_SERVER / 2) else Count = Math.min(this.ActualNodes.size, 16) if (Count < 2) Count = 2 var ArrNodes = this.GetActualNodes(); for (var i = 0; i < ArrNodes.length; i++) { var Node = ArrNodes[i]; if (!Node) continue; if (Node.TaskLastSend) { var Delta = CurTime - Node.TaskLastSend; if (Delta < global.PERIOD_GET_BLOCK || Node.StopGetBlock) { continue; } } Node.TaskLastSend = CurTime this.SendF(Node, { "Method": "TRANSACTION", "Data": Tr }, Tr.body.length + 1000) ToLogContext("Send " + TrName(Tr) + " to " + global.NodeName(Node)) Count-- if (Count <= 0) break; } } static TRANSACTION_F() { return "{body:tr}"; } TRANSACTION(Info: SocketSendInfo, CurTime: number) { var Tr = this.DataFromF(Info); ToLogContext("Receive " + TrName(Tr) + " from " + global.NodeName(Info.Node)) this.AddTransaction(Tr, false) } }; function ToLogContext(Str: string) { }; function TrName(Tr: TeraTr) { if (!Tr.HASH) global.SERVER.CheckCreateTransactionObject(Tr); var Str = global.GetHexFromArr(Tr.HASH); return "Tx:" + Str.substr(0, 8); }; global.TrName = TrName;