/* * @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://web.telegram.org/#/im?p=@terafoundation */ global.PROCESS_NAME = "WEB"; const crypto = require('crypto'); const http = require('http'), net = require('net'), url = require('url'), fs = require('fs'), querystring = require('querystring'); global.MAX_STAT_PERIOD = 600; require("../core/constant"); global.MAX_STAT_PERIOD = 600; global.DATA_PATH = GetNormalPathString(global.DATA_PATH); global.CODE_PATH = GetNormalPathString(global.CODE_PATH); require("../core/library"); require("../core/geo"); global.READ_ONLY_DB = 1; global.MAX_STAT_PERIOD = 600; var HostNodeList = []; var AllNodeList = []; var NodeBlockChain = []; var LastAlive = Date.now(); setTimeout(function () { setInterval(CheckAlive, 1000); }, 20000); setInterval(function () { process.send({cmd:"Alive"}); }, 1000); process.send({cmd:"online", message:"OK"}); process.on('message', function (msg) { LastAlive = Date.now(); switch(msg.cmd) { case "ALive": break; case "Exit": Exit(); break; case "call": var Err = 0; var Ret; try { Ret = global[msg.Name](msg.Params); } catch(e) { Err = 1; Ret = "" + e; } if(msg.id) process.send({cmd:"retcall", id:msg.id, Err:Err, Params:Ret}); break; case "retcall": var F = GlobalRunMap[msg.id]; if(F) { delete GlobalRunMap[msg.id]; F(msg.Err, msg.Params); } break; case "Stat": ADD_TO_STAT(msg.Name, msg.Value); break; case "NodeList": HostNodeList = msg.Value; AllNodeList = msg.ValueAll; break; case "NodeBlockChain": NodeBlockChain = msg.Value; break; case "DappEvent": { AddDappEventToGlobalMap(msg.Data); break; } case "ToLogClient": { ToLogClient0(msg.Str, msg.StrKey, msg.bFinal); break; } case "RetFindTX": { if(msg.WebID) { var F = global.GlobalRunMap[msg.WebID]; if(F) { delete global.GlobalRunMap[msg.WebID]; F(msg.Result, msg.ResultStr); break; } } ArrLogClient.push({text:msg.ResultStr, key:msg.TX, final:msg.bFinal, }); break; } } }); var RedirectServer; var HostingServer; function Exit() { ToLogWeb("=Exit1="); if(RedirectServer) RedirectServer.close(); if(HostingServer) HostingServer.close(); ToLogWeb("=Exit2="); process.exit(0); }; function CheckAlive() { if(global.NOALIVE) return ; var Delta = Date.now() - LastAlive; if(Delta > CHECK_STOP_CHILD_PROCESS) { Exit(); return ; } }; process.on('uncaughtException', function (err) { ToError(err.stack); ToLog(err.stack); TO_ERROR_LOG("HOSTING", 777, err); ToLog("-----------------HOSTING EXIT------------------", 0); process.exit(); }); process.on('error', function (err) { ToError("HOSTING:\n" + err.stack); ToLog(err.stack); }); if(!global.HTTP_HOSTING_PORT) { ToLogTrace("global.HTTP_HOSTING_PORT=" + global.HTTP_HOSTING_PORT); process.exit(); } var CServerDB = require("../core/db/block-db"); var KeyPair = crypto.createECDH('secp256k1'); KeyPair.setPrivateKey(Buffer.from([77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77])); global.SERVER = new CServerDB(KeyPair, undefined, undefined, false, true); global.HTTP_PORT_NUMBER = 0; require("../core/html-server"); require("../core/transaction-validator"); global.STAT_MODE = 1; setInterval(PrepareStatEverySecond, 1000); if(global.HTTPS_HOSTING_DOMAIN) { var file_sert = GetDataPath("sertif.lst"); CheckCreateDir(GetDataPath("tmp")); var greenlock = require('greenlock').create({version:'draft-12', server:'https://acme-v02.api.letsencrypt.org/directory', configDir:GetDataPath('tmp'), }); var redir = require('redirect-https')(); RedirectServer = require('http').createServer(greenlock.middleware(redir)); RedirectServer.on('error', function (err) { ToError('RedirectServer: ' + err.code); }); RedirectServer.listen(80); var GetNewSert = 1; if(fs.existsSync(file_sert)) { var certs = LoadParams(file_sert, {}); var Delta = certs.expiresAt - Date.now(); if(Delta >= 10 * 24 * 3600 * 1000) { ToLog("USE EXIST SERT. ExpiresAt: " + new Date(certs.expiresAt)); GetNewSert = 0; var tlsOptions = {key:certs.privkey, cert:certs.cert + '\r\n' + certs.chain}; HostingServer = require('https').createServer(tlsOptions, MainHTTPFunction); RunListenServer(); } } if(GetNewSert) { ToLog("Start get new SERT", 0); var opts = {domains:[global.HTTPS_HOSTING_DOMAIN], email:'progr76@gmail.com', agreeTos:true, communityMember:true, }; greenlock.register(opts).then(function (certs) { SaveParams(file_sert, certs); var tlsOptions = {key:certs.privkey, cert:certs.cert + '\r\n' + certs.chain}; HostingServer = require('https').createServer(tlsOptions, MainHTTPFunction); RunListenServer(); }, function (err) { ToError(err); }); } } else { HostingServer = http.createServer(MainHTTPFunction); RunListenServer(); } function MainHTTPFunction(request,response) { if(!request.headers) return ; if(!request.socket || !request.socket.remoteAddress) return ; SetSafeResponce(response); var DataURL = url.parse(request.url); var Params = querystring.parse(DataURL.query); var Path = querystring.unescape(DataURL.pathname); ToLogWeb("Get Path:" + Path); var Type = request.method; if(Type === "POST") { let Response = response; let postData = ""; request.addListener("data", function (postDataChunk) { if(postData.length <= 12000 && postDataChunk.length <= 12000) postData += postDataChunk; else { var Str = "Error postDataChunk.length=" + postDataChunk.length; ToLog(Str, 0); Response.writeHead(405, {'Content-Type':'text/html'}); Response.end(Str); } }); request.addListener("end", function () { var Data; if(postData && postData.length) { try { Data = JSON.parse(postData); } catch(e) { Response.writeHead(405, {'Content-Type':'text/html'}); Response.end("Error data parsing"); } } DoCommandNew(response, Type, Path, Data); }); } else { DoCommandNew(response, Type, Path, Params); } }; var bWasRun = 0; var TimeToRerun = 3000; function RunListenServer() { HostingServer.on('error', function (err) { if(err.code === 'EADDRINUSE') { TimeToRerun = Math.floor(TimeToRerun * 1.1); if(TimeToRerun > 1000000 * 1000) return ; ToLogClient('Port ' + global.HTTP_HOSTING_PORT + ' in use, retrying...'); if(HostingServer.Server) HostingServer.Server.close(); if(!bWasRun) setTimeout(function () { RunListenServer(); }, TimeToRerun); return ; } ToError("H##6"); ToError(err); }); ToLogClient("Prepare to run WEB-server on port: " + global.HTTP_HOSTING_PORT); HostingServer.listen(global.HTTP_HOSTING_PORT, '0.0.0.0', function () { if(!bWasRun) ToLogClient("Run WEB-server on port: " + global.HTTP_HOSTING_PORT); bWasRun = 1; }); }; var LangPathMap = {}; LangPathMap["ru"] = 1; LangPathMap["cn"] = 1; LangPathMap["de"] = 1; LangPathMap["blog"] = 1; LangPathMap["docs"] = 1; var WalletFileMap = {}; WalletFileMap["coinlib.js"] = 1; WalletFileMap["client.js"] = 1; WalletFileMap["diagram.js"] = 1; WalletFileMap["sha3.js"] = 1; WalletFileMap["terahashlib.js"] = 1; WalletFileMap["wallet-web.js"] = 1; WalletFileMap["wallet-lib.js"] = 1; WalletFileMap["crypto-client.js"] = 1; WalletFileMap["dapp-inner.js"] = 1; WalletFileMap["marked.js"] = 1; WalletFileMap["highlight.js"] = 1; WalletFileMap["highlight-js.js"] = 1; WalletFileMap["highlight-html.js"] = 1; WalletFileMap["codes.css"] = 1; WalletFileMap["sign-lib-min.js"] = 1; WalletFileMap["buttons.css"] = 1; WalletFileMap["style.css"] = 1; WalletFileMap["wallet.css"] = 1; WalletFileMap["history.html"] = 1; WalletFileMap["blockviewer.html"] = 1; WalletFileMap["web-wallet.html"] = 1; WalletFileMap["web-wallet-cn.html"] = 1; global.WebApi2 = {}; global.HostingCaller = {}; function DoCommandNew(response,Type,Path,Params) { if(Path.substring(0, 1) === "/") Path = Path.substring(1); var ArrPath = Path.split('/', 5); var Caller = HostingCaller; var Method = ArrPath[0]; if(ArrPath[0] === "api") { if(ArrPath[1] === "v2") { if(!global.USE_HARD_API_V2) { response.writeHead(200, {'Content-Type':'text/plain', 'Access-Control-Allow-Origin':"*"}); response.end(JSON.stringify({result:0, text:"You must set const USE_HARD_API_V2:1"})); return ; } Caller = WebApi2; } Method = ArrPath[2]; } var F = Caller[Method]; if(F) { response.writeHead(200, {'Content-Type':'text/plain', 'Access-Control-Allow-Origin':"*"}); var Ret; try { Ret = F(Params, response, ArrPath); } catch(e) { Ret = {result:0, text:e.message, text2:e.stack}; } if(Ret === null) return ; try { var Str; if(typeof Ret === "object") Str = JSON.stringify(Ret); else Str = Ret; response.end(Str); } catch(e) { ToLog("ERR PATH:" + Path); ToLog(e); response.end(); } return ; } Method = Method.toLowerCase(); if(Method === "dapp" && ArrPath.length === 2) Method = "DappTemplateFile"; switch(Method) { case "index.html": response.writeHead(301, {"Location":'/'}); return response.end(); case "file": SendBlockFile(response, ArrPath[1], ArrPath[2]); break; case "DappTemplateFile": DappTemplateFile(response, ArrPath[1]); break; case "smart": DappSmartCodeFile(response, ArrPath[1]); break; case "client": DappClientCodeFile(response, ArrPath[1]); default: { var Name = ArrPath[ArrPath.length - 1]; if(typeof Name !== "string") Name = "ErrorPath"; else if(Name.indexOf("..") >= 0 || Name.indexOf("\\") >= 0 || Name.indexOf("/") >= 0) Name = "ErrorFilePath"; if(Name === "" || LangPathMap[Name]) Name = "index.html"; if(Name.indexOf(".") < 0) Name += ".html"; var PrefixPath; if(Method === "files") { PrefixPath = "../FILES"; Name = PrefixPath + "/" + Name; SendWebFile(response, Name, Path); return ; } else if(LangPathMap[Method]) { PrefixPath = "./SITE/" + Method; } else if(WalletFileMap[Name]) PrefixPath = "./HTML"; else PrefixPath = "./SITE"; var type = Path.substr(Path.length - 3, 3); switch(type) { case ".js": Name = PrefixPath + "/JS/" + Name; break; case "css": Name = PrefixPath + "/CSS/" + Name; break; case "wav": case "mp3": Name = PrefixPath + "/SOUND/" + Name; break; case "svg": case "png": case "gif": case "jpg": case "ico": Name = PrefixPath + "/PIC/" + Name; break; case "pdf": case "zip": case "exe": case "msi": Name = PrefixPath + "/FILES/" + Name; break; default: Name = PrefixPath + "/" + Name; break; } SendWebFile(response, Name, "", 1); break; } } }; HostingCaller.GetCurrentInfo = function (Params) { var Ret = {result:1, VersionNum:global.UPDATE_CODE_VERSION_NUM, NETWORK:global.NETWORK, MaxNumBlockDB:SERVER.GetMaxNumBlockDB(), CurBlockNum:GetCurrentBlockNumByTime(), MaxAccID:DApps.Accounts.GetMaxAccount(), MaxDappsID:DApps.Smart.GetMaxNum(), CurTime:Date.now(), DELTA_CURRENT_TIME:DELTA_CURRENT_TIME, MIN_POWER_POW_TR:MIN_POWER_POW_TR, FIRST_TIME_BLOCK:FIRST_TIME_BLOCK, CONSENSUS_PERIOD_TIME:CONSENSUS_PERIOD_TIME, }; if(typeof Params === "object" && Params.Diagram == 1) { var arrNames = ["MAX:ALL_NODES", "MAX:HASH_RATE_G"]; Ret.arr = GET_STATDIAGRAMS(arrNames); } if(typeof Params === "object" && Params.BlockChain == 1) { Ret.BlockChain = NodeBlockChain; } if(typeof Params === "object" && Params.ArrLog == 1) { var ArrLog = []; for(var i = 0; i < ArrLogClient.length; i++) { var Item = ArrLogClient[i]; if(!Item.final) continue; ArrLog.push(Item); } Ret.ArrLog = ArrLog; } return Ret; }; var MaxCountViewRows = global.HTTP_MAX_COUNT_ROWS; HostingCaller.GetAccountList = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; var arr = DApps.Accounts.GetRowsAccounts(ParseNum(Params.StartNum), ParseNum(Params.CountNum)); return {result:1, arr:arr}; }; HostingCaller.GetAccount = function (id) { id = ParseNum(id); var arr = DApps.Accounts.GetRowsAccounts(id, 1); return {Item:arr[0], result:1}; }; HostingCaller.GetBlockList = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; var arr = SERVER.GetRows(ParseNum(Params.StartNum), ParseNum(Params.CountNum)); return {result:1, arr:arr}; }; HostingCaller.GetTransactionList = function (Params) { return HostingCaller.GetTransactionAll(Params); }; HostingCaller.GetTransactionAll = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; if(Params.Param3) Params.BlockNum = Params.Param3; var arr = SERVER.GetTrRows(ParseNum(Params.BlockNum), ParseNum(Params.StartNum), ParseNum(Params.CountNum)); return {result:1, arr:arr}; }; HostingCaller.GetDappList = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; var arr = DApps.Smart.GetRows(ParseNum(Params.StartNum), ParseNum(Params.CountNum)); return {result:1, arr:arr}; }; HostingCaller.GetNodeList = function (Params) { var arr = []; var List; if(typeof Params === "object" && Params.All) List = AllNodeList; else List = HostNodeList; var MaxNodes = 20; var len = List.length; var UseRandom = 0; if(len > MaxNodes) { UseRandom = 1; len = MaxNodes; } var Geo = 0; if(typeof Params === "object" && Params.Geo) Geo = 1; var mapWasAdd = {}; for(var i = 0; i < len; i++) { var Item; if(UseRandom) { var num = random(List.length); Item = List[num]; if(mapWasAdd[Item.ip]) { continue; } mapWasAdd[Item.ip] = 1; } else { Item = List[i]; } var Value = {ip:Item.ip, port:Item.portweb, }; if(Geo) { if(!Item.Geo) SetGeoLocation(Item); Value.latitude = Item.latitude; Value.longitude = Item.longitude; Value.name = Item.name; Value.port = Item.port; } arr.push(Value); } var Result = {result:1, arr:arr, VersionNum:global.UPDATE_CODE_VERSION_NUM, NETWORK:global.NETWORK, }; return Result; }; var AccountKeyMap = {}; var LastMaxNum = 0; HostingCaller.GetAccountListByKey = function (Params,ppp,bRet) { if(typeof Params !== "object" || !Params.Key) return {result:0, arr:[]}; var Accounts = DApps.Accounts; for(var num = LastMaxNum; true; num++) { if(Accounts.IsHole(num)) continue; var Data = Accounts.ReadState(num); if(!Data) break; var StrKey = GetHexFromArr(Data.PubKey); Data.Next = AccountKeyMap[StrKey]; AccountKeyMap[StrKey] = Data; } LastMaxNum = num; var arr = []; var Item = AccountKeyMap[Params.Key]; while(Item) { var Data = Accounts.ReadState(Item.Num); if(!Data) continue; if(!Data.PubKeyStr) Data.PubKeyStr = GetHexFromArr(Data.PubKey); if(Data.Currency) Data.CurrencyObj = DApps.Smart.ReadSimple(Data.Currency); if(Data.Value.Smart) { Data.SmartObj = DApps.Smart.ReadSimple(Data.Value.Smart); try { Data.SmartState = BufLib.GetObjectFromBuffer(Data.Value.Data, Data.SmartObj.StateFormat, {}); if(typeof Data.SmartState === "object") Data.SmartState.Num = Item.Num; } catch(e) { Data.SmartState = {}; } } arr.unshift(Data); Item = Item.Next; if(arr.length >= 30) break; } var Ret = {result:1, arr:arr}; if(bRet) return Ret; var Context = GetUserContext(Params); var StrInfo = JSON.stringify(Ret); if(!Params.AllData && Context.PrevAccountList === StrInfo) { return {result:0, cache:1}; } Context.PrevAccountList = StrInfo; Context.NumAccountList++; return StrInfo; }; HostingCaller.SendTransactionHex = function (Params,response) { if(typeof Params !== "object" || !Params.Hex) return {result:0, text:"object requre"}; process.RunRPC("AddTransactionFromWeb", {HexValue:Params.Hex}, function (Err,text) { var Result = {result:!Err, text:text}; var Str = JSON.stringify(Result); response.end(Str); }); return null; }; HostingCaller.DappSmartHTMLFile = function (Params) { if(typeof Params !== "object") return {result:0}; return HTTPCaller.DappSmartHTMLFile(Params); }; HostingCaller.DappBlockFile = function (Params,responce) { if(typeof Params !== "object") return {result:0}; return HTTPCaller.DappBlockFile(Params, responce); }; HostingCaller.DappInfo = function (Params) { if(typeof Params !== "object") return {result:0}; var SmartNum = ParseNum(Params.Smart); process.send({cmd:"SetSmartEvent", Smart:SmartNum}); var Context = GetUserContext(Params); var Ret = HTTPCaller.DappInfo(Params, undefined, 1); Ret.PubKey = undefined; var StrInfo = JSON.stringify(Ret); if(!Params.AllData && Context.PrevDappInfo === StrInfo) { return {result:0, cache:1}; } Context.PrevDappInfo = StrInfo; Context.NumDappInfo++; Context.LastTime = Date.now(); Ret.NumDappInfo = Context.NumDappInfo; Ret.CurTime = Date.now(); Ret.CurBlockNum = GetCurrentBlockNumByTime(); Ret.BlockNumDB = SERVER.BlockNumDB; Ret.MaxAccID = DApps.Accounts.GetMaxAccount(); Ret.MaxDappsID = DApps.Smart.GetMaxNum(); return Ret; }; HostingCaller.DappWalletList = function (Params) { if(typeof Params !== "object") return {result:0}; var Ret = HostingCaller.GetAccountListByKey(Params, undefined, 1); var Smart = ParseNum(Params.Smart); var arr = []; for(var i = 0; i < Ret.arr.length; i++) { if(Ret.arr[i].Value.Smart === Smart || Params.AllAccounts) { arr.push(Ret.arr[i]); } } Ret.arr = arr; return Ret; }; HTTPCaller.DappWalletList = HostingCaller.DappWalletList; HostingCaller.DappAccountList = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; var arr = DApps.Accounts.GetRowsAccounts(ParseNum(Params.StartNum), ParseNum(Params.CountNum), undefined, 1); return {arr:arr, result:1}; }; HostingCaller.DappSmartList = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; var arr = DApps.Smart.GetRows(ParseNum(Params.StartNum), ParseNum(Params.CountNum), undefined, undefined, Params.GetAllData, Params.TokenGenerate); return {arr:arr, result:1}; }; HostingCaller.DappBlockList = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; var arr = SERVER.GetRows(ParseNum(Params.StartNum), ParseNum(Params.CountNum)); return {arr:arr, result:1}; }; HostingCaller.DappTransactionList = function (Params) { if(typeof Params !== "object") return {result:0}; if(Params.CountNum > MaxCountViewRows) Params.CountNum = MaxCountViewRows; if(!Params.CountNum) Params.CountNum = 1; var arr = SERVER.GetTrRows(ParseNum(Params.BlockNum), ParseNum(Params.StartNum), ParseNum(Params.CountNum)); return {arr:arr, result:1}; }; HostingCaller.DappStaticCall = function (Params,response) { if(typeof Params !== "object") return {result:0}; return HTTPCaller.DappStaticCall(Params, response); }; HostingCaller.GetHistoryTransactions = function (Params) { if(typeof Params !== "object") return {result:0}; return HTTPCaller.GetHistoryTransactions(Params); }; global.GlobalRunID = 0; global.GlobalRunMap = {}; process.RunRPC = function (Name,Params,F) { if(F) { GlobalRunID++; try { process.send({cmd:"call", id:GlobalRunID, Name:Name, Params:Params}); GlobalRunMap[GlobalRunID] = F; } catch(e) { } } else { process.send({cmd:"call", id:0, Name:Name, Params:Params}); } }; setInterval(function () { DApps.Accounts.Close(); DApps.Smart.DBSmart.Close(); global.BlockDB.CloseDBFile("block-header"); global.BlockDB.CloseDBFile("block-body"); }, 500); setInterval(function () { var MaxNumBlockDB = SERVER.GetMaxNumBlockDB(); var HASHARATE_BLOCK_LENGTH = 200; var arr = SERVER.GetStatBlockchain("POWER_BLOCKCHAIN", HASHARATE_BLOCK_LENGTH); if(arr.length) { var SumPow = 0; var Count = 0; for(var i = arr.length - HASHARATE_BLOCK_LENGTH; i < arr.length; i++) if(arr[i]) { SumPow += arr[i]; Count++; } var AvgPow = SumPow / Count; var HashRate = Math.pow(2, AvgPow) / 1024 / 1024 / 1024; ADD_TO_STAT("MAX:HASH_RATE_G", HashRate); } var Count = COUNT_BLOCK_PROOF + 16 - 1; if(MaxNumBlockDB > Count) { var StartNum = MaxNumBlockDB - Count; NodeBlockChain = SERVER.BlockChainToBuf(StartNum, StartNum, MaxNumBlockDB); } }, 1000); require("./api-exchange.js"); try { require("../SITE/JS/web-addon.js"); } catch(e) { } global.LoadBlockFromNetwork = function (Params,F) { ToLog("RUN: LoadBlockFromNetwork:" + Params.BlockNum); process.RunRPC("LoadBlockFromNetwork", {BlockNum:Params.BlockNum, F:1}, function (Err,Block) { ToLog("RETURN: LoadBlockFromNetwork: " + Params.BlockNum); F(Err, Block); }); };