UCoreUtils.pas 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686
  1. unit UCoreUtils;
  2. { Copyright (c) 2018 by Herman Schoenfeld
  3. Distributed under the MIT software license, see the accompanying file LICENSE
  4. or visit http://www.opensource.org/licenses/mit-license.php.
  5. This unit is a part of the PascalCoin Project, an infinitely scalable
  6. cryptocurrency. Find us here:
  7. Web: https://www.pascalcoin.org
  8. Source: https://github.com/PascalCoin/PascalCoin
  9. Acknowledgements:
  10. - Ugochukwu Mmaduekwe - added TOperationsManager class
  11. THIS LICENSE HEADER MUST NOT BE REMOVED.
  12. }
  13. {$mode delphi}
  14. {$modeswitch nestedprocvars}
  15. interface
  16. uses
  17. Classes, SysUtils, Forms, Dialogs, LCLType, UAccounts, UBlockChain, UNode, UWallet,
  18. UBaseTypes, UCommon, UCoreObjects, UCommon.Collections, Generics.Defaults;
  19. type
  20. { TAccountComparer }
  21. TAccountComparer = class(TComparer<TAccount>)
  22. function Compare(constref ALeft, ARight: T): integer; override;
  23. class function DoCompare(constref ALeft, ARight: TAccount): integer; inline;
  24. end;
  25. { TAccountEqualityComparer }
  26. TAccountEqualityComparer = class(TEqualityComparer<TAccount>)
  27. public
  28. function Equals(constref ALeft, ARight: TAccount): boolean; override;
  29. function GetHashCode(constref AValue: TAccount): UInt32; override;
  30. class function AreEqual(constref ALeft, ARight: TAccount): boolean;
  31. class function CalcHashCode(constref AValue: TAccount): UInt32;
  32. end;
  33. { TAccountKeyComparer }
  34. TAccountKeyComparer = class(TComparer<TAccountKey>)
  35. function Compare(constref ALeft, ARight: T): integer; override;
  36. class function DoCompare(constref ALeft, ARight: TAccountKey): integer; inline;
  37. end;
  38. { TAccountKeyEqualityComparer }
  39. TAccountKeyEqualityComparer = class(TEqualityComparer<TAccountKey>)
  40. public
  41. function Equals(constref ALeft, ARight: TAccountKey): boolean; override;
  42. function GetHashCode(constref AValue: TAccountKey): UInt32; override;
  43. class function AreEqual(constref ALeft, ARight: TAccountKey): boolean;
  44. class function CalcHashCode(constref AValue: TAccountKey): UInt32;
  45. end;
  46. { TCoreTool }
  47. TCoreTool = class
  48. public
  49. class function GetSignerCandidates(ANumOps: integer; ASingleOperationFee: int64; const ACandidates: array of TAccount): TArray<TAccount>; static;
  50. class function GetOperationShortText(const OpType, OpSubType: DWord): ansistring; static; inline;
  51. class function GetUserBalance(IncludePending: boolean = False): TBalanceSummary;
  52. class function GetUserAccounts(IncludePending: boolean = False): TArray<TAccount>; overload;
  53. class function GetUserAccounts(out Balance: TBalanceSummary; IncludePending: boolean = False): TArray<TAccount>; overload;
  54. class function GetUserAccountNumbers: TArray<cardinal>;
  55. class function AreAccountBalancesGreaterThan(const ACandidates: array of TAccount; AAmount: int64; var AFaultyAccount: TAccount): boolean; static;
  56. end;
  57. { TNodeHelper }
  58. TNodeHelper = class helper for TNode
  59. function HasBestKnownBlockchainTip: boolean;
  60. function BlockTip: cardinal;
  61. function GetAccount(AAccountNumber: cardinal; AIncludePending: boolean = True): TAccount;
  62. function GetAccounts(const AAccountNumbers: array of cardinal; AIncludePending: boolean = True): TArray<TAccount>;
  63. function GetPendingOperationsAffectingAccounts(const AAccountNumbers: array of cardinal; ASkipCount, ATakeCount: integer): TArray<TOperationResume>;
  64. function GetStoredOperationsAffectingAccounts(const AAccountNumbers: array of cardinal; ABlockDepth, ASkipCount, ATakeCount: integer): TArray<TOperationResume>;
  65. end;
  66. { TAccountHelper }
  67. TAccountHelper = record helper for TAccount
  68. function GetAccountString : AnsiString;
  69. function GetDisplayString : AnsiString;
  70. function GetInfoText(const ABank : TPCBank) : utf8string;
  71. property AccountString : AnsiString read GetAccountString;
  72. property DisplayString : AnsiString read GetDisplayString;
  73. end;
  74. { TOperationResumeHelper }
  75. TOperationResumeHelper = record helper for TOperationResume
  76. function GetPrintableOPHASH : AnsiString;
  77. function GetInfoText(const ABank : TPCBank) : utf8string;
  78. end;
  79. { TTimeSpanHelper }
  80. TTimeSpanHelper = record helper for TTimeSpan
  81. function TotalBlockCount : Integer;
  82. end;
  83. { TWIZOperationsHelper }
  84. TWIZOperationsHelper = class
  85. private
  86. class function IsOwnerOfWallet(AAccount: TAccount; AWalletKeys: TWalletKeys; out AWalletKey: TWalletKey; var AErrorMessage: string): boolean; static;
  87. class function ValidateOperationsInput(const ASelectedAccounts: TArray<TAccount>; AWalletKeys: TWalletKeys; ANode: TNode; var AErrorMessage: string): boolean; static;
  88. class function UpdatePayload(const ASenderPublicKey, ADestinationPublicKey: TAccountKey; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent: string; var AEncodedPayloadBytes: TRawBytes; const APayloadEncryptionPassword: string; var AErrorMessage: string): boolean; static;
  89. class function SendPASCFinalizeAndDisplayMessage(const AOperationsTxt, AOperationToString: string; ANoOfOperations: integer; ATotalAmount, ATotalFee: int64; AOperationsHashTree: TOperationsHashTree; var AErrorMessage: string): boolean; static;
  90. class function OthersFinalizeAndDisplayMessage(const AOperationsTxt, AOperationToString: string; ANoOfOperations: integer; ATotalFee: int64; AOperationsHashTree: TOperationsHashTree; var AErrorMessage: string): boolean; static;
  91. public
  92. class function ExecuteSendPASC(const ASelectedAccounts: TArray<TAccount>; const ADestinationAccount: TAccount; AAmount, AFee: int64; const ASendPASCMode: TSendPASCMode; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean; static;
  93. class function ExecuteChangeKey(const ASelectedAccounts: TArray<TAccount>; const ASignerAccount: TAccount; APublicKey: TAccountKey; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean; static;
  94. class function ExecuteEnlistAccountForSale(const ASelectedAccounts: TArray<TAccount>; const ASignerAccount, ASellerAccount: TAccount; const APublicKey: TAccountKey; AFee, ASalePrice: int64; ALockedUntilBlock: UInt32; const AAccountSaleMode: TAccountSaleMode; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean; static;
  95. class function ExecuteDelistAccountFromSale(const ASelectedAccounts: TArray<TAccount>; const ASignerAccount: TAccount; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean; static;
  96. class function ExecuteChangeAccountInfo(const ASelectedAccounts, ASignerAccounts: TArray<TAccount>; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; const ANewName: TRawBytes; const ANewType: word; var AErrorMessage: string): boolean; static;
  97. class function ExecuteBuyAccount(const ASelectedAccounts: TArray<TAccount>; const AAccountToBuy: TAccount; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; const AAmount: int64; const ANewOwnerPublicKey: TAccountKey; var AErrorMessage: string): boolean; static;
  98. end;
  99. implementation
  100. uses
  101. ULog,
  102. UAES,
  103. UConst,
  104. UECIES,
  105. UCrypto,
  106. UMemory,
  107. UNetProtocol,
  108. UOpTransaction,
  109. UPCOrderedLists,
  110. Generics.Collections;
  111. { TCoreTool }
  112. class function TCoreTool.GetSignerCandidates(ANumOps: integer; ASingleOperationFee: int64; const ACandidates: array of TAccount): TArray<TAccount>;
  113. var
  114. i, PoorSenderCount: integer;
  115. Fee, maxSignerFee, minSignerFee: int64;
  116. acc: TAccount;
  117. begin
  118. //make deep copy of accounts!!! Very Important
  119. Result := TArrayTool<TAccount>.Copy(ACandidates);
  120. Fee := ASingleOperationFee;
  121. PoorSenderCount := 0;
  122. for i := Low(Result) to High(Result) do
  123. begin
  124. acc := Result[i];
  125. if (acc.Balance < Fee) then
  126. Inc(PoorSenderCount);
  127. end;
  128. maxSignerFee := ANumOps * Fee;
  129. minSignerFee := maxSignerFee - (PoorSenderCount * Fee);
  130. for i := High(Result) downto Low(Result) do
  131. begin
  132. acc := Result[i];
  133. if not (acc.Balance >= maxSignerFee) then
  134. TArrayTool<TAccount>.RemoveAt(Result, i);
  135. end;
  136. end;
  137. class function TCoreTool.GetOperationShortText(const OpType, OpSubType: DWord): ansistring;
  138. begin
  139. case OpType of
  140. CT_PseudoOp_Reward: case OpSubType of
  141. 0, CT_PseudoOpSubtype_Miner: Result := 'Miner Reward';
  142. CT_PseudoOpSubtype_Developer: Result := 'Developer Reward';
  143. else
  144. Result := 'Unknown';
  145. end;
  146. CT_Op_Transaction: case OpSubType of
  147. CT_OpSubtype_TransactionSender: Result := 'Send';
  148. CT_OpSubtype_TransactionReceiver: Result := 'Receive';
  149. CT_OpSubtype_BuyTransactionBuyer: Result := 'Buy Account Direct';
  150. CT_OpSubtype_BuyTransactionTarget: Result := 'Purchased Account Direct';
  151. CT_OpSubtype_BuyTransactionSeller: Result := 'Sold Account Direct';
  152. else
  153. Result := 'Unknown';
  154. end;
  155. CT_Op_Changekey: Result := 'Change Key (legacy)';
  156. CT_Op_Recover: Result := 'Recover';
  157. CT_Op_ListAccountForSale: case OpSubType of
  158. CT_OpSubtype_ListAccountForPublicSale: Result := 'For Sale';
  159. CT_OpSubtype_ListAccountForPrivateSale: Result := 'Exclusive Sale';
  160. else
  161. Result := 'Unknown';
  162. end;
  163. CT_Op_DelistAccount: Result := 'Remove Sale';
  164. CT_Op_BuyAccount: case OpSubType of
  165. CT_OpSubtype_BuyAccountBuyer: Result := 'Buy Account';
  166. CT_OpSubtype_BuyAccountTarget: Result := 'Purchased Account';
  167. CT_OpSubtype_BuyAccountSeller: Result := 'Sold Account';
  168. else
  169. Result := 'Unknown';
  170. end;
  171. CT_Op_ChangeKeySigned: Result := 'Change Key';
  172. CT_Op_ChangeAccountInfo: Result := 'Change Info';
  173. CT_Op_MultiOperation: case OpSubType of
  174. CT_OpSubtype_MultiOperation_Global: Result := 'Mixed-Transfer';
  175. CT_OpSubtype_MultiOperation_AccountInfo: Result := 'Mixed-Change';
  176. end;
  177. else
  178. Result := 'Unknown';
  179. end;
  180. end;
  181. class function TCoreTool.GetUserBalance(IncludePending: boolean = False): TBalanceSummary;
  182. begin
  183. GetUserAccounts(Result, IncludePending);
  184. end;
  185. class function TCoreTool.GetUserAccounts(IncludePending: boolean = False): TArray<TAccount>;
  186. var
  187. LBalance: TBalanceSummary;
  188. begin
  189. Result := GetUserAccounts(LBalance, IncludePending);
  190. end;
  191. class function TCoreTool.GetUserAccounts(out Balance: TBalanceSummary; IncludePending: boolean = False): TArray<TAccount>;
  192. var
  193. i, j: integer;
  194. LAccs: TList<TAccount>;
  195. LAcc: TAccount;
  196. LList: TOrderedCardinalList;
  197. LMemPool: TPCOperationsComp;
  198. Disposables: TDisposables;
  199. begin
  200. Balance := CT_BalanceSummary_Nil;
  201. LAccs := Disposables.AddObject(TList<TAccount>.Create) as TList<TAccount>;
  202. // TNode.Node.Bank.SafeBox.StartThreadSafe;
  203. LMemPool := TNode.Node.LockMempoolRead;
  204. try
  205. for i := 0 to TWallet.Keys.Count - 1 do
  206. begin
  207. LList := TWallet.Keys.AccountsKeyList.AccountKeyList[i];
  208. for j := 0 to LList.Count - 1 do
  209. begin
  210. if IncludePending then
  211. LAcc := LMemPool.SafeBoxTransaction.Account(LList.Get(j))
  212. else
  213. LAcc := TNode.Node.Bank.SafeBox.Account(LList.Get(j));
  214. LAccs.Add(LAcc);
  215. Inc(Balance.TotalPASA);
  216. Inc(Balance.TotalPASC, LAcc.Balance);
  217. end;
  218. end;
  219. finally
  220. TNode.Node.UnlockMempoolRead;
  221. // TNode.Node.Bank.SafeBox.EndThreadSave;
  222. end;
  223. LAccs.Sort(TAccountComparer.Create);
  224. Result := LAccs.ToArray;
  225. end;
  226. class function TCoreTool.GetUserAccountNumbers: TArray<cardinal>;
  227. var
  228. i, j: integer;
  229. LAccs: TSortedList<cardinal>;
  230. LList: TOrderedCardinalList;
  231. Disposables: TDisposables;
  232. begin
  233. LAccs := Disposables.AddObject(TSortedList<cardinal>.Create) as TSortedList<cardinal>;
  234. for i := 0 to TWallet.Keys.AccountsKeyList.Count - 1 do
  235. begin
  236. LList := TWallet.Keys.AccountsKeyList.AccountKeyList[i];
  237. for j := 0 to LList.Count - 1 do
  238. LAccs.Add(LList.Get(j));
  239. end;
  240. Result := LAccs.ToArray;
  241. end;
  242. class function TCoreTool.AreAccountBalancesGreaterThan(const ACandidates: array of TAccount; AAmount: int64; var AFaultyAccount: TAccount): boolean;
  243. var
  244. LIdx: integer;
  245. LAcc: TAccount;
  246. begin
  247. Result := True;
  248. for LIdx := Low(ACandidates) to High(ACandidates) do
  249. begin
  250. LAcc := ACandidates[LIdx];
  251. if not (LAcc.Balance > AAmount) then
  252. begin
  253. AFaultyAccount := LAcc;
  254. Exit(False);
  255. end;
  256. end;
  257. end;
  258. { TNodeHelper }
  259. function TNodeHelper.HasBestKnownBlockchainTip: boolean;
  260. var
  261. LReady: boolean;
  262. LMsg: ansistring;
  263. LDestBlock: cardinal;
  264. begin
  265. LReady := Self.Bank.IsReady(LMsg);
  266. if LReady and TNetData.NetData.IsGettingNewBlockChainFromClient(LMsg) then
  267. begin
  268. LDestBlock := TNetData.NetData.MaxRemoteOperationBlock.block;
  269. Result := Self.Bank.BlocksCount = TNetData.NetData.MaxRemoteOperationBlock.block;
  270. end;
  271. end;
  272. function TNodeHelper.BlockTip: cardinal;
  273. begin
  274. Result := ClipValue(Self.Bank.BlocksCount - 1, 0, MaxInt);
  275. end;
  276. function TNodeHelper.GetAccount(AAccountNumber: cardinal; AIncludePending: boolean = True): TAccount;
  277. var
  278. LOps: TArray<TAccount>;
  279. begin
  280. LOps := Self.GetAccounts([AAccountNumber], AIncludePending);
  281. Result := LOps[Low(Lops)];
  282. end;
  283. function TNodeHelper.GetAccounts(const AAccountNumbers: array of cardinal; AIncludePending: boolean = True): TArray<TAccount>;
  284. var
  285. i: integer;
  286. LMemPool : TPCOperationsComp;
  287. begin
  288. SetLength(Result, Length(AAccountNumbers));
  289. if AIncludePending then begin
  290. LMemPool := Self.LockMempoolRead;
  291. try
  292. for i := Low(AAccountNumbers) to High(AAccountNumbers) do
  293. Result[i] := LMemPool.SafeBoxTransaction.Account(AAccountNumbers[i])
  294. finally
  295. Self.UnlockMempoolRead;
  296. end;
  297. end
  298. else
  299. for i := Low(AAccountNumbers) to High(AAccountNumbers) do
  300. Result[i] := Self.Bank.SafeBox.Account(AAccountNumbers[i]);
  301. end;
  302. function TNodeHelper.GetPendingOperationsAffectingAccounts(const AAccountNumbers: array of cardinal; ASkipCount, ATakeCount: integer): TArray<TOperationResume>;
  303. var
  304. LList: TList<Cardinal>;
  305. LOps: TList<TOperationResume>;
  306. LOp: TPCOperation;
  307. LOpResume: TOperationResume;
  308. LMemPool : TPCOperationsComp;
  309. LAccNo: cardinal;
  310. LNumOps, i: integer;
  311. GC: TDisposables;
  312. begin
  313. LNumOps := 0;
  314. LList := GC.AddObject(TList<Cardinal>.Create) as TList<Cardinal>;
  315. LOps := GC.AddObject(TList<TOperationResume>.Create) as TList<TOperationResume>;
  316. for LAccNo in AAccountNumbers do
  317. begin
  318. LMemPool := Self.LockMempoolRead;
  319. try
  320. LList.Clear;
  321. LMemPool.OperationsHashTree.GetOperationsAffectingAccount(LAccNo, LList);
  322. if LList.Count > 0 then
  323. for i := LList.Count - 1 downto 0 do
  324. begin
  325. Inc(LNumOps);
  326. if (LNumOps > ASkipCount) and (LNumOps <= ASkipCount + ATakeCount) then
  327. begin
  328. LOp := LMemPool.OperationsHashTree.GetOperation(LList[i]);
  329. if TPCOperation.OperationToOperationResume(0, LOp, False, LAccNo, LOpResume) then
  330. begin
  331. LOpResume.NOpInsideBlock := i;
  332. LOpResume.Block := LMemPool.OperationBlock.block;
  333. LOpResume.Balance := LMemPool.SafeBoxTransaction.Account(LAccNo {Op.SignerAccount}).balance;
  334. LOps.Add(LOpResume);
  335. end;
  336. end;
  337. end;
  338. finally
  339. Self.UnlockMempoolRead;
  340. end;
  341. end;
  342. Result := LOps.ToArray;
  343. end;
  344. function TNodeHelper.GetStoredOperationsAffectingAccounts(const AAccountNumbers: array of cardinal; ABlockDepth, ASkipCount, ATakeCount: integer): TArray<TOperationResume>;
  345. type
  346. __TList_Cardinal = TList<cardinal>;
  347. var
  348. i: integer;
  349. LBlock: cardinal;
  350. LRelevantBlockOps: TList<Cardinal>;
  351. LOp: TPCOperation;
  352. LOpResume: TOperationResume;
  353. LFoundOps: TList<TOperationResume>;
  354. LOpsComp: TPCOperationsComp;
  355. LAccountBalances: TDictionary<cardinal, cardinal>;
  356. LAccounts: TArray<TAccount>;
  357. LDisposables: TDisposables;
  358. LBlockEnd, LNumOps: integer;
  359. LBlockTraversal: TSortedHashSet<cardinal>;
  360. LAccountsToScanAtBlock: TObjectDictionary<cardinal, __TList_Cardinal>;
  361. LAcc: TAccount;
  362. procedure MarkAccountAsScannableAtBlock(AAccountNo, ABlockNo: cardinal);
  363. begin
  364. if not LAccountsToScanAtBlock.ContainsKey(ABlockNo) then
  365. LAccountsToScanAtBlock.Add(ABlockNo, __TList_Cardinal.Create);
  366. LAccountsToScanAtBlock[ABlockNo].Add(AAccountNo);
  367. end;
  368. procedure ScanBlock(ABlockNum: cardinal);
  369. var
  370. i: integer;
  371. LAccNo: cardinal;
  372. LPrevUpdatedBlock: cardinal;
  373. LDisposables: TDisposables;
  374. begin
  375. LOpsComp := LDisposables.AddObject(TPCOperationsComp.Create(nil)) as TPCOperationsComp;
  376. LRelevantBlockOps := LDisposables.AddObject(TList<Cardinal>.Create) as TList<Cardinal>;
  377. // load block
  378. if not Bank.Storage.LoadBlockChainBlock(LOpsComp, ABlockNum) then
  379. begin
  380. TLog.NewLog(ltdebug, ClassName, 'Block ' + IntToStr(ABlockNum) + ' not found. Cannot read operations');
  381. exit;
  382. end;
  383. // scan for each account
  384. for LAccNo in LAccountsToScanAtBlock[ABlockNum] do
  385. begin
  386. LRelevantBlockOps.Clear;
  387. LOpsComp.OperationsHashTree.GetOperationsAffectingAccount(LAccNo, LRelevantBlockOps);
  388. for i := LRelevantBlockOps.Count - 1 downto 0 do
  389. begin
  390. LOp := LOpsComp.Operation[LRelevantBlockOps.Items[i]];
  391. if TPCOperation.OperationToOperationResume(i, LOp, False, LAccNo, LOpResume) then
  392. begin
  393. LOpResume.NOpInsideBlock := LRelevantBlockOps.Items[i];
  394. LOpResume.time := LOpsComp.OperationBlock.timestamp;
  395. LOpResume.Block := ABlockNum;
  396. if LAccountBalances[LAccNo] >= 0 then
  397. begin
  398. LOpResume.Balance := LAccountBalances[LAccNo];
  399. LAccountBalances.AddOrSetValue(LAccNo, LOpResume.Balance - (LOpResume.Amount + LOpResume.Fee));
  400. end
  401. else
  402. LOpResume.Balance := -1; // Undetermined
  403. // Apply skip/take
  404. Inc(LNumOps);
  405. if (LNumOps > ASkipCount) and (LNumOps <= ASkipCount + ATakeCount) then
  406. LFoundOps.Add(LOpResume);
  407. // short-cirtcuit exit if taken enough
  408. if LFoundOps.Count >= ATakeCount then
  409. exit;
  410. end;
  411. end;
  412. // Add previous updated block into traversal set
  413. LPrevUpdatedBlock := LOpsComp.PreviousUpdatedBlocks.GetPreviousUpdatedBlock(LAccNo, ABlockNum);
  414. if LPrevUpdatedBlock < ABlockNum then
  415. begin
  416. LBlockTraversal.Add(LPrevUpdatedBlock);
  417. MarkAccountAsScannableAtBlock(LAccNo, LPrevUpdatedBlock);
  418. end;
  419. end;
  420. end;
  421. function GetAccountLastUpdateBlock(constref AAccount: TAccount): cardinal;
  422. begin
  423. Result := AAccount.updated_block;
  424. end;
  425. begin
  426. // Init
  427. LNumOps := 0;
  428. LBlockTraversal := LDisposables.AddObject(TSortedHashSet<cardinal>.Create(TComparerTool<cardinal>.Inverted(TComparer<cardinal>.Default))) as TSortedHashSet<cardinal>;
  429. LAccountsToScanAtBlock := LDisposables.AddObject(TObjectDictionary<cardinal, __TList_Cardinal>.Create([doOwnsValues])) as TObjectDictionary<cardinal, __TList_Cardinal>;
  430. LFoundOps := LDisposables.AddObject(TList<TOperationResume>.Create) as TList<TOperationResume>;
  431. LAccountBalances := LDisposables.AddObject(TDictionary<cardinal, cardinal>.Create) as TDictionary<cardinal, cardinal>;
  432. LBlockEnd := ClipValue(Self.BlockTip - ABlockDepth, 0, Self.BlockTip);
  433. // First get all accounts, their balances and initial traversal set
  434. LAccounts := Self.GetAccounts(AAccountNumbers, False);
  435. for i := Low(LAccounts) to High(LAccounts) do
  436. begin
  437. // if account is modified in block-tip
  438. LAcc := LAccounts[i];
  439. LAccountBalances.AddOrSetValue(LAcc.account, LAcc.Balance); // track account balances
  440. LBlockTraversal.Add(LAcc.updated_block);
  441. MarkAccountAsScannableAtBlock(LAcc.account, LAcc.updated_block);
  442. end;
  443. // Traverse the set of "last updated" blocks in DESCENDING order
  444. while LBlockTraversal.Count > 0 do
  445. begin
  446. LBlock := TSortedHashSetTool<cardinal>.Pop(LBlockTraversal);
  447. if LBlock < LBlockEnd then
  448. continue;
  449. ScanBlock(LBlock); // note: this will update LBlockTraversals with prev updated blocks, so loops until finished
  450. if LFoundOps.Count >= ATakeCount then
  451. exit;
  452. end;
  453. // return array result
  454. Result := LFoundOps.ToArray;
  455. end;
  456. { TAccountComparer }
  457. function TAccountComparer.Compare(constref ALeft, ARight: TAccount): integer;
  458. begin
  459. Result := TAccountComparer.DoCompare(ALeft, ARight);
  460. end;
  461. class function TAccountComparer.DoCompare(constref ALeft, ARight: TAccount): integer;
  462. begin
  463. Result := TCompare.UInt64(ALeft.account, ARight.account);
  464. end;
  465. { TAccountEqualityComparer }
  466. function TAccountEqualityComparer.Equals(constref ALeft, ARight: TAccount): boolean;
  467. begin
  468. Result := TAccountEqualityComparer.AreEqual(ALeft, ARight);
  469. end;
  470. function TAccountEqualityComparer.GetHashCode(constref AValue: TAccount): UInt32;
  471. begin
  472. Result := TAccountEqualityComparer.CalcHashCode(AValue);
  473. end;
  474. class function TAccountEqualityComparer.AreEqual(constref ALeft, ARight: TAccount): boolean;
  475. begin
  476. Result :=
  477. (ALeft.account = ARight.account) and
  478. (ALeft.balance = ARight.balance) and
  479. (ALeft.updated_block = ARight.updated_block) and
  480. (ALeft.n_operation = ARight.n_operation) and
  481. TAccountKeyEqualityComparer.AreEqual(ALeft.accountInfo.accountKey, ARight.accountInfo.accountKey);
  482. end;
  483. class function TAccountEqualityComparer.CalcHashCode(constref AValue: TAccount): UInt32;
  484. begin
  485. Result := AValue.account;
  486. end;
  487. { TAccountKeyComparer }
  488. function TAccountKeyComparer.Compare(constref ALeft, ARight: T): integer;
  489. begin
  490. Result := TAccountKeyComparer.DoCompare(ALeft, ARight);
  491. end;
  492. class function TAccountKeyComparer.DoCompare(constref ALeft, ARight: TAccountKey): integer;
  493. begin
  494. Result := BytesCompare(ALeft.x, ARight.x);
  495. if Result = 0 then
  496. Result := BytesCompare(ALeft.y, ARight.y);
  497. end;
  498. { TAccountKeyEqualityComparer }
  499. function TAccountKeyEqualityComparer.Equals(constref ALeft, ARight: TAccountKey): boolean;
  500. begin
  501. Result := TAccountKeyEqualityComparer.AreEqual(ALeft, ARight);
  502. end;
  503. function TAccountKeyEqualityComparer.GetHashCode(constref AValue: TAccountKey): UInt32;
  504. begin
  505. Result := TAccountKeyEqualityComparer.CalcHashCode(AValue);
  506. end;
  507. class function TAccountKeyEqualityComparer.AreEqual(constref ALeft, ARight: TAccountKey): boolean;
  508. begin
  509. Result := TAccountKeyComparer.DoCompare(ALeft, ARight) = 0;
  510. end;
  511. class function TAccountKeyEqualityComparer.CalcHashCode(constref AValue: TAccountKey): UInt32;
  512. begin
  513. Result := TEqualityComparer<ansistring>.Default.GetHashCode(IntToStr(AValue.EC_OpenSSL_NID) + AValue.x.ToString + AValue.y.ToString);
  514. end;
  515. { TAccountHelper }
  516. function TAccountHelper.GetAccountString: ansistring;
  517. begin
  518. Result := TAccountComp.AccountNumberToAccountTxtNumber(Self.account);
  519. end;
  520. function TAccountHelper.GetDisplayString: ansistring;
  521. begin
  522. Result := GetAccountString;
  523. if Self.Name <> nil then
  524. Result := Result + ': ' + Self.Name.ToString;
  525. end;
  526. function TAccountHelper.GetInfoText(const ABank: TPCBank): utf8string;
  527. var
  528. builder: TStrings;
  529. GC: TDisposables;
  530. begin
  531. builder := GC.AddObject(TStringList.Create) as TStrings;
  532. builder.Append(Format('Account: %s %s Type:%d', [TAccountComp.AccountNumberToAccountTxtNumber(self.account), IIF(Self.Name <> nil, 'Name: ' + Self.Name.ToString, ''), Self.account_type]));
  533. builder.Append('');
  534. builder.Append(Format('Current balance: %s', [TAccountComp.FormatMoney(Self.balance)]));
  535. builder.Append('');
  536. builder.Append(Format('Updated on block: %d (%d blocks ago)', [Self.updated_block, ABank.BlocksCount - Self.updated_block]));
  537. builder.Append(Format('Public key type: %s', [TAccountComp.GetECInfoTxt(Self.accountInfo.accountKey.EC_OpenSSL_NID)]));
  538. builder.Append(Format('Base58 Public key: %s', [TAccountComp.AccountPublicKeyExport(Self.accountInfo.accountKey)]));
  539. if TAccountComp.IsAccountForSale(Self.accountInfo) then
  540. begin
  541. builder.Append('');
  542. builder.Append('** Account is for sale: **');
  543. builder.Append(Format('Price: %s', [TAccountComp.FormatMoney(Self.accountInfo.price)]));
  544. builder.Append(Format('Seller account (where to pay): %s', [TAccountComp.AccountNumberToAccountTxtNumber(Self.accountInfo.account_to_pay)]));
  545. if TAccountComp.IsAccountForSaleAcceptingTransactions(Self.accountInfo) then
  546. begin
  547. builder.Append('');
  548. builder.Append('** Private sale **');
  549. builder.Append(Format('New Base58 Public key: %s', [TAccountComp.AccountPublicKeyExport(Self.accountInfo.new_publicKey)]));
  550. builder.Append('');
  551. if TAccountComp.IsAccountLocked(Self.accountInfo, ABank.BlocksCount) then
  552. builder.Append(Format('PURCHASE IS SECURE UNTIL BLOCK %d (current %d, remains %d)',
  553. [Self.accountInfo.locked_until_block, ABank.BlocksCount, Self.accountInfo.locked_until_block - ABank.BlocksCount]))
  554. else
  555. builder.Append(Format('PURCHASE IS NOT SECURE (Expired on block %d, current %d)',
  556. [Self.accountInfo.locked_until_block, ABank.BlocksCount]));
  557. end;
  558. end;
  559. Result := builder.Text;
  560. end;
  561. { TOperationResumeHelper }
  562. function TOperationResumeHelper.GetPrintableOPHASH: ansistring;
  563. begin
  564. Result := TCrypto.ToHexaString(Self.OperationHash);
  565. end;
  566. function TOperationResumeHelper.GetInfoText(const ABank: TPCBank): utf8string;
  567. var
  568. builder: TStrings;
  569. GC: TDisposables;
  570. begin
  571. if (not Self.valid) then
  572. exit;
  573. builder := GC.AddObject(TStringList.Create) as TStrings;
  574. if Self.Block < ABank.BlocksCount then
  575. if (Self.NOpInsideBlock >= 0) then
  576. builder.Add(Format('Block: %d/%d', [Self.Block, Self.NOpInsideBlock]))
  577. else
  578. begin
  579. builder.Add(Format('Block: %d', [Self.Block]));
  580. end
  581. else
  582. builder.Add('** Pending operation not included on blockchain **');
  583. builder.Add(Format('%s', [Self.OperationTxt]));
  584. builder.Add(Format('OpType:%d Subtype:%d', [Self.OpType, Self.OpSubtype]));
  585. builder.Add(Format('Operation Hash (ophash): %s', [TCrypto.ToHexaString(Self.OperationHash)]));
  586. if (Self.OperationHash_OLD <> nil) then
  587. builder.Add(Format('Old Operation Hash (old_ophash): %s', [TCrypto.ToHexaString(Self.OperationHash_OLD)]));
  588. if (Self.OriginalPayload <> nil) then
  589. begin
  590. builder.Add(Format('Payload length:%d', [length(Self.OriginalPayload)]));
  591. if Self.PrintablePayload <> '' then
  592. builder.Add(Format('Payload (human): %s', [Self.PrintablePayload]));
  593. builder.Add(Format('Payload (Hexadecimal): %s', [TCrypto.ToHexaString(Self.OriginalPayload)]));
  594. end;
  595. if Self.Balance >= 0 then
  596. builder.Add(Format('Final balance: %s', [TAccountComp.FormatMoney(Self.Balance)]));
  597. Result := builder.Text;
  598. end;
  599. { TTimeSpanHelper }
  600. function TTimeSpanHelper.TotalBlockCount: integer;
  601. begin
  602. Result := Round(Self.TotalSeconds / CT_NewLineSecondsAvg);
  603. end;
  604. { TWIZOperationsHelper }
  605. class function TWIZOperationsHelper.ValidateOperationsInput(const ASelectedAccounts: TArray<TAccount>; AWalletKeys: TWalletKeys; ANode: TNode; var AErrorMessage: string): boolean;
  606. begin
  607. Result := True;
  608. if Length(ASelectedAccounts) = 0 then
  609. begin
  610. AErrorMessage := 'No Selected Account Found';
  611. Exit(False);
  612. end;
  613. if not Assigned(AWalletKeys) then
  614. begin
  615. AErrorMessage := 'No Wallet Keys Found';
  616. Exit(False);
  617. end;
  618. if not Assigned(ANode) then
  619. begin
  620. AErrorMessage := 'No Node Found';
  621. Exit(False);
  622. end;
  623. end;
  624. class function TWIZOperationsHelper.IsOwnerOfWallet(AAccount: TAccount; AWalletKeys: TWalletKeys; out AWalletKey: TWalletKey; var AErrorMessage: string): boolean;
  625. var
  626. LIdx: Int32;
  627. begin
  628. Result := True;
  629. LIdx := AWalletKeys.IndexOfAccountKey(AAccount.accountInfo.accountKey);
  630. if LIdx < 0 then
  631. begin
  632. AErrorMessage := Format('Selected Account "%s" Private Key Not Found In Wallet', [AAccount.AccountString]);
  633. Exit(False);
  634. end;
  635. AWalletKey := AWalletKeys.Key[LIdx];
  636. if not Assigned(AWalletKey.PrivateKey) then
  637. begin
  638. if AWalletKey.HasPrivateKey then
  639. AErrorMessage := 'Wallet is Password Protected. Please Unlock Before You Proceed.'
  640. else
  641. AErrorMessage := Format('Only Public Key of Account %s Was Found in Wallet. You Cannot Operate This Account', [AAccount.AccountString]);
  642. Exit(False);
  643. end;
  644. end;
  645. class function TWIZOperationsHelper.SendPASCFinalizeAndDisplayMessage(const AOperationsTxt, AOperationToString: string; ANoOfOperations: integer; ATotalAmount, ATotalFee: int64; AOperationsHashTree: TOperationsHashTree; var AErrorMessage: string): boolean;
  646. var
  647. LAuxs, LOperationsTxt: string;
  648. i: integer;
  649. begin
  650. LOperationsTxt := AOperationsTxt;
  651. if (ANoOfOperations > 1) then
  652. begin
  653. LAuxs := 'Total amount that dest will receive: ' + TAccountComp.FormatMoney(
  654. ATotalAmount) + #10;
  655. if Application.MessageBox(
  656. PChar('Execute ' + IntToStr(ANoOfOperations) +
  657. ' operations?' + #10 + 'Operation: ' + LOperationsTxt + #10 +
  658. LAuxs + 'Total fee: ' + TAccountComp.FormatMoney(ATotalFee) +
  659. #10 + #10 + 'Note: This operation will be transmitted to the network!'),
  660. PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
  661. idYes then
  662. Exit;
  663. end
  664. else
  665. if Application.MessageBox(PChar('Execute this operation:' +
  666. #10 + #10 + AOperationToString + #10 + #10 +
  667. 'Note: This operation will be transmitted to the network!'),
  668. PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
  669. idYes then
  670. Exit;
  671. Result := True;
  672. i := TNode.Node.AddOperations(nil, AOperationsHashTree, nil, AErrorMessage);
  673. if (i = AOperationsHashTree.OperationsCount) then
  674. begin
  675. LOperationsTxt := 'Successfully executed ' + IntToStr(i) +
  676. ' operations!' + #10 + #10 + AOperationToString;
  677. if i > 1 then
  678. ShowMessage(LOperationsTxt)
  679. else
  680. begin
  681. Application.MessageBox(
  682. PChar('Successfully executed ' + IntToStr(i) + ' operations!' +
  683. #10 + #10 + AOperationToString),
  684. PChar(Application.Title), MB_OK + MB_ICONINFORMATION);
  685. end;
  686. end
  687. else if (i > 0) then
  688. begin
  689. LOperationsTxt := 'One or more of your operations has not been executed:' +
  690. #10 + 'Errors:' + #10 + AErrorMessage + #10 + #10 +
  691. 'Total successfully executed operations: ' + IntToStr(i);
  692. ShowMessage(LOperationsTxt);
  693. end
  694. else
  695. Result := False;
  696. end;
  697. class function TWIZOperationsHelper.UpdatePayload(const ASenderPublicKey, ADestinationPublicKey: TAccountKey; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent: string; var AEncodedPayloadBytes: TRawBytes; const APayloadEncryptionPassword: string; var AErrorMessage: string): boolean;
  698. var
  699. LValid: boolean;
  700. begin
  701. if (APayloadContent = '') then
  702. Exit(True);
  703. LValid := False;
  704. AErrorMessage := 'An Error Occured During Payload Encryption.';
  705. try
  706. case APayloadEncryptionMode of
  707. pemEncryptWithSender:
  708. begin
  709. // Use sender public key
  710. AEncodedPayloadBytes := ECIESEncrypt(ASenderPublicKey, TEncoding.ANSI.GetBytes(APayloadContent));
  711. LValid := AEncodedPayloadBytes <> nil;
  712. end;
  713. pemEncryptWithRecipient:
  714. begin
  715. // With destination public key
  716. AEncodedPayloadBytes := ECIESEncrypt(ADestinationPublicKey, TEncoding.ANSI.GetBytes(APayloadContent));
  717. LValid := AEncodedPayloadBytes <> nil;
  718. end;
  719. pemEncryptWithPassword:
  720. begin
  721. // With defined password
  722. if APayloadEncryptionPassword = '' then
  723. begin
  724. AErrorMessage := 'Payload Encryption Password Cannot Be Empty With The Chosen Option : "Encrypt With Password."';
  725. Exit(False);
  726. end;
  727. AEncodedPayloadBytes := TAESComp.EVP_Encrypt_AES256(
  728. TEncoding.ANSI.GetBytes(APayloadContent), TEncoding.ANSI.GetBytes(APayloadEncryptionPassword));
  729. LValid := AEncodedPayloadBytes <> nil;
  730. end;
  731. pemNotEncrypt:
  732. begin
  733. // no encryption
  734. AEncodedPayloadBytes := TEncoding.ANSI.GetBytes(APayloadContent);
  735. LValid := True;
  736. end
  737. else
  738. begin
  739. AErrorMessage := 'Unknown Encryption Selected';
  740. Exit(False);
  741. end;
  742. end;
  743. finally
  744. if LValid then
  745. if Length(AEncodedPayloadBytes) > CT_MaxPayloadSize then
  746. begin
  747. LValid := False;
  748. AErrorMessage := Format('Payload Size Is %d Which Is Bigger Than %d', [Length(AEncodedPayloadBytes), CT_MaxPayloadSize]);
  749. end;
  750. Result := LValid;
  751. end;
  752. end;
  753. class function TWIZOperationsHelper.OthersFinalizeAndDisplayMessage(const AOperationsTxt, AOperationToString: string; ANoOfOperations: integer; ATotalFee: int64; AOperationsHashTree: TOperationsHashTree; var AErrorMessage: string): boolean;
  754. var
  755. LAuxs, LOperationsTxt: string;
  756. i: integer;
  757. begin
  758. LOperationsTxt := AOperationsTxt;
  759. if (ANoOfOperations > 1) then
  760. begin
  761. LAuxs := '';
  762. if Application.MessageBox(
  763. PChar('Execute ' + IntToStr(ANoOfOperations) +
  764. ' operations?' + #10 + 'Operation: ' + LOperationsTxt + #10 +
  765. LAuxs + 'Total fee: ' + TAccountComp.FormatMoney(ATotalFee) +
  766. #10 + #10 + 'Note: This operation will be transmitted to the network!'),
  767. PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
  768. idYes then
  769. Exit;
  770. end
  771. else
  772. if Application.MessageBox(PChar('Execute this operation:' +
  773. #10 + #10 + AOperationToString + #10 + #10 +
  774. 'Note: This operation will be transmitted to the network!'),
  775. PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
  776. idYes then
  777. Exit;
  778. Result := True;
  779. i := TNode.Node.AddOperations(nil, AOperationsHashTree, nil, AErrorMessage);
  780. if (i = AOperationsHashTree.OperationsCount) then
  781. begin
  782. LOperationsTxt := 'Successfully executed ' + IntToStr(i) +
  783. ' operations!' + #10 + #10 + AOperationToString;
  784. if i > 1 then
  785. ShowMessage(LOperationsTxt)
  786. else
  787. begin
  788. Application.MessageBox(
  789. PChar('Successfully executed ' + IntToStr(i) + ' operations!' +
  790. #10 + #10 + AOperationToString),
  791. PChar(Application.Title), MB_OK + MB_ICONINFORMATION);
  792. end;
  793. end
  794. else if (i > 0) then
  795. begin
  796. LOperationsTxt := 'One or more of your operations has not been executed:' +
  797. #10 + 'Errors:' + #10 + AErrorMessage + #10 + #10 +
  798. 'Total successfully executed operations: ' + IntToStr(i);
  799. ShowMessage(LOperationsTxt);
  800. end
  801. else
  802. Result := False;
  803. end;
  804. class function TWIZOperationsHelper.ExecuteSendPASC(const ASelectedAccounts: TArray<TAccount>; const ADestinationAccount: TAccount; AAmount, AFee: int64; const ASendPASCMode: TSendPASCMode; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean;
  805. var
  806. LWalletKey: TWalletKey;
  807. LWalletKeys: TWalletKeys;
  808. LNode: TNode;
  809. LPCOperation: TPCOperation;
  810. LOperationsHashTree: TOperationsHashTree;
  811. LTotalAmount, LTotalSignerFee, LAmount, LFee: int64;
  812. LDoOperation: boolean;
  813. LOperationsTxt, LOperationToString, LTemp: string;
  814. LAccountIdx, LNoOfOperations: integer;
  815. LCurrentAccount: TAccount;
  816. LPayloadEncodedBytes: TRawBytes;
  817. begin
  818. LWalletKeys := TWallet.Keys;
  819. LNode := TNode.Node;
  820. if not TWIZOperationsHelper.ValidateOperationsInput(ASelectedAccounts, LWalletKeys, LNode, AErrorMessage) then
  821. Exit(False);
  822. LOperationsHashTree := TOperationsHashTree.Create;
  823. try
  824. LTotalAmount := 0;
  825. LTotalSignerFee := 0;
  826. LNoOfOperations := 0;
  827. LOperationsTxt := '';
  828. LOperationToString := '';
  829. for LAccountIdx := Low(ASelectedAccounts) to High(ASelectedAccounts) do
  830. begin
  831. LPCOperation := nil; // reset LPCOperation to Nil
  832. LCurrentAccount := ASelectedAccounts[LAccountIdx];
  833. if not TWIZOperationsHelper.IsOwnerOfWallet(LCurrentAccount, LWalletKeys, LWalletKey, AErrorMessage) then
  834. Exit(False);
  835. if LCurrentAccount.account = ADestinationAccount.account then
  836. begin
  837. AErrorMessage := Format('Sender "%s" And Destination "%s" Accounts Are The Same', [LCurrentAccount.AccountString, ADestinationAccount.AccountString]);
  838. Exit(False);
  839. end;
  840. if not UpdatePayload(LCurrentAccount.accountInfo.accountKey, ADestinationAccount.accountInfo.accountKey, APayloadEncryptionMode, APayloadContent, LPayloadEncodedBytes, APayloadEncryptionPassword, AErrorMessage) then
  841. begin
  842. AErrorMessage := Format('Error Encoding Payload Of Selected Account "%s. ", Specific Error Is "%s"', [LCurrentAccount.AccountString, AErrorMessage]);
  843. Exit(False);
  844. end;
  845. LDoOperation := True;
  846. case ASendPASCMode of
  847. akaAllBalance:
  848. if LCurrentAccount.balance > 0 then
  849. begin
  850. if LCurrentAccount.balance > AFee then
  851. begin
  852. LAmount := LCurrentAccount.balance - AFee;
  853. LFee := AFee;
  854. end
  855. else
  856. begin
  857. LAmount := LCurrentAccount.balance;
  858. LFee := 0;
  859. end;
  860. end
  861. else
  862. LDoOperation := False;
  863. akaSpecifiedAmount:
  864. if LCurrentAccount.balance > UInt64(AFee) then
  865. LFee := AFee
  866. else
  867. LFee := LCurrentAccount.balance;
  868. end;
  869. if LDoOperation then
  870. begin
  871. LPCOperation := TOpTransaction.CreateTransaction(
  872. TNode.Node.Bank.Safebox.CurrentProtocol, LCurrentAccount.account, LCurrentAccount.n_operation + 1, ADestinationAccount.account, LWalletKey.PrivateKey, LAmount, LFee, LPayloadEncodedBytes);
  873. try
  874. LTemp := Format('%d. Transfer of %s PASC from %s to %s %s', [LNoOfOperations + 1, TAccountComp.FormatMoney(LAmount), LCurrentAccount.AccountString, ADestinationAccount.AccountString, sLineBreak]);
  875. if LOperationsTxt <> '' then
  876. LOperationsTxt := LOperationsTxt + LTemp + sLineBreak
  877. else
  878. LOperationsTxt := sLineBreak + LTemp;
  879. if Assigned(LPCOperation) then
  880. begin
  881. LOperationsHashTree.AddOperationToHashTree(LPCOperation);
  882. Inc(LTotalAmount, LAmount);
  883. Inc(LTotalSignerFee, LFee);
  884. Inc(LNoOfOperations);
  885. if LOperationToString <> '' then
  886. LOperationToString := LOperationToString + #10;
  887. LOperationToString := LOperationToString + LPCOperation.ToString;
  888. end;
  889. finally
  890. FreeAndNil(LPCOperation);
  891. end;
  892. end;
  893. end;
  894. if (LOperationsHashTree.OperationsCount = 0) then
  895. begin
  896. AErrorMessage := 'No Valid Operation To Execute';
  897. Exit(False);
  898. end;
  899. Exit(TWIZOperationsHelper.SendPASCFinalizeAndDisplayMessage(LOperationsTxt, LOperationToString, LNoOfOperations, LTotalAmount, LTotalSignerFee, LOperationsHashTree, AErrorMessage));
  900. finally
  901. LOperationsHashTree.Free;
  902. end;
  903. end;
  904. class function TWIZOperationsHelper.ExecuteChangeKey(const ASelectedAccounts: TArray<TAccount>; const ASignerAccount: TAccount; APublicKey: TAccountKey; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean;
  905. var
  906. LWalletKey: TWalletKey;
  907. LWalletKeys: TWalletKeys;
  908. LNode: TNode;
  909. LPCOperation: TPCOperation;
  910. LOperationsHashTree: TOperationsHashTree;
  911. LTotalSignerFee, LFee: int64;
  912. LIsV2: boolean;
  913. LOperationsTxt, LOperationToString, LTemp: string;
  914. LAccountIdx, LNoOfOperations: integer;
  915. LCurrentAccount, LSignerAccount: TAccount;
  916. LPayloadEncodedBytes: TRawBytes;
  917. label
  918. loop_start;
  919. begin
  920. LWalletKeys := TWallet.Keys;
  921. LNode := TNode.Node;
  922. LSignerAccount := ASignerAccount;
  923. if not TWIZOperationsHelper.ValidateOperationsInput(ASelectedAccounts, LWalletKeys, LNode, AErrorMessage) then
  924. Exit(False);
  925. LOperationsHashTree := TOperationsHashTree.Create;
  926. try
  927. LIsV2 := LNode.Bank.SafeBox.CurrentProtocol >= CT_PROTOCOL_2;
  928. LTotalSignerFee := 0;
  929. LNoOfOperations := 0;
  930. LOperationsTxt := '';
  931. LOperationToString := '';
  932. for LAccountIdx := Low(ASelectedAccounts) to High(ASelectedAccounts) do
  933. begin
  934. loop_start:
  935. LPCOperation := nil; // reset LPCOperation to Nil
  936. LCurrentAccount := ASelectedAccounts[LAccountIdx];
  937. if not TWIZOperationsHelper.IsOwnerOfWallet(LCurrentAccount, LWalletKeys, LWalletKey, AErrorMessage) then
  938. Exit(False);
  939. if (TAccountComp.EqualAccountKeys(LCurrentAccount.accountInfo.accountKey,
  940. APublicKey)) then
  941. begin
  942. AErrorMessage := 'New Key Is Same As Current Key';
  943. Exit(False);
  944. end;
  945. if LNode.Bank.SafeBox.CurrentProtocol >= CT_PROTOCOL_1 then
  946. begin
  947. // Signer:
  948. LSignerAccount := ASignerAccount;
  949. if (TAccountComp.IsAccountLocked(LSignerAccount.accountInfo,
  950. LNode.Bank.BlocksCount)) then
  951. begin
  952. AErrorMessage := Format('Signer Account "%s" Is Locked Until Block %u', [LSignerAccount.AccountString, LSignerAccount.accountInfo.locked_until_block]);
  953. Exit(False);
  954. end;
  955. if (not TAccountComp.EqualAccountKeys(
  956. LSignerAccount.accountInfo.accountKey, LCurrentAccount.accountInfo.accountKey)) then
  957. begin
  958. AErrorMessage := Format('Signer Account %s Is Not The Owner Of Account %s', [LSignerAccount.AccountString, LCurrentAccount.AccountString]);
  959. Exit(False);
  960. end;
  961. end
  962. else
  963. LSignerAccount := LCurrentAccount;
  964. if not UpdatePayload(LCurrentAccount.accountInfo.accountKey, APublicKey, APayloadEncryptionMode, APayloadContent, LPayloadEncodedBytes, APayloadEncryptionPassword, AErrorMessage) then
  965. begin
  966. AErrorMessage := Format('Error Encoding Payload Of Selected Account "%s. ", Specific Error Is "%s"', [LCurrentAccount.AccountString, AErrorMessage]);
  967. Exit(False);
  968. end;
  969. if LIsV2 then
  970. begin
  971. // must ensure is Signer account last if included in sender accounts (not necessarily ordered enumeration)
  972. if (LAccountIdx < Length(ASelectedAccounts) - 1) and
  973. (LCurrentAccount.account = LSignerAccount.account) then
  974. begin
  975. TArrayTool<TAccount>.Swap(ASelectedAccounts, LAccountIdx,
  976. Length(ASelectedAccounts) - 1); // ensure signer account processed last
  977. goto loop_start; // TODO: remove ugly hack with refactoring!
  978. end;
  979. // Maintain correct signer fee distribution
  980. if UInt64(LTotalSignerFee) >= LSignerAccount.balance then
  981. LFee := 0
  982. else if LSignerAccount.balance - UInt64(LTotalSignerFee) >
  983. UInt64(AFee) then
  984. LFee := AFee
  985. else
  986. LFee := LSignerAccount.balance - UInt64(LTotalSignerFee);
  987. LPCOperation := TOpChangeKeySigned.Create(LNode.Bank.Safebox.CurrentProtocol, LSignerAccount.account,
  988. LSignerAccount.n_operation + LNoOfOperations + 1, LCurrentAccount.account,
  989. LWalletKey.PrivateKey, APublicKey, LFee, LPayloadEncodedBytes);
  990. end
  991. else
  992. LPCOperation := TOpChangeKey.Create(LNode.Bank.Safebox.CurrentProtocol, LCurrentAccount.account, LCurrentAccount.n_operation +
  993. 1, LCurrentAccount.account, LWalletKey.PrivateKey, APublicKey, LFee, LPayloadEncodedBytes);
  994. try
  995. LTemp := Format('%d. Change Key To %s', [LNoOfOperations + 1, TAccountComp.GetECInfoTxt(APublicKey.EC_OpenSSL_NID), sLineBreak]);
  996. if LOperationsTxt <> '' then
  997. LOperationsTxt := LOperationsTxt + LTemp + sLineBreak
  998. else
  999. LOperationsTxt := sLineBreak + LTemp;
  1000. if Assigned(LPCOperation) then
  1001. begin
  1002. LOperationsHashTree.AddOperationToHashTree(LPCOperation);
  1003. Inc(LNoOfOperations);
  1004. Inc(LTotalSignerFee, LFee);
  1005. if LOperationToString <> '' then
  1006. LOperationToString := LOperationToString + #10;
  1007. LOperationToString := LOperationToString + LPCOperation.ToString;
  1008. end;
  1009. finally
  1010. FreeAndNil(LPCOperation);
  1011. end;
  1012. end;
  1013. if (LOperationsHashTree.OperationsCount = 0) then
  1014. begin
  1015. AErrorMessage := 'No Valid Operation to Execute';
  1016. Exit(False);
  1017. end;
  1018. Exit(TWIZOperationsHelper.OthersFinalizeAndDisplayMessage(LOperationsTxt, LOperationToString, LNoOfOperations, LTotalSignerFee, LOperationsHashTree, AErrorMessage));
  1019. finally
  1020. LOperationsHashTree.Free;
  1021. end;
  1022. end;
  1023. class function TWIZOperationsHelper.ExecuteEnlistAccountForSale(const ASelectedAccounts: TArray<TAccount>; const ASignerAccount, ASellerAccount: TAccount; const APublicKey: TAccountKey; AFee, ASalePrice: int64; ALockedUntilBlock: UInt32; const AAccountSaleMode: TAccountSaleMode; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean;
  1024. var
  1025. LWalletKey: TWalletKey;
  1026. LWalletKeys: TWalletKeys;
  1027. LNode: TNode;
  1028. LPCOperation: TPCOperation;
  1029. LOperationsHashTree: TOperationsHashTree;
  1030. LTotalSignerFee, LFee: int64;
  1031. LOperationsTxt, LOperationToString, LTemp: string;
  1032. LAccountIdx, LNoOfOperations: integer;
  1033. LCurrentAccount, LSignerAccount: TAccount;
  1034. LPayloadEncodedBytes: TRawBytes;
  1035. begin
  1036. LWalletKeys := TWallet.Keys;
  1037. LNode := TNode.Node;
  1038. LSignerAccount := ASignerAccount;
  1039. if not TWIZOperationsHelper.ValidateOperationsInput(ASelectedAccounts, LWalletKeys, LNode, AErrorMessage) then
  1040. Exit(False);
  1041. LOperationsHashTree := TOperationsHashTree.Create;
  1042. try
  1043. LTotalSignerFee := 0;
  1044. LNoOfOperations := 0;
  1045. LOperationsTxt := '';
  1046. LOperationToString := '';
  1047. for LAccountIdx := Low(ASelectedAccounts) to High(ASelectedAccounts) do
  1048. begin
  1049. LPCOperation := nil; // reset LPCOperation to Nil
  1050. LCurrentAccount := ASelectedAccounts[LAccountIdx];
  1051. if not TWIZOperationsHelper.IsOwnerOfWallet(LCurrentAccount, LWalletKeys, LWalletKey, AErrorMessage) then
  1052. Exit(False);
  1053. if TAccountComp.IsAccountForSale(LCurrentAccount.accountInfo) then
  1054. begin
  1055. AErrorMessage := Format('Account "%s" Is Already Enlisted For Sale', [LCurrentAccount.AccountString]);
  1056. Exit(False);
  1057. end;
  1058. if (ASellerAccount.account = LCurrentAccount.account) then
  1059. begin
  1060. AErrorMessage := 'Seller Account Cannot Be Same As Account To Be Sold.';
  1061. Exit(False);
  1062. end;
  1063. if AAccountSaleMode = akaPrivateSale then
  1064. begin
  1065. if TAccountComp.EqualAccountKeys(APublicKey,
  1066. LCurrentAccount.accountInfo.accountKey) then
  1067. begin
  1068. AErrorMessage := 'You Cannot Sell To An Account That You Want To Enlist For Sale.';
  1069. Exit(False);
  1070. end;
  1071. if ALockedUntilBlock = 0 then
  1072. begin
  1073. AErrorMessage := 'You Didn''t Insert a Valid Locking Block.';
  1074. Exit(False);
  1075. end;
  1076. end;
  1077. if (LNode.Bank.SafeBox.CurrentProtocol = CT_PROTOCOL_1) then
  1078. begin
  1079. AErrorMessage := 'This Operation Needs PROTOCOL 2 Active';
  1080. Exit(False);
  1081. end;
  1082. if LSignerAccount.balance > UInt64(AFee) then
  1083. LFee := AFee
  1084. else
  1085. LFee := LSignerAccount.balance;
  1086. if not UpdatePayload(LCurrentAccount.accountInfo.accountKey, LSignerAccount.accountInfo.accountKey, APayloadEncryptionMode, APayloadContent, LPayloadEncodedBytes, APayloadEncryptionPassword, AErrorMessage) then
  1087. begin
  1088. AErrorMessage := Format('Error Encoding Payload Of Selected Account "%s. ", Specific Error Is "%s"', [LCurrentAccount.AccountString, AErrorMessage]);
  1089. Exit(False);
  1090. end;
  1091. case AAccountSaleMode of
  1092. akaPublicSale:
  1093. LPCOperation := TOpListAccountForSale.CreateListAccountForSale(
  1094. LNode.Bank.Safebox.CurrentProtocol, LSignerAccount.account, LSignerAccount.n_operation + 1 + LAccountIdx,
  1095. LCurrentAccount.account, ASalePrice, LFee, ASellerAccount.account,
  1096. APublicKey, 0, LWalletKey.PrivateKey, LPayloadEncodedBytes);
  1097. akaPrivateSale:
  1098. LPCOperation := TOpListAccountForSale.CreateListAccountForSale(
  1099. LNode.Bank.Safebox.CurrentProtocol, LSignerAccount.account, LSignerAccount.n_operation + 1 + LAccountIdx,
  1100. LCurrentAccount.account, ASalePrice, LFee, ASellerAccount.account,
  1101. APublicKey, ALockedUntilBlock, LWalletKey.PrivateKey, LPayloadEncodedBytes)
  1102. else
  1103. raise Exception.Create('Invalid Account Sale Type')
  1104. end;
  1105. try
  1106. LTemp := Format('%d. Enlist Account %s For Sale At a Price Of %s PASC %s', [LNoOfOperations + 1, LCurrentAccount.DisplayString, TAccountComp.FormatMoney(ASalePrice), sLineBreak]);
  1107. if LOperationsTxt <> '' then
  1108. LOperationsTxt := LOperationsTxt + LTemp + sLineBreak
  1109. else
  1110. LOperationsTxt := sLineBreak + LTemp;
  1111. if Assigned(LPCOperation) then
  1112. begin
  1113. LOperationsHashTree.AddOperationToHashTree(LPCOperation);
  1114. Inc(LNoOfOperations);
  1115. Inc(LTotalSignerFee, LFee);
  1116. if LOperationToString <> '' then
  1117. LOperationToString := LOperationToString + #10;
  1118. LOperationToString := LOperationToString + LPCOperation.ToString;
  1119. end;
  1120. finally
  1121. FreeAndNil(LPCOperation);
  1122. end;
  1123. end;
  1124. if (LOperationsHashTree.OperationsCount = 0) then
  1125. begin
  1126. AErrorMessage := 'No Valid Operation to Execute';
  1127. Exit(False);
  1128. end;
  1129. Exit(TWIZOperationsHelper.OthersFinalizeAndDisplayMessage(LOperationsTxt, LOperationToString, LNoOfOperations, LTotalSignerFee, LOperationsHashTree, AErrorMessage));
  1130. finally
  1131. LOperationsHashTree.Free;
  1132. end;
  1133. end;
  1134. class function TWIZOperationsHelper.ExecuteDelistAccountFromSale(const ASelectedAccounts: TArray<TAccount>; const ASignerAccount: TAccount; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; var AErrorMessage: string): boolean;
  1135. var
  1136. LWalletKey: TWalletKey;
  1137. LWalletKeys: TWalletKeys;
  1138. LNode: TNode;
  1139. LPCOperation: TPCOperation;
  1140. LOperationsHashTree: TOperationsHashTree;
  1141. LTotalSignerFee, LFee: int64;
  1142. LOperationsTxt, LOperationToString, LTemp: string;
  1143. LAccountIdx, LNoOfOperations: integer;
  1144. LCurrentAccount, LSignerAccount: TAccount;
  1145. LPayloadEncodedBytes: TRawBytes;
  1146. begin
  1147. LWalletKeys := TWallet.Keys;
  1148. LNode := TNode.Node;
  1149. LSignerAccount := ASignerAccount;
  1150. if not TWIZOperationsHelper.ValidateOperationsInput(ASelectedAccounts, LWalletKeys, LNode, AErrorMessage) then
  1151. Exit(False);
  1152. LOperationsHashTree := TOperationsHashTree.Create;
  1153. try
  1154. LTotalSignerFee := 0;
  1155. LNoOfOperations := 0;
  1156. LOperationsTxt := '';
  1157. LOperationToString := '';
  1158. for LAccountIdx := Low(ASelectedAccounts) to High(ASelectedAccounts) do
  1159. begin
  1160. LPCOperation := nil; // reset LPCOperation to Nil
  1161. LCurrentAccount := ASelectedAccounts[LAccountIdx];
  1162. if not TWIZOperationsHelper.IsOwnerOfWallet(LCurrentAccount, LWalletKeys, LWalletKey, AErrorMessage) then
  1163. Exit(False);
  1164. if not TAccountComp.IsAccountForSale(LCurrentAccount.accountInfo) then
  1165. begin
  1166. AErrorMessage := Format('Account "%s" is not enlisted for sale so cannot be delisted', [LCurrentAccount.AccountString]);
  1167. Exit(False);
  1168. end;
  1169. if (TAccountComp.IsAccountLocked(LCurrentAccount.accountInfo, LNode.Bank.BlocksCount)) then
  1170. begin
  1171. AErrorMessage := Format('Target Account "%s" Is Locked Until Block %u', [LCurrentAccount.AccountString, LCurrentAccount.accountInfo.locked_until_block]);
  1172. Exit(False);
  1173. end;
  1174. if (TAccountComp.IsAccountLocked(LSignerAccount.accountInfo, LNode.Bank.BlocksCount)) then
  1175. begin
  1176. AErrorMessage := Format('Signer Account "%s" Is Locked Until Block %u', [LSignerAccount.AccountString, LSignerAccount.accountInfo.locked_until_block]);
  1177. Exit(False);
  1178. end;
  1179. if (not TAccountComp.EqualAccountKeys(LSignerAccount.accountInfo.accountKey, LCurrentAccount.accountInfo.accountKey)) then
  1180. begin
  1181. AErrorMessage := Format('Signer Account %s Is Not The Owner Of Delisted Account %s', [LSignerAccount.AccountString, LCurrentAccount.AccountString]);
  1182. Exit(False);
  1183. end;
  1184. if (LNode.Bank.SafeBox.CurrentProtocol = CT_PROTOCOL_1) then
  1185. begin
  1186. AErrorMessage := 'This Operation Needs PROTOCOL 2 Active';
  1187. Exit(False);
  1188. end;
  1189. if LSignerAccount.balance > UInt64(AFee) then
  1190. LFee := AFee
  1191. else
  1192. LFee := LSignerAccount.balance;
  1193. if not UpdatePayload(LCurrentAccount.accountInfo.accountKey, LSignerAccount.accountInfo.accountKey, APayloadEncryptionMode, APayloadContent, LPayloadEncodedBytes, APayloadEncryptionPassword, AErrorMessage) then
  1194. begin
  1195. AErrorMessage := Format('Error Encoding Payload Of Selected Account "%s. ", Specific Error Is "%s"', [LCurrentAccount.AccountString, AErrorMessage]);
  1196. Exit(False);
  1197. end;
  1198. LPCOperation := TOpDelistAccountForSale.CreateDelistAccountForSale(LNode.Bank.Safebox.CurrentProtocol,
  1199. LSignerAccount.account, LSignerAccount.n_operation + 1 + LAccountIdx, LCurrentAccount.account, LFee, LWalletKey.PrivateKey,
  1200. LPayloadEncodedBytes);
  1201. try
  1202. LTemp := Format('%d. Delist Account %s From Sale %s', [LNoOfOperations + 1, LCurrentAccount.DisplayString, sLineBreak]);
  1203. if LOperationsTxt <> '' then
  1204. LOperationsTxt := LOperationsTxt + LTemp + sLineBreak
  1205. else
  1206. LOperationsTxt := sLineBreak + LTemp;
  1207. if Assigned(LPCOperation) then
  1208. begin
  1209. LOperationsHashTree.AddOperationToHashTree(LPCOperation);
  1210. Inc(LNoOfOperations);
  1211. Inc(LTotalSignerFee, LFee);
  1212. if LOperationToString <> '' then
  1213. LOperationToString := LOperationToString + #10;
  1214. LOperationToString := LOperationToString + LPCOperation.ToString;
  1215. end;
  1216. finally
  1217. FreeAndNil(LPCOperation);
  1218. end;
  1219. end;
  1220. if (LOperationsHashTree.OperationsCount = 0) then
  1221. begin
  1222. AErrorMessage := 'No Valid Operation to Execute';
  1223. Exit(False);
  1224. end;
  1225. Exit(TWIZOperationsHelper.OthersFinalizeAndDisplayMessage(LOperationsTxt, LOperationToString, LNoOfOperations, LTotalSignerFee, LOperationsHashTree, AErrorMessage));
  1226. finally
  1227. LOperationsHashTree.Free;
  1228. end;
  1229. end;
  1230. class function TWIZOperationsHelper.ExecuteChangeAccountInfo(const ASelectedAccounts, ASignerAccounts: TArray<TAccount>; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; const ANewName: TRawBytes; const ANewType: word; var AErrorMessage: string): boolean;
  1231. var
  1232. LWalletKey: TWalletKey;
  1233. LWalletKeys: TWalletKeys;
  1234. LNode: TNode;
  1235. LPCOperation: TPCOperation;
  1236. LOperationsHashTree: TOperationsHashTree;
  1237. LTotalSignerFee, LFee: int64;
  1238. LOperationsTxt, LOperationToString, LTemp: string;
  1239. LAccountIdx, LNoOfOperations, LAccNumberIndex: integer;
  1240. LCurrentAccount, LSignerAccount: TAccount;
  1241. LPayloadEncodedBytes, LNewName: TRawBytes;
  1242. LChangeType, LChangeName: boolean;
  1243. begin
  1244. LWalletKeys := TWallet.Keys;
  1245. LNode := TNode.Node;
  1246. LChangeName := False;
  1247. LChangeType := False;
  1248. if not TWIZOperationsHelper.ValidateOperationsInput(ASelectedAccounts, LWalletKeys, LNode, AErrorMessage) then
  1249. Exit(False);
  1250. LOperationsHashTree := TOperationsHashTree.Create;
  1251. try
  1252. LTotalSignerFee := 0;
  1253. LNoOfOperations := 0;
  1254. LOperationsTxt := '';
  1255. LOperationToString := '';
  1256. for LAccountIdx := Low(ASelectedAccounts) to High(ASelectedAccounts) do
  1257. begin
  1258. LPCOperation := nil; // reset LPCOperation to Nil
  1259. LCurrentAccount := ASelectedAccounts[LAccountIdx];
  1260. if Length(ASelectedAccounts) = 1 then
  1261. begin
  1262. LSignerAccount := ASignerAccounts[0];
  1263. LNewName := ANewName;
  1264. if not TBaseType.Equals(LNewName, LCurrentAccount.Name) then
  1265. begin
  1266. LChangeName := True;
  1267. if LNewName <> nil then
  1268. begin
  1269. if (not TPCSafeBox.ValidAccountName(LNewName, AErrorMessage)) then
  1270. begin
  1271. AErrorMessage := Format('New name "%s" is not a valid name: %s ', [LNewName.ToPrintable, AErrorMessage]);
  1272. Exit(False);
  1273. end;
  1274. LAccNumberIndex := (TNode.Node.Bank.SafeBox.FindAccountByName(LNewName));
  1275. if (LAccNumberIndex >= 0) then
  1276. begin
  1277. AErrorMessage := Format('Name "%s" is used by account %s ', [LNewName.ToPrintable, TAccountComp.AccountNumberToAccountTxtNumber(LAccNumberIndex)]);
  1278. Exit(False);
  1279. end;
  1280. end;
  1281. end;
  1282. if (TBaseType.Equals(LNewName, LCurrentAccount.Name)) and (ANewType = LCurrentAccount.account_type) then
  1283. begin
  1284. AErrorMessage := 'New account name and type are same as former.';
  1285. Exit(False);
  1286. end;
  1287. end
  1288. else
  1289. begin
  1290. LSignerAccount := LCurrentAccount;
  1291. end;
  1292. LChangeType := ANewType <> LCurrentAccount.account_type;
  1293. if not TWIZOperationsHelper.IsOwnerOfWallet(LCurrentAccount, LWalletKeys, LWalletKey, AErrorMessage) then
  1294. Exit(False);
  1295. if (TAccountComp.IsAccountLocked(LCurrentAccount.accountInfo, LNode.Bank.BlocksCount)) then
  1296. begin
  1297. AErrorMessage := Format('Target Account "%s" Is Locked Until Block %u', [LCurrentAccount.AccountString, LCurrentAccount.accountInfo.locked_until_block]);
  1298. Exit(False);
  1299. end;
  1300. if (TAccountComp.IsAccountLocked(LSignerAccount.accountInfo, LNode.Bank.BlocksCount)) then
  1301. begin
  1302. AErrorMessage := Format('Signer Account "%s" Is Locked Until Block %u', [LSignerAccount.AccountString, LSignerAccount.accountInfo.locked_until_block]);
  1303. Exit(False);
  1304. end;
  1305. if (not TAccountComp.EqualAccountKeys(LSignerAccount.accountInfo.accountKey, LCurrentAccount.accountInfo.accountKey)) then
  1306. begin
  1307. AErrorMessage := Format('Signer Account %s Is Not The Owner Of Target Account %s', [LSignerAccount.AccountString, LCurrentAccount.AccountString]);
  1308. Exit(False);
  1309. end;
  1310. if (LNode.Bank.SafeBox.CurrentProtocol = CT_PROTOCOL_1) then
  1311. begin
  1312. AErrorMessage := 'This Operation Needs PROTOCOL 2 Active';
  1313. Exit(False);
  1314. end;
  1315. if LSignerAccount.balance > UInt64(AFee) then
  1316. LFee := AFee
  1317. else
  1318. LFee := LSignerAccount.balance;
  1319. if not UpdatePayload(LCurrentAccount.accountInfo.accountKey, LSignerAccount.accountInfo.accountKey, APayloadEncryptionMode, APayloadContent, LPayloadEncodedBytes, APayloadEncryptionPassword, AErrorMessage) then
  1320. begin
  1321. AErrorMessage := Format('Error Encoding Payload Of Selected Account "%s. ", Specific Error Is "%s"', [LCurrentAccount.AccountString, AErrorMessage]);
  1322. Exit(False);
  1323. end;
  1324. LPCOperation := TOpChangeAccountInfo.CreateChangeAccountInfo(LNode.Bank.Safebox.CurrentProtocol,
  1325. LSignerAccount.account, LSignerAccount.n_operation + 1, LCurrentAccount.account, LWalletKey.PrivateKey, False, CT_TECDSA_Public_Nul,
  1326. LChangeName, LNewName, LChangeType, ANewType, LFee, LPayloadEncodedBytes);
  1327. try
  1328. if (LChangeName) and (LChangeType) then
  1329. begin
  1330. LTemp := Format('%d. Change Account %s Name and Type from [%s, %d] To [%s, %d] %s', [LNoOfOperations + 1, LCurrentAccount.DisplayString, LCurrentAccount.Name.ToPrintable, LCurrentAccount.account_type, LNewName.ToPrintable, ANewType, sLineBreak]);
  1331. end
  1332. else if LChangeName then
  1333. begin
  1334. LTemp := Format('%d. Change Account %s Name from [%s] To [%s] %s', [LNoOfOperations + 1, LCurrentAccount.DisplayString, LCurrentAccount.Name.ToPrintable, LNewName.ToPrintable, sLineBreak]);
  1335. end
  1336. else if LChangeType then
  1337. begin
  1338. LTemp := Format('%d. Change Account %s Type from [%d] To [%d] %s', [LNoOfOperations + 1, LCurrentAccount.DisplayString, LCurrentAccount.account_type, ANewType, sLineBreak]);
  1339. end;
  1340. if LOperationsTxt <> '' then
  1341. LOperationsTxt := LOperationsTxt + LTemp + sLineBreak
  1342. else
  1343. LOperationsTxt := sLineBreak + LTemp;
  1344. if Assigned(LPCOperation) then
  1345. begin
  1346. LOperationsHashTree.AddOperationToHashTree(LPCOperation);
  1347. Inc(LNoOfOperations);
  1348. Inc(LTotalSignerFee, LFee);
  1349. if LOperationToString <> '' then
  1350. LOperationToString := LOperationToString + #10;
  1351. LOperationToString := LOperationToString + LPCOperation.ToString;
  1352. end;
  1353. finally
  1354. FreeAndNil(LPCOperation);
  1355. end;
  1356. end;
  1357. if (LOperationsHashTree.OperationsCount = 0) then
  1358. begin
  1359. AErrorMessage := 'No Valid Operation to Execute';
  1360. Exit(False);
  1361. end;
  1362. Exit(TWIZOperationsHelper.OthersFinalizeAndDisplayMessage(LOperationsTxt, LOperationToString, LNoOfOperations, LTotalSignerFee, LOperationsHashTree, AErrorMessage));
  1363. finally
  1364. LOperationsHashTree.Free;
  1365. end;
  1366. end;
  1367. class function TWIZOperationsHelper.ExecuteBuyAccount(const ASelectedAccounts: TArray<TAccount>; const AAccountToBuy: TAccount; AFee: int64; const APayloadEncryptionMode: TPayloadEncryptionMode; const APayloadContent, APayloadEncryptionPassword: string; const AAmount: int64; const ANewOwnerPublicKey: TAccountKey; var AErrorMessage: string): boolean;
  1368. var
  1369. LWalletKey: TWalletKey;
  1370. LWalletKeys: TWalletKeys;
  1371. LNode: TNode;
  1372. LPCOperation: TPCOperation;
  1373. LOperationsHashTree: TOperationsHashTree;
  1374. LTotalSignerFee, LFee: int64;
  1375. LOperationsTxt, LOperationToString, LTemp: string;
  1376. LAccountIdx, LNoOfOperations, LAccNumberIndex: integer;
  1377. LCurrentAccount, LSignerAccount: TAccount;
  1378. LPayloadEncodedBytes, LNewName: TRawBytes;
  1379. begin
  1380. LWalletKeys := TWallet.Keys;
  1381. LNode := TNode.Node;
  1382. if not TWIZOperationsHelper.ValidateOperationsInput(ASelectedAccounts, LWalletKeys, LNode, AErrorMessage) then
  1383. Exit(False);
  1384. LOperationsHashTree := TOperationsHashTree.Create;
  1385. try
  1386. LTotalSignerFee := 0;
  1387. LNoOfOperations := 0;
  1388. LOperationsTxt := '';
  1389. LOperationToString := '';
  1390. // although buyaccount does not support multioperation for now, this loop was intentionally put here
  1391. for LAccountIdx := Low(ASelectedAccounts) to High(ASelectedAccounts) do
  1392. begin
  1393. LPCOperation := nil; // reset LPCOperation to Nil
  1394. LCurrentAccount := ASelectedAccounts[LAccountIdx];
  1395. LSignerAccount := LCurrentAccount;
  1396. if Length(ASelectedAccounts) <> 1 then
  1397. begin
  1398. AErrorMessage := 'Cannot Buy Accounts With MultiOperations. Use Only 1 Account';
  1399. Exit(False);
  1400. end;
  1401. if (LNode.Bank.SafeBox.CurrentProtocol = CT_PROTOCOL_1) then
  1402. begin
  1403. AErrorMessage := 'This Operation Needs PROTOCOL 2 Active';
  1404. Exit(False);
  1405. end;
  1406. if not TAccountComp.IsAccountForSale(AAccountToBuy.accountInfo) then
  1407. begin
  1408. AErrorMessage := Format('Account "%s" is not enlisted for sale', [AAccountToBuy.AccountString]);
  1409. Exit(False);
  1410. end;
  1411. if LSignerAccount.balance > UInt64(AFee) then
  1412. LFee := AFee
  1413. else
  1414. LFee := LSignerAccount.balance;
  1415. if ((AAmount + LFee) > LCurrentAccount.balance) then
  1416. begin
  1417. AErrorMessage := Format('Insufficient Funds in Account "%s" to buy Account "%s"', [LCurrentAccount.AccountString, AAccountToBuy.AccountString]);
  1418. Exit(False);
  1419. end;
  1420. if not UpdatePayload(LCurrentAccount.accountInfo.accountKey, LSignerAccount.accountInfo.accountKey, APayloadEncryptionMode, APayloadContent, LPayloadEncodedBytes, APayloadEncryptionPassword, AErrorMessage) then
  1421. begin
  1422. AErrorMessage := Format('Error Encoding Payload Of Selected Account "%s. ", Specific Error Is "%s"', [LCurrentAccount.AccountString, AErrorMessage]);
  1423. Exit(False);
  1424. end;
  1425. LPCOperation := TOpBuyAccount.CreateBuy(LNode.Bank.Safebox.CurrentProtocol, LCurrentAccount.account, LCurrentAccount.n_operation + 1, AAccountToBuy.account, AAccountToBuy.accountInfo.account_to_pay,
  1426. AAccountToBuy.accountInfo.price, AAmount, LFee, ANewOwnerPublicKey, LWalletKey.PrivateKey, LPayloadEncodedBytes);
  1427. try
  1428. LTemp := Format('%d. Buy Account %s for %s PASC %s', [LNoOfOperations + 1, AAccountToBuy.AccountString, TAccountComp.FormatMoney(AAmount), sLineBreak]);
  1429. if LOperationsTxt <> '' then
  1430. LOperationsTxt := LOperationsTxt + LTemp + sLineBreak
  1431. else
  1432. LOperationsTxt := sLineBreak + LTemp;
  1433. if Assigned(LPCOperation) then
  1434. begin
  1435. LOperationsHashTree.AddOperationToHashTree(LPCOperation);
  1436. Inc(LNoOfOperations);
  1437. Inc(LTotalSignerFee, LFee);
  1438. if LOperationToString <> '' then
  1439. LOperationToString := LOperationToString + #10;
  1440. LOperationToString := LOperationToString + LPCOperation.ToString;
  1441. end;
  1442. finally
  1443. FreeAndNil(LPCOperation);
  1444. end;
  1445. end;
  1446. if (LOperationsHashTree.OperationsCount = 0) then
  1447. begin
  1448. AErrorMessage := 'No Valid Operation to Execute';
  1449. Exit(False);
  1450. end;
  1451. Exit(TWIZOperationsHelper.OthersFinalizeAndDisplayMessage(LOperationsTxt, LOperationToString, LNoOfOperations, LTotalSignerFee, LOperationsHashTree, AErrorMessage));
  1452. finally
  1453. LOperationsHashTree.Free;
  1454. end;
  1455. end;
  1456. end.