/* * @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 * as fs from 'fs' import DBRow from '../core/db/db-row' import '../core/rest_tables' import { secp256k1 } from '../core/library' import { TYPE_TRANSACTION } from '../constant/account' import { DB_FORMAT } from '../constant/db-format' const MAX_SUM_TER = 1e9; const MAX_SUM_CENT = 1e9; import DBLib from "../core/db/db" global.HistoryDB = new DBLib(); const FILE_NAME_HISTORY = "history-body"; var WorkStructHistory = {}; const BLOCK_CREATE_INTERVAL = 10; const WorkStructTransfer = {}; const WorkStructTransfer2 = {}; const WorkStructTransfer3 = {}; import DApp from './dapp' class MerkleDBRow extends DBRow { private MerkleTree private MerkleArr private MerkleCalc constructor(FileName, DataSize, Format, bReadOnly) { super(FileName, DataSize, Format, bReadOnly) this.InitMerkleTree() } InitMerkleTree() { this.MerkleTree = undefined this.MerkleArr = [] this.MerkleCalc = {} } CalcMerkleTree(bForceUpdate) { if (!this.MerkleTree || bForceUpdate) { this.MerkleCalc = {} this.MerkleTree = { LevelsHash: [this.MerkleArr], RecalcCount: 0 } var GetMaxNum = this.GetMaxNum(); for (var num = 0; num <= GetMaxNum; num++) { var Buf = this.Read(num, 1); if (!Buf) { if (global.WATCHDOG_DEV) global.ToErrorTx("CalcMerkleTree: Break account reading on num: " + num) break; } this.MerkleArr[num] = global.shaarr(Buf) this.MerkleCalc[num] = 1 } } this.MerkleTree.RecalcCount = 0 global.UpdateMerklTree(this.MerkleTree, this.MerkleCalc, 0) this.MerkleCalc = {} return this.MerkleTree.Root; } Write(Data) { var RetBuf: any = {}; var bRes = DBRow.prototype.Write.call(this, Data, RetBuf); if (bRes) { var Hash = global.shaarr(RetBuf.Buf); this.MerkleArr[Data.Num] = Hash this.MerkleCalc[Data.Num] = 1 } return bRes; } Truncate(LastNum) { DBRow.prototype.Truncate.call(this, LastNum) if (this.MerkleArr.length !== LastNum + 1) { this.MerkleArr.length = LastNum + 1 this.MerkleCalc[LastNum] = 1 } } }; export default class AccountApp extends DApp { CreateTrCount FORMAT_ACCOUNT_ROW SIZE_ACCOUNT_ROW DBState FORMAT_ACCOUNT_ROW_REST SIZE_ACCOUNT_ROW_REST DBRest DBAct DBActPrev FORMAT_STATE_HISTORY DBStateHistory HistoryFormatArr DBStateTX: DBRow DBAccountsHash WasCheckRestDB ResultTx DBChanges constructor() { super() var bReadOnly = (global.PROCESS_NAME !== "TX"); this.CreateTrCount = 0 this.FORMAT_ACCOUNT_ROW = "{\ Currency:uint,\ PubKey:arr33,\ Name:str40,\ Value:{SumCOIN:uint,SumCENT:uint32, OperationID:uint,Smart:uint32,Data:arr80},\ BlockNumCreate:uint,\ Adviser:uint,\ Reserve:arr9,\ }" this.SIZE_ACCOUNT_ROW = 6 + 33 + 40 + (6 + 4 + 6 + 84) + 6 + 6 + 9 this.DBState = new MerkleDBRow("accounts-state", this.SIZE_ACCOUNT_ROW, this.FORMAT_ACCOUNT_ROW, bReadOnly) this.FORMAT_ACCOUNT_ROW_REST = "{\ Arr:[{\ BlockNum:uint,\ Value:{SumCOIN:uint,SumCENT:uint32, OperationID:uint,Smart:uint32,Data:arr80,Reserv:arr96},\ }],\ Reserv0:arr10,\ }" this.SIZE_ACCOUNT_ROW_REST = 1024 this.DBRest = new DBRow("accounts-rest", this.SIZE_ACCOUNT_ROW_REST, this.FORMAT_ACCOUNT_ROW_REST, bReadOnly) this.DBAct = new DBRow("accounts-act", 6 + 6 + (6 + 4 + 6 + 6 + 84) + 1 + 11, "{ID:uint, BlockNum:uint,PrevValue:{SumCOIN:uint,SumCENT:uint32, NextPos:uint, OperationID:uint,Smart:uint32,Data:arr80}, Mode:byte, TrNum:uint16, Reserve: arr9}", bReadOnly) this.DBActPrev = new DBRow("accounts-act-prev", this.DBAct.DataSize, this.DBAct.Format, bReadOnly) this.FORMAT_STATE_HISTORY = "{NextPos:uint,Reserv:arr2}" this.DBStateHistory = new DBRow("history-state", 8, this.FORMAT_STATE_HISTORY, bReadOnly) global.HistoryDB.OpenDBFile(FILE_NAME_HISTORY, !bReadOnly) this.HistoryFormatArr = ["{Type:byte, BlockNum:uint32,TrNum:uint16, NextPos:uint}", "{Type:byte, BlockNum:uint32,TrNum:uint16, NextPos:uint, Direct:str1,CorrID:uint, SumCOIN:uint,SumCENT:uint32}"] this.DBStateTX = new DBRow("accounts-tx", 6 + 6 + 88, "{BlockNum:uint, BlockNumMin:uint, Reserve: arr88}", bReadOnly) if (global.READ_ONLY_DB) return; this.DBAccountsHash = new DBRow("accounts-hash3", 6 + 32 + 32 + 32 + 6 + 6 + 14, "{BlockNum:uint, AccHash:hash, SumHash:hash, SmartHash:hash, AccountMax:uint, SmartCount:uint, Reserve: arr14}", bReadOnly) if (global.START_SERVER) return; if (!bReadOnly) this.Start() setInterval(this.ControlActSize.bind(this), 60 * 1000) } Start(bClean?) { if (global.LOCAL_RUN) bClean = 1 if (!bClean && this.DBState.GetMaxNum() + 1 >= global.BLOCK_PROCESSING_LENGTH2) return; this.DBState.MerkleTree = undefined this.DBState.Truncate(- 1) this.DBStateHistory.Truncate(- 1) this.DBAct.Truncate(- 1) this.DBActPrev.Truncate(- 1) this.DBAccountsHash.Truncate(- 1) this.DBStateTX.Truncate(- 1) this.DBRest.Truncate(- 1) this._DBStateWrite({ Num: 0, PubKey: [], Value: { BlockNum: 1, SumCOIN: 0.95 * global.TOTAL_SUPPLY_TERA }, Name: "System account" }, 1) for (var i = 1; i < 8; i++) { this._DBStateWrite({ Num: i, PubKey: [], Value: { BlockNum: 1 }, Name: "" }) } this._DBStateWrite({ Num: 8, PubKey: global.GetArrFromHex(global.ARR_PUB_KEY[0]), Value: { BlockNum: 1, SumCOIN: 0.05 * global.TOTAL_SUPPLY_TERA }, Name: "Founder account" }) this._DBStateWrite({ Num: 9, PubKey: global.GetArrFromHex(global.ARR_PUB_KEY[1]), Value: { BlockNum: 1, SumCOIN: 0 }, Name: "Developer account" }) for (var i = 10; i < global.BLOCK_PROCESSING_LENGTH2; i++) this._DBStateWrite({ Num: i, PubKey: global.GetArrFromHex(global.ARR_PUB_KEY[i - 8]), Value: { BlockNum: 1 }, Name: "" }) this.DBStateTX.Write({ Num: 0, BlockNum: 0 }) this.CalcMerkleTree(1) var FileItem = global.HistoryDB.OpenDBFile(FILE_NAME_HISTORY, 1); fs.ftruncateSync(FileItem.fd, 0) global.ToLog("MAX_NUM:" + this.DBState.GetMaxNum()) } Close() { this.DBState.Close() this.DBActPrev.Close() this.DBAct.Close() if (this.DBAccountsHash) this.DBAccountsHash.Close() if (this.DBStateTX) this.DBStateTX.Close() this.DBRest.Close() if (this.DBStateHistory) this.DBStateHistory.Close() } ClearDataBase() { this.Start(1) } CheckRestDB() { if (!global.SERVER) return; if (this.WasCheckRestDB) return; this.WasCheckRestDB = 1 var MaxNumBlock = global.SERVER.GetMaxNumBlockDB(); if (this.DBState.GetMaxNum() >= 0 && this.DBRest.GetMaxNum() < 0 && MaxNumBlock > 0) { this.FillRestDB(MaxNumBlock) } } FillRestDB(BlockNum) { global.ToLog("******************************START FillRestDB") var Max = this.DBState.GetMaxNum(); for (var Num = 0; Num <= Max; Num++) { var Data = this.DBState.Read(Num); var RestData = this.ReadRest(Num); if (Num % 10000 === 0) global.ToLog("Fill Rest DB : " + Num) RestData.Arr[0] = { BlockNum: BlockNum, Value: Data.Value } this.DBRest.Write(RestData) } global.ToLog("******************************FINISH FillRestDB") } _DBStateWrite(Data?, BlockNum?, b?) { this.CheckRestDB() this.DBState.Write(Data) if (Data.Num === undefined) throw "Error undefined Num DBRest !!"; var RestData = this.ReadRest(Data.Num); DoRest(RestData, Data, BlockNum) this.DBRest.Write(RestData) } _DBStateTruncate(Num) { this.DBState.Truncate(Num) this.DBRest.Truncate(Num) } ReadRest(Num) { var COUNT_STATE = 5; var RestData = this.DBRest.Read(Num); if (!RestData || RestData.Arr.length !== COUNT_STATE) { RestData = { Num: Num, Arr: [] } for (var i = 0; i < COUNT_STATE; i++) RestData.Arr[i] = { BlockNum: 0, Value: {} } } if (RestData.Arr.length !== COUNT_STATE) throw "Error RestData.Arr.length = (" + RestData.Arr.length + ")"; return RestData; } ControlActSize() { var MaxNum = this.DBAct.GetMaxNum(); if (MaxNum >= global.TRANSACTION_PROOF_COUNT * 2) { global.ToLog("Rename act files") this.DBActPrev.CloseDBFile(this.DBActPrev.FileName) this.DBAct.CloseDBFile(this.DBAct.FileName) if (fs.existsSync(this.DBActPrev.FileNameFull)) { var FileNameFull2 = this.DBActPrev.FileNameFull + "_del"; try { fs.renameSync(this.DBActPrev.FileNameFull, FileNameFull2) } catch (e) { global.ToErrorTx("Can-t rename for delete act-file: " + FileNameFull2 + " " + e) return; } fs.unlinkSync(FileNameFull2) } try { fs.renameSync(this.DBAct.FileNameFull, this.DBActPrev.FileNameFull) } catch (e) { global.ToErrorTx("Can-t rename act-file!" + e) return; } } } GetSenderNum(BlockNum, Body) { var Type = Body[0]; if (Type && Body.length > 90) { switch (Type) { case TYPE_TRANSACTION.TYPE_TRANSACTION_CREATE: { if (BlockNum % BLOCK_CREATE_INTERVAL !== 0) return 0; var Num = BlockNum; return Num; } case TYPE_TRANSACTION.TYPE_TRANSACTION_TRANSFER: var Num = global.ReadUintFromArr(Body, 1 + 1 + 6); return Num; case TYPE_TRANSACTION.TYPE_TRANSACTION_ACC_HASH: return - 1; } } return 0; } OnDeleteBlock(Block) { if (Block.BlockNum < 1) return; this.DeleteAct(Block.BlockNum) } OnWriteBlockStart(Block) { this.CreateTrCount = 0 if (Block.BlockNum < 1) return; this.OnDeleteBlock(Block) this.BeginBlock() } OnWriteBlockFinish(Block) { try { this.BeginTransaction() this.DoCoinBaseTR(Block) this.CommitTransaction(Block.BlockNum, 0xFFFF) } catch (e) { this.RollBackTransaction() global.ToErrorTx("BlockNum:" + Block.BlockNum + " - DoCoinBaseTR: " + e) } this.CommitBlock(Block) } OnWriteTransaction(Block, Body, BlockNum, TrNum, ContextFrom) { var Result; try { Result = this.OnWriteTransactionTR(Block, Body, BlockNum, TrNum, ContextFrom) } catch (e) { Result = "" + e if (global.WATCHDOG_DEV) global.ToErrorTx("BlockNum:" + BlockNum + ":" + e) } if (Result !== true) { this.RollBackTransaction() } return Result; } OnWriteTransactionTR(Block, Body, BlockNum, TrNum, ContextFrom) { var Type = Body[0]; var Result; switch (Type) { case TYPE_TRANSACTION.TYPE_TRANSACTION_CREATE: { Result = this.TRCreateAccount(Body, BlockNum, TrNum, ContextFrom) break; } case TYPE_TRANSACTION.TYPE_DEPRECATED_TRANSFER1: { Result = this.TRTransferMoney(Block, Body, BlockNum, TrNum, DB_FORMAT.FORMAT_MONEY_TRANSFER, WorkStructTransfer) break; } case TYPE_TRANSACTION.TYPE_DEPRECATED_TRANSFER2: { Result = this.TRTransferMoney(Block, Body, BlockNum, TrNum, DB_FORMAT.FORMAT_MONEY_TRANSFER2, WorkStructTransfer2) break; } case TYPE_TRANSACTION.TYPE_TRANSACTION_TRANSFER: { Result = this.TRTransferMoney(Block, Body, BlockNum, TrNum, DB_FORMAT.FORMAT_MONEY_TRANSFER3, WorkStructTransfer3) break; } case TYPE_TRANSACTION.TYPE_TRANSACTION_ACC_HASH: { Result = 1 if (global.LOCAL_RUN || global.TEST_NETWORK) { } else { if (BlockNum < global.START_BLOCK_ACCOUNT_HASH + 200000) break; } var BlockNumHash = BlockNum - global.DELTA_BLOCK_ACCOUNT_HASH; if (!this.TRCheckAccountHash(Body, BlockNum, TrNum)) { Result = "BAD ACCOUNT HASH" global.ToLog("2. ****FIND BAD ACCOUNT HASH IN BLOCK: " + BlockNumHash + " DO BLOCK=" + BlockNum) } else { Result = true } break; } } return Result; } DoCoinBaseTR(Block) { if (Block.BlockNum < global.START_MINING) return; var SysData = this.ReadStateTR(0); var SysBalance = SysData.Value.SumCOIN; const REF_PERIOD_START = global.START_MINING; var AccountID = global.ReadUintFromArr(Block.AddrHash, 0); if (AccountID < 8) return; var Data = this.ReadStateTR(AccountID); if (Data && Data.Currency === 0 && Data.BlockNumCreate < Block.BlockNum) { var Sum; if (Block.BlockNum >= global.NEW_FORMULA_START) { if (Block.BlockNum <= global.NEW_FORMULA_TARGET1) { Sum = SysBalance * 43 * 43 / 100 / global.TOTAL_SUPPLY_TERA var KMult = (global.NEW_FORMULA_TARGET2 - Block.BlockNum) / (global.NEW_FORMULA_TARGET2 - global.NEW_FORMULA_START); Sum = KMult * Sum } else { Sum = global.NEW_FORMULA_KTERA * SysBalance / global.TOTAL_SUPPLY_TERA } } else { var Power = global.GetPowPower(Block.PowHash); if (Block.BlockNum >= global.NEW_BLOCK_REWARD1) Power = 43 Sum = Power * Power * SysBalance / global.TOTAL_SUPPLY_TERA / 100 } var CoinTotal = { SumCOIN: 0, SumCENT: 0 }; var CoinSum = global.COIN_FROM_FLOAT(Sum); if (!global.ISZERO(CoinSum)) { if (Data.Adviser >= 8 && Block.BlockNum < global.REF_PERIOD_END) { var RefData = this.ReadStateTR(Data.Adviser); if (RefData && RefData.BlockNumCreate < Block.BlockNum - global.REF_PERIOD_MINING) { var K = (global.REF_PERIOD_END - Block.BlockNum) / (global.REF_PERIOD_END - REF_PERIOD_START); var CoinAdv = global.COIN_FROM_FLOAT(Sum * K); this.SendMoneyTR(Block, 0, Data.Adviser, CoinAdv, Block.BlockNum, 0xFFFF, "", "Adviser coin base [" + AccountID + "]", 1) global.ADD(CoinTotal, CoinAdv) global.ADD(CoinSum, CoinAdv) } } this.SendMoneyTR(Block, 0, AccountID, CoinSum, Block.BlockNum, 0xFFFF, "", "Coin base", 1) global.ADD(CoinTotal, CoinSum) var CoinDevelop = global.CopyObjValue(CoinTotal); global.DIV(CoinDevelop, 100) if (!global.ISZERO(CoinDevelop)) this.SendMoneyTR(Block, 0, 9, CoinDevelop, Block.BlockNum, 0xFFFF, "", "Developers support", 1) } } } GetVerifyTransaction(Block, BlockNum, TrNum, Body) { if (Block.VersionBody === 1) { var Result = Block.arrContentResult[TrNum]; if (!Result) return - 1; else return Result; } return 0; } GetObjectTransaction(Body) { var Type = Body[0]; var format; switch (Type) { case TYPE_TRANSACTION.TYPE_TRANSACTION_CREATE: { format = DB_FORMAT.FORMAT_CREATE break; } case TYPE_TRANSACTION.TYPE_DEPRECATED_TRANSFER1: { format = DB_FORMAT.FORMAT_MONEY_TRANSFER break; } case TYPE_TRANSACTION.TYPE_DEPRECATED_TRANSFER2: { format = DB_FORMAT.FORMAT_MONEY_TRANSFER2 break; } case TYPE_TRANSACTION.TYPE_TRANSACTION_TRANSFER: { format = DB_FORMAT.FORMAT_MONEY_TRANSFER3 break; } case TYPE_TRANSACTION.TYPE_TRANSACTION_ACC_HASH: { format = DB_FORMAT.FORMAT_ACCOUNT_HASH3 break; } default: return ""; } var TR; try { TR = global.BufLib.GetObjectFromBuffer(Body, format, {}) } catch (e) { } return TR; } GetScriptTransaction(Body) { var TR = this.GetObjectTransaction(Body); if (!TR) return ""; if (TR.Body && TR.Body.length) { var App = global.DAppByType[TR.Body[0]]; if (App) { TR.Body = JSON.parse(App.GetScriptTransaction(TR.Body)) } } global.ConvertBufferToStr(TR) return JSON.stringify(TR, undefined, 2); } TRCheckAccountHash(Body, BlockNum, TrNum?) { if (BlockNum % global.PERIOD_ACCOUNT_HASH !== 0) { return 1; } try { var TR = global.BufLib.GetObjectFromBuffer(Body, DB_FORMAT.FORMAT_ACCOUNT_HASH3, {}); } catch (e) { return 0; } if (BlockNum < global.START_BLOCK_ACCOUNT_HASH + 200000) { return 1; } var Item = this.GetAccountHashItem(TR.BlockNum); if (Item && Item.BlockNum === TR.BlockNum) { if (global.CompareArr(Item.AccHash, TR.AccHash) === 0) { if (TR.BlockNum >= global.START_BLOCK_ACCOUNT_HASH3) { if (global.CompareArr(Item.SmartHash, TR.SmartHash) === 0 && Item.AccountMax === TR.AccountMax && Item.SmartCount === TR.SmartCount) { return 1; } else { return 0; } } return 1; } else { return 0; } } else { return 2; } } TRCreateAccount(Body, BlockNum, TrNum, ContextFrom) { if (Body.length < 90) return "Error length transaction"; var CheckMinPower = 1; if (BlockNum >= 7000000 || global.LOCAL_RUN || global.TEST_NETWORK) { if (ContextFrom && ContextFrom.To.length === 1 && ContextFrom.To[0].ID === 0 && ContextFrom.To[0].SumCOIN >= global.PRICE_DAO(BlockNum).NewAccount) { CheckMinPower = 0 } else { if (BlockNum % BLOCK_CREATE_INTERVAL !== 0) return "The create transaction is not possible in this block: " + BlockNum; if (this.CreateTrCount > 0) return "The account creation transaction was already in this block: " + BlockNum; } } this.CreateTrCount++ var power; if (BlockNum >= global.BLOCKNUM_TICKET_ALGO) { var Tr: any = { body: Body }; global.SERVER.CheckCreateTransactionObject(Tr) power = Tr.power } else { power = global.GetPowPower(global.shaarr(Body)) } if (global.TEST_NETWORK && BlockNum >= 3290000) { CheckMinPower = 0 } if (CheckMinPower && BlockNum < 19600000) { var MinPower; if (BlockNum < 2500000) MinPower = global.MIN_POWER_POW_ACC_CREATE else if (BlockNum < 2800000) MinPower = global.MIN_POWER_POW_ACC_CREATE + 2 else MinPower = global.MIN_POWER_POW_ACC_CREATE + 3 if (power < MinPower) return "Error min power POW for create account (update client)"; } try { var TR = global.BufLib.GetObjectFromBuffer(Body, DB_FORMAT.FORMAT_CREATE, {}); } catch (e) { return "Error transaction format"; } if (BlockNum >= 3500000 && !TR.Name) return "Account name required"; if (BlockNum >= 5700000 && !TR.Name.trim()) return "Account name required"; var Account = this.NewAccountTR(BlockNum, TrNum); Account.Currency = TR.Currency Account.PubKey = TR.PubKey Account.Name = TR.Name Account.Adviser = TR.Adviser Account.Value.Smart = TR.Smart this.WriteStateTR(Account, TrNum) this.ResultTx = Account.Num return true; } TRTransferMoney(Block, Body, BlockNum, TrNum, format_money_transfer, workstructtransfer) { if (Body.length < 103) return "Error length transaction"; try { var TR = global.BufLib.GetObjectFromBuffer(Body, format_money_transfer, workstructtransfer); } catch (e) { return "Error transaction format"; } if (!TR.Version) TR.Version = 0 var Data = this.ReadStateTR(TR.FromID); if (!Data) return "Error sender's account ID: " + TR.FromID; if (TR.Version < 3 && TR.Currency !== Data.Currency) return "Error sender's currency"; if (TR.Version < 3) { if (TR.OperationID !== Data.Value.OperationID) return "Error OperationID (expected: " + Data.Value.OperationID + " for ID: " + TR.FromID + ")"; } else { if (TR.OperationID < Data.Value.OperationID) return "Error OperationID (expected: " + Data.Value.OperationID + " for ID: " + TR.FromID + ")"; var MaxCountOperationID = 100; if (BlockNum >= global.BLOCKNUM_TICKET_ALGO) MaxCountOperationID = 1000000 if (TR.OperationID > Data.Value.OperationID + MaxCountOperationID) return "Error too much OperationID (expected max: " + (Data.Value.OperationID + MaxCountOperationID) + " for ID: " + TR.FromID + ")"; } if (BlockNum >= global.SMART_BLOCKNUM_START) { if (TR.To.length > 10) return "The number of recipients has been exceeded (max=5, current count=" + TR.To.length + ")"; } if (TR.Body && TR.Body.length && TR.To.length > 1) { return "Error - global.DApps transaction can not be used in a multiple transaction"; } var TotalSum = { SumCOIN: 0, SumCENT: 0 }; var MapItem = {}; var bWas = 0; for (var i = 0; i < TR.To.length; i++) { var Item = TR.To[i]; if (Item.SumCOIN > MAX_SUM_TER) return "Error MAX_SUM_COIN"; if (Item.SumCENT >= MAX_SUM_CENT) return "Error MAX_SUM_CENT"; if (TR.Version < 3) { if (Item.ID === TR.FromID || MapItem[Item.ID]) continue; MapItem[Item.ID] = 1 } bWas = 1 global.ADD(TotalSum, Item) } if (!bWas && TR.Version < 3) return "No significant recipients"; var ZeroSum = 0; if (TotalSum.SumCOIN === 0 && TotalSum.SumCENT === 0) { if (TR.Version < 3) return "No money transaction"; else ZeroSum = 1 } if (Data.Value.SumCOIN < TotalSum.SumCOIN || (Data.Value.SumCOIN === TotalSum.SumCOIN && Data.Value.SumCENT < TotalSum.SumCENT)) return "Not enough money on the account"; if (BlockNum >= global.NEW_ACCOUNT_INCREMENT) Data.Value.OperationID = TR.OperationID Data.Value.OperationID++ TR.Value = TotalSum var arr = []; MapItem = {} var arrpub = []; for (var i = 0; i < TR.To.length; i++) { var Item = TR.To[i]; var DataTo = this.ReadStateTR(Item.ID); if (!DataTo) return "Error receiver account ID: " + Item.ID; if (!ZeroSum && Data.Currency !== DataTo.Currency) return "Error receiver currency"; for (var j = 0; j < 33; j++) arrpub[arrpub.length] = DataTo.PubKey[j] if (DataTo.Value.Smart) { if (TR.To.length > 1) return "Error - smart accounts can not be used in a multiple transaction"; } if (TR.Version === 3 && Item.ID === 0 && Item.PubKey && Item.PubKey.length === 33) { if (Item.SumCOIN < global.PRICE_DAO(BlockNum).NewAccount) return "Not enough money for create account with index: " + i; var name = TR.Description; var index = name.indexOf("\n"); if (index !== - 1) name = name.substr(0, index) var Account = this.NewAccountTR(BlockNum, TrNum); Account.PubKey = Item.PubKey Account.Name = name this.WriteStateTR(Account, TrNum) this.ResultTx = Account.Num Item.ID = Account.Num this.SendMoneyTR(Block, Data.Num, Account.Num, { SumCOIN: Item.SumCOIN, SumCENT: Item.SumCENT }, BlockNum, TrNum, TR.Description, TR.Description, 1) this.SendMoneyTR(Block, Account.Num, 0, { SumCOIN: global.PRICE_DAO(BlockNum).NewAccount, SumCENT: 0 }, BlockNum, TrNum, "Fee for create account", "", 1) } else { if (TR.Version < 3) { if (Item.ID === TR.FromID || MapItem[Item.ID]) continue; MapItem[Item.ID] = 1 } this.SendMoneyTR(Block, Data.Num, DataTo.Num, { SumCOIN: Item.SumCOIN, SumCENT: Item.SumCENT }, BlockNum, TrNum, TR.Description, TR.Description, 0) arr.push(DataTo) } } if (TR.Version < 3 && arr.length === 0) return "No recipients"; var hash; if (TR.Version === 2 || TR.Version === 3) { for (var j = 0; j < Body.length - 64 - 12; j++) arrpub[arrpub.length] = Body[j] hash = global.SHA3BUF(arrpub, BlockNum) } else if (!TR.Version) { hash = global.SHA3BUF(Body.slice(0, Body.length - 64 - 12), BlockNum) } else { return "Error transaction version"; } var Result: any = 0; if (Data.PubKey[0] === 2 || Data.PubKey[0] === 3) try { Result = secp256k1.verify(hash, TR.Sign, Data.PubKey) } catch (e) { } if (!Result) { return "Error sign transaction"; } if (TR.Body && TR.Body.length) { var App = global.DAppByType[TR.Body[0]]; if (App) { TR.FromPubKey = Data.PubKey Result = App.OnWriteTransaction(Block, TR.Body, BlockNum, TrNum, TR); if (Result as any !== true) return Result; } } return true; } ReadState(Num) { var Data = this.DBState.Read(Num); if (Data) Data.WN = "" return Data; } GetMinBlockAct() { var DBAct; var MaxNum = this.DBActPrev.GetMaxNum(); if (MaxNum === - 1) DBAct = this.DBAct else DBAct = this.DBActPrev var Item = DBAct.Read(0); if (!Item) return - 1; else return Item.BlockNum; } DeleteAct(BlockNumFrom) { if (global.START_SERVER) throw "DeleteAct START_SERVER"; if (BlockNumFrom > 0) { var StateTX = this.DBStateTX.Read(0); StateTX.BlockNum = BlockNumFrom - 1 this.DBStateTX.Write(StateTX) } this.DeleteActOneDB(this.DBAct, BlockNumFrom) this.DeleteActOneDB(this.DBActPrev, BlockNumFrom) this.DBAccountsHash.Truncate(Math.trunc(BlockNumFrom / global.PERIOD_ACCOUNT_HASH)) } DeleteActOneDB(DBAct, BlockNum) { var MaxNum = DBAct.GetMaxNum(); if (MaxNum === - 1) return; for (var num = MaxNum; num >= 0; num--) { var ItemCheck = DBAct.Read(num); if (!ItemCheck) { global.ToLogTrace("!ItemCheck") throw "ERRR DeleteActOneDB"; } if (ItemCheck.BlockNum < BlockNum) { this.ProcessingDeleteAct(DBAct, num + 1) return; } } this.ProcessingDeleteAct(DBAct, 0) } ProcessingDeleteAct(DBAct, StartNum) { var Map = {}; var bWas = 0; var NumTruncateState; for (var num = StartNum; true; num++) { var Item = DBAct.Read(num); if (!Item) break; bWas = 1 if (Map[Item.ID]) continue; Map[Item.ID] = 1 if (Item.Mode === 1) { if (!NumTruncateState) NumTruncateState = Item.ID } else { var Data = this.DBState.Read(Item.ID); Data.Value = Item.PrevValue this._DBStateWrite(Data, Item.BlockNum, 1) var History = this.DBStateHistory.Read(Item.ID); if (History) { History.NextPos = Item.PrevValue.NextPos this.DBStateHistory.Write(History) } } } if (bWas) { if (NumTruncateState) { this._DBStateTruncate(NumTruncateState - 1) this.DBStateHistory.Truncate(NumTruncateState - 1) } DBAct.Truncate(StartNum - 1) } } FindBlockInAct(DBAct, BlockNum) { return DBAct.FastFindBlockNum(BlockNum); } GetHole() { if (global.TEST_NETWORK) return []; return [{ s: 8300, f: 186478 }]; } IsHole(num) { if (global.ALL_VIEW_ROWS) return 0; var ArrHole = this.GetHole(); for (var i = 0; i < ArrHole.length; i++) if (num >= ArrHole[i].s && num <= ArrHole[i].f) return 1; return 0; } FindAccounts(PubKeyArr, map, nSet) { var Count = 0; for (var num = 0; true; num++) { if (this.IsHole(num)) continue; var Data = this.ReadState(num); if (!Data) break; for (var i = 0; i < PubKeyArr.length; i++) if (global.CompareArr(Data.PubKey, PubKeyArr[i]) === 0) { map[Data.Num] = i Count++ } } return Count; } GetWalletAccountsByMap(map) { var arr = []; for (var key in map) { var Num = parseInt(key); var Data = this.ReadState(Num); if (Data) { if (!Data.PubKeyStr) Data.PubKeyStr = global.GetHexFromArr(Data.PubKey) arr.push(Data) Data.WN = map[key] Data.Name = global.NormalizeName(Data.Name) if (Data.Currency) Data.CurrencyObj = global.DApps.Smart.ReadSimple(Data.Currency) if (Data.Value.Smart) { Data.SmartObj = global.DApps.Smart.ReadSimple(Data.Value.Smart) if (Data.SmartObj) Data.SmartState = this.GetSmartState(Data, Data.SmartObj.StateFormat) else Data.SmartState = {} } } } return arr; } GetMaxAccount() { return this.DBState.GetMaxNum(); } GetRowsAccounts(start, count, Filter?, bGetState?) { if (Filter) { Filter = Filter.trim() } var F; if (Filter) { if (Filter.substring(0, 1) === "=") { Filter = Filter.substring(1) try { F = global.CreateEval(Filter, "Cur,Currency,ID,Operation,Amount,Adviser,Name,PubKey,Smart,BlockNum") } catch (e) { F = undefined global.ToLog("" + e) } } else { Filter = Filter.toUpperCase() } } var WasError = 0; var arr = []; for (var num = start; true; num++) { if (this.IsHole(num)) continue; var Data = this.ReadState(num); if (!Data) break; if (!Data.PubKeyStr) Data.PubKeyStr = global.GetHexFromArr(Data.PubKey) Data.Name = global.NormalizeName(Data.Name) if (F) { var Cur = Data.Currency; var Currency = Data.Currency; var ID = Data.Num; var Operation = Data.Value.OperationID; var Amount = global.FLOAT_FROM_COIN(Data.Value); var Adviser = Data.Adviser; var Name = Data.Name; var PubKey = global.GetHexFromArr(Data.PubKey); var Smart = Data.Value.Smart; try { if (!F(Cur, Currency, ID, Operation, Amount, Adviser, Name, PubKey, Smart, Data.BlockNumCreate)) continue; } catch (e) { if (!WasError) global.ToLog("" + e) WasError = 1 } } else if (Filter) { var Amount = global.FLOAT_FROM_COIN(Data.Value); var PubKey = global.GetHexFromArr(Data.PubKey); var Str = "" + Data.Num + " " + Data.Value.OperationID + " " + Data.Name.toUpperCase() + " " + Data.Adviser + " " + Amount + " " + PubKey + " " + Smart + " " + Data.BlockNumCreate; if (Str.indexOf(Filter) < 0) continue; } if (bGetState) { if (Data.Currency) Data.CurrencyObj = global.DApps.Smart.ReadSimple(Data.Currency) if (Data.Value.Smart) { Data.SmartObj = global.DApps.Smart.ReadSimple(Data.Value.Smart) if (Data.SmartObj) Data.SmartState = this.GetSmartState(Data, Data.SmartObj.StateFormat) else Data.SmartState = {} } } arr.push(Data) count-- if (count < 1) break; } return arr; } GetSmartState(StateData, StateFormat) { var SmartState; try { SmartState = global.BufLib.GetObjectFromBuffer(StateData.Value.Data, StateFormat, {}) if (typeof SmartState === "object") SmartState.Num = StateData.Num } catch (e) { SmartState = {} } return SmartState; } GetActsMaxNum() { return this.DBActPrev.GetMaxNum() + this.DBAct.GetMaxNum(); } GetActList(start, count, b?) { var arr = []; var num; for (num = start; num < start + count; num++) { var Item = this.DBActPrev.Read(num); if (!Item) break; Item.Num = "Prev." + Item.Num if (Item.TrNum === 0xFFFF) Item.TrNum = "" arr.push(Item) if (arr.length > count) return arr; } start = num - this.DBActPrev.GetMaxNum() - 1 for (num = start; num < start + count; num++) { var Item = this.DBAct.Read(num); if (!Item) break; Item.Num = Item.Num if (Item.TrNum === 0xFFFF) Item.TrNum = "" arr.push(Item) if (arr.length > count) return arr; } return arr; } GetHashOrUndefined(BlockNum) { if (BlockNum % global.PERIOD_ACCOUNT_HASH !== 0) return undefined; var Item = this.GetAccountHashItem(BlockNum); if (Item) return Item.AccHash; else return undefined; } GetAccountHashItem(BlockNum) { var Item = this.DBAccountsHash.Read(Math.trunc(BlockNum / global.PERIOD_ACCOUNT_HASH)); return Item; } GetHashedMaxBlockNum() { var Num = this.DBAccountsHash.GetMaxNum(); if (Num >= 0) { var Data = this.DBAccountsHash.Read(Num); return Data.BlockNum; } else return 0; } CalcHash(Block, BlockMaxAccount) { if (Block.BlockNum % global.PERIOD_ACCOUNT_HASH !== 0) return; if (this.DBState.WasUpdate) { this.CalcMerkleTree() } var Hash = this.DBState.MerkleHash; var SmartHash; var SmartCount = global.DApps.Smart.GetMaxNum() + 1; if (SmartCount > 0) { var MaxSmart = global.DApps.Smart.DBSmart.Read(SmartCount - 1); SmartHash = MaxSmart.SumHash } else { SmartHash = [] } var Data = { Num: Block.BlockNum / global.PERIOD_ACCOUNT_HASH, BlockNum: Block.BlockNum, AccHash: Hash, SumHash: Block.SumHash, AccountMax: BlockMaxAccount, SmartHash: SmartHash, SmartCount: SmartCount }; this.DBAccountsHash.Write(Data) this.DBAccountsHash.Truncate(Block.BlockNum / global.PERIOD_ACCOUNT_HASH) return Data; } CalcMerkleTree(bForce?) { this.DBState.MerkleHash = this.DBState.CalcMerkleTree(bForce) this.DBState.WasUpdate = 0 } GetAdviserByMiner(Map, Id) { var Adviser = Map[Id]; if (Adviser === undefined) { var Item = this.ReadState(Id); if (Item) Adviser = Item.Adviser else Adviser = 0 Map[Id] = Adviser } return Adviser; } BeginBlock() { this.DBChanges = { BlockMap: {}, BlockMaxAccount: this.GetMaxAccount(), BlockHistory: [], BlockEvent: [], } } BeginTransaction() { global.TickCounter = 35000 this.DBChanges.TRMap = {} this.DBChanges.TRMaxAccount = this.DBChanges.BlockMaxAccount this.DBChanges.RollBackTransaction = 0 this.DBChanges.TRHistory = [] this.DBChanges.TREvent = [] } RollBackTransaction() { this.DBChanges.RollBackTransaction = 1 } CommitBlock(Block) { var BlockNum = Block.BlockNum; var DBChanges = this.DBChanges; for (var i = 0; i < DBChanges.BlockHistory.length; i++) { var Data = DBChanges.BlockHistory[i]; var Account = DBChanges.BlockMap[Data.CurID]; Data.Type = 1 Data.NextPos = Account.Value.NextPos Account.Value.NextPos = this.SaveHistory(Data) } var arr = []; for (var key in DBChanges.BlockMap) { key = global.ParseNum(key) var Data = DBChanges.BlockMap[key]; if (Data.Changed) { arr.push(Data) } } arr.sort(function(a, b) { return a.Num - b.Num; }) for (var i = 0; i < arr.length; i++) { var Account = arr[i]; var BackLog = { Num: undefined, ID: Account.Num, BlockNum: BlockNum, PrevValue: Account.BackupValue, TrNum: Account.ChangeTrNum, Mode: Account.New }; this.DBAct.Write(BackLog) } for (var i = 0; i < arr.length; i++) { var Account = arr[i]; this._DBStateWrite(Account, BlockNum, 0) } for (var i = 0; i < arr.length; i++) { var Account = arr[i]; var History = { Num: Account.Num, NextPos: Account.Value.NextPos }; this.DBStateHistory.Write(History) } for (var i = 0; i < DBChanges.BlockEvent.length; i++) { var Data = DBChanges.BlockEvent[i]; var Has = global.TreeFindTX.LoadValue("Smart:" + Data.Smart, 1); if (Has) { process.send({ cmd: "DappEvent", Data: Data }) } } global.TickCounter = 0 this.DBChanges = undefined this.CalcHash(Block, DBChanges.BlockMaxAccount) var StateTX = this.DBStateTX.Read(0); StateTX.BlockNum = BlockNum this.DBStateTX.Write(StateTX) } CommitTransaction(BlockNum, TrNum) { var DBChanges = this.DBChanges; if (DBChanges.RollBackTransaction) return false; DBChanges.BlockMaxAccount = DBChanges.TRMaxAccount for (var key in DBChanges.TRMap) { key = global.ParseNum(key) var Data = DBChanges.TRMap[key]; if (Data.Changed) { DBChanges.BlockMap[key] = Data if (Data.New) this.OnWriteNewAccountTR(Data, BlockNum, TrNum) } } for (var i = 0; i < DBChanges.TRHistory.length; i++) DBChanges.BlockHistory.push(DBChanges.TRHistory[i]) for (var i = 0; i < DBChanges.TREvent.length; i++) { DBChanges.BlockEvent.push(DBChanges.TREvent[i]) } global.TickCounter = 0 return true; } OnWriteNewAccountTR(Data, BlockNum, TrNum) { if (BlockNum < global.SMART_BLOCKNUM_START) Data.Value.Smart = 0 Data.BlockNumCreate = BlockNum if (Data.Adviser > this.GetMaxAccount()) Data.Adviser = 0 if (Data.Value.Smart > global.DApps.Smart.GetMaxNum()) Data.Value.Smart = 0 if (Data.Currency > global.DApps.Smart.GetMaxNum()) Data.Currency = 0 if (Data.Currency) { var Smart = global.DApps.Smart.ReadSmart(Data.Currency); if (!Smart || !Smart.TokenGenerate) Data.Currency = 0 } } NewAccountTR(BlockNum, TrNum) { var DBChanges = this.DBChanges; DBChanges.TRMaxAccount++ var Data: any = { Num: DBChanges.TRMaxAccount, New: 1, Changed: 1, ChangeTrNum: TrNum, BackupValue: {}, PubKey: [], Currency: 0, Adviser: 0, Value: { SumCOIN: 0, SumCENT: 0, OperationID: 0, Smart: 0, Data: [] } }; this.DBChanges.TRMap[Data.Num] = Data return Data; } ReadStateTR(Num) { Num = global.ParseNum(Num) var TRMap = this.DBChanges.TRMap; var Data = TRMap[Num]; if (!Data) { var Value; var BlockMap = this.DBChanges.BlockMap; var BData = BlockMap[Num]; if (!BData) { BData = this.DBState.Read(Num) if (!BData) return undefined; BData.Num = Num Value = BData.Value var BHistory = this.DBStateHistory.Read(Num); if (BHistory) Value.NextPos = BHistory.NextPos BData.BackupValue = { SumCOIN: Value.SumCOIN, SumCENT: Value.SumCENT, OperationID: Value.OperationID, Smart: Value.Smart, Data: Value.Data, NextPos: Value.NextPos } BlockMap[Num] = BData } Value = BData.Value Data = { Num: Num, Currency: BData.Currency, PubKey: BData.PubKey, Name: BData.Name, BlockNumCreate: BData.BlockNumCreate, Adviser: BData.Adviser, Value: { SumCOIN: Value.SumCOIN, SumCENT: Value.SumCENT, OperationID: Value.OperationID, Smart: Value.Smart, Data: global.CopyArr(Value.Data), NextPos: Value.NextPos }, BackupValue: BData.BackupValue } TRMap[Num] = Data } return Data; } WriteStateTR(Data, TrNum) { Data.Changed = 1 Data.ChangeTrNum = TrNum } SendMoneyTR(Block, FromID, ToID, CoinSum, BlockNum, TrNum, DescriptionFrom, DescriptionTo, OperationCount) { FromID = global.ParseNum(FromID) ToID = global.ParseNum(ToID) if (CoinSum.SumCENT >= 1e9) { throw "ERROR SumCENT>=1e9"; } var FromData = this.ReadStateTR(FromID); if (!FromData) { throw "Send: Error account FromNum: " + FromID; } if (!global.SUB(FromData.Value, CoinSum)) { throw "Not enough money on the account ID:" + FromID; } this.WriteStateTR(FromData, TrNum) if (FromID > 15) { this.DBChanges.TRHistory.push({ Direct: "-", Receive: 0, CurID: FromID, CorrID: ToID, BlockNum: BlockNum, TrNum: TrNum, FromID: FromID, ToID: ToID, SumCOIN: CoinSum.SumCOIN, SumCENT: CoinSum.SumCENT, Description: DescriptionFrom, FromOperationID: FromData.Value.OperationID, Currency: FromData.Currency }) } var ToData = this.ReadStateTR(ToID); if (!ToData) { throw "Send: Error account ToNum: " + ToID; } global.ADD(ToData.Value, CoinSum) this.WriteStateTR(ToData, TrNum) if (ToID > 15) { this.DBChanges.TRHistory.push({ Direct: "+", Receive: 1, CurID: ToID, CorrID: FromID, BlockNum: BlockNum, TrNum: TrNum, FromID: FromID, ToID: ToID, SumCOIN: CoinSum.SumCOIN, SumCENT: CoinSum.SumCENT, Description: DescriptionTo, FromOperationID: FromData.Value.OperationID, Currency: ToData.Currency }) } FromData.Value.OperationID += OperationCount if (FromData.Value.Smart) { var Context = { FromID: FromID, ToID: ToID, Description: DescriptionFrom, Value: CoinSum }; global.RunSmartMethod(Block, FromData.Value.Smart, FromData, BlockNum, TrNum, Context, "OnSend") } if (ToData.Value.Smart) { var Context = { FromID: FromID, ToID: ToID, Description: DescriptionTo, Value: CoinSum }; global.RunSmartMethod(Block, ToData.Value.Smart, ToData, BlockNum, TrNum, Context, "OnGet") } } GetSignTransferTx(TR, PrivKey) { var Arr; if (TR.Version === 2 || TR.Version === 3) { var format; if (TR.Version === 2) format = DB_FORMAT.FORMAT_MONEY_TRANSFER_BODY2 else format = DB_FORMAT.FORMAT_MONEY_TRANSFER_BODY3 Arr = [] for (var i = 0; i < TR.To.length; i++) { var Item = TR.To[i]; var DataTo = global.DApps.Accounts.ReadState(Item.ID); if (!DataTo) return [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, 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 j = 0; j < 33; j++) Arr[Arr.length] = DataTo.PubKey[j] } var Body = global.BufLib.GetBufferFromObject(TR, format, global.MAX_TRANSACTION_SIZE, {}); for (var j = 0; j < Body.length; j++) Arr[Arr.length] = Body[j] } else { Arr = global.BufLib.GetBufferFromObject(TR, DB_FORMAT.FORMAT_MONEY_TRANSFER_BODY, global.MAX_TRANSACTION_SIZE, {}) } var sigObj = secp256k1.sign(global.SHA3BUF(Arr), Buffer.from(PrivKey)); return sigObj.signature; } SaveHistory(Data) { var FileItem = global.HistoryDB.OpenDBFile(FILE_NAME_HISTORY, 1); var FD = FileItem.fd; var Position = FileItem.size; if (!Position) Position = 100 var BufWrite = global.BufLib.GetBufferFromObject(Data, this.HistoryFormatArr[Data.Type], 100, WorkStructHistory); var written = fs.writeSync(FD, BufWrite, 0, BufWrite.length, Position); if (written !== BufWrite.length) { global.TO_ERROR_LOG("DB-HISTORY", 10, "Error write to file:" + written + " <> " + BufWrite.length) throw "Error write to FILE_NAME_HISTORY"; return false; } if (Position >= FileItem.size) { FileItem.size = Position + written } return Position; } GetHistory(Num, Count, StartPos, MinConfirm?) { if (!MinConfirm) MinConfirm = 0 var MaxNumBlockDB = global.SERVER.GetMaxNumBlockDB(); var Position = StartPos; var FileItem = global.HistoryDB.OpenDBFile(FILE_NAME_HISTORY, 0); var FD = FileItem.fd; if (Position === undefined) { var Account = this.DBStateHistory.Read(Num); if (!Account) { return []; } Position = Account.NextPos } var arr = []; while (Count > 0 && Position) { Count-- var BufRead = global.BufLib.GetNewBuffer(100); var bytesRead = fs.readSync(FD, BufRead, 0, BufRead.length, Position); if (bytesRead < 13) { global.ToLog("bytesRead<13 Position=" + Position) break; } var Type = BufRead[0]; var format = this.HistoryFormatArr[Type]; if (!format) { global.ToLog("Error from history, type = " + Type) break; } var Item = global.BufLib.GetObjectFromBuffer(BufRead, format, WorkStructHistory); Item.Pos = Position Position = Item.NextPos if (MinConfirm) { if (Item.BlockNum + MinConfirm > MaxNumBlockDB) { continue; } } arr.push(Item) } return arr; } CalcTotalSum(Currency) { var SumCoin = { SumCOIN: 0, SumCENT: 0 }; for (var num = 0; true; num++) { var Data = this.ReadState(num); if (!Data) break; if (Data.Currency === Currency) { global.ADD(SumCoin, Data.Value) } } return global.FLOAT_FROM_COIN(SumCoin); } }; var App = new AccountApp; global.DApps["Accounts"] = App; global.DAppByType[TYPE_TRANSACTION.TYPE_TRANSACTION_CREATE] = App; global.DAppByType[TYPE_TRANSACTION.TYPE_DEPRECATED_TRANSFER1] = App; global.DAppByType[TYPE_TRANSACTION.TYPE_DEPRECATED_TRANSFER2] = App; global.DAppByType[TYPE_TRANSACTION.TYPE_TRANSACTION_TRANSFER] = App; global.DAppByType[TYPE_TRANSACTION.TYPE_TRANSACTION_ACC_HASH] = App;