Browse Source

Merge: upstream updates

Herman Schoenfeld 7 years ago
parent
commit
3721d0851e

+ 141 - 0
PIP/PIP-0009.md

@@ -0,0 +1,141 @@
+<pre>
+  PIP: PIP-0009
+  Title: RandomHash: GPU & ASIC Resistant Hash Algorithm
+  Type: Protocol
+  Impact: Hard-Fork
+  Author: Herman Schoenfeld <i>&lt;[email protected]&gt;</i>
+  Comments-URI: https://discord.gg/sJqcgtD  (channel #pip-0009)
+  Status: Draft
+  Created: 2017-12-17
+</pre>
+
+## Summary
+
+A GPU and ASIC resistant hashing algorithm change is proposed in order to resolve the current mining centralisation siutation and to prevent future dual-mining centralisation.
+
+## Motivation
+
+PascalCoin is currently experiencing 99% mining centralisation by a single pool which has severely impacted eco-system growth and adoption. Exchanges are reticent to list PASC due to the risk of double-spend attacks and infrastructure providers are reticent to invest due to low-volume and stunted price-growth. 
+
+### Background
+PascalCoin is a 100% original Proof-of-Work coin offering a unique value proposition focused on scalability. After the initial launch, a healthy decentralised mining community emerged and became active in the coins eco-system, as expected. However, after 9 months a single pool (herein referred to as Pool-X) managed to centralise mining over a short period of time. At the time, it was believed that a technical exploit was being employed by Pool-X, but this possibility was ruled out after exhaustive analysis and review by the developers and 3rd parties. It is now understood why and how this centralisation occured, and how it can be fixed.
+
+**It’s an economics issue, not a technical issue**. Since PascalCoin is GPU-friendly PoW coin, it has become a prime candidate for "dual-miners", especially Ethereum-centric Pool-X. Dual-miners are miners who mine two independent coins simultaneously using the same electricity. This works because some coins are memory-hard (Ethereum) and others are not (PascalCoin). When mining memory-hard coins, GPUs have an abundance of idle computational power which can be re-purposed to simultaneously mine a non-memory-hard coin like PascalCoin. Whilst a great technical innovation, the introduction of dual-mining has fundamentally changed the economics and incentive-model of mining for the "secondary coin". 
+
+Ordinarily, a coins mining eco-system grows organically with interest and centralisation does not occur. This is due to the "hashpower follows price" law. As price grows organically due to interest, so do the number of miners. If there are too many miners, the coin becomes unprofitable, and some miners leave. This homeostasis between mining, price and eco-system size is part of the economic formula that makes cryptocurrencies work.
+
+With dual-mining, this is broken. Dual-mining has led to coins with small user-base to have totally disproportionate number of miners who mine the coin even when "unprofitable". In the case of PascalCoin, miners are primarily on Pool-X to mine Ethereum, not PascalCoin. So the number of PascalCoin miners are a reflection of Ethereum's ecosystem, not PascalCoin's. Also, these miners mine PascalCoin because they have latent computing power, so it technically costs them nothing to mine PascalCoin. As a result, they mine PascalCoin even when it's unprofitable thus forcing out ordinary miners who are not dual-mining. 
+
+**These mis-aligned economic incentives result in a rapid convergence to 99% centralisation, even though no actor is malicious.**
+
+
+
+## Specification
+
+A GPU and ASIC-resistant hash algorithm called **Random Hash** is proposed to resolve and prevent dual-mining centralisation. Random Hash, defined first here, is a "high-level cryptographic hash" that employs other well-known cryptographic hash functions in multiple rounds in a non-deterministic manner.
+
+### Overview
+
+- A total of 16 cryptographic hash functions are used
+- A total of 16 rounds of hashing are applied
+- The hash function used in round N is randomly selected using the last byte of round N-1 output
+- The input digest for round N is composed of the output of round N-1 salted with the output of a randoml  previous round 1..N
+- The first and last rounds always use SHA2-256
+
+
+### RandomHash pseudo-code
+
+        const
+            HASH_ALGO = [
+                SHA2_256
+                SHA2_384
+                SHA2_512
+                SHA3,
+                RIPEMD160,
+                RIPEMD256,
+                RIPEMD320,
+                Blake2b, 
+                Blake2s,
+                Tiger2,
+                Snerfu,
+                Grindahl512,
+                Haval,
+                MD5
+                RadioGatun32
+                Whirlpool
+            ]
+
+        Function RandomHash(input : RawBytes) : RawBytes;
+        var
+            Values : array[0..15] of TRawBytes;
+            HashFunc : function (bytes: TRawBytes) : RawBytes of object;
+            SaltDigest : RawBytes;
+        begin
+            Values = array[0..15] of RawBytes;
+
+            // Perform 16 rounds of hashing 
+            for i = 0 to 15
+                if i = 0 then                    
+                    HashFunc = HASH_ALGO[0]   // first round is SHA2-256
+                    SaltDigest = []
+                else
+                    input = Values[i - 1]
+                    let random16 = input.LastByte % 16
+                    if i = 15 then
+                        HashFunc = HASH_ALGO[0]   // last round is SHA2-256
+                    else 
+                        HashFunc =  HASH_ALGO[random16] 
+                    SaltDigest = Values[random16 % i]
+
+                // Perform this round of hashing
+                Values[i] = HashFunc(input ++ SaltDigest)
+            end     
+            Result := Values[15]
+        end;
+
+
+### GPU Resistance 
+
+GPU performance is generally driven by duplicating a code-block of sequential, non-branching instructions across many physical computational modules and executing them in parallel. Each duplicated code-block accesses an independent and isolated region of memory without any inter-dependence on another. The fundamental strategy employed by RandomHash to hinder GPU performance is by breaking parallelizability of the code-block. Only the entire RandomHash can be parallelized, not it's vast internal instructions replete with non-deterministic branching and executive decision-making. 
+
+In particular, the hash-path taken by a single RandomHash trial varies for every input and is not known until the path is finished.
+
+<pre>
+Example Hash-Paths:
+RandomHash("input1") = <b>SHA256</b>(SHA3(Blake2s(Whirlpool(....... <b>SHA256</b>(digest))))))))))))))))
+RandomHash("input2") = <b>SHA256</b>(RIPEMD160(Tiger(Snerfu(..... <b>SHA256</b>(digest)))))))))))))))) 
+</pre>
+
+Since each internal hashing round is inter-dependent on the outputs of other rounds, their memory cannot be isolated. As a result, it is expected that a single RandomHash round cannot be decomposed into parallelizable sub-code-blocks. Thus a single code-block loaded into a module will need to contain 16 hashing algoriths, 14 decisions and 14 branches, quite inefficient for a GPU.
+
+Overall, a GPU implementation of RandomHash may still be faster than an CPU implementation, but not by orders of magnitude as is observed in standard cryptocurrency hashing algorithms. 
+
+**An additional step of memory-hardness can also be added if necessary (TBD) in order to further constrict GPU performance** (TBD)
+
+### ASIC Resistance 
+
+ASIC-resistance is fundamentally achieved on a cost-benefit-analysis basis. Since 16 hash algorithms are employed, the R&D costs of a RandomHash ASIC are equivalent to that of 16 ordinary mining ASICS. Furthermore, due to the non-deterministic branching and large number of hashing-paths outlined above, an ASIC implementation will inevitable result in a very high number of cells and inter-connections between them, resulting in poor performance. It is expected that since the costs to develop will far exceed the ROI, no rational economic actor will undertake ASIC development of RandomHash.
+
+### Hard-Fork Activation
+
+- Community nominates block X for hard-fork activation by consensus
+- On and after block X, block header is hashed using RandomHash
+- Before block X, block header is hashed using current SHA2-256D
+
+## Rationale
+
+Aside from a hash algorithm change, the only other known option to resolve 99% mining centralisation is to encourage other large Ethereum mining pools to also dual-mine PascalCoin. Even if this were achieved, it would still price-out ordinary pools and solo-miners, which is undesirable. Efforts to encourage other dual-miners were undertaking by the developers and community and have failed. As a result, this option is no longer considered viable. Changing the hash algorithm is now the only known option to resolve centralisation.
+
+Within the scope of changing hash algorithm, other possible hash algorithms like Equihash were considered. However, these were ruled out due to their excessive memory consumption contradicting. PascalCoin's requirements to run on low-end hardware without voluminous amounts of ast memory available to validate block hashes.
+
+## Backwards Compatibility
+
+This PIP is not backwards compatible and requires a hard-fork activation. Previous hashing algorithm must be retained in order to validate blocks mined prior to the hard-fork.
+ 
+## Reference Implementation
+
+TBD
+ 
+## Links
+
+TBD

+ 1 - 0
PIP/README.md

@@ -15,4 +15,5 @@ If they wish to continue, copy [this template](PIP-template.md) and ensure your
 | [6](PIP-0006.md)      | Salvage orphaned transactions            | Herman Schoenfeld              | Protocol       | Active   |
 | [6](PIP-0006.md)      | Salvage orphaned transactions            | Herman Schoenfeld              | Protocol       | Active   |
 | [7](PIP-0007.md)      | New Wallet GUI                           | Herman Schoenfeld              | Front-End      | Draft    |
 | [7](PIP-0007.md)      | New Wallet GUI                           | Herman Schoenfeld              | Front-End      | Draft    |
 | [8](PIP-0008.md)      | Hook(s) to start external programs       | Preben Björn Biermann Madsen   | Backend        | Draft    |
 | [8](PIP-0008.md)      | Hook(s) to start external programs       | Preben Björn Biermann Madsen   | Backend        | Draft    |
+| [9](PIP-0009.md)      | RandomHash: GPU & ASIC Resistant Hash Algorithm | Herman Schoenfeld              | Protocol       | Draft    |
 
 

+ 0 - 3
PascalCoinWallet.lpi

@@ -19,9 +19,6 @@
     <i18n>
     <i18n>
       <EnableI18N LFM="False"/>
       <EnableI18N LFM="False"/>
     </i18n>
     </i18n>
-    <VersionInfo>
-      <StringTable ProductVersion=""/>
-    </VersionInfo>
     <BuildModes Count="1">
     <BuildModes Count="1">
       <Item1 Name="Default" Default="True"/>
       <Item1 Name="Default" Default="True"/>
     </BuildModes>
     </BuildModes>

+ 30 - 0
README.txt

@@ -34,6 +34,36 @@ Also, consider a donation at PascalCoin development account: "0-10"
 
 
 ## History:  
 ## History:  
 
 
+### Future Build 2.1.4 - 2017-12-22
+- Pending operations buffer cached to file to allow daemon/app restart without losing pending operations
+- Less memory usage thanks to a Public keys centralised buffer
+- JSON-RPC changes
+  - Added param "n_operation" to "Operation Object" JSON-RPC call
+  - New method "findnoperation": Search an operation made to an account based on n_operation field
+    - Params:
+	  - "account" : Account
+	  - "n_operation" : n_operation field (n_operation is an incremental value to protect double spend)
+	- Result:
+	  - If success, returns an Operation Object	  
+  - New method "findnoperations": Search an operation made to an account based on n_operation 
+    - Params:
+	  - "account" : Account
+	  - "n_operation_min" : Min n_operation to search
+	  - "n_operation_max" : Max n_operation to search
+	  - "start_block" : (optional) Block number to start search. 0=Search all, including pending operations
+	- Result:
+	  - If success, returns an array of Operation Object
+  - New method "decodeophash": Decodes block/account/n_operation info of a 32 bytes ophash
+    - Params:
+      - "ophash" : HEXASTRING with an ophash (ophash is 32 bytes, so must be 64 hexa valid chars)
+    - Result:
+      - "block" : Integer. Block number. 0=unknown or pending
+      - "account" : Integer. Account number
+      - "n_operation" : Integer. n_operation used by the account. n_operation is an incremental value, cannot be used twice on same account.
+      - "md160hash" : HEXASTRING with MD160 hash
+- Solved bug that caused to delete blockchain when checking memory 
+- Minor optimizations
+
 ### Build 2.1.3.0 - 2017-11-15
 ### Build 2.1.3.0 - 2017-11-15
 - Fixed BUG when buying account assigning an invalid public key
 - Fixed BUG when buying account assigning an invalid public key
 - Added maxim value to node servers buffer, deleting old node servers not used, this improves speed
 - Added maxim value to node servers buffer, deleting old node servers not used, this improves speed

+ 2 - 2
Units/Forms/UFRMMemoText.lfm

@@ -3,7 +3,7 @@ object FRMMemoText: TFRMMemoText
   Height = 428
   Height = 428
   Top = 234
   Top = 234
   Width = 745
   Width = 745
-  BorderIcons = [biSystemMenu]
+  BorderIcons = [biSystemMenu, biMaximize]
   Caption = 'Information'
   Caption = 'Information'
   ClientHeight = 428
   ClientHeight = 428
   ClientWidth = 745
   ClientWidth = 745
@@ -13,7 +13,7 @@ object FRMMemoText: TFRMMemoText
   Font.Name = 'Tahoma'
   Font.Name = 'Tahoma'
   OnCreate = FormCreate
   OnCreate = FormCreate
   Position = poOwnerFormCenter
   Position = poOwnerFormCenter
-  LCLVersion = '1.6.0.4'
+  LCLVersion = '1.6.4.0'
   object pnlBottom: TPanel
   object pnlBottom: TPanel
     Left = 0
     Left = 0
     Height = 55
     Height = 55

+ 4 - 3
Units/Forms/UFRMPayloadDecoder.lfm

@@ -15,7 +15,8 @@ object FRMPayloadDecoder: TFRMPayloadDecoder
   Font.Name = 'Tahoma'
   Font.Name = 'Tahoma'
   OnCreate = FormCreate
   OnCreate = FormCreate
   Position = poOwnerFormCenter
   Position = poOwnerFormCenter
-  LCLVersion = '1.6.0.4'
+  LCLVersion = '1.8.0.6'
+  Visible = False
   object Label1: TLabel
   object Label1: TLabel
     Left = 20
     Left = 20
     Height = 13
     Height = 13
@@ -38,7 +39,7 @@ object FRMPayloadDecoder: TFRMPayloadDecoder
     ParentFont = False
     ParentFont = False
   end
   end
   object lblDateTime: TLabel
   object lblDateTime: TLabel
-    Left = 255
+    Left = 280
     Height = 19
     Height = 19
     Top = 51
     Top = 51
     Width = 30
     Width = 30
@@ -51,7 +52,7 @@ object FRMPayloadDecoder: TFRMPayloadDecoder
     ParentFont = False
     ParentFont = False
   end
   end
   object Label6: TLabel
   object Label6: TLabel
-    Left = 195
+    Left = 220
     Height = 13
     Height = 13
     Top = 56
     Top = 56
     Width = 52
     Width = 52

+ 46 - 18
Units/Forms/UFRMPayloadDecoder.pas

@@ -92,7 +92,7 @@ implementation
 
 
 {$R *.lfm}
 {$R *.lfm}
 
 
-Uses UNode, UTime, UECIES, UAES, UAccounts, USettings;
+Uses UNode, UTime, UECIES, UAES, UAccounts, UCommon, UFRMMemoText, USettings;
 
 
 { TFRMPayloadDecoder }
 { TFRMPayloadDecoder }
 
 
@@ -124,11 +124,13 @@ end;
 
 
 procedure TFRMPayloadDecoder.DoFind(Const OpHash : String);
 procedure TFRMPayloadDecoder.DoFind(Const OpHash : String);
 Var
 Var
-  r : TRawBytes;
+  r,md160 : TRawBytes;
   pcops : TPCOperationsComp;
   pcops : TPCOperationsComp;
-  b : Cardinal;
+  nBlock,nAccount,nN_Operation : Cardinal;
   opbi : Integer;
   opbi : Integer;
   opr : TOperationResume;
   opr : TOperationResume;
+  strings : TStrings;
+  FRM : TFRMMemoText;
 begin
 begin
   // Search for an operation based on "ophash"
   // Search for an operation based on "ophash"
   if (trim(OpHash)='') then begin
   if (trim(OpHash)='') then begin
@@ -136,23 +138,49 @@ begin
     exit;
     exit;
   end;
   end;
   try
   try
-    r := TCrypto.HexaToRaw(trim(ophash));
+    r := TCrypto.HexaToRaw(trim(OpHash));
     if (r='') then begin
     if (r='') then begin
       raise Exception.Create('Value is not an hexadecimal string');
       raise Exception.Create('Value is not an hexadecimal string');
     end;
     end;
-    pcops := TPCOperationsComp.Create(Nil);
-    try
-      If not TNode.Node.FindOperation(pcops,r,b,opbi) then begin
-        raise Exception.Create('Value is not a valid OpHash');
-      end;
-      If not TPCOperation.OperationToOperationResume(b,pcops.Operation[opbi],pcops.Operation[opbi].SignerAccount,opr) then begin
-        raise Exception.Create('Internal error 20161114-1');
-      end;
-      opr.NOpInsideBlock:=opbi;
-      opr.time:=pcops.OperationBlock.timestamp;
+    // Build 2.1.4 new decoder option: Check if OpHash is a posible double spend
+    If not TPCOperation.DecodeOperationHash(r,nBlock,nAccount,nN_Operation,md160) then begin
+      raise Exception.Create('Value is not a valid OPHASH because can''t extract Block/Account/N_Operation info');
+    end;
+    Case TNode.Node.FindNOperation(nBlock,nAccount,nN_Operation,opr) of
+      invalid_params : raise Exception.Create(Format('Not a valid OpHash searching at Block:%d Account:%d N_Operation:%d',[nBlock,nAccount,nN_Operation]));
+      blockchain_block_not_found : raise Exception.Create('Your blockchain file does not contain all blocks to find');
+      found : ;
+    else raise Exception.Create('ERROR DEV 20171120-6');
+    end;
+    If (TPCOperation.EqualOperationHashes(opr.OperationHash,r)) Or
+       (TPCOperation.EqualOperationHashes(opr.OperationHash_OLD,r)) then begin
+      // Found!
       OpResume := opr;
       OpResume := opr;
-    finally
-      pcops.Free;
+    end else begin
+      // Not found!
+      strings := TStringList.Create;
+      try
+        strings.Add('Posible double spend detected!');
+        strings.Add(Format('OpHash: %s',[OpHash]));
+        strings.Add(Format('Decode OpHash info: Block:%d Account:%s N_Operation:%d',[nBlock,TAccountComp.AccountNumberToAccountTxtNumber(nAccount),nN_Operation]));
+        strings.Add('');
+        strings.Add('Real OpHash found in PascalCoin Blockchain:');
+        strings.Add(Format('OpHash: %s',[TCrypto.ToHexaString(opr.OperationHash)]));
+        strings.Add(Format('Decode OpHash info: Block:%d Account:%s N_Operation:%d',[opr.Block,TAccountComp.AccountNumberToAccountTxtNumber(opr.SignerAccount),opr.n_operation]));
+        If (opr.Block=0) then begin
+          strings.Add('* Note: This is a pending operation not included on Blockchain');
+        end;
+        OpResume := opr; // Do show operation resume!
+        FRM := TFRMMemoText.Create(Self);
+        try
+          FRM.InitData('Posible double spend detected',strings.Text);
+          FRM.ShowModal;
+        finally
+          FRM.Free;
+        end;
+      finally
+        strings.Free;
+      end;
     end;
     end;
   Except
   Except
     OpResume := CT_TOperationResume_NUL;
     OpResume := CT_TOperationResume_NUL;
@@ -256,8 +284,8 @@ begin
       exit;
       exit;
     end;
     end;
     If (Value.NOpInsideBlock>=0) then
     If (Value.NOpInsideBlock>=0) then
-      lblBlock.Caption := inttostr(Value.Block)+'/'+inttostr(Value.NOpInsideBlock+1)
-    else lblBlock.Caption := inttostr(Value.Block);
+      lblBlock.Caption := inttostr(Value.Block)+'/'+inttostr(Value.NOpInsideBlock+1)+' '+IntToStr(Value.n_operation)
+    else lblBlock.Caption := inttostr(Value.Block)+' '+IntToStr(Value.n_operation);
     if Value.time>10000 then begin
     if Value.time>10000 then begin
       lblDateTime.Caption := DateTimeToStr(UnivDateTime2LocalDateTime(UnixToUnivDateTime(Value.time)));
       lblDateTime.Caption := DateTimeToStr(UnivDateTime2LocalDateTime(UnixToUnivDateTime(Value.time)));
       lblDateTime.Font.Color := clBlack;
       lblDateTime.Font.Color := clBlack;

+ 1504 - 0
Units/Forms/UFRMWallet.lfm

@@ -0,0 +1,1504 @@
+object FRMWallet: TFRMWallet
+  Left = 389
+  Height = 600
+  Top = 201
+  Width = 865
+  Caption = 'Pascal Coin Wallet, JSON-RPC Miner & Explorer'
+  ClientHeight = 580
+  ClientWidth = 865
+  Color = clBtnFace
+  Constraints.MinHeight = 600
+  Constraints.MinWidth = 865
+  Font.Color = clWindowText
+  Font.Height = -11
+  Font.Name = 'Tahoma'
+  Menu = MainMenu
+  OnCreate = FormCreate
+  OnDestroy = FormDestroy
+  Position = poOwnerFormCenter
+  LCLVersion = '1.6.4.0'
+  object pnlTop: TPanel
+    Left = 0
+    Height = 91
+    Top = 0
+    Width = 865
+    Align = alTop
+    BevelOuter = bvNone
+    ClientHeight = 91
+    ClientWidth = 865
+    TabOrder = 0
+    object Image1: TImage
+      Left = 15
+      Height = 62
+      Top = 15
+      Width = 62
+      AutoSize = True
+      Picture.Data = {
+        1754506F727461626C654E6574776F726B477261706869630A0F000089504E47
+        0D0A1A0A0000000D494844520000003E0000003E080600000073C1A844000000
+        0473424954080808087C086488000000097048597300000B1200000B1201D2DD
+        7EFC0000001C74455874536F6674776172650041646F62652046697265776F72
+        6B7320435336E8BCB28C00000016744558744372656174696F6E2054696D6500
+        30362F30372F313610A27E9700000E62494441546881CD9B7D9C54D579C7BFE7
+        CEECEEC0BE22B02F2CB033B3C2F2EA0ED0801491B56068ADBCA40511492DD12A
+        A468C57CAC298444AC1113623EB15A6391A8D40815431B8C2FA9489301444113
+        1844C00576980576D91D0177667760999DB9A77FDCBB77EFECBCEDABEDEFF339
+        9FB973EFB9E79CDF739EE7B9CF7913524AFA0B42881B80BF016600A5C020200B
+        50F424F51401424003E0050E00FF25A5FCBCDFDAD6D7C485100B818780C9D936
+        6BFD8D15458D55134B2C958EC1B9F3A69655267B2F100A07F71EBB70E6A8EF72
+        F3EF3EAD8FEE3D76C1DE165107000781E7A494EFF7693BFB82B8106234F028F0
+        0D4751EEC97B6EAD883C78FB841BF2B333F37A53AEB72158F7FADE9A331B7E75
+        7870A8353200D80ABC28A53CDBEB36F786B810620CB0D6A288998B66387D1BEE
+        FEDA2867715E696F1B9508DE8660DDA2A7767B0F7B2F8E045E03364929CFF5B4
+        BC1E1117420C02FE1158B96466F9914DAB664EEE6DEF7615260194038F4B295F
+        EC4939DD262E84A804B60FC9B35D39F8D38585FDD5C3E970B0DA7F72FE13EF85
+        FC81ABA7815552CA2FBAF37EB7880B21165B14F1E4FABBA634AE5B32F9A6EE36
+        361D3CDE4B3485AE612FCAC55E980B80CFDF8CAFB19982EC2C5CCEC171EF2CF9
+        F16EF71B1F7887032BA594FFD3D5BA94AE641242D884103FC81D90F183FD1B17
+        C8F4A40508058405146B6C128AF63C01566FFE905BD6BECD96DD278D7B5B769F
+        E496B56FB37AF38709DFD9FEDD3955BFF9FEDC904511BF1442ACEC0A1FE82271
+        E0896C9BF5CE73AF2C1B39ADA27074C21C4268C42C5960CD02AB2D41CAEAF8B5
+        649904A1C173E612404CCFBA8FD60350357158D2C6CD9B5A56B97FE382E61C5B
+        C63F74957C4AE2428801428817726C19B7D56DF9666942072614B06480A59D98
+        89744622F29D8460C9024B264D57DA0884C200869A83A6EA00F6A2DCB8AACD98
+        565138FAFC9665A5B90332560921D60B21327B4C1C7838DB669D757ECBB2E109
+        492B197AC36D897BD992E6BF21A82C3CBEA051ACB9C76BFD2DBA3072D23415F2
+        B333F3CEBDB26C648E2D6331B03455DEA4CE4D08B1386F60E69AB32FDF551E47
+        5A283A69ABF6AB5841B1E836ADFF0A85E54FFE0A5FC3971DEF49F385D4FF6BD7
+        E7BF085253DF44CE800CA6940F05244DA1304774F57F6CE91443282EE7E018AD
+        E88C83D5FE93331E7DD31A55E5FDC91C5E42E24288511645ECDABF714138CEA6
+        8502964C4DBD0DD266E765D105A3206E7C3869E37A8B4AC760562F98C8F2D989
+        5DCE5B1FD71E99FFC47BB940A594B2A5F3F338551742E4015BBF7DDBB8DAE4A4
+        CD292B36E92AEFF6F43AAA4C8923672EF1AD67DCD8EFDD86C77B29EEF9BCA965
+        9577DCE43C0BFC520831A0F3F34436FE9DC2FC01D6E756CC981573570813D98C
+        8E6B6B668CADB6DBAFAF3198A0E8BE47ADBF85490FFDA7E1FDCDD8FEDD395585
+        F903CA4860EF31C4F561E4CA8F9E5E5018578A92614A995A4AD6F3D62C7C0D4D
+        7DC7AE0B58F8E42EE30B60C66FBE3F371B7854085164BEDFB9C7D72D99597E22
+        2E0C55AC1DCECC92614A26D2E65EB764E1FE43BF0DA51322100AB3FAC58FE2EE
+        4FAB281C3DC939C40F3C60BE6F38372184D3A288BD97B6FD6D6EAC17171D0187
+        35CB445427AD98D4BF5D408A85823F594AA03994B091F9B9D9B8C6D8D13CBBE4
+        7CE3256ACEF9C91998C5948A1180CAF9C6266AEA2F775B00675E5A1AE7F1BD0D
+        C1BAF2FB5EB7A239BA4688EDF1EF2D9AE13C15F7E952F44F94D9735B4C6A1FD3
+        FB9A709A426D494903B8C695E3DEF613DCAFFD08F7AB3FE4F659DAA76AF6D4B1
+        B8373D8CFBE70FB06EF96C00460CCDE3D78F2D60568AC8CD8C67DE3C1A77CF59
+        9C573AC939E414F06D8396E9F9E20D777F6D54DC5B8A0584D524800C530C9E41
+        BC97CFC473F27CCAC6554D9F1CE3283DD5B59A40C6D80DE1B6FB08E7B0412CBC
+        A902F7C6453C347F625AE2893C3CC08E35731CC0FD31C4851077388A728FC5D9
+        B631D050620394CEDFEE4E42F09C3893B271F691238C50154B269ECF7D3A7187
+        519EFBB05713D224871E1B587866C54CCAD24470EDF17E67388BF34AB36DD626
+        21C4748338B0FA9E5B2B2271B985253625251FABF29E13A753132F1B61984B53
+        4B2B81E62BDAFDD222A30EDF052DE2B3175F6704440885E5B32B5296DD1EEF27
+        C23F2D725D0256012842080BE07AF0F60937C41357B4EFB7503A7ADFF84D2200
+        2503DFF986948DAB9AF9A7BA63B4E2F9BCD6B8EF1A576E945BDBA039367BC975
+        3175271A93771577CDBADE01FC851042518049D9366B6DE2919799B41223F9D8
+        7BB182D8F3E11F92565E39616C4CA8EB3951A3DD1FE330CA72FFB1633C5E35D9
+        691AC30B7CFEB8E83306B32694247DE62CCE2BCDB02A2D80430196DE5851D498
+        90B45E59CC35ED5A904028C282AF2E756FDBCB46EA31BDE6307DE7B5AAEDC38B
+        0D82BE0B5A6F97150F32699D9692392FA3FC34C3D79BC7979C011629C0F43B6F
+        2EB7C56769AFACF33526219885A391F77C762A65C5AE1B26749041C1734CEB5D
+        4DCDB57A7CF51735122566B5D6EAD97920B5E34C3561A13D2FB100D3ACC0B0A2
+        820129E2CB04D3444224BE063C9F9D4859B146BC43909E639AA05C639D467DEE
+        4FB4A8AF6ACAF531F53FF3EB43299D1740D5C4E4AA0E50E9189C0B14588141F3
+        A6969525CFDA3E76360D5F63AED598BC69894F1CA7BD2F254D812081667DA261
+        787B282DF1D56B13A6F692EB681FC47B6A1A7878D39E94652F98664F394E07B8
+        797C8903685580046A8ED1386375CB206F4A52D5F3A820A320557C67EB52566C
+        1F51026A146414CFD18E78DE35C661D4597B41B3637BF12090929D1F9CA0EA3B
+        DB52960BB07AC184B47974276EB302D6C45924A0EAA4CC044D8958D2A8518E1C
+        AB4E5AE9AC193742B40DD43650238676548E751AE5B83FFECCC8EFBB7099AACD
+        EFB2C793DAAE41EBED74F66D824D00AA7CEBFEC4F3BD964CD37C996986D49829
+        B5C50C60DC1F1FE796250F242CAA3F51569883E7D9451464A79C5F3420E6BD18
+        4DD2DB3A8CDEEC94543D8908A8FA373CAAE03B973A46EF0FE46767B2F37B73BB
+        4CBA1D0A104DFA54464155759B543B08CB28A8119011ED574FE9ECBBAF919F9D
+        897BC3BC9E4473AA156D513E71CF4B69EAE5881678A851CD468502510B9AEC34
+        4B711FF0F49C453751E918CCCE755F4FEBC593E08A15080642E170D2D54E43AD
+        75F2BA5A77C4EDC2F896B7879FFD89FCEC4C56CF9FC8FABBA6F4A698162BF0C5
+        DE63172249772B18BD6DD1ED59271B8D5D036B0A868C51567FA0AC3087E5B32B
+        58BD6062B7EDD98CB73EAE3D029A8A9F3DEABB9C3B6F6A8A18468DE8443BC7E6
+        0223C038EE4DDDF06143B19716D2BE9070FAEC05EAFC5F32A42087F1CE61804A
+        C3C520D567FDD832AD4C1B5B8ACB39047B610E55E30B713906F598AC19FEC0D5
+        56E00B2B70E0779FD6FFD9DA3B2625CF2D555DCD15888AF86752E2F93C35F1F5
+        7FFFD72C9F3FD38801AAEEDD409DFF4B562D9EC5FABF9B0BD136D6FFE2B73CFE
+        F26EA68D2DC5FDD33B2112866858F3297D84FFD853D30AECB5026FEF3D76E19E
+        B46FA8914EA3341D06F1DA646F02602FCA83482BA082AAE2A9D6161C5CD71741
+        E41AA811DC87B4098CAACAE1A640A7EF48031CACF60F05762AC0F1B6889AE76D
+        08A6FF1645DB40D57B211AD61A1C0D43F41ABEFAD4C3C52A975D231EB9465353
+        138196AB80BE32DA5E46FBACCBD06CFD5EDF920E84C2C196D6B6A152CA538A94
+        B215D8B76DCFE9F47121688D899AC847AF41F41A7B3CC955BD72D4309DB496CC
+        66E1720ED6CB0853DB1800C03E74A04EBA6FB7A23DFFCEB14F817DD031E7B6E9
+        473B3C5D8F02D4B60ED29130BE3A7FCAECF6A2824EC43535AF2C2F32B4A05DCD
+        01AAC6EB4EB08FF1D2FBD556E025E8085C76855A2343BC0DC1BA2E6FE6512346
+        48EB3999DA4A5CE54375FBD6465F0BA73B71399651909DA1094F8DE2B2E7F3FB
+        A7E6771AE6F61DBC0DC13A6F43B01870834E5C4A191142FC6CD153BB6F3BF42F
+        7FD5F55D4C5285A88AE774EAE92697E33A9DB8F68E7D880DFBE0422D4688B482
+        1AA5C026A89A50DC33565DC0DA573F3905EC97525E81D805857F3DECBD38A14B
+        4EAE133C3517533E77D9F34DAA7EADE33A7A4DB3E57EEAE5760442E1E08EFDDE
+        E180B127CE202EA56C067EB1E8A9DD5D737226245AA534C33E382B96ACDAA68F
+        01FA6F03B1192B9EDF7728AACAADE6ADA09D574B9F3DECBD587AB0DA7F926EE0
+        4892D50B689FEEFD6A082682B72158B77D5FCD04E0DFCCF76388EB7B43D7CF7F
+        E2BDE42B7E9D906841DE8C74D3BDFD8DE98FBCE9071E9152C638A2443B2276F8
+        03576B96FC78B7BB2B05A79BE077397ABEF2D15B6C78E3F007FEC0D54629E5BF
+        777E16475CF77AF7BFF1817774FB4826157C8DA9EDBB374B3EBDC1C16AFFC9C7
+        B6FD7120DADEF93824DCE726A5FC1258F48D27770D4C67EFE954DDE51CD2C5A6
+        F61D02A17070CEBA772291A8FAB8943261FB936EF093527E1455E5DA39EBDE89
+        0642E1A43B79922DCB823669D09BB1734F1008858323BEB5B5B6A5B5ED15E09D
+        64F952EE6C9452EE68696DDB317CF9D6BA44E49B42E1942B1B5FB57DEBA4CF36
+        5F6DFBB994F2692965D2F9C42E6DDB1642ACCC1B98B962D73FDF36D0BCF7AD29
+        14C6E34D1EBC98B75FF73702A17070F8F2AD752DAD6D6F4829D7A7CBDFE5FDEA
+        4288FB142136EC5CF7F5BA54876AFE2F70B0DA7F72EE63EF360742E15780CD52
+        CAD40B6C748FB802CC069E5E32B3FCF2EB8FCEAEEA556BFB080F6EDABFE78577
+        8F5F1F55E57D52CADF76F5BD9E1CCD7002CFE7D83246EEFEE15F5A93EE5FEF67
+        781B8275D31F79D3EF0F5C6D04BE29A54C3D13D2093D3E85A49F2F7B619273C8
+        E91D6BE638BEAAB329815038B8E2F97D87B6EFAB71001B81D7A494DDDE3FDADB
+        E35783D0760C3E34C939E4F88E35739CFD79FC6AEDAB9F9CDAB1DF6B8FAA7207
+        F0EC577EFC2AAE106D9FE8DDC0BD39B60C75CD62D7A53B6F2EEFB5160442E1E0
+        736F7FF6E9CBEF575BCF34368F0236A311BED0EB36F7C311CB29685BAA6EC9B4
+        2A6D33C797D48F1B39885B5DA505378F2F71245BB1E97CC4F240756361A83552
+        0C7C023CFDFFF28865D2C2852803FE1CED50ED686018900B64A2CDFE48200C5C
+        035A8046A006F83DF0DF40BD94FD334BF1BFCAF3090585D7E9BD000000004945
+        4E44AE426082
+      }
+    end
+    object lblCurrentBlockCaption: TLabel
+      Left = 90
+      Height = 13
+      Top = 11
+      Width = 60
+      Caption = 'Total Blocks:'
+      ParentColor = False
+    end
+    object lblCurrentBlock: TLabel
+      Left = 166
+      Height = 13
+      Top = 11
+      Width = 18
+      Caption = '000'
+      ParentColor = False
+    end
+    object lblCurrentBlockTimeCaption: TLabel
+      Left = 90
+      Height = 13
+      Top = 26
+      Width = 90
+      Caption = 'Current Block Age:'
+      ParentColor = False
+    end
+    object lblCurrentBlockTime: TLabel
+      Left = 198
+      Height = 13
+      Top = 26
+      Width = 81
+      Caption = '000 seconds ago'
+      ParentColor = False
+    end
+    object lblOperationsPendingCaption: TLabel
+      Left = 90
+      Height = 13
+      Top = 41
+      Width = 98
+      Caption = 'Pending Operations:'
+      ParentColor = False
+    end
+    object lblOperationsPending: TLabel
+      Left = 207
+      Height = 13
+      Top = 41
+      Width = 18
+      Caption = '000'
+      ParentColor = False
+    end
+    object lblMiningStatusCaption: TLabel
+      Left = 90
+      Height = 13
+      Top = 56
+      Width = 65
+      Caption = 'Miner Clients:'
+      ParentColor = False
+    end
+    object lblMinersClients: TLabel
+      Left = 177
+      Height = 13
+      Top = 56
+      Width = 18
+      Caption = '000'
+      ParentColor = False
+    end
+    object lblCurrentDifficultyCaption: TLabel
+      Left = 429
+      Height = 13
+      Top = 11
+      Width = 76
+      Caption = 'Current Target:'
+      ParentColor = False
+    end
+    object lblCurrentDifficulty: TLabel
+      Left = 518
+      Height = 13
+      Top = 11
+      Width = 18
+      Caption = '000'
+      ParentColor = False
+    end
+    object lblTimeAverage: TLabel
+      Left = 370
+      Height = 13
+      Top = 26
+      Width = 18
+      Caption = '000'
+      ParentColor = False
+    end
+    object Label4: TLabel
+      Left = 285
+      Height = 13
+      Top = 26
+      Width = 69
+      Caption = 'Time average:'
+      ParentColor = False
+    end
+    object Label8: TLabel
+      Left = 90
+      Height = 13
+      Top = 70
+      Width = 63
+      Caption = 'Node Status:'
+      ParentColor = False
+    end
+    object lblNodeStatus: TLabel
+      Left = 168
+      Height = 13
+      Top = 70
+      Width = 15
+      Caption = '???'
+      ParentColor = False
+    end
+    object Label5: TLabel
+      Left = 285
+      Height = 13
+      Top = 11
+      Width = 48
+      Caption = 'Accounts:'
+      ParentColor = False
+    end
+    object lblCurrentAccounts: TLabel
+      Left = 344
+      Height = 13
+      Top = 11
+      Width = 18
+      Caption = '000'
+      ParentColor = False
+    end
+    object lblTimeAverageAux: TLabel
+      Left = 370
+      Height = 13
+      Top = 41
+      Width = 18
+      Caption = '000'
+      Font.Color = clGray
+      Font.Height = -11
+      Font.Name = 'Tahoma'
+      ParentColor = False
+      ParentFont = False
+    end
+    object Label16: TLabel
+      Left = 360
+      Height = 13
+      Top = 56
+      Width = 76
+      Caption = 'Blocks Found:'
+      Font.Color = clWindowText
+      Font.Height = -11
+      Font.Name = 'Tahoma'
+      Font.Style = [fsBold]
+      ParentColor = False
+      ParentFont = False
+    end
+    object lblBlocksFound: TLabel
+      Left = 450
+      Height = 13
+      Hint = 'Blocks found while Miner is running...'
+      Top = 56
+      Width = 21
+      Caption = '000'
+      Font.Color = clWindowText
+      Font.Height = -11
+      Font.Name = 'Tahoma'
+      Font.Style = [fsBold]
+      ParentColor = False
+      ParentFont = False
+      ParentShowHint = False
+      ShowHint = True
+    end
+    object lblReceivedMessages: TLabel
+      Cursor = crHandPoint
+      Left = 360
+      Height = 23
+      Top = 66
+      Width = 184
+      Caption = 'Received Messages'
+      Font.Color = clRed
+      Font.Height = -19
+      Font.Name = 'Tahoma'
+      Font.Style = [fsBold]
+      ParentColor = False
+      ParentFont = False
+      OnClick = lblReceivedMessagesClick
+    end
+    object lblBuild: TLabel
+      Left = 586
+      Height = 23
+      Top = 3
+      Width = 49
+      Caption = 'Build'
+      Font.Color = clWindowText
+      Font.Height = -19
+      Font.Name = 'Tahoma'
+      Font.Style = [fsBold]
+      ParentColor = False
+      ParentFont = False
+    end
+  end
+  object StatusBar: TStatusBar
+    Left = 0
+    Height = 23
+    Top = 557
+    Width = 865
+    Panels = <    
+      item
+        Alignment = taCenter
+        Text = 'Server Active'
+        Width = 130
+      end    
+      item
+        Text = 'Connection status'
+        Width = 430
+      end    
+      item
+        Text = 'Blocks'
+        Width = 50
+      end>
+    SimplePanel = False
+  end
+  object PageControl: TPageControl
+    Left = 0
+    Height = 466
+    Top = 91
+    Width = 865
+    ActivePage = tsMyAccounts
+    Align = alClient
+    TabIndex = 0
+    TabOrder = 2
+    OnChange = PageControlChange
+    object tsMyAccounts: TTabSheet
+      Caption = 'Account Explorer'
+      ClientHeight = 440
+      ClientWidth = 857
+      object Splitter1: TSplitter
+        Left = 380
+        Height = 374
+        Top = 66
+        Width = 5
+      end
+      object pnlMyAccountsTop: TPanel
+        Left = 0
+        Height = 66
+        Top = 0
+        Width = 857
+        Align = alTop
+        BevelOuter = bvNone
+        ClientHeight = 66
+        ClientWidth = 857
+        TabOrder = 0
+        object Label18: TLabel
+          Left = 11
+          Height = 13
+          Top = 35
+          Width = 61
+          Caption = 'Find account'
+          ParentColor = False
+        end
+        object cbMyPrivateKeys: TComboBox
+          Left = 260
+          Height = 21
+          Top = 7
+          Width = 411
+          ItemHeight = 13
+          OnChange = cbMyPrivateKeysChange
+          Style = csDropDownList
+          TabOrder = 0
+        end
+        object cbExploreMyAccounts: TCheckBox
+          Left = 11
+          Height = 19
+          Top = 10
+          Width = 235
+          Caption = 'Explore accounts with one of my Wallet Keys'
+          OnClick = cbExploreMyAccountsClick
+          TabOrder = 1
+        end
+        object ebFindAccountNumber: TEdit
+          Left = 87
+          Height = 21
+          Top = 33
+          Width = 83
+          OnChange = ebFindAccountNumberChange
+          OnExit = ebFindAccountNumberExit
+          TabOrder = 3
+        end
+        object bbChangeKeyName: TBitBtn
+          Left = 685
+          Height = 25
+          Top = 5
+          Width = 126
+          Caption = 'Change Key name'
+          OnClick = bbChangeKeyNameClick
+          TabOrder = 2
+        end
+        object cbFilterAccounts: TCheckBox
+          Left = 260
+          Height = 19
+          Top = 35
+          Width = 145
+          Caption = 'Filter accounts by balance'
+          OnClick = cbFilterAccountsClick
+          TabOrder = 4
+        end
+        object ebFilterAccountByBalanceMin: TEdit
+          Left = 412
+          Height = 21
+          Hint = 'Min balance'
+          Top = 33
+          Width = 83
+          OnExit = ebFilterAccountByBalanceMinExit
+          OnKeyPress = ebFilterAccountByBalanceMinKeyPress
+          TabOrder = 5
+        end
+        object ebFilterAccountByBalanceMax: TEdit
+          Left = 503
+          Height = 21
+          Hint = 'Max balance'
+          Top = 33
+          Width = 83
+          OnExit = ebFilterAccountByBalanceMinExit
+          OnKeyPress = ebFilterAccountByBalanceMinKeyPress
+          TabOrder = 6
+        end
+        object sbSearchAccount: TSpeedButton
+          Left = 176
+          Height = 22
+          Top = 32
+          Width = 23
+          Glyph.Data = {
+            36030000424D3803000000000000360000002800000010000000100000000100
+            18000000000000000000120B0000120B00000000000000000000FF00FF4A667C
+            BE9596FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
+            FFFF00FFFF00FFFF00FF6B9CC31E89E84B7AA3C89693FF00FFFF00FFFF00FFFF
+            00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF4BB4FE51B5FF
+            2089E94B7AA2C69592FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
+            FFFF00FFFF00FFFF00FFFF00FF51B7FE51B3FF1D87E64E7AA0CA9792FF00FFFF
+            00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
+            51B7FE4EB2FF1F89E64E7BA2B99497FF00FFFF00FFFF00FFFF00FFFF00FFFF00
+            FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF52B8FE4BB1FF2787D95F6A76FF
+            00FFB0857FC09F94C09F96BC988EFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
+            FF00FFFF00FF55BDFFB5D6EDBF9D92BB9B8CE7DAC2FFFFE3FFFFE5FDFADAD8C3
+            B3B58D85FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFCEA795FD
+            EEBEFFFFD8FFFFDAFFFFDBFFFFE6FFFFFBEADDDCAE837FFF00FFFF00FFFF00FF
+            FF00FFFF00FFFF00FFFF00FFC1A091FBDCA8FEF7D0FFFFDBFFFFE3FFFFF8FFFF
+            FDFFFFFDC6A99CFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFC1A091FEE3ACF1
+            C491FCF2CAFFFFDDFFFFE4FFFFF7FFFFF7FFFFE9EEE5CBB9948CFF00FFFF00FF
+            FF00FFFF00FFFF00FFC2A191FFE6AEEEB581F7DCAEFEFDD8FFFFDFFFFFE3FFFF
+            E4FFFFE0F3ECD2BB968EFF00FFFF00FFFF00FFFF00FFFF00FFBC978CFBE7B7F4
+            C791F2C994F8E5B9FEFCD8FFFFDDFFFFDCFFFFE0E2D2BAB68E86FF00FFFF00FF
+            FF00FFFF00FFFF00FFFF00FFD9C3A9FFFEE5F7DCB8F2C994F5D4A5FAE8BDFDF4
+            C9FDFBD6B69089FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFB58D85E8
+            DEDDFFFEF2F9D8A3F4C48CF9D49FFDEAB8D0B49FB89086FF00FFFF00FFFF00FF
+            FF00FFFF00FFFF00FFFF00FFFF00FFAD827FC9AA9EEFE0B7EFDFB2E7CEACB890
+            86B89086FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF
+            00FFFF00FFBA968ABB988CB79188FF00FFFF00FFFF00FFFF00FF
+          }
+          OnClick = sbSearchAccountClick
+        end
+      end
+      object pnlAccounts: TPanel
+        Left = 0
+        Height = 374
+        Top = 66
+        Width = 380
+        Align = alLeft
+        BevelOuter = bvNone
+        ClientHeight = 374
+        ClientWidth = 380
+        TabOrder = 1
+        object dgAccounts: TDrawGrid
+          Left = 0
+          Height = 340
+          Top = 0
+          Width = 380
+          Align = alClient
+          ExtendedSelect = False
+          TabOrder = 0
+          TitleFont.Color = clWindowText
+          TitleFont.Height = -11
+          TitleFont.Name = 'Tahoma'
+          OnClick = dgAccountsClick
+        end
+        object pnlAccountsInfo: TPanel
+          Left = 0
+          Height = 34
+          Top = 340
+          Width = 380
+          Align = alBottom
+          BevelOuter = bvNone
+          ClientHeight = 34
+          ClientWidth = 380
+          TabOrder = 1
+          object Label17: TLabel
+            Left = 5
+            Height = 13
+            Top = 10
+            Width = 48
+            Caption = 'Accounts:'
+            ParentColor = False
+          end
+          object Label19: TLabel
+            Left = 136
+            Height = 13
+            Top = 10
+            Width = 41
+            Caption = 'Balance:'
+            ParentColor = False
+          end
+          object lblAccountsCount: TLabel
+            Left = 60
+            Height = 13
+            Top = 10
+            Width = 18
+            Caption = '000'
+            ParentColor = False
+          end
+          object lblAccountsBalance: TLabel
+            Left = 200
+            Height = 13
+            Top = 10
+            Width = 18
+            Caption = '000'
+            ParentColor = False
+          end
+          object bbAccountsRefresh: TBitBtn
+            Left = 302
+            Height = 25
+            Top = 6
+            Width = 75
+            Anchors = [akTop, akRight]
+            Caption = 'Refresh'
+            Glyph.Data = {
+              36030000424D3603000000000000360000002800000010000000100000000100
+              18000000000000030000120B0000120B00000000000000000000FF00FFFF00FF
+              C2A6A4C2A6A4C2A6A4C2A6A4C2A6A4C2A6A4C2A6A4C2A6A4C2A6A4C2A6A4C2A6
+              A4C2A6A4FF00FFFF00FFFF00FFFF00FFC2A6A4FEFCFBFEFCFBFEFCFBFEFCFBFE
+              FCFBFEFCFBFEFCFBFEFCFBFEFCFBFEFCFBC2A6A4FF00FFFF00FFFF00FFFF00FF
+              C2A6A4FEFCFBFEFCFBFEFCFBFEFCFBD8EBD6018A02018A02D8EBD6FEFCFBFEFC
+              FBC2A6A4FF00FFFF00FFFF00FFFF00FFC2A6A4FEFBF7FEFBF7018A02D8EAD201
+              8A02D8EAD2D8EAD2018A02FEFBF7FEFBF7C2A6A4FF00FFFF00FFFF00FFFF00FF
+              C2A6A4FEF9F4FEF9F4018A02018A02D8E8D0FEF9F4FEF9F4D8E8D0FEF9F4FEF9
+              F4C2A6A4FF00FFFF00FFFF00FFFF00FFC2A6A4FEF7F0FEF7F0018A02018A0201
+              8A02FEF7F0FEF7F0FEF7F0FEF7F0FEF7F0C2A6A4FF00FFFF00FFFF00FFFF00FF
+              C2A6A4FEF5ECFEF5ECFEF5ECFEF5ECFEF5EC018A02018A02018A02FEF5ECFEF5
+              ECC2A6A4FF00FFFF00FFFF00FFFF00FFC2A6A4FEF3E9FEF3E9D8E3C7FEF3E9FE
+              F3E9D8E3C7018A02018A02FEF3E9FEF3E9C2A6A4FF00FFFF00FFFF00FFFF00FF
+              C2A6A4FFF1E5FFF1E5018A02D9E2C3D9E2C3018A02D9E2C3018A02FFF1E5FFF1
+              E5C2A6A4FF00FFFF00FFFF00FFFF00FFC2A6A4FFF0E2FFF0E2D9E1C1018A0201
+              8A02D9E1C1DDCFC2DDCFC2DDCFC2DDCFC2C2A6A4FF00FFFF00FFFF00FFFF00FF
+              C2A6A4FFEEDEFFEEDEFFEEDEFFEEDEFFEEDEFFEEDEC5B5A9C3B4A8C2B3A7C1B2
+              A6C2A6A4FF00FFFF00FFFF00FFFF00FFC2A6A4FFECDAFFECDAFFECDAFFECDAFF
+              ECDAFFECDAB0A296B0A296B0A296B0A296C2A6A4FF00FFFF00FFFF00FFFF00FF
+              C2A6A4FFEAD7FFEAD7FFEAD7FFEAD7FFEAD7C9B9ACFBF8F4FBF8F4E6DAD9C2A6
+              A4FF00FFFF00FFFF00FFFF00FFFF00FFC2A6A4FFE8D3FFE8D3FFE8D3FFE8D3FF
+              E8D3C9B9ACFBF8F4DFCEC7C2A6A4FF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
+              C2A6A4FFE6D0FFE6D0FFE6D0FFE6D0FFE6D0C9B9ACDFCEC7C2A6A4FF00FFFF00
+              FFFF00FFFF00FFFF00FFFF00FFFF00FFC2A6A4C2A6A4C2A6A4C2A6A4C2A6A4C2
+              A6A4C2A6A4C2A6A4FF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
+            }
+            OnClick = bbAccountsRefreshClick
+            TabOrder = 0
+          end
+        end
+      end
+      object pcAccountsOptions: TPageControl
+        Left = 385
+        Height = 374
+        Top = 66
+        Width = 472
+        ActivePage = tsAccountOperations
+        Align = alClient
+        TabIndex = 0
+        TabOrder = 2
+        object tsAccountOperations: TTabSheet
+          Caption = 'Operations of selected Account'
+          ClientHeight = 348
+          ClientWidth = 464
+          object dgAccountOperations: TDrawGrid
+            Left = 0
+            Height = 348
+            Top = 0
+            Width = 464
+            Align = alClient
+            ExtendedSelect = False
+            TabOrder = 0
+            TitleFont.Color = clWindowText
+            TitleFont.Height = -11
+            TitleFont.Name = 'Tahoma'
+            OnDblClick = MiDecodePayloadClick
+          end
+        end
+        object tsMultiSelectAccounts: TTabSheet
+          Caption = 'Selected accounts for massive operations'
+          ClientHeight = 368
+          ClientWidth = 472
+          ImageIndex = 1
+          object dgSelectedAccounts: TDrawGrid
+            Left = 41
+            Height = 253
+            Top = 31
+            Width = 320
+            Align = alLeft
+            ExtendedSelect = False
+            TabOrder = 0
+            TitleFont.Color = clWindowText
+            TitleFont.Height = -11
+            TitleFont.Name = 'Tahoma'
+          end
+          object pnlSelectedAccountsTop: TPanel
+            Left = 0
+            Height = 31
+            Top = 0
+            Width = 450
+            Align = alTop
+            BevelOuter = bvNone
+            ClientHeight = 31
+            ClientWidth = 450
+            Font.Color = clWindowText
+            Font.Height = -13
+            Font.Name = 'Tahoma'
+            Font.Style = [fsBold]
+            ParentFont = False
+            TabOrder = 1
+            object Label15: TLabel
+              Left = 41
+              Height = 16
+              Top = 4
+              Width = 361
+              Caption = 'Select multiple accounts to execute massive operations'
+              ParentColor = False
+            end
+          end
+          object pnlSelectedAccountsBottom: TPanel
+            Left = 0
+            Height = 26
+            Top = 284
+            Width = 450
+            Align = alBottom
+            BevelOuter = bvNone
+            ClientHeight = 26
+            ClientWidth = 450
+            TabOrder = 2
+            object Label20: TLabel
+              Left = 41
+              Height = 13
+              Top = 6
+              Width = 48
+              Caption = 'Accounts:'
+              ParentColor = False
+            end
+            object lblSelectedAccountsCount: TLabel
+              Left = 96
+              Height = 13
+              Top = 6
+              Width = 18
+              Caption = '000'
+              ParentColor = False
+            end
+            object Label22: TLabel
+              Left = 156
+              Height = 13
+              Top = 6
+              Width = 88
+              Caption = 'Accounts Balance:'
+              ParentColor = False
+            end
+            object lblSelectedAccountsBalance: TLabel
+              Left = 250
+              Height = 13
+              Top = 6
+              Width = 18
+              Caption = '000'
+              ParentColor = False
+            end
+          end
+          object pnlSelectedAccountsLeft: TPanel
+            Left = 0
+            Height = 253
+            Top = 31
+            Width = 41
+            Align = alLeft
+            BevelOuter = bvNone
+            ClientHeight = 253
+            ClientWidth = 41
+            TabOrder = 3
+            object sbSelectedAccountsAdd: TSpeedButton
+              Left = 2
+              Height = 31
+              Top = 0
+              Width = 33
+              Caption = '>'
+              OnClick = sbSelectedAccountsAddClick
+            end
+            object sbSelectedAccountsAddAll: TSpeedButton
+              Left = 2
+              Height = 31
+              Top = 37
+              Width = 33
+              Caption = '>>'
+              OnClick = sbSelectedAccountsAddAllClick
+            end
+            object sbSelectedAccountsDel: TSpeedButton
+              Left = 2
+              Height = 31
+              Top = 74
+              Width = 33
+              Caption = '<'
+              OnClick = sbSelectedAccountsDelClick
+            end
+            object sbSelectedAccountsDelAll: TSpeedButton
+              Left = 2
+              Height = 31
+              Top = 111
+              Width = 33
+              Caption = '<<'
+              OnClick = sbSelectedAccountsDelAllClick
+            end
+          end
+          object bbSelectedAccountsOperation: TBitBtn
+            Left = 367
+            Height = 61
+            Top = 31
+            Width = 75
+            Caption = 'Operations'
+            Glyph.Data = {
+              F6060000424DF606000000000000360000002800000018000000180000000100
+              180000000000C0060000120B0000120B00000000000000000000FF00FFFF00FF
+              FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00
+              FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF
+              00FFFF00FFFF00FF019ACF019ACF019ACF019ACFFF00FFFF00FFFF00FFFF00FF
+              FF00FFFF00FFFF00FFFF00FFFF00FFFF00FF0C8518FF00FFFF00FFFF00FFFF00
+              FFFF00FFFF00FFFF00FFFF00FF0D9FD18BD4EE6BD3F845C0ED28B0E0019ACF01
+              9ACF019ACF019ACFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF0C85180C8518
+              FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF069CD076C8E5A9E9FE6DD8
+              FF75DBFF77DCFF77DBFF63D1F930B3E3029BD0019ACF019ACF019ACF019ACFFF
+              00FF0C85181399220C8518FF00FFFF00FFFF00FFFF00FFFF00FFFF00FF019ACF
+              34AFD9BCE9F86ED8FF6FD8FE70D8FE70D8FE71D8FF0C85180C85180C85180C85
+              180C85180C85180C85180C85181DAC31139A220C8518FF00FFFF00FFFF00FFFF
+              00FFFF00FF019ACF1FA9D68FD3EB97E4FF6FD9FE71D9FE71D9FE71D9FE0C8518
+              57E38851DD7E4AD77443D0693BC95E34C1522BBA4725B33C1EAE33149B230C85
+              18FF00FFFF00FFFF00FFFF00FF019ACF31B1DC49B7DEBDEEFB71DDFE77DEFE77
+              DEFE77DEFE0C85185EE89059E48953DE804CD87645D16C3DCA6035C2542DBB49
+              26B53F1FAF35149B250C8518FF00FFFF00FFFF00FF019ACF52C2E71DA7D5ADE2
+              F38FE8FF7CE2FE7CE3FE7CE3FE0C851861EB955FE9925AE58B54DF824DD97846
+              D26D3ECB6237C4562FBD4C27B64021B037159B250C8518FF00FFFF00FF019ACF
+              60CAEF1FA8D85EC1E1C2E6ED8ACEE08FCFE18ECFE10C851861EB9561EB955FEA
+              935CE58D56E0844FDB7A48D47040CD6538C65931BF4D1DA3320C8518FF00FFFF
+              00FFFF00FF019ACF65CFF53EB7E52CA9D4C5EFF8ACF3FEA5F2FFA5F2FF0C8518
+              61EB9561EB9561EB9561EB945CE68E57E18650DC7C49D57242CE6727AD410C85
+              18FF00FFFF00FFFF00FFFF00FF019ACF69D1F855C4F32A9CC673CBE7D6FEFDB1
+              FBFDB2FBFD0C85180C85180C85180C85180C85180C85180C85180C851852DD7F
+              32B6500C851898FAFF019ACFFF00FFFF00FFFF00FF019ACF77D5FC5CC8FB748E
+              A224A8D5B9E7F3D5F5F9D5F6F9D6F6FADCFAFBCDFDFCB9FCFCAFFAFCB0FAFCB1
+              FAFC0C85183ABE5C0C85189FFCFFA4FFFF43C1E2019ACFFF00FFFF00FF019ACF
+              8BDBFF5FCDFFB7898973C3DD18A2D218A2D216A2D215A1D21AA4D391D7EBEBFE
+              FDDBFDFCC5FBFBC2FBFB0C85180C851883E4F3B6FDFFBAFFFFB5FCFD019ACFFF
+              00FFFF00FF019ACF99E2FF67D3FFB88989FEF5ECFDF3EBF0EFEAE5EBE8D6E5E6
+              A4D2E025A6D34DB9DDE5F8FBF5FDFCEBFCFB0C8518C4FBFF9CE4F2DAFEFFD9FE
+              FFE3FFFFADE9F5019ACFFF00FF019ACF9FE9FF70DCFFB88989FEF3E9FFF2E6FE
+              F3E9FEF3E9FEF3E9FEF3E9D4E4E439ADD422A5D49DD8ECF1F9FBEEEFEFE9FDFF
+              CEEEF7F8FFFFF7FFFFFEFFFFE9F9FD019ACFFF00FF019ACFA7EFFF76E5FFB889
+              89FFF2E5FFF0E2FFF2E5FFF2E5FFF2E5FFF2E5FFF2E5EAEBE38EC9DA44B0D501
+              9ACF019ACF019ACF019ACF019ACF019ACF019ACF019ACF019ACFFF00FF019ACF
+              ABF6FF7EEDFFB88989FFF0E2FFEFDFFFF0E2FFF0E2FFF0E2FFF0E2FFF0E2FEEE
+              E0FBECDEFAEBDEF6E6D9B8898993F7FF019ACFFF00FFFF00FFFF00FFFF00FFFF
+              00FFFF00FF019ACFC7FFFF82F5FFB88989FFEEDFFFECDBFFEEDFFFEEDFFFEEDF
+              FFEEDFF9E8D9DECCC1D9CABDCFBDB4C8B3ACB88989B5FFFF019ACFFF00FFFF00
+              FFFF00FFFF00FFFF00FFFF00FF019ACFA4E0F0A0FDFFB88989FFECDBFFEBD8FF
+              ECDBFFECDBFFECDBFFECDBF5E2D2C4ABA7C2A8A5BBA39FC2AFA9B88989019ACF
+              FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF019ACFECFFFFB889
+              89FFEBD8FFEAD5FFEBD8FFEBD8FFEBD8FFEBD8FFEBD8D9C8C5FEFEFDFEF6EFDE
+              C9C0B88989FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
+              FF00FF019ACFB88989FFE9D5FFE8D3FFE9D5FFE9D5FFE9D5FFE9D5FFE9D5C6AD
+              A9FEF8F2E8D4CACD9999FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF
+              00FFFF00FFFF00FFFF00FFFF00FFB88989FFE7D1FFE7D0FFE7D1FFE7D1FFE7D1
+              FFE7D1E7CEBFD3BFB9E8D5CCCD9999FF00FFFF00FFFF00FFFF00FFFF00FFFF00
+              FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFB88989FFE6CFFFE6CFFF
+              E6CFFFE6CFFFE6CFFFE6CFD5BBB2E0CCC5CD9999FF00FFFF00FFFF00FFFF00FF
+              FF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFB889
+              89B88989B88989B88989B88989B88989B88989B88989B88989FF00FFFF00FFFF
+              00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FFFF00FF
+            }
+            Layout = blGlyphTop
+            OnClick = bbSelectedAccountsOperationClick
+            TabOrder = 4
+          end
+        end
+      end
+    end
+    object tsPendingOperations: TTabSheet
+      Caption = 'Pending Operations'
+      ClientHeight = 440
+      ClientWidth = 857
+      ImageIndex = 5
+      object dgPendingOperations: TDrawGrid
+        Left = 0
+        Height = 354
+        Top = 86
+        Width = 857
+        Align = alClient
+        ExtendedSelect = False
+        TabOrder = 0
+        TitleFont.Color = clWindowText
+        TitleFont.Height = -11
+        TitleFont.Name = 'Tahoma'
+        OnDblClick = MiDecodePayloadClick
+      end
+      object pnlPendingOperations: TPanel
+        Left = 0
+        Height = 86
+        Top = 0
+        Width = 857
+        Align = alTop
+        BevelOuter = bvNone
+        BorderWidth = 10
+        ClientHeight = 86
+        ClientWidth = 857
+        TabOrder = 1
+        object Label10: TLabel
+          Left = 10
+          Height = 66
+          Top = 10
+          Width = 837
+          Align = alClient
+          AutoSize = False
+          Caption = 'Here you can see Operations transmited/received from other nodes that will be included in next block. There is no guarantee that other nodes will include them when mining, so it''s important that you mine too to help include Operations to the main BlockChain'
+          Font.Color = clWindowText
+          Font.Height = -16
+          Font.Name = 'Tahoma'
+          ParentColor = False
+          ParentFont = False
+          WordWrap = True
+        end
+      end
+    end
+    object tsBlockChain: TTabSheet
+      Caption = 'Block Explorer'
+      ClientHeight = 440
+      ClientWidth = 857
+      ImageIndex = 1
+      object Panel2: TPanel
+        Left = 0
+        Height = 41
+        Top = 0
+        Width = 857
+        Align = alTop
+        BevelOuter = bvNone
+        ClientHeight = 41
+        ClientWidth = 857
+        TabOrder = 0
+        object Label9: TLabel
+          Left = 11
+          Height = 13
+          Top = 10
+          Width = 102
+          Caption = 'Filter by blocks range'
+          ParentColor = False
+        end
+        object ebBlockChainBlockStart: TEdit
+          Left = 125
+          Height = 21
+          Top = 7
+          Width = 57
+          OnExit = ebBlockChainBlockStartExit
+          OnKeyPress = ebBlockChainBlockStartKeyPress
+          TabOrder = 0
+        end
+        object ebBlockChainBlockEnd: TEdit
+          Left = 185
+          Height = 21
+          Top = 7
+          Width = 57
+          OnExit = ebBlockChainBlockStartExit
+          OnKeyPress = ebBlockChainBlockStartKeyPress
+          TabOrder = 1
+        end
+      end
+      object dgBlockChainExplorer: TDrawGrid
+        Left = 0
+        Height = 399
+        Top = 41
+        Width = 857
+        Align = alClient
+        ExtendedSelect = False
+        TabOrder = 1
+        TitleFont.Color = clWindowText
+        TitleFont.Height = -11
+        TitleFont.Name = 'Tahoma'
+      end
+    end
+    object tsOperations: TTabSheet
+      Caption = 'Operations Explorer'
+      ClientHeight = 440
+      ClientWidth = 857
+      ImageIndex = 1
+      object Panel1: TPanel
+        Left = 0
+        Height = 41
+        Top = 0
+        Width = 857
+        Align = alTop
+        BevelOuter = bvNone
+        ClientHeight = 41
+        ClientWidth = 857
+        TabOrder = 0
+        object Label2: TLabel
+          Left = 11
+          Height = 13
+          Top = 10
+          Width = 102
+          Caption = 'Filter by blocks range'
+          ParentColor = False
+        end
+        object ebFilterOperationsStartBlock: TEdit
+          Left = 125
+          Height = 21
+          Top = 7
+          Width = 57
+          OnExit = ebFilterOperationsAccountExit
+          OnKeyPress = ebFilterOperationsAccountKeyPress
+          TabOrder = 0
+        end
+        object ebFilterOperationsEndBlock: TEdit
+          Left = 185
+          Height = 21
+          Top = 7
+          Width = 57
+          OnExit = ebFilterOperationsAccountExit
+          OnKeyPress = ebFilterOperationsAccountKeyPress
+          TabOrder = 1
+        end
+      end
+      object dgOperationsExplorer: TDrawGrid
+        Left = 0
+        Height = 399
+        Top = 41
+        Width = 857
+        Align = alClient
+        ExtendedSelect = False
+        TabOrder = 1
+        TitleFont.Color = clWindowText
+        TitleFont.Height = -11
+        TitleFont.Name = 'Tahoma'
+      end
+    end
+    object tsLogs: TTabSheet
+      Caption = 'Logs'
+      ClientHeight = 440
+      ClientWidth = 857
+      ImageIndex = 2
+      object pnlTopLogs: TPanel
+        Left = 0
+        Height = 41
+        Top = 0
+        Width = 857
+        Align = alTop
+        BevelOuter = bvNone
+        ClientHeight = 41
+        ClientWidth = 857
+        TabOrder = 0
+        object cbShowDebugLogs: TCheckBox
+          Left = 15
+          Height = 19
+          Top = 10
+          Width = 102
+          Caption = 'Show Debug Logs'
+          TabOrder = 0
+        end
+      end
+      object memoLogs: TMemo
+        Left = 0
+        Height = 399
+        Top = 41
+        Width = 857
+        Align = alClient
+        ScrollBars = ssBoth
+        TabOrder = 1
+        WordWrap = False
+      end
+    end
+    object tsNodeStats: TTabSheet
+      Caption = 'Node Stats'
+      ClientHeight = 440
+      ClientWidth = 857
+      ImageIndex = 3
+      object Label3: TLabel
+        Left = 15
+        Height = 13
+        Top = 15
+        Width = 177
+        Caption = 'Active Connections:'
+        ParentColor = False
+      end
+      object Label6: TLabel
+        Left = 15
+        Height = 13
+        Top = 291
+        Width = 198
+        Anchors = [akLeft, akRight, akBottom]
+        Caption = 'Known Node Servers:'
+        ParentColor = False
+      end
+      object Label7: TLabel
+        Left = 15
+        Height = 13
+        Top = 187
+        Width = 99
+        Anchors = [akLeft, akRight, akBottom]
+        Caption = 'Blacklisted Nodes'
+        ParentColor = False
+      end
+      object memoNetConnections: TMemo
+        Left = 15
+        Height = 145
+        Top = 34
+        Width = 830
+        Anchors = [akTop, akLeft, akRight, akBottom]
+        ReadOnly = True
+        ScrollBars = ssBoth
+        TabOrder = 0
+        WordWrap = False
+      end
+      object memoNetServers: TMemo
+        Left = 15
+        Height = 116
+        Top = 310
+        Width = 830
+        Anchors = [akLeft, akRight, akBottom]
+        ReadOnly = True
+        ScrollBars = ssBoth
+        TabOrder = 1
+        WordWrap = False
+      end
+      object memoNetBlackLists: TMemo
+        Left = 16
+        Height = 79
+        Top = 206
+        Width = 829
+        Anchors = [akLeft, akRight, akBottom]
+        ReadOnly = True
+        ScrollBars = ssBoth
+        TabOrder = 2
+        WordWrap = False
+      end
+    end
+    object tsMessages: TTabSheet
+      Caption = 'Messages'
+      ClientHeight = 440
+      ClientWidth = 857
+      ImageIndex = 6
+      object Label11: TLabel
+        Left = 15
+        Height = 13
+        Top = 151
+        Width = 51
+        Caption = 'Messages:'
+        ParentColor = False
+      end
+      object Label12: TLabel
+        Left = 315
+        Height = 13
+        Top = 11
+        Width = 85
+        Caption = 'Message to send:'
+        ParentColor = False
+      end
+      object Label13: TLabel
+        Left = 15
+        Height = 13
+        Top = 11
+        Width = 107
+        Caption = 'Available Connections:'
+        ParentColor = False
+      end
+      object Label14: TLabel
+        Left = 410
+        Height = 13
+        Top = 11
+        Width = 361
+        Caption = '(Messages will be encrypted, so only dest connection will be able to read it)'
+        Font.Color = clGrayText
+        Font.Height = -11
+        Font.Name = 'Tahoma'
+        ParentColor = False
+        ParentFont = False
+      end
+      object lbNetConnections: TListBox
+        Left = 15
+        Height = 96
+        Top = 30
+        Width = 275
+        ItemHeight = 0
+        MultiSelect = True
+        ScrollWidth = 273
+        TabOrder = 0
+      end
+      object bbSendAMessage: TButton
+        Left = 315
+        Height = 25
+        Top = 101
+        Width = 525
+        Caption = 'Send a Message'
+        OnClick = bbSendAMessageClick
+        TabOrder = 1
+      end
+      object memoMessages: TMemo
+        Left = 15
+        Height = 249
+        Top = 170
+        Width = 821
+        Anchors = [akTop, akLeft, akRight, akBottom]
+        Font.Color = clWindowText
+        Font.Height = -16
+        Font.Name = 'Tahoma'
+        Lines.Strings = (
+          'dsfa ñldsaf ñlk dasf'
+          'dsfkldañfs ñl'
+        )
+        ParentFont = False
+        ReadOnly = True
+        ScrollBars = ssBoth
+        TabOrder = 2
+      end
+      object memoMessageToSend: TMemo
+        Left = 315
+        Height = 61
+        Top = 30
+        Width = 525
+        Lines.Strings = (
+          'memoMessageToSend'
+        )
+        TabOrder = 3
+        WantReturns = False
+      end
+    end
+  end
+  object TrayIcon: TTrayIcon
+    Visible = True
+    OnDblClick = TrayIconDblClick
+    left = 45
+    top = 20
+  end
+  object TimerUpdateStatus: TTimer
+    OnTimer = TimerUpdateStatusTimer
+    left = 25
+    top = 45
+  end
+  object MainMenu: TMainMenu
+    left = 165
+    top = 160
+    object miProject: TMenuItem
+      Caption = 'Project'
+      object miPrivatekeys: TMenuItem
+        Caption = 'Private Keys'
+        ShortCut = 16464
+        OnClick = miPrivatekeysClick
+      end
+      object miN1: TMenuItem
+        Caption = '-'
+      end
+      object miOptions: TMenuItem
+        Caption = 'Options'
+        ShortCut = 16463
+        OnClick = miOptionsClick
+      end
+      object IPnodes1: TMenuItem
+        Caption = 'Available Node List'
+        OnClick = IPnodes1Click
+      end
+      object N1: TMenuItem
+        Caption = '-'
+      end
+      object MiClose: TMenuItem
+        Caption = 'Close'
+        OnClick = MiCloseClick
+      end
+    end
+    object MiOperations: TMenuItem
+      Caption = 'Operations'
+      object miNewOperation: TMenuItem
+        Caption = 'New single Operation'
+        ShortCut = 120
+        OnClick = miNewOperationClick
+      end
+      object MiFindOperationbyOpHash: TMenuItem
+        Caption = 'Find Operation by OpHash'
+        ShortCut = 116
+        OnClick = MiFindOperationbyOpHashClick
+      end
+      object MiDecodePayload: TMenuItem
+        Caption = 'Decode Payload'
+        ShortCut = 113
+        OnClick = MiDecodePayloadClick
+      end
+      object MiFindaccount: TMenuItem
+        Caption = 'Find account'
+        ShortCut = 16454
+        OnClick = MiFindaccountClick
+      end
+      object MiAccountInformation: TMenuItem
+        Caption = 'Account Information'
+        ShortCut = 112
+        OnClick = MiAccountInformationClick
+      end
+      object N2: TMenuItem
+        Caption = '-'
+      end
+      object MiAddaccounttoSelected: TMenuItem
+        Caption = 'Add account to selected'
+        ShortCut = 117
+        OnClick = MiAddaccounttoSelectedClick
+      end
+      object MiRemoveaccountfromselected: TMenuItem
+        Caption = 'Remove account from selected'
+        ShortCut = 118
+        OnClick = MiRemoveaccountfromselectedClick
+      end
+      object MiMultiaccountoperation: TMenuItem
+        Caption = 'Multi account operation'
+        OnClick = MiMultiaccountoperationClick
+      end
+      object N3: TMenuItem
+        Caption = '-'
+      end
+      object MiFindpreviousaccountwithhighbalance: TMenuItem
+        Caption = 'Find previous account with high balance'
+        ShortCut = 16498
+        OnClick = MiFindpreviousaccountwithhighbalanceClick
+      end
+      object MiFindnextaccountwithhighbalance: TMenuItem
+        Caption = 'Find next account with high balance'
+        ShortCut = 114
+        OnClick = MiFindnextaccountwithhighbalanceClick
+      end
+    end
+    object miAbout: TMenuItem
+      Caption = 'About'
+      object miAboutPascalCoin: TMenuItem
+        Caption = 'About Pascal Coin...'
+        OnClick = miAboutPascalCoinClick
+      end
+    end
+  end
+  object ImageListIcons: TImageList
+    Height = 48
+    left = 105
+    top = 180
+    Bitmap = {
+      4C69020000001000000030000000000000000000000000000000000000000000
+      00000000000016161616010101010C0C0C0C7171717100000000000000000000
+      0000000000000000000000000000000000000000000000000000000000002324
+      252266B3FF0066B3FF0066B3FF0066B3FF0066B3FF00586D7F40000000000000
+      0000000000000000000000000000000000000000000000000000000000002324
+      252266B3FF0066B3FF0066B3FF0066B3FF0066B3FF00586D7F40000000000000
+      00000000000000000000000000000000000000000000000000005252525266B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF00252627240000
+      000000000000000000000000000000000000000000007878787866B3FF0066B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0033597F0066B3FF001D1E
+      1F1D00000000000000000000000000000000000000007878787866B3FF0066B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0033597F0066B3FF001D1E
+      1F1D00000000000000000000000000000000000000002B2F302966B3FF0066B3
+      FF006CB6FF006EB7FF006AB5FF0066B3FF0066B3FF00060B0F0066B3FF0068AC
+      F00E000000000000000000000000000000007474747466B3FF0067B4FF0077BB
+      FF0080C0FF0083C1FF007DBFFF00558BBF0066B3FF002C4E6F0066B3FF0066B3
+      FF003234382E0000000000000000000000007474747466B3FF0067B4FF0077BB
+      FF0080C0FF0083C1FF007DBFFF00558BBF0066B3FF002C4E6F0066B3FF0066B3
+      FF003234382E0000000000000000000000002B2F302966B3FF0070B8FF0080C0
+      FF007AACDF0000000000000000000000000000000000000000002C4E6F0066B3
+      FF0068ACF00E00000000000000000000000066B3FF0068B4FF007FC0FF0092C9
+      FF004F677F000000000000000000000000000000000000000000000000001321
+      2F0066B3FF006E6E6E6E000000000000000066B3FF0068B4FF007FC0FF0092C9
+      FF004F677F000000000000000000000000000000000000000000000000001321
+      2F0066B3FF006E6E6E6E000000000000000066B3FF006EB7FF0087C3FF009BCD
+      FF0034424F000000000000000000000000000000000000000000000000000000
+      000066B3FF002B2C2F29000000002122232066B3FF0079BDFF0094CAFF00ACD6
+      FF000000000000000000B7DBFF00000000006894BF00375B7F00000000000000
+      000066B3FF0066B3FF00000000002122232066B3FF0079BDFF0094CAFF00ACD6
+      FF000000000000000000B7DBFF00000000006894BF00375B7F00000000000000
+      000066B3FF0066B3FF00000000005E768F3F66B3FF007EBFFF009ACDFF00B3D9
+      FF000000000000000000C0E0FF0000000000597D9F0073BAFF00000000000000
+      000066B3FF0066B3FF000000000066B3FF0067B3FF0085C2FF00A3D1FF00BFDF
+      FF000000000000000000CFE7FF000000000025323F007ABDFF00000000000000
+      000066B3FF0066B3FF000000000066B3FF0067B3FF0085C2FF00A3D1FF00BFDF
+      FF000000000000000000CFE7FF000000000025323F007ABDFF00000000000000
+      000066B3FF0066B3FF000000000066B3FF0068B4FF0087C3FF00A6D3FF00C3E1
+      FF000000000000000000D5EAFF000B0D0F0013191F007CBEFF00000000000000
+      000066B3FF0066B3FF000000000066B3FF006AB5FF0089C4FF00A8D4FF00C7E3
+      FF000000000000000000DAEDFF0051606F00000000007DBFFF00000000000000
+      000066B3FF0066B3FF002323232366B3FF006AB5FF0089C4FF00A8D4FF00C7E3
+      FF000000000000000000DAEDFF0051606F00000000007DBFFF00000000000000
+      000066B3FF0066B3FF002323232366B3FF0069B4FF0088C4FF00A7D3FF00C6E3
+      FF000000000000000000D9ECFF00697C8F0000000000659ACF00000000000000
+      000066B3FF0066B3FF000D0D0D0D66B3FF0067B3FF0085C2FF00A3D1FF00BFDF
+      FF00000000000000000033393F002A323B000000000000000000000000002037
+      4F0066B3FF0066B3FF000202020266B3FF0067B3FF0085C2FF00A3D1FF00BFDF
+      FF00000000000000000033393F002A323B000000000000000000000000002037
+      4F0066B3FF0066B3FF000202020266B3FF0066B3FF0082C1FF009ECFFF00B9DC
+      FF000000000000000000000000000000000000000000000000000000000060A8
+      EF0066B3FF0066B3FF000505050566B3FF0066B3FF0079BDFF0094CAFF0096BA
+      DF0000000000000000000000000000000000000000000000000039648F0066B3
+      FF0066B3FF0066B3FF001111111166B3FF0066B3FF0079BDFF0094CAFF0096BA
+      DF0000000000000000000000000000000000000000000000000039648F0066B3
+      FF0066B3FF0066B3FF001111111166B3FF0066B3FF0074BAFF008EC7FF007A9D
+      BF0000000000000000000000000000000000000000000D161F0066B3FF0066B3
+      FF0066B3FF0066B3FF002323232366B3FF0066B3FF0068B4FF007FC0FF002D3E
+      4F000000000065829F0026333F008CC6FF0070B0EF0066B3FF0066B3FF0066B3
+      FF0066B3FF0066B3FF000000000066B3FF0066B3FF0068B4FF007FC0FF002D3E
+      4F000000000065829F0026333F008CC6FF0070B0EF0066B3FF0066B3FF0066B3
+      FF0066B3FF0066B3FF000000000066B3FF0066B3FF0066B3FF0077BCFF001924
+      2F00000000007198BF0012181F0083C2FF0070B8FF0066B3FF0066B3FF0066B3
+      FF0066B3FF0066B3FF0000000000586D7F4066B3FF0066B3FF0067B4FF000000
+      00000000000083C1FF003E5F7F005C96CF0066B3FF0066B3FF0066B3FF0066B3
+      FF0066B3FF0066B3FF0000000000586D7F4066B3FF0066B3FF0067B4FF000000
+      00000000000083C1FF003E5F7F005C96CF0066B3FF0066B3FF0066B3FF0066B3
+      FF0066B3FF0066B3FF00000000002022221F66B3FF0066B3FF0066B3FF000000
+      00000000000079BCFF0073BAFF0068B4FF0066B3FF0066B3FF0066B3FF0066B3
+      FF0066B3FF0066B3FF00000000000000000066B3FF0066B3FF0066B3FF000000
+      00000000000066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3
+      FF0066B3FF002022221F000000000000000066B3FF0066B3FF0066B3FF000000
+      00000000000066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3
+      FF0066B3FF002022221F000000000000000066B3FF0066B3FF0066B3FF000000
+      00000000000066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3
+      FF0066B3FF006868686800000000000000001414151466B3FF0066B3FF000000
+      00000000000066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3
+      FF0068ACF00E0000000000000000000000001414151466B3FF0066B3FF000000
+      00000000000066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3
+      FF0068ACF00E0000000000000000000000007F7F7F7F66B3FF0066B3FF000000
+      00000000000066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3
+      FF002B2F3029000000000000000000000000000000002B2F312966B3FF0066B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0068AC
+      F00E00000000000000000000000000000000000000002B2F312966B3FF0066B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0068AC
+      F00E00000000000000000000000000000000000000000000000066B3FF0066B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF002C30
+      322A0000000000000000000000000000000000000000000000006D6D6D6D66B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF00292A2D270000
+      00000000000000000000000000000000000000000000000000006D6D6D6D66B3
+      FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF0066B3FF00292A2D270000
+      0000000000000000000000000000000000000000000000000000000000002A2B
+      2E2866B3FF0066B3FF0066B3FF0066B3FF0066B3FF005E768F3F000000000000
+      0000000000000000000000000000000000000000000000000000000000000000
+      000000000000292929290B0B0B0B111111110000000000000000000000000000
+      0000000000000000000000000000000000000000000000000000000000000000
+      000000000000292929290B0B0B0B111111110000000000000000000000000000
+      0000000000000000000000000000000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000
+      00FF000000FF000000FF000000FF
+    }
+  end
+end

+ 1934 - 0
Units/Forms/UFRMWallet.pas

@@ -0,0 +1,1934 @@
+unit UFRMWallet;
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{ Copyright (c) 2016 by Albert Molina
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  This unit is a part of Pascal Coin, a P2P crypto currency without need of
+  historical operations.
+
+  If you like it, consider a donation using BitCoin:
+  16K3HCZRhFUtM8GdWRcfKeaa6KsuyxZaYk
+
+  }
+
+interface
+
+{$I ./../PascalCoin/config.inc}
+
+uses
+{$IFnDEF FPC}
+  pngimage, Windows, AppEvnts, ShlObj,
+{$ELSE}
+  LCLIntf, LCLType, LMessages,
+{$ENDIF}
+  Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
+  Dialogs, ExtCtrls, ComCtrls, UWalletKeys, StdCtrls,
+  ULog, Grids, UAppParams,
+  UBlockChain, UNode, UGridUtils, UAccounts, Menus, ImgList,
+  UNetProtocol, UCrypto, Buttons, UPoolMining, URPC, UFRMAccountSelect;
+
+Const
+  CM_PC_WalletKeysChanged = WM_USER + 1;
+  CM_PC_NetConnectionUpdated = WM_USER + 2;
+
+type
+  TMinerPrivateKey = (mpk_NewEachTime, mpk_Random, mpk_Selected);
+
+  { TFRMWallet }
+
+  TFRMWallet = class(TForm)
+    pnlTop: TPanel;
+    Image1: TImage;
+    sbSearchAccount: TSpeedButton;
+    StatusBar: TStatusBar;
+    PageControl: TPageControl;
+    tsMyAccounts: TTabSheet;
+    tsOperations: TTabSheet;
+    TrayIcon: TTrayIcon;
+    TimerUpdateStatus: TTimer;
+    tsLogs: TTabSheet;
+    pnlTopLogs: TPanel;
+    cbShowDebugLogs: TCheckBox;
+    memoLogs: TMemo;
+    pnlMyAccountsTop: TPanel;
+    dgAccounts: TDrawGrid;
+    cbMyPrivateKeys: TComboBox;
+    Splitter1: TSplitter;
+    MainMenu: TMainMenu;
+    miProject: TMenuItem;
+    miOptions: TMenuItem;
+    miPrivatekeys: TMenuItem;
+    miN1: TMenuItem;
+    miAbout: TMenuItem;
+    miAboutPascalCoin: TMenuItem;
+    miNewOperation: TMenuItem;
+    Panel1: TPanel;
+    Label2: TLabel;
+    ebFilterOperationsStartBlock: TEdit;
+    ebFilterOperationsEndBlock: TEdit;
+    tsNodeStats: TTabSheet;
+    memoNetConnections: TMemo;
+    memoNetServers: TMemo;
+    memoNetBlackLists: TMemo;
+    Label3: TLabel;
+    Label6: TLabel;
+    Label7: TLabel;
+    lblCurrentBlockCaption: TLabel;
+    lblCurrentBlock: TLabel;
+    lblCurrentBlockTimeCaption: TLabel;
+    lblCurrentBlockTime: TLabel;
+    lblOperationsPendingCaption: TLabel;
+    lblOperationsPending: TLabel;
+    lblMiningStatusCaption: TLabel;
+    lblMinersClients: TLabel;
+    lblCurrentDifficultyCaption: TLabel;
+    lblCurrentDifficulty: TLabel;
+    lblTimeAverage: TLabel;
+    Label4: TLabel;
+    tsBlockChain: TTabSheet;
+    Panel2: TPanel;
+    Label9: TLabel;
+    ebBlockChainBlockStart: TEdit;
+    ebBlockChainBlockEnd: TEdit;
+    Label8: TLabel;
+    lblNodeStatus: TLabel;
+    tsPendingOperations: TTabSheet;
+    dgPendingOperations: TDrawGrid;
+    pnlPendingOperations: TPanel;
+    Label10: TLabel;
+    cbExploreMyAccounts: TCheckBox;
+    N1: TMenuItem;
+    MiClose: TMenuItem;
+    MiDecodePayload: TMenuItem;
+    ImageListIcons: TImageList;
+    ApplicationEvents: {$IFDEF FPC}TApplicationProperties{$ELSE}TApplicationEvents{$ENDIF};
+    Label5: TLabel;
+    lblCurrentAccounts: TLabel;
+    lblTimeAverageAux: TLabel;
+    tsMessages: TTabSheet;
+    lbNetConnections: TListBox;
+    bbSendAMessage: TButton;
+    Label11: TLabel;
+    memoMessages: TMemo;
+    memoMessageToSend: TMemo;
+    Label12: TLabel;
+    Label13: TLabel;
+    Label14: TLabel;
+    Label16: TLabel;
+    lblBlocksFound: TLabel;
+    pnlAccounts: TPanel;
+    pnlAccountsInfo: TPanel;
+    Label17: TLabel;
+    Label19: TLabel;
+    lblAccountsCount: TLabel;
+    lblAccountsBalance: TLabel;
+    lblReceivedMessages: TLabel;
+    lblBuild: TLabel;
+    ebFindAccountNumber: TEdit;
+    Label18: TLabel;
+    IPnodes1: TMenuItem;
+    bbChangeKeyName: TBitBtn;
+    pcAccountsOptions: TPageControl;
+    tsAccountOperations: TTabSheet;
+    dgAccountOperations: TDrawGrid;
+    tsMultiSelectAccounts: TTabSheet;
+    dgSelectedAccounts: TDrawGrid;
+    pnlSelectedAccountsTop: TPanel;
+    pnlSelectedAccountsBottom: TPanel;
+    pnlSelectedAccountsLeft: TPanel;
+    sbSelectedAccountsAdd: TSpeedButton;
+    sbSelectedAccountsAddAll: TSpeedButton;
+    sbSelectedAccountsDel: TSpeedButton;
+    sbSelectedAccountsDelAll: TSpeedButton;
+    Label20: TLabel;
+    lblSelectedAccountsCount: TLabel;
+    Label22: TLabel;
+    lblSelectedAccountsBalance: TLabel;
+    bbSelectedAccountsOperation: TBitBtn;
+    Label15: TLabel;
+    MiOperations: TMenuItem;
+    MiAddaccounttoSelected: TMenuItem;
+    MiRemoveaccountfromselected: TMenuItem;
+    N2: TMenuItem;
+    MiMultiaccountoperation: TMenuItem;
+    N3: TMenuItem;
+    MiFindnextaccountwithhighbalance: TMenuItem;
+    MiFindpreviousaccountwithhighbalance: TMenuItem;
+    MiFindaccount: TMenuItem;
+    cbFilterAccounts: TCheckBox;
+    ebFilterAccountByBalanceMin: TEdit;
+    ebFilterAccountByBalanceMax: TEdit;
+    bbAccountsRefresh: TBitBtn;
+    dgBlockChainExplorer: TDrawGrid;
+    dgOperationsExplorer: TDrawGrid;
+    MiFindOperationbyOpHash: TMenuItem;
+    MiAccountInformation: TMenuItem;
+    procedure FormCreate(Sender: TObject);
+    procedure FormDestroy(Sender: TObject);
+    procedure sbSearchAccountClick(Sender: TObject);
+    procedure TimerUpdateStatusTimer(Sender: TObject);
+    procedure cbMyPrivateKeysChange(Sender: TObject);
+    procedure dgAccountsClick(Sender: TObject);
+    procedure miOptionsClick(Sender: TObject);
+    procedure miAboutPascalCoinClick(Sender: TObject);
+    procedure miNewOperationClick(Sender: TObject);
+    procedure miPrivatekeysClick(Sender: TObject);
+    procedure dgAccountsColumnMoved(Sender: TObject; FromIndex,
+      ToIndex: Integer);
+    procedure dgAccountsFixedCellClick(Sender: TObject; ACol, ARow: Integer);
+    procedure PageControlChange(Sender: TObject);
+    procedure ebFilterOperationsAccountExit(Sender: TObject);
+    procedure ebFilterOperationsAccountKeyPress(Sender: TObject; var Key: Char);
+    procedure ebBlockChainBlockStartExit(Sender: TObject);
+    procedure ebBlockChainBlockStartKeyPress(Sender: TObject; var Key: Char);
+    procedure cbExploreMyAccountsClick(Sender: TObject);
+    procedure MiCloseClick(Sender: TObject);
+    procedure MiDecodePayloadClick(Sender: TObject);
+    procedure TrayIconDblClick(Sender: TObject);
+    procedure ApplicationEventsMinimize(Sender: TObject);
+    procedure bbSendAMessageClick(Sender: TObject);
+    procedure lblReceivedMessagesClick(Sender: TObject);
+    procedure ebFindAccountNumberChange(Sender: TObject);
+    procedure ebFindAccountNumberExit(Sender: TObject);
+    procedure IPnodes1Click(Sender: TObject);
+    procedure bbChangeKeyNameClick(Sender: TObject);
+    procedure sbSelectedAccountsAddClick(Sender: TObject);
+    procedure sbSelectedAccountsAddAllClick(Sender: TObject);
+    procedure sbSelectedAccountsDelClick(Sender: TObject);
+    procedure sbSelectedAccountsDelAllClick(Sender: TObject);
+    procedure bbSelectedAccountsOperationClick(Sender: TObject);
+    procedure MiAddaccounttoSelectedClick(Sender: TObject);
+    procedure MiRemoveaccountfromselectedClick(Sender: TObject);
+    procedure MiMultiaccountoperationClick(Sender: TObject);
+    procedure MiFindnextaccountwithhighbalanceClick(Sender: TObject);
+    procedure MiFindpreviousaccountwithhighbalanceClick(Sender: TObject);
+    procedure MiFindaccountClick(Sender: TObject);
+    procedure bbAccountsRefreshClick(Sender: TObject);
+    procedure ebFilterAccountByBalanceMinExit(Sender: TObject);
+    procedure ebFilterAccountByBalanceMinKeyPress(Sender: TObject;
+      var Key: Char);
+    procedure cbFilterAccountsClick(Sender: TObject);
+    procedure MiFindOperationbyOpHashClick(Sender: TObject);
+    procedure MiAccountInformationClick(Sender: TObject);
+  private
+    FBackgroundPanel : TPanel;
+    FMinersBlocksFound: Integer;
+    procedure SetMinersBlocksFound(const Value: Integer);
+    Procedure CheckIsReady;
+    Procedure FinishedLoadingApp;
+    Procedure FillAccountInformation(Const Strings : TStrings; Const AccountNumber : Cardinal);
+    Procedure FillOperationInformation(Const Strings : TStrings; Const OperationResume : TOperationResume);
+  protected
+    { Private declarations }
+    FNode : TNode;
+    FIsActivated : Boolean;
+    FWalletKeys : TWalletKeysExt;
+    FLog : TLog;
+    FAppParams : TAppParams;
+    FNodeNotifyEvents : TNodeNotifyEvents;
+    FAccountsGrid : TAccountsGrid;
+    FSelectedAccountsGrid : TAccountsGrid;
+    FOperationsAccountGrid : TOperationsGrid;
+    FPendingOperationsGrid : TOperationsGrid;
+    FOrderedAccountsKeyList : TOrderedAccountKeysList;
+    FOperationsExplorerGrid : TOperationsGrid;
+    FBlockChainGrid : TBlockChainGrid;
+    FMinerPrivateKeyType : TMinerPrivateKey;
+    FUpdating : Boolean;
+    FMessagesUnreadCount : Integer;
+    FMinAccountBalance : Int64;
+    FMaxAccountBalance : Int64;
+    FPoolMiningServer : TPoolMiningServer;
+    FRPCServer : TRPCServer;
+    FMustProcessWalletChanged : Boolean;
+    FMustProcessNetConnectionUpdated : Boolean;
+    Procedure OnNewAccount(Sender : TObject);
+    Procedure OnReceivedHelloMessage(Sender : TObject);
+    Procedure OnNetStatisticsChanged(Sender : TObject);
+    procedure OnNewLog(logtype : TLogType; Time : TDateTime; ThreadID : Cardinal; Const sender, logtext : AnsiString);
+    procedure OnWalletChanged(Sender : TObject);
+    procedure OnNetConnectionsUpdated(Sender : TObject);
+    procedure OnNetNodeServersUpdated(Sender : TObject);
+    procedure OnNetBlackListUpdated(Sender : TObject);
+    Procedure OnNodeMessageEvent(NetConnection : TNetConnection; MessageData : TRawBytes);
+    Procedure OnSelectedAccountsGridUpdated(Sender : TObject);
+    Procedure OnMiningServerNewBlockFound(Sender : TObject);
+    Procedure UpdateConnectionStatus;
+    Procedure UpdateAccounts(RefreshData : Boolean);
+    Procedure UpdateBlockChainState;
+    Procedure UpdatePrivateKeys;
+    Procedure UpdateOperations;
+    Procedure LoadAppParams;
+    Procedure SaveAppParams;
+    Procedure UpdateConfigChanged;
+    Procedure UpdateNodeStatus;
+    Procedure UpdateAvailableConnections;
+    procedure Activate; override;
+    Function ForceMining : Boolean; virtual;
+    Function GetAccountKeyForMiner : TAccountKey;
+    Procedure DoUpdateAccounts;
+    Function DoUpdateAccountsFilter : Boolean;
+    procedure CM_WalletChanged(var Msg: TMessage); message CM_PC_WalletKeysChanged;
+    procedure CM_NetConnectionUpdated(var Msg: TMessage); message CM_PC_NetConnectionUpdated;
+  public
+    { Public declarations }
+    Property WalletKeys : TWalletKeysExt read FWalletKeys;
+    Property MinersBlocksFound : Integer read FMinersBlocksFound write SetMinersBlocksFound;
+  end;
+
+var
+  FRMWallet: TFRMWallet;
+
+implementation
+
+{$IFnDEF FPC}
+  {$R *.dfm}
+{$ELSE}
+  {$R *.lfm}
+{$ENDIF}
+
+Uses UFolderHelper, UOpenSSL, UOpenSSLdef, UConst, UTime, UFileStorage,
+  UThread, UOpTransaction, UECIES, UFRMPascalCoinWalletConfig,
+  UFRMAbout, UFRMOperation, UFRMWalletKeys, UFRMPayloadDecoder, UFRMNodesIp, UFRMMemoText;
+
+Type
+  TThreadActivate = Class(TPCThread)
+  protected
+    procedure BCExecute; override;
+  End;
+
+{ TThreadActivate }
+
+procedure TThreadActivate.BCExecute;
+begin
+  // Read Operations saved from disk
+  TNode.Node.InitSafeboxAndOperations; // New Build 2.1.4 to load pending operations buffer
+  TNode.Node.AutoDiscoverNodes(CT_Discover_IPs);
+  TNode.Node.NetServer.Active := true;
+  Synchronize( FRMWallet.DoUpdateAccounts );
+  Synchronize( FRMWallet.FinishedLoadingApp );
+end;
+
+{ TFRMWallet }
+
+procedure TFRMWallet.Activate;
+Var ips : AnsiString;
+  nsarr : TNodeServerAddressArray;
+begin
+  inherited;
+  if FIsActivated then exit;
+  FIsActivated := true;
+  try
+    // Check OpenSSL dll
+    if Not LoadSSLCrypt then raise Exception.Create('Cannot load '+SSL_C_LIB+#10+'To use this software make sure this file is available on you system or reinstall the application');
+    TCrypto.InitCrypto;
+    // Read Wallet
+    Try
+      FWalletKeys.WalletFileName := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'WalletKeys.dat';
+    Except
+      On E:Exception do begin
+        E.Message := 'Cannot open your wallet... Perhaps another instance of Pascal Coin is active!'+#10+#10+E.Message;
+        Raise;
+      end;
+    End;
+    ips := FAppParams.ParamByName[CT_PARAM_TryToConnectOnlyWithThisFixedServers].GetAsString('');
+    TNode.DecodeIpStringToNodeServerAddressArray(ips,nsarr);
+    TNetData.NetData.DiscoverFixedServersOnly(nsarr);
+    setlength(nsarr,0);
+    // Creating Node:
+    FNode := TNode.Node;
+    FNode.NetServer.Port := FAppParams.ParamByName[CT_PARAM_InternetServerPort].GetAsInteger(CT_NetServer_Port);
+    FNode.PeerCache := FAppParams.ParamByName[CT_PARAM_PeerCache].GetAsString('')+';'+CT_Discover_IPs;
+    // Create RPC server
+    FRPCServer := TRPCServer.Create;
+    FRPCServer.WalletKeys := WalletKeys;
+    FRPCServer.Active := FAppParams.ParamByName[CT_PARAM_JSONRPCEnabled].GetAsBoolean(false);
+    FRPCServer.ValidIPs := FAppParams.ParamByName[CT_PARAM_JSONRPCAllowedIPs].GetAsString('127.0.0.1');
+    WalletKeys.SafeBox := FNode.Bank.SafeBox;
+    // Check Database
+    FNode.Bank.StorageClass := TFileStorage;
+    TFileStorage(FNode.Bank.Storage).DatabaseFolder := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'Data';
+    TFileStorage(FNode.Bank.Storage).Initialize;
+    // Init Grid
+    //FAccountsGrid.Node := FNode;
+    FSelectedAccountsGrid.Node := FNode;
+    FWalletKeys.OnChanged := OnWalletChanged;
+    FAccountsGrid.Node := FNode;
+    FOperationsAccountGrid.Node := FNode;
+    // Reading database
+    TThreadActivate.Create(false).FreeOnTerminate := true;
+    FNodeNotifyEvents.Node := FNode;
+    // Init
+    TNetData.NetData.OnReceivedHelloMessage := OnReceivedHelloMessage;
+    TNetData.NetData.OnStatisticsChanged := OnNetStatisticsChanged;
+    TNetData.NetData.OnNetConnectionsUpdated := onNetConnectionsUpdated;
+    TNetData.NetData.OnNodeServersUpdated := OnNetNodeServersUpdated;
+    TNetData.NetData.OnBlackListUpdated := OnNetBlackListUpdated;
+    //
+    TimerUpdateStatus.Interval := 1000;
+    TimerUpdateStatus.Enabled := true;
+    UpdateConfigChanged;
+  Except
+    On E:Exception do begin
+      E.Message := 'An error occurred during initialization. Application cannot continue:'+#10+#10+E.Message+#10+#10+'Application will close...';
+      Application.MessageBox(PChar(E.Message),PChar(Application.Title),MB_ICONERROR+MB_OK);
+      Halt;
+    end;
+  end;
+  UpdatePrivateKeys;
+  UpdateAccounts(false);
+  if FAppParams.ParamByName[CT_PARAM_FirstTime].GetAsBoolean(true) then begin
+    FAppParams.ParamByName[CT_PARAM_FirstTime].SetAsBoolean(false);
+    miAboutPascalCoinClick(Nil);
+  end;
+
+end;
+
+procedure TFRMWallet.ApplicationEventsMinimize(Sender: TObject);
+begin
+  {$IFnDEF FPC}
+  Hide();
+  WindowState := wsMinimized;
+  TimerUpdateStatus.Enabled := false;
+  { Show the animated tray icon and also a hint balloon. }
+  TrayIcon.Visible := True;
+  TrayIcon.ShowBalloonHint;
+  {$ENDIF}
+end;
+
+procedure TFRMWallet.bbAccountsRefreshClick(Sender: TObject);
+begin
+  UpdateAccounts(true);
+end;
+
+procedure TFRMWallet.bbChangeKeyNameClick(Sender: TObject);
+var i : Integer;
+  name : String;
+begin
+  if (cbMyPrivateKeys.ItemIndex<0) then  exit;
+  i := PtrInt(cbMyPrivateKeys.Items.Objects[cbMyPrivateKeys.ItemIndex]);
+  if (i<0) Or (i>=FWalletKeys.Count) then raise Exception.Create('Must select a Key');
+  name := FWalletKeys.Key[i].Name;
+  if InputQuery('Change Key name','Input new name',name) then begin
+    FWalletKeys.SetName(i,name);
+  end;
+  UpdatePrivateKeys;
+end;
+
+procedure TFRMWallet.bbSelectedAccountsOperationClick(Sender: TObject);
+var l : TOrderedCardinalList;
+begin
+  CheckIsReady;
+  if FSelectedAccountsGrid.AccountsCount<=0 then raise Exception.Create('Must select at least 1 account');
+  With TFRMOperation.Create(Self) do
+  Try
+    l := FSelectedAccountsGrid.LockAccountsList;
+    try
+      SenderAccounts.CopyFrom(l);
+    finally
+      FSelectedAccountsGrid.UnlockAccountsList;
+    end;
+    DefaultFee := FAppParams.ParamByName[CT_PARAM_DefaultFee].GetAsInt64(0);
+    WalletKeys := FWalletKeys;
+    ShowModal;
+  Finally
+    Free;
+  End;
+end;
+
+procedure TFRMWallet.bbSendAMessageClick(Sender: TObject);
+Var basem,m : String;
+  them, errors : AnsiString;
+  i,n : Integer;
+  nc : TNetConnection;
+begin
+  CheckIsReady;
+  if (lbNetConnections.SelCount<=0) Or (lbNetConnections.ItemIndex<0) then raise Exception.Create('Select at least one connection');
+  if lbNetConnections.SelCount<=0 then n := 1
+  else n := lbNetConnections.SelCount;
+
+  basem := memoMessageToSend.Lines.Text;
+  m := '';
+  // Clear non valid characters:
+  for i := 1 to length(basem) do begin
+    if basem[i] in [#32..#127] then m := m + basem[i]
+    else m:=m+'.';
+  end;
+
+  if trim(m)='' then raise Exception.Create('No message');
+
+  if Application.MessageBox(PChaR('Send this message to '+inttostr(n)+' nodes?'+#10+
+    'NOTE: Sending unauthorized messages will be considered spam and you will be banned'+#10+
+    #10+
+    'Message: '+#10+
+    m),PChar(Application.Title),MB_ICONQUESTION+MB_YESNO+MB_DEFBUTTON1)<>IdYes then exit;
+  them := m;
+  if n>1 then begin
+    for i := 0 to lbNetConnections.Items.Count - 1 do begin
+      if lbNetConnections.Selected[i] then begin
+        nc := TNetConnection(lbNetconnections.Items.Objects[i]);
+        if TNetData.NetData.ConnectionExistsAndActive(nc) then begin
+          FNode.SendNodeMessage(nc,m,errors);
+          memoMessages.Lines.Add(DateTimeToStr(now)+' Sent to '+nc.ClientRemoteAddr+' > '+m);
+        end;
+      end;
+    end;
+  end else begin
+    nc := TNetConnection(lbNetconnections.Items.Objects[lbNetconnections.ItemIndex]);
+    if TNetData.NetData.ConnectionExistsAndActive(nc) then begin
+      FNode.SendNodeMessage(nc,m,errors);
+      memoMessages.Lines.Add(DateTimeToStr(now)+' Sent to '+nc.ClientRemoteAddr+' > '+m);
+    end;
+  end;
+
+  Application.MessageBox(PChaR('Message sent to '+inttostr(n)+' nodes'+#10+
+    'Message: '+#10+m),PChar(Application.Title),MB_ICONINFORMATION+MB_OK);
+end;
+
+procedure TFRMWallet.cbExploreMyAccountsClick(Sender: TObject);
+begin
+  cbMyPrivateKeys.Enabled := cbExploreMyAccounts.Checked;
+  UpdateAccounts(true);
+  UpdateOperations;
+end;
+
+procedure TFRMWallet.cbFilterAccountsClick(Sender: TObject);
+begin
+  If not DoUpdateAccountsFilter then UpdateAccounts(true);
+end;
+
+procedure TFRMWallet.cbMyPrivateKeysChange(Sender: TObject);
+begin
+  UpdateAccounts(true);
+end;
+
+procedure TFRMWallet.CheckIsReady;
+Var isready : AnsiString;
+begin
+  if Not Assigned(FNode) then Abort;
+
+  if Not FNode.IsReady(isready) then begin
+    Raise Exception.Create('You cannot do this operation now:'+#10+#10+isready);
+  end;
+end;
+
+procedure TFRMWallet.CM_NetConnectionUpdated(var Msg: TMessage);
+Const CT_BooleanToString : Array[Boolean] of String = ('False','True');
+Var i : integer;
+ NC : TNetConnection;
+ l : TList;
+ sClientApp, sLastConnTime : String;
+ strings, sNSC, sRS, sDisc : TStrings;
+ hh,nn,ss,ms : Word;
+begin
+  Try
+    if Not TNetData.NetData.NetConnections.TryLockList(100,l) then exit;
+    try
+      strings := memoNetConnections.Lines;
+      sNSC := TStringList.Create;
+      sRS := TStringList.Create;
+      sDisc := TStringList.Create;
+      strings.BeginUpdate;
+      Try
+        for i := 0 to l.Count - 1 do begin
+          NC := l[i];
+          If NC.Client.BytesReceived>0 then begin
+            sClientApp := '['+IntToStr(NC.NetProtocolVersion.protocol_version)+'-'+IntToStr(NC.NetProtocolVersion.protocol_available)+'] '+NC.ClientAppVersion;
+          end else begin
+            sClientApp := '(no data)';
+          end;
+
+          if NC.Connected then begin
+            if NC.Client.LastCommunicationTime>1000 then begin
+              DecodeTime(now - NC.Client.LastCommunicationTime,hh,nn,ss,ms);
+              if (hh=0) and (nn=0) And (ss<10) then begin
+                sLastConnTime := ' - Last comunication <10 sec.';
+              end else begin
+                sLastConnTime := Format(' - Last comunication %.2dm%.2ds',[(hh*60)+nn,ss]);
+              end;
+            end else begin
+              sLastConnTime := '';
+            end;
+            if NC is TNetServerClient then begin
+              sNSC.Add(Format('Client: IP:%s Block:%d Sent/Received:%d/%d Bytes - %s - Time offset %d - Active since %s %s',
+                [NC.ClientRemoteAddr,NC.RemoteOperationBlock.block,NC.Client.BytesSent,NC.Client.BytesReceived,sClientApp,NC.TimestampDiff,DateTimeElapsedTime(NC.CreatedTime),sLastConnTime]));
+            end else begin
+              if NC.IsMyselfServer then sNSC.Add(Format('MySelf IP:%s Sent/Received:%d/%d Bytes - %s - Time offset %d - Active since %s %s',
+                [NC.ClientRemoteAddr,NC.Client.BytesSent,NC.Client.BytesReceived,sClientApp,NC.TimestampDiff,DateTimeElapsedTime(NC.CreatedTime),sLastConnTime]))
+              else begin
+                sRS.Add(Format('Remote Server: IP:%s Block:%d Sent/Received:%d/%d Bytes - %s - Time offset %d - Active since %s %s',
+                [NC.ClientRemoteAddr,NC.RemoteOperationBlock.block,NC.Client.BytesSent,NC.Client.BytesReceived,sClientApp,NC.TimestampDiff,DateTimeElapsedTime(NC.CreatedTime),sLastConnTime]));
+              end;
+            end;
+          end else begin
+            if NC is TNetServerClient then begin
+              sDisc.Add(Format('Disconnected client: IP:%s - %s',[NC.ClientRemoteAddr,sClientApp]));
+            end else if NC.IsMyselfServer then begin
+              sDisc.Add(Format('Disconnected MySelf IP:%s - %s',[NC.ClientRemoteAddr,sClientApp]));
+            end else begin
+              sDisc.Add(Format('Disconnected Remote Server: IP:%s %s - %s',[NC.ClientRemoteAddr,CT_BooleanToString[NC.Connected],sClientApp]));
+            end;
+          end;
+        end;
+        strings.Clear;
+        strings.Add(Format('Connections Updated %s Clients:%d Servers:%d (valid servers:%d)',[DateTimeToStr(now),sNSC.Count,sRS.Count,TNetData.NetData.NetStatistics.ServersConnectionsWithResponse]));
+        strings.AddStrings(sRS);
+        strings.AddStrings(sNSC);
+        if sDisc.Count>0 then begin
+          strings.Add('');
+          strings.Add('Disconnected connections: '+Inttostr(sDisc.Count));
+          strings.AddStrings(sDisc);
+        end;
+      Finally
+        strings.EndUpdate;
+        sNSC.Free;
+        sRS.Free;
+        sDisc.Free;
+      End;
+      //CheckMining;
+    finally
+      TNetData.NetData.NetConnections.UnlockList;
+    end;
+  Finally
+    FMustProcessNetConnectionUpdated := false;
+  End;
+end;
+
+procedure TFRMWallet.CM_WalletChanged(var Msg: TMessage);
+begin
+  UpdatePrivateKeys;
+  FMustProcessWalletChanged := false;
+end;
+
+procedure TFRMWallet.dgAccountsClick(Sender: TObject);
+begin
+  UpdateOperations;
+end;
+
+procedure TFRMWallet.dgAccountsColumnMoved(Sender: TObject; FromIndex, ToIndex: Integer);
+begin
+  SaveAppParams;
+end;
+
+procedure TFRMWallet.dgAccountsFixedCellClick(Sender: TObject; ACol,
+  ARow: Integer);
+begin
+  SaveAppParams;
+end;
+
+procedure TFRMWallet.DoUpdateAccounts;
+begin
+  UpdateAccounts(true);
+end;
+
+function TFRMWallet.DoUpdateAccountsFilter: Boolean;
+Var m,bmin,bmax:Int64;
+  doupd : Boolean;
+begin
+  if FUpdating then exit;
+  FUpdating := true;
+  Try
+    If Not TAccountComp.TxtToMoney(ebFilterAccountByBalanceMin.Text,bmin) then bmin := 0;
+    If not TAccountComp.TxtToMoney(ebFilterAccountByBalanceMax.Text,bmax) then bmax := CT_MaxWalletAmount;
+    if (bmax<bmin) or (bmax=0) then bmax := CT_MaxWalletAmount;
+    if bmin>bmax then bmin := 0;
+    doupd := (bmin<>FMinAccountBalance) Or (bmax<>FMaxAccountBalance);
+    FMinAccountBalance := bmin;
+    FMaxAccountBalance := bmax;
+    if bmin>0 then
+      ebFilterAccountByBalanceMin.Text:=TAccountComp.FormatMoney(bmin)
+    else ebFilterAccountByBalanceMin.Text := '';
+    if bmax<CT_MaxWalletAmount then
+      ebFilterAccountByBalanceMax.Text := TAccountComp.FormatMoney(bmax)
+    else ebFilterAccountByBalanceMax.Text := '';
+    if cbFilterAccounts.Checked then begin
+      ebFilterAccountByBalanceMin.ParentFont := true;
+      ebFilterAccountByBalanceMax.ParentFont := true;
+    end else begin
+      ebFilterAccountByBalanceMin.font.Color := clDkGray;
+      ebFilterAccountByBalanceMax.font.Color := clDkGray;
+    end;
+  Finally
+    FUpdating := false;
+  End;
+  if doupd then UpdateAccounts(true);
+  Result := doupd;
+end;
+
+procedure TFRMWallet.ebBlockChainBlockStartExit(Sender: TObject);
+var bstart,bend : Int64;
+begin
+  If FUpdating then exit;
+  FUpdating := True;
+  Try
+    bstart := StrToInt64Def(ebBlockChainBlockStart.Text,-1);
+    bend := StrToInt64Def(ebBlockChainBlockEnd.Text,-1);
+    FBlockChainGrid.SetBlocks(bstart,bend);
+    if FBlockChainGrid.BlockStart>=0 then ebBlockChainBlockStart.Text := Inttostr(FBlockChainGrid.BlockStart) else ebBlockChainBlockStart.Text := '';
+    if FBlockChainGrid.BlockEnd>=0 then ebBlockChainBlockEnd.Text := Inttostr(FBlockChainGrid.BlockEnd) else ebBlockChainBlockEnd.Text := '';
+  Finally
+    FUpdating := false;
+  End;
+end;
+
+procedure TFRMWallet.ebBlockChainBlockStartKeyPress(Sender: TObject;
+  var Key: Char);
+begin
+  if key=#13 then  ebBlockChainBlockStartExit(Nil);
+end;
+
+procedure TFRMWallet.ebFilterAccountByBalanceMinExit(Sender: TObject);
+begin
+  DoUpdateAccountsFilter;
+end;
+
+procedure TFRMWallet.ebFilterAccountByBalanceMinKeyPress(Sender: TObject;
+  var Key: Char);
+begin
+  if key=#13 then DoUpdateAccountsFilter;
+end;
+
+procedure TFRMWallet.ebFilterOperationsAccountExit(Sender: TObject);
+Var bstart,bend : Int64;
+begin
+  If FUpdating then exit;
+  FUpdating := True;
+  Try
+    bstart := StrToInt64Def(ebFilterOperationsStartBlock.Text,-1);
+    if bstart>=0 then ebFilterOperationsStartBlock.Text := Inttostr(bstart) else ebFilterOperationsStartBlock.Text := '';
+    bend := StrToInt64Def(ebFilterOperationsEndBlock.Text,-1);
+    if bend>=0 then ebFilterOperationsEndBlock.Text := Inttostr(bend) else ebFilterOperationsEndBlock.Text := '';
+    FOperationsExplorerGrid.SetBlocks(bstart,bend);
+  Finally
+    FUpdating := false;
+  End;
+end;
+
+procedure TFRMWallet.ebFilterOperationsAccountKeyPress(Sender: TObject;
+  var Key: Char);
+begin
+  if key=#13 then  ebFilterOperationsAccountExit(Nil);
+end;
+
+procedure TFRMWallet.ebFindAccountNumberChange(Sender: TObject);
+Var an : Cardinal;
+begin
+  if Trim(ebFindAccountNumber.Text)='' then begin
+    ebFindAccountNumber.Color := clWindow;
+    ebFindAccountNumber.Font.Color := clDkGray;
+  end else if TAccountComp.AccountTxtNumberToAccountNumber(ebFindAccountNumber.Text,an) then begin
+    ebFindAccountNumber.Color := clWindow;
+    if FAccountsGrid.MoveRowToAccount(an) then begin
+      ebFindAccountNumber.Font.Color := clWindowText;
+    end else begin
+      ebFindAccountNumber.Font.Color := clRed;
+    end;
+  end else begin
+    // Invalid value
+    ebFindAccountNumber.Color := clRed;
+    ebFindAccountNumber.Font.Color := clWindowText;
+  end;
+end;
+
+procedure TFRMWallet.ebFindAccountNumberExit(Sender: TObject);
+begin
+  ebFindAccountNumber.Text := '';
+end;
+
+procedure TFRMWallet.FinishedLoadingApp;
+begin
+  FPoolMiningServer := TPoolMiningServer.Create;
+  FPoolMiningServer.Port := FAppParams.ParamByName[CT_PARAM_JSONRPCMinerServerPort].GetAsInteger(CT_JSONRPCMinerServer_Port);
+  FPoolMiningServer.MinerAccountKey := GetAccountKeyForMiner;
+  FPoolMiningServer.MinerPayload := FAppParams.ParamByName[CT_PARAM_MinerName].GetAsString('');
+  FNode.Operations.AccountKey := GetAccountKeyForMiner;
+  FPoolMiningServer.Active := FAppParams.ParamByName[CT_PARAM_JSONRPCMinerServerActive].GetAsBoolean(true);
+  FPoolMiningServer.OnMiningServerNewBlockFound := OnMiningServerNewBlockFound;
+  If Assigned(FBackgroundPanel) then begin
+    FreeAndNil(FBackgroundPanel);
+  end;
+  PageControl.Visible:=True;
+  PageControl.Enabled:=True;
+end;
+
+procedure TFRMWallet.FillAccountInformation(const Strings: TStrings; Const AccountNumber: Cardinal);
+Var account : TAccount;
+  s : String;
+begin
+  if AccountNumber<0 then exit;
+  account := FNode.Operations.SafeBoxTransaction.Account(AccountNumber);
+  if account.name<>'' then s:='Name: '+account.name
+  else s:='';
+  Strings.Add(Format('Account: %s %s Type:%d',[TAccountComp.AccountNumberToAccountTxtNumber(AccountNumber),s,account.account_type]));
+  Strings.Add('');
+  Strings.Add(Format('Current balance: %s',[TAccountComp.FormatMoney(account.balance)]));
+  Strings.Add('');
+  Strings.Add(Format('Updated on block: %d  (%d blocks ago)',[account.updated_block,FNode.Bank.BlocksCount-account.updated_block]));
+  Strings.Add(Format('Public key type: %s',[TAccountComp.GetECInfoTxt(account.accountInfo.accountKey.EC_OpenSSL_NID)]));
+  Strings.Add(Format('Base58 Public key: %s',[TAccountComp.AccountPublicKeyExport(account.accountInfo.accountKey)]));
+  if TAccountComp.IsAccountForSale(account.accountInfo) then begin
+    Strings.Add('');
+    Strings.Add('** Account is for sale: **');
+    Strings.Add(Format('Price: %s',[TAccountComp.FormatMoney(account.accountInfo.price)]));
+    Strings.Add(Format('Seller account (where to pay): %s',[TAccountComp.AccountNumberToAccountTxtNumber(account.accountInfo.account_to_pay)]));
+    if TAccountComp.IsAccountForSaleAcceptingTransactions(account.accountInfo) then begin
+      Strings.Add('');
+      Strings.Add('** Private sale **');
+      Strings.Add(Format('New Base58 Public key: %s',[TAccountComp.AccountPublicKeyExport(account.accountInfo.new_publicKey)]));
+      Strings.Add('');
+      if TAccountComp.IsAccountLocked(account.accountInfo,FNode.Bank.BlocksCount) then begin
+        Strings.Add(Format('PURCHASE IS SECURE UNTIL BLOCK %d (current %d, remains %d)',
+          [account.accountInfo.locked_until_block,FNode.Bank.BlocksCount,account.accountInfo.locked_until_block-FNode.Bank.BlocksCount]));
+      end else begin
+        Strings.Add(Format('PURCHASE IS NOT SECURE (Expired on block %d, current %d)',
+          [account.accountInfo.locked_until_block,FNode.Bank.BlocksCount]));
+      end;
+    end;
+  end;
+end;
+
+procedure TFRMWallet.FillOperationInformation(const Strings: TStrings; Const OperationResume: TOperationResume);
+begin
+  If (not OperationResume.valid) then exit;
+  If OperationResume.Block<FNode.Bank.BlocksCount then
+    if (OperationResume.NOpInsideBlock>=0) then begin
+      Strings.Add(Format('Block: %d/%d',[OperationResume.Block,OperationResume.NOpInsideBlock]))
+    end else begin
+      Strings.Add(Format('Block: %d',[OperationResume.Block]))
+    end
+  else Strings.Add('** Pending operation not included on blockchain **');
+  Strings.Add(Format('%s',[OperationResume.OperationTxt]));
+  Strings.Add(Format('OpType:%d Subtype:%d',[OperationResume.OpType,OperationResume.OpSubtype]));
+  Strings.Add(Format('Operation Hash (ophash): %s',[TCrypto.ToHexaString(OperationResume.OperationHash)]));
+  If (OperationResume.OperationHash_OLD<>'') then begin
+    Strings.Add(Format('Old Operation Hash (old_ophash): %s',[TCrypto.ToHexaString(OperationResume.OperationHash_OLD)]));
+  end;
+  if (OperationResume.OriginalPayload<>'') then begin
+    Strings.Add(Format('Payload length:%d',[length(OperationResume.OriginalPayload)]));
+    If OperationResume.PrintablePayload<>'' then begin
+      Strings.Add(Format('Payload (human): %s',[OperationResume.PrintablePayload]));
+    end;
+    Strings.Add(Format('Payload (Hexadecimal): %s',[TCrypto.ToHexaString(OperationResume.OriginalPayload)]));
+  end;
+  If OperationResume.Balance>=0 then begin
+    Strings.Add(Format('Final balance: %s',[TAccountComp.FormatMoney(OperationResume.Balance)]));
+  end;
+end;
+
+function TFRMWallet.ForceMining: Boolean;
+begin
+  Result := false;
+end;
+
+procedure TFRMWallet.FormCreate(Sender: TObject);
+Var i : Integer;
+begin
+  FBackgroundPanel := Nil;
+  FMustProcessWalletChanged := false;
+  FMustProcessNetConnectionUpdated := false;
+  FRPCServer := Nil;
+  FNode := Nil;
+  FPoolMiningServer := Nil;
+  FMinAccountBalance := 0;
+  FMaxAccountBalance := CT_MaxWalletAmount;
+  FMessagesUnreadCount := 0;
+  lblReceivedMessages.Visible := false;
+  memoNetConnections.Lines.Clear;
+  memoNetServers.Lines.Clear;
+  memoNetBlackLists.Lines.Clear;
+  memoMessages.Lines.Clear;
+  memoMessageToSend.Lines.Clear;
+  FUpdating := false;
+  FOrderedAccountsKeyList := Nil;
+  TimerUpdateStatus.Enabled := false;
+  FIsActivated := false;
+  FWalletKeys := TWalletKeysExt.Create(Self);
+  for i := 0 to StatusBar.Panels.Count - 1 do begin
+    StatusBar.Panels[i].Text := '';
+  end;
+  FLog := TLog.Create(Self);
+  FLog.OnNewLog := OnNewLog;
+  FLog.SaveTypes := [];
+  If Not ForceDirectories(TFolderHelper.GetPascalCoinDataFolder) then raise Exception.Create('Cannot create dir: '+TFolderHelper.GetPascalCoinDataFolder);
+  FAppParams := TAppParams.Create(self);
+  FAppParams.FileName := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'AppParams.prm';
+  FNodeNotifyEvents := TNodeNotifyEvents.Create(Self);
+  FNodeNotifyEvents.OnBlocksChanged := OnNewAccount;
+  FNodeNotifyEvents.OnNodeMessageEvent := OnNodeMessageEvent;
+  FAccountsGrid := TAccountsGrid.Create(Self);
+  FAccountsGrid.DrawGrid := dgAccounts;
+  FAccountsGrid.AllowMultiSelect := True;
+  FSelectedAccountsGrid := TAccountsGrid.Create(Self);
+  FSelectedAccountsGrid.DrawGrid := dgSelectedAccounts;
+  FSelectedAccountsGrid.OnUpdated := OnSelectedAccountsGridUpdated;
+  FOperationsAccountGrid := TOperationsGrid.Create(Self);
+  FOperationsAccountGrid.DrawGrid := dgAccountOperations;
+  FOperationsAccountGrid.MustShowAlwaysAnAccount := true;
+  FPendingOperationsGrid := TOperationsGrid.Create(Self);
+  FPendingOperationsGrid.DrawGrid := dgPendingOperations;
+  FPendingOperationsGrid.AccountNumber := -1; // all
+  FPendingOperationsGrid.PendingOperations := true;
+  FOperationsExplorerGrid := TOperationsGrid.Create(Self);
+  FOperationsExplorerGrid.DrawGrid := dgOperationsExplorer;
+  FOperationsExplorerGrid.AccountNumber := -1;
+  FOperationsExplorerGrid.PendingOperations := False;
+  FBlockChainGrid := TBlockChainGrid.Create(Self);
+  FBlockChainGrid.DrawGrid := dgBlockChainExplorer;
+  FWalletKeys.OnChanged := OnWalletChanged;
+  LoadAppParams;
+  UpdatePrivateKeys;
+  UpdateBlockChainState;
+  UpdateConnectionStatus;
+  PageControl.ActivePage := tsMyAccounts;
+  pcAccountsOptions.ActivePage := tsAccountOperations;
+  ebFilterOperationsStartBlock.Text := '';
+  ebFilterOperationsEndBlock.Text := '';
+  cbExploreMyAccountsClick(nil);
+
+  TrayIcon.Visible := true;
+  TrayIcon.Hint := Self.Caption;
+  TrayIcon.BalloonTitle := 'Restoring the window.';
+  TrayIcon.BalloonHint :=
+    'Double click the system tray icon to restore Pascal Coin';
+  TrayIcon.BalloonFlags := bfInfo;
+  MinersBlocksFound := 0;
+  lblBuild.Caption := 'Build: '+CT_ClientAppVersion;
+  {$IFDEF TESTNET}
+  Image1.visible := false;
+  {$ENDIF}
+  PageControl.Enabled := False;
+  PageControl.Visible := False;
+  FBackgroundPanel := TPanel.Create(Self);
+  FBackgroundPanel.Parent:=Self;
+  FBackgroundPanel.Align:=alClient;
+  FBackgroundPanel.Font.Size:=15;
+end;
+
+procedure TFRMWallet.FormDestroy(Sender: TObject);
+Var i : Integer;
+  step : String;
+begin
+  TLog.NewLog(ltinfo,Classname,'Destroying form - START');
+  Try
+  FreeAndNil(FRPCServer);
+  FreeAndNil(FPoolMiningServer);
+  step := 'Saving params';
+  SaveAppParams;
+  FreeAndNil(FAppParams);
+  //
+  step := 'Assigning nil events';
+  FLog.OnNewLog :=Nil;
+  FNodeNotifyEvents.Node := Nil;
+  FOperationsAccountGrid.Node := Nil;
+  FOperationsExplorerGrid.Node := Nil;
+  FPendingOperationsGrid.Node := Nil;
+  FAccountsGrid.Node := Nil;
+  FSelectedAccountsGrid.Node := Nil;
+  TNetData.NetData.OnReceivedHelloMessage := Nil;
+  TNetData.NetData.OnStatisticsChanged := Nil;
+  TNetData.NetData.OnNetConnectionsUpdated := Nil;
+  TNetData.NetData.OnNodeServersUpdated := Nil;
+  TNetData.NetData.OnBlackListUpdated := Nil;
+  //
+
+  step := 'Destroying NodeNotifyEvents';
+  FreeAndNil(FNodeNotifyEvents);
+  //
+  step := 'Assigning Nil to TNetData';
+  TNetData.NetData.OnReceivedHelloMessage := Nil;
+  TNetData.NetData.OnStatisticsChanged := Nil;
+
+  step := 'Destroying grids operators';
+  FreeAndNil(FOperationsAccountGrid);
+  FreeAndNil(FOperationsExplorerGrid);
+  FreeAndNil(FBlockChainGrid);
+
+  step := 'Ordered Accounts Key list';
+  FreeAndNil(FOrderedAccountsKeyList);
+
+  step := 'Desactivating Node';
+  TNode.Node.NetServer.Active := false;
+  FNode := Nil;
+
+  TNetData.NetData.Free;
+
+  step := 'Processing messages 1';
+  Application.ProcessMessages;
+
+  step := 'Destroying Node';
+  TNode.Node.Free;
+
+  step := 'Destroying Wallet';
+  FreeAndNil(FWalletKeys);
+  step := 'Processing messages 2';
+  Application.ProcessMessages;
+  step := 'Destroying stringslist';
+  Except
+    On E:Exception do begin
+      TLog.NewLog(lterror,Classname,'Error destroying Form step: '+step+' Errors ('+E.ClassName+'): ' +E.Message);
+    end;
+  End;
+  TLog.NewLog(ltinfo,Classname,'Destroying form - END');
+  FreeAndNil(FLog);
+  Sleep(100);
+end;
+
+procedure TFRMWallet.sbSearchAccountClick(Sender: TObject);
+Var F : TFRMAccountSelect;
+begin
+  F := TFRMAccountSelect.Create(Self);
+  try
+    F.Node := FNode;
+    F.WalletKeys := FWalletKeys;
+    F.ShowModal;
+  finally
+    F.Free;
+  end;
+end;
+
+function TFRMWallet.GetAccountKeyForMiner: TAccountKey;
+Var PK : TECPrivateKey;
+  i : Integer;
+  PublicK : TECDSA_Public;
+begin
+  Result := CT_TECDSA_Public_Nul;
+  if Not Assigned(FWalletKeys) then exit;
+  if Not Assigned(FAppParams) then exit;
+  case FMinerPrivateKeyType of
+    mpk_NewEachTime: PublicK := CT_TECDSA_Public_Nul;
+    mpk_Selected: begin
+      PublicK := TAccountComp.RawString2Accountkey(FAppParams.ParamByName[CT_PARAM_MinerPrivateKeySelectedPublicKey].GetAsString(''));
+    end;
+  else
+    // Random
+    PublicK := CT_TECDSA_Public_Nul;
+    if FWalletKeys.Count>0 then PublicK := FWalletKeys.Key[Random(FWalletKeys.Count)].AccountKey;
+  end;
+  i := FWalletKeys.IndexOfAccountKey(PublicK);
+  if i>=0 then begin
+    if (FWalletKeys.Key[i].CryptedKey='') then i:=-1;
+  end;
+  if i<0 then begin
+    PK := TECPrivateKey.Create;
+    try
+      PK.GenerateRandomPrivateKey(CT_Default_EC_OpenSSL_NID);
+      FWalletKeys.AddPrivateKey('New for miner '+DateTimeToStr(Now), PK);
+      PublicK := PK.PublicKey;
+    finally
+      PK.Free;
+    end;
+  end;
+  Result := PublicK;
+end;
+
+procedure TFRMWallet.IPnodes1Click(Sender: TObject);
+Var FRM : TFRMNodesIp;
+begin
+  FRM := TFRMNodesIp.Create(Self);
+  Try
+    FRM.AppParams := FAppParams;
+    FRM.ShowModal;
+  Finally
+    FRM.Free;
+  End;
+end;
+
+procedure TFRMWallet.lblReceivedMessagesClick(Sender: TObject);
+begin
+  PageControl.ActivePage := tsMessages;
+end;
+
+procedure TFRMWallet.LoadAppParams;
+Var ms : TMemoryStream;
+  s : AnsiString;
+  fvi : TFileVersionInfo;
+begin
+  ms := TMemoryStream.Create;
+  Try
+    s := FAppParams.ParamByName[CT_PARAM_GridAccountsStream].GetAsString('');
+    ms.WriteBuffer(s[1],length(s));
+    ms.Position := 0;
+    // Disabled on V2: FAccountsGrid.LoadFromStream(ms);
+  Finally
+    ms.Free;
+  End;
+  If FAppParams.FindParam(CT_PARAM_MinerName)=Nil then begin
+    // New configuration... assigning a new random value
+    fvi := TFolderHelper.GetTFileVersionInfo(Application.ExeName);
+    FAppParams.ParamByName[CT_PARAM_MinerName].SetAsString('New Node '+DateTimeToStr(Now)+' - '+
+      fvi.InternalName+' Build:'+fvi.FileVersion);
+  end;
+  FBlockChainGrid.ShowTimeAverageColumns:={$IFDEF SHOW_AVERAGE_TIME_STATS}True;{$ELSE}False;{$ENDIF}
+  UpdateConfigChanged;
+end;
+
+procedure TFRMWallet.miAboutPascalCoinClick(Sender: TObject);
+begin
+  With TFRMAbout.Create(Self) do
+  try
+    showmodal;
+  finally
+    free;
+  end;
+end;
+
+procedure TFRMWallet.MiAccountInformationClick(Sender: TObject);
+Var F : TFRMMemoText;
+  accn : Int64;
+  s,title : String;
+  account : TAccount;
+  strings : TStrings;
+  i : Integer;
+  opr : TOperationResume;
+begin
+  accn := -1;
+  title := '';
+  strings := TStringList.Create;
+  try
+    opr := CT_TOperationResume_NUL;
+    if PageControl.ActivePage=tsOperations then begin
+      i := FOperationsExplorerGrid.DrawGrid.Row;
+      if (i>0) and (i<=FOperationsExplorerGrid.OperationsResume.Count) then begin
+        opr := FOperationsExplorerGrid.OperationsResume.OperationResume[i-1];
+      end;
+    end else if PageControl.ActivePage=tsPendingOperations then begin
+      i := FPendingOperationsGrid.DrawGrid.Row;
+      if (i>0) and (i<=FPendingOperationsGrid.OperationsResume.Count) then begin
+        opr := FPendingOperationsGrid.OperationsResume.OperationResume[i-1];
+      end;
+    end else if PageControl.ActivePage=tsMyAccounts then begin
+      accn := FAccountsGrid.AccountNumber(dgAccounts.Row);
+      if accn<0 then raise Exception.Create('Select an account');
+      FillAccountInformation(strings,accn);
+      title := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(accn)+' info';
+      i := FOperationsAccountGrid.DrawGrid.Row;
+      if (i>0) and (i<=FOperationsAccountGrid.OperationsResume.Count) then begin
+        opr := FOperationsAccountGrid.OperationsResume.OperationResume[i-1];
+      end;
+    end;
+    If (opr.valid) then begin
+      if accn>=0 then strings.Add('')
+      else title := 'Operation info';
+      strings.Add('Operation info:');
+      FillOperationInformation(strings,opr);
+    end else if accn<0 then Raise Exception.Create('No info available');
+    F := TFRMMemoText.Create(Self);
+    Try
+      F.Caption := title;
+      F.Memo.Lines.Assign(strings);
+      F.ShowModal;
+    Finally
+      F.Free;
+    End;
+  finally
+    strings.free;
+  end;
+end;
+
+procedure TFRMWallet.MiAddaccounttoSelectedClick(Sender: TObject);
+begin
+  PageControl.ActivePage := tsMyAccounts;
+  PageControlChange(Nil);
+  pcAccountsOptions.ActivePage := tsMultiSelectAccounts;
+  sbSelectedAccountsAddClick(Sender);
+end;
+
+procedure TFRMWallet.MiCloseClick(Sender: TObject);
+begin
+  Close;
+end;
+
+procedure TFRMWallet.MiDecodePayloadClick(Sender: TObject);
+begin
+  if PageControl.ActivePage=tsOperations then begin
+    FOperationsExplorerGrid.ShowModalDecoder(FWalletKeys,FAppParams);
+  end else if PageControl.ActivePage=tsPendingOperations then begin
+    FPendingOperationsGrid.ShowModalDecoder(FWalletKeys,FAppParams);
+  end else if PageControl.ActivePage=tsMyAccounts then begin
+    FOperationsAccountGrid.ShowModalDecoder(FWalletKeys,FAppParams);
+  end;
+end;
+
+procedure TFRMWallet.MiFindaccountClick(Sender: TObject);
+begin
+  PageControl.ActivePage := tsMyAccounts;
+  PageControlChange(Nil);
+  ebFindAccountNumber.SetFocus;
+end;
+
+procedure TFRMWallet.MiFindnextaccountwithhighbalanceClick(Sender: TObject);
+Var an  : Cardinal;
+  an64 : Int64;
+  start : TAccount;
+begin
+  PageControl.ActivePage := tsMyAccounts;
+  PageControlChange(Nil);
+  an64 := FAccountsGrid.AccountNumber(dgAccounts.Row);
+  if an64<0 then an := 0
+  else an := an64;
+  If an>=FNode.Bank.SafeBox.AccountsCount then exit;
+  start := FNode.Bank.SafeBox.Account(an);
+  while (an<FNode.Bank.SafeBox.AccountsCount)  do begin
+    if FNode.Bank.SafeBox.Account(an).balance>start.balance then break
+    else inc(an);
+  end;
+  if (an<FNode.Bank.SafeBox.AccountsCount) then FAccountsGrid.MoveRowToAccount(an)
+  else raise Exception.Create('Not found any account higher than '+TAccountComp.AccountNumberToAccountTxtNumber(start.account)+' with balance higher than '+
+    TAccountComp.FormatMoney(start.balance));
+end;
+
+procedure TFRMWallet.MiFindOperationbyOpHashClick(Sender: TObject);
+Var FRM : TFRMPayloadDecoder;
+  oph : String;
+begin
+  oph := '';
+  if Not InputQuery('Search operation by OpHash','Insert Operation Hash value (OpHash)',oph) then exit;
+  //
+  FRM := TFRMPayloadDecoder.Create(Self);
+  try
+    FRM.Init(CT_TOperationResume_NUL,WalletKeys,FAppParams);
+    FRM.DoFind(oph);
+    FRM.ShowModal;
+  finally
+    FRM.Free;
+  end;
+end;
+
+procedure TFRMWallet.MiFindpreviousaccountwithhighbalanceClick(Sender: TObject);
+Var an  : Cardinal;
+  an64 : Int64;
+  start : TAccount;
+begin
+  PageControl.ActivePage := tsMyAccounts;
+  PageControlChange(Nil);
+  an64 := FAccountsGrid.AccountNumber(dgAccounts.Row);
+  if an64<0 then an := FNode.Bank.SafeBox.AccountsCount-1
+  else an := an64;
+  If an>=FNode.Bank.SafeBox.AccountsCount then exit;
+  start := FNode.Bank.SafeBox.Account(an);
+  while (an>0)  do begin
+    if FNode.Bank.SafeBox.Account(an).balance>start.balance then break
+    else dec(an);
+  end;
+  if (FNode.Bank.SafeBox.Account(an).balance>start.balance) then FAccountsGrid.MoveRowToAccount(an)
+  else raise Exception.Create('Not found any account lower than '+TAccountComp.AccountNumberToAccountTxtNumber(start.account)+' with balance higher than '+
+    TAccountComp.FormatMoney(start.balance));
+end;
+
+procedure TFRMWallet.MiMultiaccountoperationClick(Sender: TObject);
+begin
+  PageControl.ActivePage := tsMyAccounts;
+  pcAccountsOptions.ActivePage := tsMultiSelectAccounts;
+  bbSelectedAccountsOperationClick(Sender);
+end;
+
+procedure TFRMWallet.miNewOperationClick(Sender: TObject);
+var l : TOrderedCardinalList;
+begin
+  CheckIsReady;
+  With TFRMOperation.Create(Self) do
+  Try
+    l := TOrderedCardinalList.Create;
+    try
+      If FAccountsGrid.SelectedAccounts(l)<1 then raise Exception.Create('No row selected');
+      SenderAccounts.CopyFrom(l);
+    finally
+      l.Free;
+    end;
+    DefaultFee := FAppParams.ParamByName[CT_PARAM_DefaultFee].GetAsInt64(0);
+    WalletKeys := FWalletKeys;
+    ShowModal;
+  Finally
+    Free;
+  End;
+end;
+
+procedure TFRMWallet.miOptionsClick(Sender: TObject);
+begin
+  With TFRMPascalCoinWalletConfig.Create(Self) do
+  try
+    AppParams := Self.FAppParams;
+    WalletKeys := Self.FWalletKeys;
+    if ShowModal=MrOk then begin
+      SaveAppParams;
+      UpdateConfigChanged;
+    end;
+  finally
+    free;
+  end;
+end;
+
+procedure TFRMWallet.miPrivatekeysClick(Sender: TObject);
+Var FRM : TFRMWalletKeys;
+begin
+  FRM := TFRMWalletKeys.Create(Self);
+  Try
+    FRM.WalletKeys := FWalletKeys;
+    FRM.ShowModal;
+    UpdatePrivateKeys;
+  Finally
+    FRM.Free;
+  End;
+end;
+
+procedure TFRMWallet.MiRemoveaccountfromselectedClick(Sender: TObject);
+begin
+  PageControl.ActivePage := tsMyAccounts;
+  PageControlChange(Nil);
+  pcAccountsOptions.ActivePage := tsMultiSelectAccounts;
+  sbSelectedAccountsDelClick(Sender);
+end;
+
+procedure TFRMWallet.OnMiningServerNewBlockFound(Sender: TObject);
+begin
+  FPoolMiningServer.MinerAccountKey := GetAccountKeyForMiner;
+end;
+
+procedure TFRMWallet.OnNetBlackListUpdated(Sender: TObject);
+Const CT_TRUE_FALSE : Array[Boolean] Of AnsiString = ('FALSE','TRUE');
+Var i,j,n : integer;
+ P : PNodeServerAddress;
+ l : TList;
+ strings : TStrings;
+begin
+  l := TNetData.NetData.NodeServersAddresses.LockList;
+  try
+    strings := memoNetBlackLists.Lines;
+    strings.BeginUpdate;
+    Try
+      strings.Clear;
+      strings.Add('BlackList Updated: '+DateTimeToStr(now)+' by TID:'+IntToHex(TThread.CurrentThread.ThreadID,8));
+      j := 0; n:=0;
+      for i := 0 to l.Count - 1 do begin
+        P := l[i];
+        if (P^.is_blacklisted) then begin
+          inc(n);
+          if Not P^.its_myself then begin
+            inc(j);
+            strings.Add(Format('Blacklist IP:%s:%d LastConnection:%s Reason: %s',
+              [
+               P^.ip,P^.port,
+               DateTimeToStr(UnivDateTime2LocalDateTime( UnixToUnivDateTime(P^.last_connection))),P^.BlackListText]));
+          end;
+        end;
+      end;
+      Strings.Add(Format('Total Blacklisted IPs: %d (Total %d)',[j,n]));
+    Finally
+      strings.EndUpdate;
+    End;
+  finally
+    TNetData.NetData.NodeServersAddresses.UnlockList;
+  end;
+end;
+
+procedure TFRMWallet.OnNetConnectionsUpdated(Sender: TObject);
+begin
+  if FMustProcessNetConnectionUpdated then exit;
+  FMustProcessNetConnectionUpdated := true;
+  PostMessage(Self.Handle,CM_PC_NetConnectionUpdated,0,0);
+end;
+
+procedure TFRMWallet.OnNetNodeServersUpdated(Sender: TObject);
+Var i : integer;
+ P : PNodeServerAddress;
+ l : TList;
+ strings : TStrings;
+ s : String;
+begin
+  l := TNetData.NetData.NodeServersAddresses.LockList;
+  try
+    strings := memoNetServers.Lines;
+    strings.BeginUpdate;
+    Try
+      strings.Clear;
+      strings.Add('NodeServers Updated: '+DateTimeToStr(now) +' Count: '+inttostr(l.Count));
+      for i := 0 to l.Count - 1 do begin
+        P := l[i];
+        if Not (P^.is_blacklisted) then begin
+          s := Format('Server IP:%s:%d',[P^.ip,P^.port]);
+          if Assigned(P.netConnection) then begin
+            If P.last_connection>0 then  s := s+ ' ** ACTIVE **'
+            else s := s+' ** TRYING TO CONNECT **';
+          end;
+          if P.its_myself then begin
+            s := s+' ** NOT VALID ** '+P.BlackListText;
+          end;
+          if P.last_connection>0 then begin
+            s := s + ' Last connection: '+DateTimeToStr(UnivDateTime2LocalDateTime( UnixToUnivDateTime(P^.last_connection)));
+          end;
+          if P.last_connection_by_server>0 then begin
+            s := s + ' Last server connection: '+DateTimeToStr(UnivDateTime2LocalDateTime( UnixToUnivDateTime(P^.last_connection_by_server)));
+          end;
+          if (P.last_attempt_to_connect>0) then begin
+            s := s + ' Last attempt to connect: '+DateTimeToStr(P^.last_attempt_to_connect);
+          end;
+          if (P.total_failed_attemps_to_connect>0) then begin
+            s := s + ' (Attempts: '+inttostr(P^.total_failed_attemps_to_connect)+')';
+          end;
+
+          strings.Add(s);
+        end;
+      end;
+    Finally
+      strings.EndUpdate;
+    End;
+  finally
+    TNetData.NetData.NodeServersAddresses.UnlockList;
+  end;
+end;
+
+procedure TFRMWallet.OnNetStatisticsChanged(Sender: TObject);
+Var NS : TNetStatistics;
+begin
+  //CheckMining;
+  if Assigned(FNode) then begin
+    If FNode.NetServer.Active then begin
+      StatusBar.Panels[0].Text := 'Active (Port '+Inttostr(FNode.NetServer.Port)+')';
+    end else StatusBar.Panels[0].Text := 'Server stopped';
+    NS := TNetData.NetData.NetStatistics;
+    StatusBar.Panels[1].Text := Format('Connections:%d Clients:%d Servers:%d - Rcvd:%d Kb Send:%d Kb',
+      [NS.ActiveConnections,NS.ClientsConnections,NS.ServersConnections,NS.BytesReceived DIV 1024,NS.BytesSend DIV 1024]);
+  end else begin
+    StatusBar.Panels[0].Text := '';
+    StatusBar.Panels[1].Text := '';
+  end;
+end;
+
+procedure TFRMWallet.OnNewAccount(Sender: TObject);
+begin
+  Try
+    UpdateAccounts(false);
+    UpdateBlockChainState;
+  Except
+    On E:Exception do begin
+      E.Message := 'Error at OnNewAccount '+E.Message;
+      Raise;
+    end;
+  end;
+end;
+
+procedure TFRMWallet.OnNewLog(logtype: TLogType; Time : TDateTime; ThreadID : Cardinal; const sender,logtext: AnsiString);
+Var s : AnsiString;
+begin
+  if (logtype=ltdebug) And (Not cbShowDebugLogs.Checked) then exit;
+  if ThreadID=MainThreadID then s := ' MAIN:' else s:=' TID:';
+  if MemoLogs.Lines.Count>300 then begin
+    // Limit max lines in logs...
+    memoLogs.Lines.BeginUpdate;
+    try
+      while memoLogs.Lines.Count>250 do memoLogs.Lines.Delete(0);
+    finally
+      memoLogs.Lines.EndUpdate;
+    end;
+  end;
+  memoLogs.Lines.Add(formatDateTime('dd/mm/yyyy hh:nn:ss.zzz',Time)+s+IntToHex(ThreadID,8)+' ['+CT_LogType[Logtype]+'] <'+sender+'> '+logtext);
+  //
+end;
+
+procedure TFRMWallet.OnNodeMessageEvent(NetConnection: TNetConnection; MessageData: TRawBytes);
+Var s : String;
+begin
+  inc(FMessagesUnreadCount);
+  if Assigned(NetConnection) then begin
+    s := DateTimeToStr(now)+' Message received from '+NetConnection.ClientRemoteAddr;
+    memoMessages.Lines.Add(DateTimeToStr(now)+' Message received from '+NetConnection.ClientRemoteAddr+' Length '+inttostr(Length(MessageData))+' bytes');
+    memoMessages.Lines.Add('RECEIVED> '+MessageData);
+    if FAppParams.ParamByName[CT_PARAM_ShowModalMessages].GetAsBoolean(false) then begin
+      s := DateTimeToStr(now)+' Message from '+NetConnection.ClientRemoteAddr+#10+
+         'Length '+inttostr(length(MessageData))+' bytes'+#10+#10;
+      if TCrypto.IsHumanReadable(MessageData) then begin
+         s := s + MessageData;
+      end else begin
+         s := s +'Value in hexadecimal:'+#10+
+              TCrypto.ToHexaString(MessageData);
+      end;
+      Application.MessageBox(PChar(s),PChar(Application.Title),MB_ICONINFORMATION+MB_OK);
+    end;
+  end else begin
+    memoMessages.Lines.Add(DateTimeToStr(now)+' Internal message: '+MessageData);
+  end;
+  if FMessagesUnreadCount>1 then lblReceivedMessages.Caption := Format('You have received %d messages',[FMessagesUnreadCount])
+  else lblReceivedMessages.Caption := 'You have received 1 message';
+  lblReceivedMessages.Visible := true;
+end;
+
+procedure TFRMWallet.OnReceivedHelloMessage(Sender: TObject);
+Var nsarr : TNodeServerAddressArray;
+  i : Integer;
+  s : AnsiString;
+begin
+  //CheckMining;
+  // Update node servers Peer Cache
+  nsarr := TNetData.NetData.GetValidNodeServers(true,0);
+  s := '';
+  for i := low(nsarr) to High(nsarr) do begin
+    if (s<>'') then s := s+';';
+    s := s + nsarr[i].ip+':'+IntToStr( nsarr[i].port );
+  end;
+  FAppParams.ParamByName[CT_PARAM_PeerCache].SetAsString(s);
+  TNode.Node.PeerCache := s;
+end;
+
+procedure TFRMWallet.OnSelectedAccountsGridUpdated(Sender: TObject);
+begin
+  lblSelectedAccountsCount.Caption := Inttostr(FSelectedAccountsGrid.AccountsCount);
+  lblSelectedAccountsBalance.Caption := TAccountComp.FormatMoney( FSelectedAccountsGrid.AccountsBalance );
+end;
+
+procedure TFRMWallet.OnWalletChanged(Sender: TObject);
+begin
+  if FMustProcessWalletChanged then exit;
+  FMustProcessWalletChanged := true;
+  PostMessage(Self.Handle,CM_PC_WalletKeysChanged,0,0);
+end;
+
+procedure TFRMWallet.PageControlChange(Sender: TObject);
+begin
+  MiDecodePayload.Enabled := false;
+  if PageControl.ActivePage=tsMyAccounts then begin
+    //FAccountsGrid.Node := FNode;
+    MiDecodePayload.Enabled := true;
+    FSelectedAccountsGrid.Node := FNode;
+  end else begin
+    //FAccountsGrid.Node := Nil;
+    FSelectedAccountsGrid.Node := Nil;
+  end;
+  if PageControl.ActivePage=tsPendingOperations then begin
+    FPendingOperationsGrid.Node := FNode;
+    MiDecodePayload.Enabled := true;
+  end else FPendingOperationsGrid.Node := Nil;
+  if PageControl.ActivePage=tsBlockChain then FBlockChainGrid.Node := FNode
+  else FBlockChainGrid.Node := Nil;
+  if PageControl.ActivePage=tsOperations then begin
+    FOperationsExplorerGrid.Node := FNode;
+    MiDecodePayload.Enabled := true;
+  end else FOperationsExplorerGrid.Node := Nil;
+  if PageControl.ActivePage=tsMessages then begin
+    UpdateAvailableConnections;
+    FMessagesUnreadCount := 0;
+    lblReceivedMessages.Visible := false;
+  end;
+end;
+
+procedure TFRMWallet.SaveAppParams;
+Var ms : TMemoryStream;
+  s : AnsiString;
+begin
+  ms := TMemoryStream.Create;
+  Try
+    FAccountsGrid.SaveToStream(ms);
+    ms.Position := 0;
+    setlength(s,ms.Size);
+    ms.ReadBuffer(s[1],ms.Size);
+    FAppParams.ParamByName[CT_PARAM_GridAccountsStream].SetAsString(s);
+  Finally
+    ms.Free;
+  End;
+end;
+
+procedure TFRMWallet.sbSelectedAccountsAddAllClick(Sender: TObject);
+Var lsource,ltarget : TOrderedCardinalList;
+  i : Integer;
+begin
+  lsource := FAccountsGrid.LockAccountsList;
+  Try
+    ltarget := FSelectedAccountsGrid.LockAccountsList;
+    Try
+      for i := 0 to lsource.Count-1 do begin
+        if FWalletKeys.IndexOfAccountKey(FNode.Bank.SafeBox.Account(lsource.Get(i)).accountInfo.accountKey)<0 then raise Exception.Create(Format('You cannot operate with account %d because private key not found in your wallet',[lsource.Get(i)]));
+        ltarget.Add(lsource.Get(i));
+      end;
+    Finally
+      FSelectedAccountsGrid.UnlockAccountsList;
+    End;
+  Finally
+    FAccountsGrid.UnlockAccountsList;
+  End;
+end;
+
+procedure TFRMWallet.sbSelectedAccountsAddClick(Sender: TObject);
+Var l, selected : TOrderedCardinalList;
+  an : Int64;
+  i : Integer;
+begin
+  an := FAccountsGrid.AccountNumber(dgAccounts.Row);
+  if (an<0) then raise Exception.Create('No account selected');
+  if FWalletKeys.IndexOfAccountKey(FNode.Bank.SafeBox.Account(an).accountInfo.accountkey)<0 then
+    raise Exception.Create(Format('You cannot add %s account because private key not found in your wallet.'#10+#10+'You''re not the owner!',
+      [TAccountComp.AccountNumberToAccountTxtNumber(an)]));
+  // Add
+  l := FSelectedAccountsGrid.LockAccountsList;
+  selected := TOrderedCardinalList.Create;
+  Try
+    FAccountsGrid.SelectedAccounts(selected);
+    for i := 0 to selected.Count-1 do begin
+      l.Add(selected.Get(i));
+    end;
+  Finally
+    selected.Free;
+    FSelectedAccountsGrid.UnlockAccountsList;
+  End;
+end;
+
+procedure TFRMWallet.sbSelectedAccountsDelAllClick(Sender: TObject);
+Var l : TOrderedCardinalList;
+begin
+  l := FSelectedAccountsGrid.LockAccountsList;
+  try
+    l.Clear;
+  finally
+    FSelectedAccountsGrid.UnlockAccountsList;
+  end;
+end;
+
+procedure TFRMWallet.sbSelectedAccountsDelClick(Sender: TObject);
+Var an : Int64;
+  l : TOrderedCardinalList;
+begin
+  l := FSelectedAccountsGrid.LockAccountsList;
+  try
+    an := FSelectedAccountsGrid.AccountNumber(dgSelectedAccounts.Row);
+    if an>=0 then l.Remove(an);
+  finally
+    FSelectedAccountsGrid.UnlockAccountsList;
+  end;
+end;
+
+procedure TFRMWallet.SetMinersBlocksFound(const Value: Integer);
+begin
+  FMinersBlocksFound := Value;
+  lblBlocksFound.Caption := Inttostr(Value);
+  if Value>0 then lblBlocksFound.Font.Color := clGreen
+  else lblBlocksFound.Font.Color := clDkGray;
+end;
+
+procedure TFRMWallet.TimerUpdateStatusTimer(Sender: TObject);
+begin
+  Try
+    UpdateConnectionStatus;
+    UpdateBlockChainState;
+    UpdateNodeStatus;
+  Except
+    On E:Exception do begin
+      E.Message := 'Exception at TimerUpdate '+E.ClassName+': '+E.Message;
+      TLog.NewLog(lterror,ClassName,E.Message);
+    end;
+  End;
+end;
+
+procedure TFRMWallet.TrayIconDblClick(Sender: TObject);
+begin
+  TrayIcon.Visible := False;
+  TimerUpdateStatus.Enabled := true;
+  Show();
+  WindowState := wsNormal;
+  Application.BringToFront();
+end;
+
+procedure TFRMWallet.UpdateAccounts(RefreshData : Boolean);
+Var accl : TOrderedCardinalList;
+  l : TOrderedCardinalList;
+  i,j,k : Integer;
+  c  : Cardinal;
+  applyfilter : Boolean;
+  acc : TAccount;
+begin
+  If Not Assigned(FOrderedAccountsKeyList) Then exit;
+  if Not RefreshData then begin
+    dgAccounts.Invalidate;
+    exit;
+  end;
+  applyfilter := (cbFilterAccounts.Checked) and ((FMinAccountBalance>0) Or (FMaxAccountBalance<CT_MaxWalletAmount));
+  FAccountsGrid.ShowAllAccounts := (Not cbExploreMyAccounts.Checked) And (not applyfilter);
+  if Not FAccountsGrid.ShowAllAccounts then begin
+    accl := FAccountsGrid.LockAccountsList;
+    Try
+      accl.Clear;
+      if cbExploreMyAccounts.Checked then begin
+        if cbMyPrivateKeys.ItemIndex<0 then exit;
+        if cbMyPrivateKeys.ItemIndex=0 then begin
+          // All keys in the wallet
+          for i := 0 to FWalletKeys.Count - 1 do begin
+            j := FOrderedAccountsKeyList.IndexOfAccountKey(FWalletKeys[i].AccountKey);
+            if (j>=0) then begin
+              l := FOrderedAccountsKeyList.AccountKeyList[j];
+              for k := 0 to l.Count - 1 do begin
+                if applyfilter then begin
+                  acc := FNode.Bank.SafeBox.Account(l.Get(k));
+                  if (acc.balance>=FMinAccountBalance) And (acc.balance<=FMaxAccountBalance) then accl.Add(acc.account);
+                end else accl.Add(l.Get(k));
+              end;
+            end;
+          end;
+        end else begin
+          i := PtrInt(cbMyPrivateKeys.Items.Objects[cbMyPrivateKeys.ItemIndex]);
+          if (i>=0) And (i<FWalletKeys.Count) then begin
+            j := FOrderedAccountsKeyList.IndexOfAccountKey(FWalletKeys[i].AccountKey);
+            if (j>=0) then begin
+              l := FOrderedAccountsKeyList.AccountKeyList[j];
+              for k := 0 to l.Count - 1 do begin
+                if applyfilter then begin
+                  acc := FNode.Bank.SafeBox.Account(l.Get(k));
+                  if (acc.balance>=FMinAccountBalance) And (acc.balance<=FMaxAccountBalance) then accl.Add(acc.account);
+                end else accl.Add(l.Get(k));
+              end;
+            end;
+          end;
+        end;
+      end else begin
+        // There is a filter... check every account...
+        c := 0;
+        while (c<FNode.Bank.SafeBox.AccountsCount) do begin
+          acc := FNode.Bank.SafeBox.Account(c);
+          if (acc.balance>=FMinAccountBalance) And (acc.balance<=FMaxAccountBalance) then accl.Add(acc.account);
+          inc(c);
+        end;
+      end;
+    Finally
+      FAccountsGrid.UnlockAccountsList;
+    End;
+    lblAccountsCount.Caption := inttostr(accl.Count);
+  end else begin
+    lblAccountsCount.Caption := inttostr(FNode.Bank.AccountsCount);
+  end;
+  bbChangeKeyName.Enabled := cbExploreMyAccounts.Checked;
+  // Show Totals:
+  lblAccountsBalance.Caption := TAccountComp.FormatMoney(FAccountsGrid.AccountsBalance);
+  UpdateOperations;
+end;
+
+procedure TFRMWallet.UpdateAvailableConnections;
+Var i : integer;
+ NC : TNetConnection;
+ l : TList;
+begin
+  if Not TNetData.NetData.NetConnections.TryLockList(100,l) then exit;
+  try
+    lbNetConnections.Items.BeginUpdate;
+    Try
+      lbNetConnections.Items.Clear;
+      for i := 0 to l.Count - 1 do begin
+        NC := l[i];
+        if NC.Connected then begin
+          if NC is TNetServerClient then begin
+            if Not NC.IsMyselfServer then begin
+              lbNetConnections.Items.AddObject(Format('Client: IP:%s',[NC.ClientRemoteAddr]),NC);
+            end;
+          end else begin
+            if Not NC.IsMyselfServer then begin
+              lbNetConnections.Items.AddObject(Format('Server: IP:%s',[NC.ClientRemoteAddr]),NC);
+            end;
+          end;
+        end;
+      end;
+    Finally
+      lbNetConnections.Items.EndUpdate;
+    End;
+  finally
+    TNetData.NetData.NetConnections.UnlockList;
+  end;
+end;
+
+procedure TFRMWallet.UpdateBlockChainState;
+Var isMining : boolean;
+//  hr : Int64;
+  i,mc : Integer;
+  s : String;
+  mtl : TList;
+  f, favg : real;
+begin
+  UpdateNodeStatus;
+  mc := 0;
+//  hr := 0;
+  if Assigned(FNode) then begin
+    if FNode.Bank.BlocksCount>0 then begin
+      lblCurrentBlock.Caption :=  Inttostr(FNode.Bank.BlocksCount)+' (0..'+Inttostr(FNode.Bank.BlocksCount-1)+')'; ;
+    end else lblCurrentBlock.Caption :=  '(none)';
+    lblCurrentAccounts.Caption := Inttostr(FNode.Bank.AccountsCount);
+    lblCurrentBlockTime.Caption := UnixTimeToLocalElapsedTime(FNode.Bank.LastOperationBlock.timestamp);
+    lblOperationsPending.Caption := Inttostr(FNode.Operations.Count);
+    lblCurrentDifficulty.Caption := InttoHex(FNode.Operations.OperationBlock.compact_target,8);
+    favg := FNode.Bank.GetActualTargetSecondsAverage(CT_CalcNewTargetBlocksAverage);
+    f := (CT_NewLineSecondsAvg - favg) / CT_NewLineSecondsAvg;
+    lblTimeAverage.Caption := 'Last '+Inttostr(CT_CalcNewTargetBlocksAverage)+': '+FormatFloat('0.0',favg)+' sec. (Optimal '+Inttostr(CT_NewLineSecondsAvg)+'s) Deviation '+FormatFloat('0.00%',f*100);
+    if favg>=CT_NewLineSecondsAvg then begin
+      lblTimeAverage.Font.Color := clNavy;
+    end else begin
+      lblTimeAverage.Font.Color := clOlive;
+    end;
+    lblTimeAverageAux.Caption := Format('Last %d: %s sec. - %d: %s sec. - %d: %s sec. - %d: %s sec. - %d: %s sec.',[
+        CT_CalcNewTargetBlocksAverage * 2 ,FormatFloat('0.0',FNode.Bank.GetActualTargetSecondsAverage(CT_CalcNewTargetBlocksAverage * 2)),
+        ((CT_CalcNewTargetBlocksAverage * 3) DIV 2) ,FormatFloat('0.0',FNode.Bank.GetActualTargetSecondsAverage((CT_CalcNewTargetBlocksAverage * 3) DIV 2)),
+        ((CT_CalcNewTargetBlocksAverage DIV 4)*3),FormatFloat('0.0',FNode.Bank.GetActualTargetSecondsAverage(((CT_CalcNewTargetBlocksAverage DIV 4)*3))),
+        CT_CalcNewTargetBlocksAverage DIV 2,FormatFloat('0.0',FNode.Bank.GetActualTargetSecondsAverage(CT_CalcNewTargetBlocksAverage DIV 2)),
+        CT_CalcNewTargetBlocksAverage DIV 4,FormatFloat('0.0',FNode.Bank.GetActualTargetSecondsAverage(CT_CalcNewTargetBlocksAverage DIV 4))]);
+  end else begin
+    isMining := false;
+    lblCurrentBlock.Caption := '';
+    lblCurrentAccounts.Caption := '';
+    lblCurrentBlockTime.Caption := '';
+    lblOperationsPending.Caption := '';
+    lblCurrentDifficulty.Caption := '';
+    lblTimeAverage.Caption := '';
+    lblTimeAverageAux.Caption := '';
+  end;
+  if (Assigned(FPoolMiningServer)) And (FPoolMiningServer.Active) then begin
+    If FPoolMiningServer.ClientsCount>0 then begin
+      lblMinersClients.Caption := IntToStr(FPoolMiningServer.ClientsCount)+' connected JSON-RPC clients';
+      lblMinersClients.Font.Color := clNavy;
+    end else begin
+      lblMinersClients.Caption := 'No JSON-RPC clients';
+      lblMinersClients.Font.Color := clDkGray;
+    end;
+    MinersBlocksFound := FPoolMiningServer.ClientsWins;
+  end else begin
+    MinersBlocksFound := 0;
+    lblMinersClients.Caption := 'JSON-RPC server not active';
+    lblMinersClients.Font.Color := clRed;
+  end;
+end;
+
+procedure TFRMWallet.UpdateConfigChanged;
+Var wa : Boolean;
+  i : Integer;
+begin
+  tsLogs.TabVisible := FAppParams.ParamByName[CT_PARAM_ShowLogs].GetAsBoolean(false);
+  if (Not tsLogs.TabVisible) then begin
+    FLog.OnNewLog := Nil;
+    if PageControl.ActivePage = tsLogs then PageControl.ActivePage := tsMyAccounts;
+  end else FLog.OnNewLog := OnNewLog;
+  if FAppParams.ParamByName[CT_PARAM_SaveLogFiles].GetAsBoolean(false) then begin
+    if FAppParams.ParamByName[CT_PARAM_SaveDebugLogs].GetAsBoolean(false) then FLog.SaveTypes := CT_TLogTypes_ALL
+    else FLog.SaveTypes := CT_TLogTypes_DEFAULT;
+    FLog.FileName := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'PascalCointWallet.log';
+  end else begin
+    FLog.SaveTypes := [];
+    FLog.FileName := '';
+  end;
+  if Assigned(FNode) then begin
+    wa := FNode.NetServer.Active;
+    FNode.NetServer.Port := FAppParams.ParamByName[CT_PARAM_InternetServerPort].GetAsInteger(CT_NetServer_Port);
+    FNode.NetServer.Active := wa;
+    FNode.Operations.BlockPayload := FAppParams.ParamByName[CT_PARAM_MinerName].GetAsString('');
+    FNode.NodeLogFilename := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'blocks.log';
+  end;
+  if Assigned(FPoolMiningServer) then begin
+    if FPoolMiningServer.Port<>FAppParams.ParamByName[CT_PARAM_JSONRPCMinerServerPort].GetAsInteger(CT_JSONRPCMinerServer_Port) then begin
+      FPoolMiningServer.Active := false;
+      FPoolMiningServer.Port := FAppParams.ParamByName[CT_PARAM_JSONRPCMinerServerPort].GetAsInteger(CT_JSONRPCMinerServer_Port);
+    end;
+    FPoolMiningServer.Active :=FAppParams.ParamByName[CT_PARAM_JSONRPCMinerServerActive].GetAsBoolean(true);
+    FPoolMiningServer.UpdateAccountAndPayload(GetAccountKeyForMiner,FAppParams.ParamByName[CT_PARAM_MinerName].GetAsString(''));
+  end;
+  if Assigned(FRPCServer) then begin
+    FRPCServer.Active := FAppParams.ParamByName[CT_PARAM_JSONRPCEnabled].GetAsBoolean(false);
+    FRPCServer.ValidIPs := FAppParams.ParamByName[CT_PARAM_JSONRPCAllowedIPs].GetAsString('127.0.0.1');
+  end;
+  i := FAppParams.ParamByName[CT_PARAM_MinerPrivateKeyType].GetAsInteger(Integer(mpk_Random));
+  if (i>=Integer(Low(TMinerPrivatekey))) And (i<=Integer(High(TMinerPrivatekey))) then FMinerPrivateKeyType := TMinerPrivateKey(i)
+  else FMinerPrivateKeyType := mpk_Random;
+end;
+
+procedure TFRMWallet.UpdateConnectionStatus;
+var errors : AnsiString;
+begin
+  UpdateNodeStatus;
+  OnNetStatisticsChanged(Nil);
+  if Assigned(FNode) then begin
+    if FNode.IsBlockChainValid(errors) then begin
+      StatusBar.Panels[2].Text := Format('Last account time:%s',
+       [FormatDateTime('dd/mm/yyyy hh:nn:ss',UnivDateTime2LocalDateTime(UnixToUnivDateTime( FNode.Bank.LastOperationBlock.timestamp )))]);
+    end else begin
+      StatusBar.Panels[2].Text := 'NO BLOCKCHAIN: '+errors;
+    end;
+  end else begin
+    StatusBar.Panels[2].Text := '';
+  end;
+end;
+
+procedure TFRMWallet.UpdateNodeStatus;
+Var status : AnsiString;
+begin
+  If Not Assigned(FNode) then begin
+    lblNodeStatus.Font.Color := clRed;
+    lblNodeStatus.Caption := 'Initializing...';
+  end else begin
+    If FNode.IsReady(status) then begin
+      if TNetData.NetData.NetStatistics.ActiveConnections>0 then begin
+        lblNodeStatus.Font.Color := clGreen;
+        if TNetData.NetData.IsDiscoveringServers then begin
+          lblNodeStatus.Caption := 'Discovering servers';
+        end else if TNetData.NetData.IsGettingNewBlockChainFromClient then begin
+          lblNodeStatus.Caption := 'Obtaining new blockchain';
+        end else begin
+          lblNodeStatus.Caption := 'Running';
+        end;
+      end else begin
+        lblNodeStatus.Font.Color := clRed;
+        lblNodeStatus.Caption := 'Alone in the world...';
+      end;
+    end else begin
+      lblNodeStatus.Font.Color := clRed;
+      lblNodeStatus.Caption := status;
+    end;
+  end;
+  If Assigned(FBackgroundPanel) then begin
+    FBackgroundPanel.Font.Color:=lblNodeStatus.Font.Color;
+    FBackgroundPanel.Caption:='Please wait until finished: '+lblNodeStatus.Caption;
+  end;
+end;
+
+procedure TFRMWallet.UpdateOperations;
+Var accn : Int64;
+begin
+  accn := FAccountsGrid.AccountNumber(dgAccounts.Row);
+  FOperationsAccountGrid.AccountNumber := accn;
+end;
+
+procedure TFRMWallet.UpdatePrivateKeys;
+Var i,last_i : Integer;
+  wk : TWalletKey;
+  s : AnsiString;
+begin
+  If (Not Assigned(FOrderedAccountsKeyList)) And (Assigned(FNode)) Then begin
+    FOrderedAccountsKeyList := TOrderedAccountKeysList.Create(FNode.Bank.SafeBox,false);
+  end;
+  if (cbMyPrivateKeys.ItemIndex>=0) then last_i := PtrInt(cbMyPrivateKeys.Items.Objects[cbMyPrivateKeys.ItemIndex])
+  else last_i := -1;
+  cbMyPrivateKeys.items.BeginUpdate;
+  Try
+    cbMyPrivateKeys.Items.Clear;
+    For i:=0 to FWalletKeys.Count-1 do begin
+      wk := FWalletKeys.Key[i];
+      if assigned(FOrderedAccountsKeyList) then begin
+        FOrderedAccountsKeyList.AddAccountKey(wk.AccountKey);
+      end;
+      if (wk.Name='') then begin
+        s := 'Sha256='+TCrypto.ToHexaString( TCrypto.DoSha256( TAccountComp.AccountKey2RawString(wk.AccountKey) ) );
+      end else begin
+        s := wk.Name;
+      end;
+      if Not Assigned(wk.PrivateKey) then s := s + '(*)';
+      cbMyPrivateKeys.Items.AddObject(s,TObject(i));
+    end;
+    cbMyPrivateKeys.Sorted := true;
+    cbMyPrivateKeys.Sorted := false;
+    cbMyPrivateKeys.Items.InsertObject(0,'(All my private keys)',TObject(-1));
+  Finally
+    cbMyPrivateKeys.Items.EndUpdate;
+  End;
+  last_i := cbMyPrivateKeys.Items.IndexOfObject(TObject(last_i));
+  if last_i<0 then last_i := 0;
+  if cbMyPrivateKeys.Items.Count>last_i then cbMyPrivateKeys.ItemIndex := last_i
+  else if cbMyPrivateKeys.Items.Count>=0 then cbMyPrivateKeys.ItemIndex := 0;
+end;
+
+initialization
+  FRMWallet := Nil;
+end.

+ 1 - 1
Units/Forms/UUserInterface.pas

@@ -990,7 +990,7 @@ end;
 procedure TLoadDatabaseThread.BCExecute;
 procedure TLoadDatabaseThread.BCExecute;
 begin
 begin
   // Read Operations saved from disk
   // Read Operations saved from disk
-  TNode.Node.Bank.DiskRestoreFromOperations(CT_MaxBlock);
+  TNode.Node.InitSafeboxAndOperations; // New Build 2.1.4 to load pending operations buffer
   TNode.Node.AutoDiscoverNodes(CT_Discover_IPs);
   TNode.Node.AutoDiscoverNodes(CT_Discover_IPs);
   TNode.Node.NetServer.Active := true;
   TNode.Node.NetServer.Active := true;
   Synchronize( TUserInterface.FinishedLoadingDatabase );
   Synchronize( TUserInterface.FinishedLoadingDatabase );

+ 171 - 0
Units/PascalCoin/UAccountKeyStorage.pas

@@ -0,0 +1,171 @@
+unit UAccountKeyStorage;
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+interface
+
+uses
+  Classes, SysUtils, UAccounts, UThread, UCommon;
+
+type
+  TAccountKeyStorateData = record
+    ptrAccountKey : PAccountKey;
+    counter : Integer;
+  end;
+  PAccountKeyStorageData = ^TAccountKeyStorateData;
+
+  { TAccountKeyStorage }
+
+  // This class reduces memory because allows to reuse account keys
+  // Based on tests, allows a 10-20% memory reduction when multiple accounts use the same Account key
+  TAccountKeyStorage = Class
+  private
+    FAccountKeys : TPCThreadList;
+    Function Find(list : TList; const accountKey: TAccountKey; var Index: Integer): Boolean;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    function AddAccountKey(Const accountKey: TAccountKey) : PAccountKey;
+    procedure RemoveAccountKey(Const accountKey: TAccountKey);
+    class function KS : TAccountKeyStorage;
+    function LockList : TList;
+    procedure UnlockList;
+  end;
+
+implementation
+
+uses
+  ULog;
+
+var _aks : TAccountKeyStorage = Nil;
+
+{ TAccountKeyStorage }
+
+function TAccountKeyStorage.Find(list : TList; const accountKey: TAccountKey; var Index: Integer): Boolean;
+var L, H, I: Integer;
+  C : Integer;
+begin
+  Result := False;
+  L := 0;
+  H := list.Count - 1;
+  while L <= H do
+  begin
+    I := (L + H) shr 1;
+    C := Integer(PAccountKeyStorageData(list[i])^.ptrAccountKey^.EC_OpenSSL_NID) - Integer(accountKey.EC_OpenSSL_NID);
+    if C=0 then begin
+      C := BinStrComp(PAccountKeyStorageData(list[i])^.ptrAccountKey^.x,accountKey.x);
+      if C=0 then begin
+        C := BinStrComp(PAccountKeyStorageData(list[i])^.ptrAccountKey^.y,accountKey.y);
+      end;
+    end;
+    if C < 0 then L := I + 1 else
+    begin
+      H := I - 1;
+      if C = 0 then
+      begin
+        Result := True;
+        L := I;
+      end;
+    end;
+  end;
+  Index := L;
+end;
+
+constructor TAccountKeyStorage.Create;
+begin
+  FAccountKeys := TPCThreadList.Create('TAccountKeyStorage');
+end;
+
+destructor TAccountKeyStorage.Destroy;
+Var l : TList;
+  i : Integer;
+  P1 : PAccountKeyStorageData;
+  P2 : PAccountKey;
+begin
+  l := FAccountKeys.LockList;
+  try
+    For i:=0 to l.Count-1 do begin
+      P1 := l[i];
+      P2 := P1^.ptrAccountKey;
+      Dispose(P1);
+      Dispose(P2);
+    end;
+    l.Clear;
+  finally
+    FAccountKeys.UnlockList;
+  end;
+  FreeAndNil(FAccountKeys);
+  inherited Destroy;
+end;
+
+function TAccountKeyStorage.AddAccountKey(const accountKey: TAccountKey): PAccountKey;
+var l : TList;
+  i : Integer;
+  P : PAccountKeyStorageData;
+begin
+  Result := Nil;
+  l := FAccountKeys.LockList;
+  try
+    If Find(l,accountKey,i) then begin
+      Result := PAccountKeyStorageData(l[i]).ptrAccountKey;
+      inc( PAccountKeyStorageData(l[i]).counter );
+    end else begin
+      New(P);
+      New(P^.ptrAccountKey);
+      P^.counter:=1;
+      P^.ptrAccountKey^:=accountKey;
+      Result := P^.ptrAccountKey;
+      l.Insert(i,P);
+    end;
+  finally
+    FAccountKeys.UnlockList;
+  end;
+end;
+
+procedure TAccountKeyStorage.RemoveAccountKey(const accountKey: TAccountKey);
+var l : TList;
+  i : Integer;
+  P : PAccountKeyStorageData;
+begin
+  l := FAccountKeys.LockList;
+  try
+    If Find(l,accountKey,i) then begin
+      P := PAccountKeyStorageData(l[i]);
+      dec( P^.counter );
+      If P^.counter<0 then begin
+        TLog.NewLog(lterror,Self.ClassName,'ERROR DEV 20171110-2');
+      end;
+    end else begin
+      TLog.NewLog(lterror,Self.ClassName,'ERROR DEV 20171110-1');
+    end;
+  finally
+    FAccountKeys.UnlockList;
+  end;
+end;
+
+class function TAccountKeyStorage.KS: TAccountKeyStorage;
+begin
+  if Not Assigned(_aks) then begin
+    _aks := TAccountKeyStorage.Create;
+  end;
+  Result := _aks;
+end;
+
+function TAccountKeyStorage.LockList: TList;
+begin
+  Result := FAccountKeys.LockList;
+end;
+
+procedure TAccountKeyStorage.UnlockList;
+begin
+  FAccountKeys.UnlockList;
+end;
+
+initialization
+  _aks := Nil;
+finalization
+  FreeAndNil(_aks);
+end.
+

+ 87 - 11
Units/PascalCoin/UAccounts.pas

@@ -365,7 +365,7 @@ Const
 implementation
 implementation
 
 
 uses
 uses
-  SysUtils, ULog, UOpenSSLdef, UOpenSSL;
+  SysUtils, ULog, UOpenSSLdef, UOpenSSL, UAccountKeyStorage;
 
 
 { TPascalCoinProtocol }
 { TPascalCoinProtocol }
 
 
@@ -1147,9 +1147,21 @@ end;
 
 
 // New on version 2: To reduce mem usage
 // New on version 2: To reduce mem usage
 {$DEFINE uselowmem}
 {$DEFINE uselowmem}
+{$DEFINE useAccountKeyStorage}
 
 
 {$IFDEF uselowmem}
 {$IFDEF uselowmem}
 Type
 Type
+  {$IFDEF useAccountKeyStorage}
+  TAccountInfoKS = Record
+    state : TAccountState;
+    accountKeyKS: PAccountKey; // Change instead of TAccountKey
+    locked_until_block : Cardinal;
+    price : UInt64;
+    account_to_pay : Cardinal;
+    new_publicKeyKS : PAccountKey;
+  end;
+  {$ENDIF}
+
   { In order to store less memory on RAM, those types will be used
   { In order to store less memory on RAM, those types will be used
     to store in RAM memory (better than to use original ones)
     to store in RAM memory (better than to use original ones)
     This will reduce 10-15% of memory usage.
     This will reduce 10-15% of memory usage.
@@ -1157,7 +1169,11 @@ Type
     of originals, but}
     of originals, but}
   TMemAccount = Record // TAccount with less memory usage
   TMemAccount = Record // TAccount with less memory usage
     // account number is discarded (-4 bytes)
     // account number is discarded (-4 bytes)
+    {$IFDEF useAccountKeyStorage}
+    accountInfoKS : TAccountInfoKS;
+    {$ELSE}
     accountInfo : TDynRawBytes;
     accountInfo : TDynRawBytes;
+    {$ENDIF}
     balance: UInt64;
     balance: UInt64;
     updated_block: Cardinal;
     updated_block: Cardinal;
     n_operation: Cardinal;
     n_operation: Cardinal;
@@ -1168,7 +1184,11 @@ Type
 
 
   TMemOperationBlock = Record // TOperationBlock with less memory usage
   TMemOperationBlock = Record // TOperationBlock with less memory usage
     // block number is discarded (-4 bytes)
     // block number is discarded (-4 bytes)
+    {$IFDEF useAccountKeyStorage}
+    account_keyKS: PAccountKey;
+    {$ELSE}
     account_key: TDynRawBytes;
     account_key: TDynRawBytes;
+    {$ENDIF}
     reward: UInt64;
     reward: UInt64;
     fee: UInt64;
     fee: UInt64;
     protocol_version: Word;
     protocol_version: Word;
@@ -1203,8 +1223,17 @@ Var raw : TRawBytes;
 {$ENDIF}
 {$ENDIF}
 begin
 begin
   {$IFDEF uselowmem}
   {$IFDEF uselowmem}
+  {$IFDEF useAccountKeyStorage}
+  dest.accountInfoKS.state:=source.accountInfo.state;
+  dest.accountInfoKS.accountKeyKS:=TAccountKeyStorage.KS.AddAccountKey(source.accountInfo.accountKey);
+  dest.accountInfoKS.locked_until_block:=source.accountInfo.locked_until_block;
+  dest.accountInfoKS.price:=source.accountInfo.price;
+  dest.accountInfoKS.account_to_pay:=source.accountInfo.account_to_pay;
+  dest.accountInfoKS.new_publicKeyKS:=TAccountKeyStorage.KS.AddAccountKey(source.accountInfo.new_publicKey);
+  {$ELSE}
   TAccountComp.AccountInfo2RawString(source.accountInfo,raw);
   TAccountComp.AccountInfo2RawString(source.accountInfo,raw);
   TBaseType.To256RawBytes(raw,dest.accountInfo);
   TBaseType.To256RawBytes(raw,dest.accountInfo);
+  {$ENDIF}
   dest.balance := source.balance;
   dest.balance := source.balance;
   dest.updated_block:=source.updated_block;
   dest.updated_block:=source.updated_block;
   dest.n_operation:=source.n_operation;
   dest.n_operation:=source.n_operation;
@@ -1223,8 +1252,17 @@ var raw : TRawBytes;
 begin
 begin
   {$IFDEF uselowmem}
   {$IFDEF uselowmem}
   dest.account:=account_number;
   dest.account:=account_number;
+  {$IFDEF useAccountKeyStorage}
+  dest.accountInfo.state:=source.accountInfoKS.state;
+  dest.accountInfo.accountKey:=source.accountInfoKS.accountKeyKS^;
+  dest.accountInfo.locked_until_block:=source.accountInfoKS.locked_until_block;
+  dest.accountInfo.price:=source.accountInfoKS.price;
+  dest.accountInfo.account_to_pay:=source.accountInfoKS.account_to_pay;
+  dest.accountInfo.new_publicKey:=source.accountInfoKS.new_publicKeyKS^;
+  {$ELSE}
   TBaseType.ToRawBytes(source.accountInfo,raw);
   TBaseType.ToRawBytes(source.accountInfo,raw);
   TAccountComp.RawString2AccountInfo(raw,dest.accountInfo);
   TAccountComp.RawString2AccountInfo(raw,dest.accountInfo);
+  {$ENDIF}
   dest.balance := source.balance;
   dest.balance := source.balance;
   dest.updated_block:=source.updated_block;
   dest.updated_block:=source.updated_block;
   dest.n_operation:=source.n_operation;
   dest.n_operation:=source.n_operation;
@@ -1243,8 +1281,12 @@ var raw : TRawBytes;
 {$ENDIF}
 {$ENDIF}
 Begin
 Begin
   {$IFDEF uselowmem}
   {$IFDEF uselowmem}
+  {$IFDEF useAccountKeyStorage}
+  dest.blockchainInfo.account_keyKS:=TAccountKeyStorage.KS.AddAccountKey(source.blockchainInfo.account_key);
+  {$ELSE}
   TAccountComp.AccountKey2RawString(source.blockchainInfo.account_key,raw);
   TAccountComp.AccountKey2RawString(source.blockchainInfo.account_key,raw);
   TBaseType.To256RawBytes(raw,dest.blockchainInfo.account_key);
   TBaseType.To256RawBytes(raw,dest.blockchainInfo.account_key);
+  {$ENDIF}
   dest.blockchainInfo.reward:=source.blockchainInfo.reward;
   dest.blockchainInfo.reward:=source.blockchainInfo.reward;
   dest.blockchainInfo.fee:=source.blockchainInfo.fee;
   dest.blockchainInfo.fee:=source.blockchainInfo.fee;
   dest.blockchainInfo.protocol_version:=source.blockchainInfo.protocol_version;
   dest.blockchainInfo.protocol_version:=source.blockchainInfo.protocol_version;
@@ -1275,8 +1317,12 @@ var i : Integer;
 begin
 begin
   {$IFDEF uselowmem}
   {$IFDEF uselowmem}
   dest.blockchainInfo.block:=block_number;
   dest.blockchainInfo.block:=block_number;
+  {$IFDEF useAccountKeyStorage}
+  dest.blockchainInfo.account_key := source.blockchainInfo.account_keyKS^;
+  {$ELSE}
   TBaseType.ToRawBytes(source.blockchainInfo.account_key,raw);
   TBaseType.ToRawBytes(source.blockchainInfo.account_key,raw);
   TAccountComp.RawString2Accountkey(raw,dest.blockchainInfo.account_key);
   TAccountComp.RawString2Accountkey(raw,dest.blockchainInfo.account_key);
+  {$ENDIF}
   dest.blockchainInfo.reward:=source.blockchainInfo.reward;
   dest.blockchainInfo.reward:=source.blockchainInfo.reward;
   dest.blockchainInfo.fee:=source.blockchainInfo.fee;
   dest.blockchainInfo.fee:=source.blockchainInfo.fee;
   dest.blockchainInfo.protocol_version:=source.blockchainInfo.protocol_version;
   dest.blockchainInfo.protocol_version:=source.blockchainInfo.protocol_version;
@@ -1302,9 +1348,14 @@ end;
 function TPCSafeBox.Account(account_number: Cardinal): TAccount;
 function TPCSafeBox.Account(account_number: Cardinal): TAccount;
 var b : Cardinal;
 var b : Cardinal;
 begin
 begin
-  b := account_number DIV CT_AccountsPerBlock;
-  if (b>=FBlockAccountsList.Count) then raise Exception.Create('Invalid account: '+IntToStr(account_number));
-  ToTAccount(PBlockAccount(FBlockAccountsList.Items[b])^.accounts[account_number MOD CT_AccountsPerBlock],account_number,Result);
+  StartThreadSafe;
+  try
+    b := account_number DIV CT_AccountsPerBlock;
+    if (b<0) Or (b>=FBlockAccountsList.Count) then raise Exception.Create('Invalid account: '+IntToStr(account_number));
+    ToTAccount(PBlockAccount(FBlockAccountsList.Items[b])^.accounts[account_number MOD CT_AccountsPerBlock],account_number,Result);
+  finally
+    EndThreadSave;
+  end;
 end;
 end;
 
 
 
 
@@ -1368,13 +1419,23 @@ end;
 
 
 function TPCSafeBox.AccountsCount: Integer;
 function TPCSafeBox.AccountsCount: Integer;
 begin
 begin
-  Result := BlocksCount * CT_AccountsPerBlock;
+  StartThreadSafe;
+  try
+    Result := BlocksCount * CT_AccountsPerBlock;
+  finally
+    EndThreadSave;
+  end;
 end;
 end;
 
 
 function TPCSafeBox.Block(block_number: Cardinal): TBlockAccount;
 function TPCSafeBox.Block(block_number: Cardinal): TBlockAccount;
 begin
 begin
-  if (block_number>=FBlockAccountsList.Count) then raise Exception.Create('Invalid block number: '+inttostr(block_number));
-  ToTBlockAccount(PBlockAccount(FBlockAccountsList.Items[block_number])^,block_number,Result);
+  StartThreadSafe;
+  try
+    if (block_number<0) Or (block_number>=FBlockAccountsList.Count) then raise Exception.Create('Invalid block number: '+inttostr(block_number));
+    ToTBlockAccount(PBlockAccount(FBlockAccountsList.Items[block_number])^,block_number,Result);
+  finally
+    EndThreadSave;
+  end;
 end;
 end;
 
 
 class function TPCSafeBox.BlockAccountToText(const block: TBlockAccount): AnsiString;
 class function TPCSafeBox.BlockAccountToText(const block: TBlockAccount): AnsiString;
@@ -1386,7 +1447,12 @@ end;
 
 
 function TPCSafeBox.BlocksCount: Integer;
 function TPCSafeBox.BlocksCount: Integer;
 begin
 begin
-  Result := FBlockAccountsList.Count;
+  StartThreadSafe;
+  try
+    Result := FBlockAccountsList.Count;
+  finally
+    EndThreadSave;
+  end;
 end;
 end;
 
 
 class function TPCSafeBox.CalcBlockHash(const block : TBlockAccount; useProtocol2Method : Boolean): TRawBytes;
 class function TPCSafeBox.CalcBlockHash(const block : TBlockAccount; useProtocol2Method : Boolean): TRawBytes;
@@ -1482,9 +1548,14 @@ end;
 
 
 function TPCSafeBox.CalcSafeBoxHash: TRawBytes;
 function TPCSafeBox.CalcSafeBoxHash: TRawBytes;
 begin
 begin
-  // If No buffer to hash is because it's firts block... so use Genesis: CT_Genesis_Magic_String_For_Old_Block_Hash
-  if (FBufferBlocksHash='') then Result := TCrypto.DoSha256(CT_Genesis_Magic_String_For_Old_Block_Hash)
-  else Result := TCrypto.DoSha256(FBufferBlocksHash);
+  StartThreadSafe;
+  try
+    // If No buffer to hash is because it's firts block... so use Genesis: CT_Genesis_Magic_String_For_Old_Block_Hash
+    if (FBufferBlocksHash='') then Result := TCrypto.DoSha256(CT_Genesis_Magic_String_For_Old_Block_Hash)
+    else Result := TCrypto.DoSha256(FBufferBlocksHash);
+  finally
+    EndThreadSave;
+  end;
 end;
 end;
 
 
 function TPCSafeBox.CanUpgradeToProtocol2: Boolean;
 function TPCSafeBox.CanUpgradeToProtocol2: Boolean;
@@ -2358,6 +2429,11 @@ begin
     AccountKeyListAddAccounts(newAccountInfo.accountKey,[account_number]);
     AccountKeyListAddAccounts(newAccountInfo.accountKey,[account_number]);
   end;
   end;
 
 
+  {$IFDEF useAccountKeyStorage}
+  // Delete old references prior to change
+  TAccountKeyStorage.KS.RemoveAccountKey(acc.accountInfo.accountKey);
+  TAccountKeyStorage.KS.RemoveAccountKey(acc.accountInfo.new_publicKey);
+  {$ENDIF}
   acc.accountInfo := newAccountInfo;
   acc.accountInfo := newAccountInfo;
   // Name:
   // Name:
   If acc.name<>newName then begin
   If acc.name<>newName then begin

+ 42 - 7
Units/PascalCoin/UBlockChain.pas

@@ -118,6 +118,7 @@ Type
     time : Cardinal;
     time : Cardinal;
     AffectedAccount : Cardinal;
     AffectedAccount : Cardinal;
     SignerAccount : Int64; // Is the account that executes this operation
     SignerAccount : Int64; // Is the account that executes this operation
+    n_operation : Cardinal;
     DestAccount : Int64;   //
     DestAccount : Int64;   //
     SellerAccount : Int64; // Protocol 2 - only used when is a pay to transaction
     SellerAccount : Int64; // Protocol 2 - only used when is a pay to transaction
     newKey : TAccountKey;
     newKey : TAccountKey;
@@ -185,7 +186,8 @@ Type
     Property HasValidSignature : Boolean read FHasValidSignature;
     Property HasValidSignature : Boolean read FHasValidSignature;
     Class function OperationHash_OLD(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHash_OLD(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHashValid(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHashValid(op : TPCOperation; Block : Cardinal) : TRawBytes;
-    Class function DecodeOperationHash(Const operationHash : TRawBytes; var block, account,n_operation : Cardinal) : Boolean;
+    Class function DecodeOperationHash(Const operationHash : TRawBytes; var block, account,n_operation : Cardinal; var md160Hash : TRawBytes) : Boolean;
+    Class function EqualOperationHashes(Const operationHash1, operationHash2 : TRawBytes) : Boolean;
     Class function FinalOperationHashAsHexa(Const operationHash : TRawBytes) : AnsiString;
     Class function FinalOperationHashAsHexa(Const operationHash : TRawBytes) : AnsiString;
     function Sha256 : TRawBytes;
     function Sha256 : TRawBytes;
   End;
   End;
@@ -340,6 +342,8 @@ Type
     function DoInitialize:Boolean; virtual; abstract;
     function DoInitialize:Boolean; virtual; abstract;
     Function DoCreateSafeBoxStream(blockCount : Cardinal) : TStream; virtual; abstract;
     Function DoCreateSafeBoxStream(blockCount : Cardinal) : TStream; virtual; abstract;
     Procedure DoEraseStorage; virtual; abstract;
     Procedure DoEraseStorage; virtual; abstract;
+    Procedure DoSavePendingBufferOperations(OperationsHashTree : TOperationsHashTree); virtual; abstract;
+    Procedure DoLoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree); virtual; abstract;
   public
   public
     Function LoadBlockChainBlock(Operations : TPCOperationsComp; Block : Cardinal) : Boolean;
     Function LoadBlockChainBlock(Operations : TPCOperationsComp; Block : Cardinal) : Boolean;
     Function SaveBlockChainBlock(Operations : TPCOperationsComp) : Boolean;
     Function SaveBlockChainBlock(Operations : TPCOperationsComp) : Boolean;
@@ -359,6 +363,8 @@ Type
     Function HasUpgradedToVersion2 : Boolean; virtual; abstract;
     Function HasUpgradedToVersion2 : Boolean; virtual; abstract;
     Procedure CleanupVersion1Data; virtual; abstract;
     Procedure CleanupVersion1Data; virtual; abstract;
     Procedure EraseStorage;
     Procedure EraseStorage;
+    Procedure SavePendingBufferOperations(OperationsHashTree : TOperationsHashTree);
+    Procedure LoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree);
   End;
   End;
 
 
   TStorageClass = Class of TStorage;
   TStorageClass = Class of TStorage;
@@ -404,7 +410,7 @@ Type
   End;
   End;
 
 
 Const
 Const
-  CT_TOperationResume_NUL : TOperationResume = (valid:false;Block:0;NOpInsideBlock:-1;OpType:0;OpSubtype:0;time:0;AffectedAccount:0;SignerAccount:-1;DestAccount:-1;SellerAccount:-1;newKey:(EC_OpenSSL_NID:0;x:'';y:'');OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:'';PrintablePayload:'';OperationHash:'';OperationHash_OLD:'';errors:'');
+  CT_TOperationResume_NUL : TOperationResume = (valid:false;Block:0;NOpInsideBlock:-1;OpType:0;OpSubtype:0;time:0;AffectedAccount:0;SignerAccount:-1;n_operation:0;DestAccount:-1;SellerAccount:-1;newKey:(EC_OpenSSL_NID:0;x:'';y:'');OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:'';PrintablePayload:'';OperationHash:'';OperationHash_OLD:'';errors:'');
 
 
 implementation
 implementation
 
 
@@ -413,7 +419,7 @@ uses
   SysUtils, Variants, {Graphics,}
   SysUtils, Variants, {Graphics,}
   {Controls, Forms,}
   {Controls, Forms,}
   Dialogs, {StdCtrls,}
   Dialogs, {StdCtrls,}
-  UTime, UConst, UOpTransaction;
+  UTime, UConst, UOpTransaction, UCommon;
 
 
 { TPCBank }
 { TPCBank }
 
 
@@ -1791,6 +1797,7 @@ begin
     FOnChanged := lastNE;
     FOnChanged := lastNE;
   end;
   end;
   If Assigned(FOnChanged) then FOnChanged(Self);
   If Assigned(FOnChanged) then FOnChanged(Self);
+  errors := '';
   Result := true;
   Result := true;
 end;
 end;
 
 
@@ -1864,6 +1871,16 @@ begin
   DoEraseStorage;
   DoEraseStorage;
 end;
 end;
 
 
+procedure TStorage.SavePendingBufferOperations(OperationsHashTree : TOperationsHashTree);
+begin
+  DoSavePendingBufferOperations(OperationsHashTree);
+end;
+
+procedure TStorage.LoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree);
+begin
+  DoLoadPendingBufferOperations(OperationsHashTree);
+end;
+
 function TStorage.LoadBlockChainBlock(Operations: TPCOperationsComp; Block: Cardinal): Boolean;
 function TStorage.LoadBlockChainBlock(Operations: TPCOperationsComp; Block: Cardinal): Boolean;
 begin
 begin
   if (Block<FirstBlock) Or (Block>LastBlock) then result := false
   if (Block<FirstBlock) Or (Block>LastBlock) then result := false
@@ -1926,7 +1943,6 @@ begin
   FReadOnly := Value;
   FReadOnly := Value;
 end;
 end;
 
 
-
 { TPCOperation }
 { TPCOperation }
 
 
 constructor TPCOperation.Create;
 constructor TPCOperation.Create;
@@ -1960,13 +1976,15 @@ begin
 end;
 end;
 
 
 class function TPCOperation.DecodeOperationHash(const operationHash: TRawBytes;
 class function TPCOperation.DecodeOperationHash(const operationHash: TRawBytes;
-  var block, account, n_operation: Cardinal): Boolean;
+  var block, account, n_operation: Cardinal; var md160Hash : TRawBytes) : Boolean;
   { Decodes a previously generated OperationHash }
   { Decodes a previously generated OperationHash }
 var ms : TMemoryStream;
 var ms : TMemoryStream;
 begin
 begin
   Result := false;
   Result := false;
-  account:=0;
-  n_operation:=0;
+  block :=0;
+  account :=0;
+  n_operation :=0;
+  md160Hash:='';
   if length(operationHash)<>32 then exit;
   if length(operationHash)<>32 then exit;
   ms := TMemoryStream.Create;
   ms := TMemoryStream.Create;
   try
   try
@@ -1975,12 +1993,28 @@ begin
     ms.Read(block,4);
     ms.Read(block,4);
     ms.Read(account,4);
     ms.Read(account,4);
     ms.Read(n_operation,4);
     ms.Read(n_operation,4);
+    SetLength(md160Hash, 20);
+    ms.ReadBuffer(md160Hash[1], 20);
     Result := true;
     Result := true;
   finally
   finally
     ms.free;
     ms.free;
   end;
   end;
 end;
 end;
 
 
+class function TPCOperation.EqualOperationHashes(const operationHash1,operationHash2: TRawBytes): Boolean;
+  // operationHash1 and operationHash2 must be in RAW format (Not hexadecimal string!)
+var b0,b1,b2,r1,r2 : TRawBytes;
+begin
+  // First 4 bytes of OpHash are block number. If block=0 then is an unknown block, otherwise must match
+  b1 := copy(operationHash1,1,4);
+  b2 := copy(operationHash2,1,4);
+  r1 := copy(operationHash1,5,length(operationHash1)-4);
+  r2 := copy(operationHash2,5,length(operationHash2)-4);
+  b0 := TCrypto.HexaToRaw('00000000');
+  Result := (BinStrComp(r1,r2)=0) // Both right parts must be equal
+    AND ((BinStrComp(b1,b0)=0) Or (BinStrComp(b2,b0)=0) Or (BinStrComp(b1,b2)=0)); // b is 0 value or b1=b2 (b = block number)
+end;
+
 class function TPCOperation.FinalOperationHashAsHexa(const operationHash: TRawBytes): AnsiString;
 class function TPCOperation.FinalOperationHashAsHexa(const operationHash: TRawBytes): AnsiString;
 begin
 begin
   Result := TCrypto.ToHexaString(Copy(operationHash,5,28));
   Result := TCrypto.ToHexaString(Copy(operationHash,5,28));
@@ -2090,6 +2124,7 @@ begin
   OperationResume.AffectedAccount := Affected_account_number;
   OperationResume.AffectedAccount := Affected_account_number;
   OperationResume.OpType:=Operation.OpType;
   OperationResume.OpType:=Operation.OpType;
   OperationResume.SignerAccount := Operation.SignerAccount;
   OperationResume.SignerAccount := Operation.SignerAccount;
+  OperationResume.n_operation := Operation.N_Operation;
   Result := false;
   Result := false;
   case Operation.OpType of
   case Operation.OpType of
     CT_Op_Transaction : Begin
     CT_Op_Transaction : Begin

+ 1 - 1
Units/PascalCoin/UConst.pas

@@ -131,7 +131,7 @@ Const
   CT_OpSubtype_ChangeKeySigned            = 71;
   CT_OpSubtype_ChangeKeySigned            = 71;
   CT_OpSubtype_ChangeAccountInfo          = 81;
   CT_OpSubtype_ChangeAccountInfo          = 81;
 
 
-  CT_ClientAppVersion : AnsiString = '2.1.3';
+  CT_ClientAppVersion : AnsiString = {$IFDEF PRODUCTION}'2.1.4'{$ELSE}{$IFDEF TESTNET}'TESTNET 2.1.4'{$ELSE}{$ENDIF}{$ENDIF};
 
 
   CT_Discover_IPs =  'bpascal1.dynamic-dns.net;bpascal2.dynamic-dns.net;pascalcoin2.ddns.net;pascalcoin1.dynamic-dns.net;pascalcoin1.dns1.us';
   CT_Discover_IPs =  'bpascal1.dynamic-dns.net;bpascal2.dynamic-dns.net;pascalcoin2.ddns.net;pascalcoin1.dynamic-dns.net;pascalcoin1.dns1.us';
 
 

+ 60 - 1
Units/PascalCoin/UFileStorage.pas

@@ -36,6 +36,7 @@ Type
   private
   private
     FStorageLock : TPCCriticalSection;
     FStorageLock : TPCCriticalSection;
     FBlockChainStream : TFileStream;
     FBlockChainStream : TFileStream;
+    FPendingBufferOperationsStream : TFileStream;
     FStreamFirstBlockNumber : Int64;
     FStreamFirstBlockNumber : Int64;
     FStreamLastBlockNumber : Int64;
     FStreamLastBlockNumber : Int64;
     FBlockHeadersFirstBytePosition : TArrayOfInt64;
     FBlockHeadersFirstBytePosition : TArrayOfInt64;
@@ -50,6 +51,7 @@ Type
     procedure SetDatabaseFolder(const Value: AnsiString);
     procedure SetDatabaseFolder(const Value: AnsiString);
     Procedure ClearStream;
     Procedure ClearStream;
     Procedure GrowStreamUntilPos(Stream : TStream; newPos : Int64; DeleteDataStartingAtCurrentPos : Boolean);
     Procedure GrowStreamUntilPos(Stream : TStream; newPos : Int64; DeleteDataStartingAtCurrentPos : Boolean);
+    Function GetPendingBufferOperationsStream : TFileStream;
   protected
   protected
     procedure SetReadOnly(const Value: Boolean); override;
     procedure SetReadOnly(const Value: Boolean); override;
     procedure SetOrphan(const Value: TOrphan); override;
     procedure SetOrphan(const Value: TOrphan); override;
@@ -68,6 +70,8 @@ Type
     function DoInitialize : Boolean; override;
     function DoInitialize : Boolean; override;
     Function DoCreateSafeBoxStream(blockCount : Cardinal) : TStream; override;
     Function DoCreateSafeBoxStream(blockCount : Cardinal) : TStream; override;
     Procedure DoEraseStorage; override;
     Procedure DoEraseStorage; override;
+    Procedure DoSavePendingBufferOperations(OperationsHashTree : TOperationsHashTree); override;
+    Procedure DoLoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree); override;
   public
   public
     Constructor Create(AOwner : TComponent); Override;
     Constructor Create(AOwner : TComponent); Override;
     Destructor Destroy; Override;
     Destructor Destroy; Override;
@@ -140,6 +144,7 @@ end;
 procedure TFileStorage.ClearStream;
 procedure TFileStorage.ClearStream;
 begin
 begin
   FreeAndNil(FBlockChainStream);
   FreeAndNil(FBlockChainStream);
+  FreeAndNil(FPendingBufferOperationsStream);
   FStreamFirstBlockNumber := 0;
   FStreamFirstBlockNumber := 0;
   FStreamLastBlockNumber := -1;
   FStreamLastBlockNumber := -1;
   SetLength(FBlockHeadersFirstBytePosition,0);
   SetLength(FBlockHeadersFirstBytePosition,0);
@@ -165,6 +170,27 @@ begin
   Stream.Position := newPos;
   Stream.Position := newPos;
 end;
 end;
 
 
+function TFileStorage.GetPendingBufferOperationsStream: TFileStream;
+Var fs : TFileStream;
+  fn : TFileName;
+  fm : Word;
+begin
+  If Not Assigned(FPendingBufferOperationsStream) then begin
+    fn := GetFolder(Orphan)+PathDelim+'pendingbuffer.ops';
+    If FileExists(fn) then fm := fmOpenReadWrite+fmShareExclusive
+    else fm := fmCreate+fmShareExclusive;
+    Try
+      FPendingBufferOperationsStream := TFileStream.Create(fn,fm);
+    Except
+      On E:Exception do begin
+        TLog.NewLog(ltError,ClassName,'Error opening PendingBufferOperationsStream '+fn+' ('+E.ClassName+'):'+ E.Message);
+        Raise;
+      end;
+    end;
+  end;
+  Result := FPendingBufferOperationsStream;
+end;
+
 procedure TFileStorage.CopyConfiguration(const CopyFrom: TStorage);
 procedure TFileStorage.CopyConfiguration(const CopyFrom: TStorage);
 begin
 begin
   inherited;
   inherited;
@@ -182,6 +208,7 @@ begin
   SetLength(FBlockHeadersFirstBytePosition,0);
   SetLength(FBlockHeadersFirstBytePosition,0);
   FStreamFirstBlockNumber := 0;
   FStreamFirstBlockNumber := 0;
   FStreamLastBlockNumber := -1;
   FStreamLastBlockNumber := -1;
+  FPendingBufferOperationsStream := Nil;
   FStorageLock := TPCCriticalSection.Create('TFileStorage_StorageLock');
   FStorageLock := TPCCriticalSection.Create('TFileStorage_StorageLock');
 end;
 end;
 
 
@@ -234,7 +261,7 @@ begin
   Result := Nil;
   Result := Nil;
   fn := GetSafeboxCheckpointingFileName(GetFolder(Orphan),blockCount);
   fn := GetSafeboxCheckpointingFileName(GetFolder(Orphan),blockCount);
   If (fn<>'') and (FileExists(fn)) then begin
   If (fn<>'') and (FileExists(fn)) then begin
-    Result := TFileStream.Create(fn,fmOpenRead);
+    Result := TFileStream.Create(fn,fmOpenRead+fmShareDenyWrite);
   end;
   end;
   If Not Assigned(Result) then begin
   If Not Assigned(Result) then begin
     err := 'Cannot load SafeBoxStream (block:'+IntToStr(blockCount)+') file:'+fn;
     err := 'Cannot load SafeBoxStream (block:'+IntToStr(blockCount)+') file:'+fn;
@@ -254,6 +281,38 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure TFileStorage.DoSavePendingBufferOperations(OperationsHashTree : TOperationsHashTree);
+Var fs : TFileStream;
+begin
+  LockBlockChainStream;
+  Try
+    fs := GetPendingBufferOperationsStream;
+    fs.Position:=0;
+    fs.Size:=0;
+    OperationsHashTree.SaveOperationsHashTreeToStream(fs,true);
+    TLog.NewLog(ltdebug,ClassName,Format('DoSavePendingBufferOperations operations:%d',[OperationsHashTree.OperationsCount]));
+  finally
+    UnlockBlockChainStream;
+  end;
+end;
+
+procedure TFileStorage.DoLoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree);
+Var fs : TFileStream;
+  errors : AnsiString;
+  n : Integer;
+begin
+  LockBlockChainStream;
+  Try
+    fs := GetPendingBufferOperationsStream;
+    fs.Position:=0;
+    If OperationsHashTree.LoadOperationsHashTreeFromStream(fs,true,true,errors) then begin
+      TLog.NewLog(ltInfo,ClassName,Format('DoLoadPendingBufferOperations loaded operations:%d',[OperationsHashTree.OperationsCount]));
+    end else TLog.NewLog(ltError,ClassName,Format('DoLoadPendingBufferOperations ERROR: loaded operations:%d errors:%s',[OperationsHashTree.OperationsCount,errors]));
+  finally
+    UnlockBlockChainStream;
+  end;
+end;
+
 function TFileStorage.DoLoadBlockChain(Operations: TPCOperationsComp; Block: Cardinal): Boolean;
 function TFileStorage.DoLoadBlockChain(Operations: TPCOperationsComp; Block: Cardinal): Boolean;
 Var stream : TStream;
 Var stream : TStream;
   iBlockHeaders : Integer;
   iBlockHeaders : Integer;

+ 5 - 2
Units/PascalCoin/UNetProtocol.pas

@@ -526,6 +526,8 @@ begin
            ((nsa.last_connection>0) Or (nsa.last_connection_by_server>0))
            ((nsa.last_connection>0) Or (nsa.last_connection_by_server>0))
           ))
           ))
       then begin
       then begin
+        TLog.NewLog(ltdebug,ClassName,Format('Delete node server address: %s : %d last_connection:%d last_connection_by_server:%d total_failed_attemps:%d last_attempt_to_connect:%s ',
+          [nsa.ip,nsa.port,nsa.last_connection,nsa.last_connection_by_server,nsa.total_failed_attemps_to_connect,FormatDateTime('dd/mm/yyyy hh:nn:ss',nsa.last_attempt_to_connect)]));
         DeleteNetClient(l,i);
         DeleteNetClient(l,i);
         dec(FNetStatistics.NodeServersListCount);
         dec(FNetStatistics.NodeServersListCount);
         inc(FNetStatistics.NodeServersDeleted);
         inc(FNetStatistics.NodeServersDeleted);
@@ -2451,7 +2453,7 @@ Begin
       end;
       end;
       //
       //
       if (Abs(FTimestampDiff) > CT_MaxFutureBlockTimestampOffset) then begin
       if (Abs(FTimestampDiff) > CT_MaxFutureBlockTimestampOffset) then begin
-        TLog.NewLog(ltError,ClassName,'Detected a node ('+ClientRemoteAddr+') with incorrect timestamp: '+IntToStr(connection_ts)+' offset '+IntToStr(FTimestampDiff) );
+        TLog.NewLog(ltDebug,ClassName,'Detected a node ('+ClientRemoteAddr+') with incorrect timestamp: '+IntToStr(connection_ts)+' offset '+IntToStr(FTimestampDiff) );
       end;
       end;
     end;
     end;
     if (connection_has_a_server>0) And (Not SameText(Client.RemoteHost,'localhost')) And (Not SameText(Client.RemoteHost,'127.0.0.1'))
     if (connection_has_a_server>0) And (Not SameText(Client.RemoteHost,'localhost')) And (Not SameText(Client.RemoteHost,'127.0.0.1'))
@@ -2690,6 +2692,7 @@ begin
                 iDebugStep := 1000;
                 iDebugStep := 1000;
                 case HeaderData.operation of
                 case HeaderData.operation of
                   CT_NetOp_Hello : Begin
                   CT_NetOp_Hello : Begin
+                    iDebugStep := 1100;
                     DoProcess_Hello(HeaderData,ReceiveDataBuffer);
                     DoProcess_Hello(HeaderData,ReceiveDataBuffer);
                   End;
                   End;
                   CT_NetOp_Message : Begin
                   CT_NetOp_Message : Begin
@@ -2735,7 +2738,7 @@ begin
     end;
     end;
   Except
   Except
     On E:Exception do begin
     On E:Exception do begin
-      E.Message := E.Message+' DoSendAndWaitForResponse step '+Inttostr(iDebugStep);
+      E.Message := E.Message+' DoSendAndWaitForResponse step '+Inttostr(iDebugStep)+' Header.operation:'+Inttostr(HeaderData.operation);
       Raise;
       Raise;
     end;
     end;
   End;
   End;

+ 202 - 35
Units/PascalCoin/UNode.pas

@@ -36,6 +36,8 @@ Type
 
 
   { TNode }
   { TNode }
 
 
+  TSearchOperationResult = (found, invalid_params, blockchain_block_not_found);
+
   TNode = Class(TComponent)
   TNode = Class(TComponent)
   private
   private
     FNodeLog : TLog;
     FNodeLog : TLog;
@@ -70,7 +72,7 @@ Type
     Property Operations : TPCOperationsComp read FOperations;
     Property Operations : TPCOperationsComp read FOperations;
     //
     //
     Function AddNewBlockChain(SenderConnection : TNetConnection; NewBlockOperations: TPCOperationsComp; var newBlockAccount: TBlockAccount; var errors: AnsiString): Boolean;
     Function AddNewBlockChain(SenderConnection : TNetConnection; NewBlockOperations: TPCOperationsComp; var newBlockAccount: TBlockAccount; var errors: AnsiString): Boolean;
-    Function AddOperations(SenderConnection : TNetConnection; Operations : TOperationsHashTree; OperationsResult : TOperationsResumeList; var errors: AnsiString): Integer;
+    Function AddOperations(SenderConnection : TNetConnection; OperationsHashTree : TOperationsHashTree; OperationsResult : TOperationsResumeList; var errors: AnsiString): Integer;
     Function AddOperation(SenderConnection : TNetConnection; Operation : TPCOperation; var errors: AnsiString): Boolean;
     Function AddOperation(SenderConnection : TNetConnection; Operation : TPCOperation; var errors: AnsiString): Boolean;
     Function SendNodeMessage(Target : TNetConnection; TheMessage : AnsiString; var errors : AnsiString) : Boolean;
     Function SendNodeMessage(Target : TNetConnection; TheMessage : AnsiString; var errors : AnsiString) : Boolean;
     //
     //
@@ -78,7 +80,11 @@ Type
     //
     //
     procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation : Integer);
     procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation : Integer);
     Function FindOperation(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : Boolean;
     Function FindOperation(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : Boolean;
+    Function FindOperationExt(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : TSearchOperationResult;
+    Function FindNOperation(block, account, n_operation : Cardinal; var OpResume : TOperationResume) : TSearchOperationResult;
+    Function FindNOperations(account, start_block : Cardinal; allow_search_previous : Boolean; n_operation_low, n_operation_high : Cardinal; OpResumeList : TOperationsResumeList) : TSearchOperationResult;
     //
     //
+    Procedure InitSafeboxAndOperations;
     Procedure AutoDiscoverNodes(Const ips : AnsiString);
     Procedure AutoDiscoverNodes(Const ips : AnsiString);
     Function IsBlockChainValid(var WhyNot : AnsiString) : Boolean;
     Function IsBlockChainValid(var WhyNot : AnsiString) : Boolean;
     Function IsReady(Var CurrentProcess : AnsiString) : Boolean;
     Function IsReady(Var CurrentProcess : AnsiString) : Boolean;
@@ -160,6 +166,8 @@ Var i,j : Integer;
   errors2 : AnsiString;
   errors2 : AnsiString;
   OpBlock : TOperationBlock;
   OpBlock : TOperationBlock;
   opsht : TOperationsHashTree;
   opsht : TOperationsHashTree;
+  minBlockResend : Cardinal;
+  resendOp : TPCOperation;
 begin
 begin
   Result := false;
   Result := false;
   errors := '';
   errors := '';
@@ -223,10 +231,22 @@ begin
     if Result then begin
     if Result then begin
       opsht := TOperationsHashTree.Create;
       opsht := TOperationsHashTree.Create;
       Try
       Try
+        j := Random(3); // j=0,1 or 2
+        If (Bank.LastBlockFound.OperationBlock.block>j) then
+          minBlockResend:=Bank.LastBlockFound.OperationBlock.block - j
+        else minBlockResend:=1;
         for i := 0 to FOperations.Count - 1 do begin
         for i := 0 to FOperations.Count - 1 do begin
-          opsht.AddOperationToHashTree(FOperations.Operation[i]);
-          // Add to sent operations
-          FSentOperations.Add(FOperations.Operation[i].Sha256,Bank.LastBlockFound.OperationBlock.block);
+          resendOp := FOperations.Operation[i];
+          j := FSentOperations.GetTag(resendOp.Sha256);
+          if (j=0) Or (j<=minBlockResend) then begin
+            // Only will "re-send" operations that where received on block <= minBlockResend
+            opsht.AddOperationToHashTree(resendOp);
+            // Add to sent operations
+            FSentOperations.SetTag(resendOp.Sha256,FOperations.OperationBlock.block); // Set tag new value
+            FSentOperations.Add(FOperations.Operation[i].Sha256,Bank.LastBlockFound.OperationBlock.block);
+          end else begin
+            TLog.NewLog(ltInfo,ClassName,'Sanitized operation not included to resend (j:'+IntToStr(j)+'>'+inttostr(minBlockResend)+') ('+inttostr(i+1)+'/'+inttostr(FOperations.Count)+'): '+FOperations.Operation[i].ToString);
+          end;
         end;
         end;
         if opsht.OperationsCount>0 then begin
         if opsht.OperationsCount>0 then begin
           TLog.NewLog(ltinfo,classname,'Resending '+IntToStr(opsht.OperationsCount)+' operations for new block');
           TLog.NewLog(ltinfo,classname,'Resending '+IntToStr(opsht.OperationsCount)+' operations for new block');
@@ -243,7 +263,7 @@ begin
           end;
           end;
         end;
         end;
         if j>0 then begin
         if j>0 then begin
-          TLog.NewLog(ltdebug,ClassName,'Buffer Sent operations: Deleted '+IntToStr(j)+' old operations');
+          TLog.NewLog(ltInfo,ClassName,'Buffer Sent operations: Deleted '+IntToStr(j)+' old operations');
         end;
         end;
         TLog.NewLog(ltdebug,ClassName,'Buffer Sent operations: '+IntToStr(FSentOperations.Count));
         TLog.NewLog(ltdebug,ClassName,'Buffer Sent operations: '+IntToStr(FSentOperations.Count));
         // Notify to clients
         // Notify to clients
@@ -282,7 +302,7 @@ begin
   End;
   End;
 end;
 end;
 
 
-function TNode.AddOperations(SenderConnection : TNetConnection; Operations : TOperationsHashTree; OperationsResult : TOperationsResumeList; var errors: AnsiString): Integer;
+function TNode.AddOperations(SenderConnection : TNetConnection; OperationsHashTree : TOperationsHashTree; OperationsResult : TOperationsResumeList; var errors: AnsiString): Integer;
   {$IFDEF BufferOfFutureOperations}
   {$IFDEF BufferOfFutureOperations}
   Procedure Process_BufferOfFutureOperations(valids_operations : TOperationsHashTree);
   Procedure Process_BufferOfFutureOperations(valids_operations : TOperationsHashTree);
   Var i,j, nAdded, nDeleted : Integer;
   Var i,j, nAdded, nDeleted : Integer;
@@ -318,20 +338,18 @@ function TNode.AddOperations(SenderConnection : TNetConnection; Operations : TOp
   {$ENDIF}
   {$ENDIF}
 Var
 Var
   i,j : Integer;
   i,j : Integer;
-  operationscomp : TPCOperationsComp;
   valids_operations : TOperationsHashTree;
   valids_operations : TOperationsHashTree;
   nc : TNetConnection;
   nc : TNetConnection;
   e : AnsiString;
   e : AnsiString;
-  mtl : TList;
   s : String;
   s : String;
   OPR : TOperationResume;
   OPR : TOperationResume;
   ActOp : TPCOperation;
   ActOp : TPCOperation;
-  sAcc : TAccount;
+  {$IFDEF BufferOfFutureOperations}sAcc : TAccount;{$ENDIF}
 begin
 begin
   Result := -1;
   Result := -1;
   if Assigned(OperationsResult) then OperationsResult.Clear;
   if Assigned(OperationsResult) then OperationsResult.Clear;
   if FDisabledsNewBlocksCount>0 then begin
   if FDisabledsNewBlocksCount>0 then begin
-    errors := Format('Cannot Add Operations due is adding disabled - OpCount:%d',[Operations.OperationsCount]);
+    errors := Format('Cannot Add Operations due is adding disabled - OpCount:%d',[OperationsHashTree.OperationsCount]);
     TLog.NewLog(ltinfo,Classname,errors);
     TLog.NewLog(ltinfo,Classname,errors);
     exit;
     exit;
   end;
   end;
@@ -340,7 +358,7 @@ begin
   valids_operations := TOperationsHashTree.Create;
   valids_operations := TOperationsHashTree.Create;
   try
   try
     TLog.NewLog(ltdebug,Classname,Format('AddOperations Connection:%s Operations:%d',[
     TLog.NewLog(ltdebug,Classname,Format('AddOperations Connection:%s Operations:%d',[
-      Inttohex(PtrInt(SenderConnection),8),Operations.OperationsCount]));
+      Inttohex(PtrInt(SenderConnection),8),OperationsHashTree.OperationsCount]));
     if Not TPCThread.TryProtectEnterCriticalSection(Self,4000,FLockNodeOperations) then begin
     if Not TPCThread.TryProtectEnterCriticalSection(Self,4000,FLockNodeOperations) then begin
       s := 'Cannot AddOperations due blocking lock operations node';
       s := 'Cannot AddOperations due blocking lock operations node';
       TLog.NewLog(lterror,Classname,s);
       TLog.NewLog(lterror,Classname,s);
@@ -350,17 +368,17 @@ begin
       {$IFDEF BufferOfFutureOperations}
       {$IFDEF BufferOfFutureOperations}
       Process_BufferOfFutureOperations(valids_operations);
       Process_BufferOfFutureOperations(valids_operations);
       {$ENDIF}
       {$ENDIF}
-      for j := 0 to Operations.OperationsCount-1 do begin
-        ActOp := Operations.GetOperation(j);
-        If (FOperations.OperationsHashTree.IndexOfOperation(ActOp)<0) And (FSentOperations.GetTag(ActOp.Sha256)=0) then begin
+      for j := 0 to OperationsHashTree.OperationsCount-1 do begin
+        ActOp := OperationsHashTree.GetOperation(j);
+        If (FOperations.OperationsHashTree.IndexOfOperation(ActOp)<0) then begin
           // Protocol 2 limitation: In order to prevent spam of operations without Fee, will protect it
           // Protocol 2 limitation: In order to prevent spam of operations without Fee, will protect it
           If (ActOp.OperationFee=0) And (Bank.SafeBox.CurrentProtocol>=CT_PROTOCOL_2) And
           If (ActOp.OperationFee=0) And (Bank.SafeBox.CurrentProtocol>=CT_PROTOCOL_2) And
              (FOperations.OperationsHashTree.CountOperationsBySameSignerWithoutFee(ActOp.SignerAccount)>=CT_MaxAccountOperationsPerBlockWithoutFee) then begin
              (FOperations.OperationsHashTree.CountOperationsBySameSignerWithoutFee(ActOp.SignerAccount)>=CT_MaxAccountOperationsPerBlockWithoutFee) then begin
             e := Format('Account %s zero fee operations per block limit:%d',[TAccountComp.AccountNumberToAccountTxtNumber(ActOp.SignerAccount),CT_MaxAccountOperationsPerBlockWithoutFee]);
             e := Format('Account %s zero fee operations per block limit:%d',[TAccountComp.AccountNumberToAccountTxtNumber(ActOp.SignerAccount),CT_MaxAccountOperationsPerBlockWithoutFee]);
             if (errors<>'') then errors := errors+' ';
             if (errors<>'') then errors := errors+' ';
-            errors := errors+'Op '+IntToStr(j+1)+'/'+IntToStr(Operations.OperationsCount)+':'+e;
+            errors := errors+'Op '+IntToStr(j+1)+'/'+IntToStr(OperationsHashTree.OperationsCount)+':'+e;
             TLog.NewLog(ltdebug,Classname,Format('AddOperation invalid/duplicated %d/%d: %s  - Error:%s',
             TLog.NewLog(ltdebug,Classname,Format('AddOperation invalid/duplicated %d/%d: %s  - Error:%s',
-              [(j+1),Operations.OperationsCount,ActOp.ToString,e]));
+              [(j+1),OperationsHashTree.OperationsCount,ActOp.ToString,e]));
             if Assigned(OperationsResult) then begin
             if Assigned(OperationsResult) then begin
               TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
               TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
               OPR.valid := false;
               OPR.valid := false;
@@ -370,12 +388,11 @@ begin
               OperationsResult.Add(OPR);
               OperationsResult.Add(OPR);
             end;
             end;
           end else begin
           end else begin
-            // Buffer to prevent cyclic sending new on 1.5.4
-            FSentOperations.Add(ActOp.Sha256,FOperations.OperationBlock.block);
             if (FOperations.AddOperation(true,ActOp,e)) then begin
             if (FOperations.AddOperation(true,ActOp,e)) then begin
               inc(Result);
               inc(Result);
+              FSentOperations.Add(ActOp.Sha256,FOperations.OperationBlock.block);
               valids_operations.AddOperationToHashTree(ActOp);
               valids_operations.AddOperationToHashTree(ActOp);
-              TLog.NewLog(ltdebug,Classname,Format('AddOperation %d/%d: %s',[(j+1),Operations.OperationsCount,ActOp.ToString]));
+              TLog.NewLog(ltdebug,Classname,Format('AddOperation %d/%d: %s',[(j+1),OperationsHashTree.OperationsCount,ActOp.ToString]));
               if Assigned(OperationsResult) then begin
               if Assigned(OperationsResult) then begin
                 TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
                 TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
                 OPR.NOpInsideBlock:=FOperations.Count-1;
                 OPR.NOpInsideBlock:=FOperations.Count-1;
@@ -384,9 +401,9 @@ begin
               end;
               end;
             end else begin
             end else begin
               if (errors<>'') then errors := errors+' ';
               if (errors<>'') then errors := errors+' ';
-              errors := errors+'Op '+IntToStr(j+1)+'/'+IntToStr(Operations.OperationsCount)+':'+e;
+              errors := errors+'Op '+IntToStr(j+1)+'/'+IntToStr(OperationsHashTree.OperationsCount)+':'+e;
               TLog.NewLog(ltdebug,Classname,Format('AddOperation invalid/duplicated %d/%d: %s  - Error:%s',
               TLog.NewLog(ltdebug,Classname,Format('AddOperation invalid/duplicated %d/%d: %s  - Error:%s',
-                [(j+1),Operations.OperationsCount,ActOp.ToString,e]));
+                [(j+1),OperationsHashTree.OperationsCount,ActOp.ToString,e]));
               if Assigned(OperationsResult) then begin
               if Assigned(OperationsResult) then begin
                 TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
                 TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
                 OPR.valid := false;
                 OPR.valid := false;
@@ -403,7 +420,7 @@ begin
                    ((sAcc.n_operation=ActOp.N_Operation) AND (sAcc.balance=0) And (ActOp.OperationFee>0) And (ActOp.OpType = CT_Op_Changekey)) then begin
                    ((sAcc.n_operation=ActOp.N_Operation) AND (sAcc.balance=0) And (ActOp.OperationFee>0) And (ActOp.OpType = CT_Op_Changekey)) then begin
                   If FBufferAuxWaitingOperations.IndexOfOperation(ActOp)<0 then begin
                   If FBufferAuxWaitingOperations.IndexOfOperation(ActOp)<0 then begin
                     FBufferAuxWaitingOperations.AddOperationToHashTree(ActOp);
                     FBufferAuxWaitingOperations.AddOperationToHashTree(ActOp);
-                    TLog.NewLog(ltInfo,Classname,Format('New FromBufferWaitingOperations %d/%d (new buffer size:%d): %s',[j+1,Operations.OperationsCount,FBufferAuxWaitingOperations.OperationsCount,ActOp.ToString]));
+                    TLog.NewLog(ltInfo,Classname,Format('New FromBufferWaitingOperations %d/%d (new buffer size:%d): %s',[j+1,OperationsHashTree.OperationsCount,FBufferAuxWaitingOperations.OperationsCount,ActOp.ToString]));
                   end;
                   end;
                 end;
                 end;
               end;
               end;
@@ -411,15 +428,29 @@ begin
             end;
             end;
           end;
           end;
         end else begin
         end else begin
-          errors := errors + 'Unable to add operation as it has already been added.';
-          {$IFDEF HIGHLOG}TLog.NewLog(ltdebug,Classname,Format('AddOperation made before %d/%d: %s',[(j+1),Operations.OperationsCount,ActOp.ToString]));{$ENDIF}
+          e := Format('AddOperation made before %d/%d: %s',[(j+1),OperationsHashTree.OperationsCount,ActOp.ToString]);
+          if (errors<>'') then errors := errors+' ';
+          errors := errors + e;
+          if Assigned(OperationsResult) then begin
+            TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
+            OPR.valid := false;
+            OPR.NOpInsideBlock:=-1;
+            OPR.OperationHash := '';
+            OPR.errors := e;
+            OperationsResult.Add(OPR);
+          end;
+          {$IFDEF HIGHLOG}TLog.NewLog(ltdebug,Classname,Format('AddOperation made before %d/%d: %s',[(j+1),OperationsHashTree.OperationsCount,ActOp.ToString]));{$ENDIF}
         end;
         end;
       end;
       end;
+      // Save operations buffer
+      If Result<>0 then begin
+        Bank.Storage.SavePendingBufferOperations(Self.Operations.OperationsHashTree);
+      end;
     finally
     finally
       FLockNodeOperations.Release;
       FLockNodeOperations.Release;
       if Result<>0 then begin
       if Result<>0 then begin
         TLog.NewLog(ltdebug,Classname,Format('Finalizing AddOperations Connection:%s Operations:%d valids:%d',[
         TLog.NewLog(ltdebug,Classname,Format('Finalizing AddOperations Connection:%s Operations:%d valids:%d',[
-          Inttohex(PtrInt(SenderConnection),8),Operations.OperationsCount,Result ]));
+          Inttohex(PtrInt(SenderConnection),8),OperationsHashTree.OperationsCount,Result ]));
       end;
       end;
     end;
     end;
     if Result=0 then exit;
     if Result=0 then exit;
@@ -751,19 +782,142 @@ begin
   if (acc.updated_block>0) Or (acc.account=0) then DoGetFromBlock(acc.updated_block,acc.balance,MaxDepth,0);
   if (acc.updated_block>0) Or (acc.account=0) then DoGetFromBlock(acc.updated_block,acc.balance,MaxDepth,0);
 end;
 end;
 
 
-function TNode.FindOperation(const OperationComp: TPCOperationsComp;
+function TNode.FindNOperation(block, account, n_operation: Cardinal;
+  var OpResume: TOperationResume): TSearchOperationResult;
+  // Note: block = 0 search in all blocks. If Block>0 must match a valid block with operation with this account
+var oprl : TOperationsResumeList;
+begin
+  oprl := TOperationsResumeList.Create;
+  try
+    Result := FindNOperations(account,block,block=0,n_operation,n_operation,oprl);
+    If oprl.Count>0 then begin
+      OpResume := oprl.OperationResume[0];
+    end else OpResume := CT_TOperationResume_NUL;
+  finally
+    oprl.Free;
+  end;
+end;
+
+function TNode.FindNOperations(account, start_block : Cardinal; allow_search_previous : Boolean; n_operation_low, n_operation_high: Cardinal; OpResumeList: TOperationsResumeList): TSearchOperationResult;
+var i : Integer;
+  op : TPCOperation;
+  aux_block, block : Cardinal;
+  OperationComp : TPCOperationsComp;
+  opr : TOperationResume;
+  n_operation : Cardinal;
+begin
+  OpResumeList.Clear;
+  Result := invalid_params;
+  block := start_block;
+  If (block>=Bank.BlocksCount) then exit; // Invalid block number
+  If (account>=Bank.AccountsCount) then exit; // Invalid account number
+  If (n_operation_high<n_operation_low) then exit;
+  n_operation := Bank.SafeBox.Account(account).n_operation;
+  if (n_operation>n_operation_high) then n_operation := n_operation_high;
+  If (block=0) then begin
+    // Start searching on pending blocks
+    Operations.Lock;
+    Try
+      For i:=Operations.Count-1 downto 0 do begin
+        op := Operations.Operation[i];
+        If (op.SignerAccount=account) then begin
+          If (op.N_Operation<=n_operation) then begin
+            TPCOperation.OperationToOperationResume(0,op,account,opr);
+            opr.Balance:=-1;
+            OpResumeList.Add(opr);
+            dec(n_operation);
+            Exit;
+          end;
+        end;
+      end;
+      block := Bank.SafeBox.Account(account).updated_block;
+    finally
+      Operations.Unlock;
+    end;
+  end;
+  // Search in previous blocks
+  OperationComp := TPCOperationsComp.Create(Nil);
+  Try
+    While (n_operation>0) And (n_operation>=n_operation_low) And (block>0) do begin
+      aux_block := block;
+      If Not Bank.LoadOperations(OperationComp,block) then begin
+        Result := blockchain_block_not_found; // Cannot continue searching!
+        exit;
+      end;
+      For i:=OperationComp.Count-1 downto 0 do begin
+        op := OperationComp.Operation[i];
+        if (op.SignerAccount=account) then begin
+          If (n_operation_high=n_operation_low) and (op.N_Operation=n_operation) // If searchin only 1 n_operation, n_operation must match
+            Or
+            (n_operation_high>n_operation_low) and (op.N_Operation<=n_operation) and (op.N_Operation>=n_operation_low) and (op.N_Operation<=n_operation_high) then begin
+            TPCOperation.OperationToOperationResume(block,op,account,opr);
+            opr.time:=Bank.SafeBox.Block(block).blockchainInfo.timestamp;
+            opr.NOpInsideBlock:=i;
+            opr.Balance:=-1;
+            OpResumeList.Add(opr);
+            if (n_operation>n_operation_low) then dec(n_operation)
+            else begin
+              Result := found;
+              Exit;
+            end;
+          end else begin
+            If (op.N_Operation < n_operation) then begin
+              If (n_operation_high>n_operation_low) then Result := found; // multiple search, result is found (not an error)
+              Exit // First occurrence is lower
+            end;
+          end;
+          block := op.Previous_Signer_updated_block;
+        end else if op.DestinationAccount=account then begin
+          block := op.Previous_Destination_updated_block;
+        end else if op.SellerAccount=account then begin
+          block := op.Previous_Seller_updated_block;
+        end;
+      end;
+      if (block>aux_block) then exit // Error... not found a valid block positioning
+      else if (block=aux_block) then begin
+        if ((start_block=0) Or (allow_search_previous)) then dec(block) // downgrade until found a block with operations
+        else Exit; // Not found in current block
+      end else if (start_block>0) and (not allow_search_previous) and (OpResumeList.Count=0) then Exit; // does not need to decrease
+    end;
+  finally
+    OperationComp.Free;
+  end;
+  Result := found;
+end;
+
+procedure TNode.InitSafeboxAndOperations;
+var opht : TOperationsHashTree;
+  oprl : TOperationsResumeList;
+  errors : AnsiString;
+  n : Integer;
+begin
+  Bank.DiskRestoreFromOperations(CT_MaxBlock);
+  opht := TOperationsHashTree.Create;
+  oprl := TOperationsResumeList.Create;
+  try
+    Bank.Storage.LoadPendingBufferOperations(opht); // New Build 2.1.4 to load pending operations buffer
+    n := AddOperations(Nil,opht,oprl,errors);
+    TLog.NewLog(ltInfo,ClassName,Format('Pending buffer restored operations:%d added:%d final_operations:%d errors:%s',[opht.OperationsCount,n,Operations.OperationsHashTree.OperationsCount,errors]));
+  finally
+    opht.Free;
+    oprl.Free;
+  end;
+end;
+
+function TNode.FindOperationExt(const OperationComp: TPCOperationsComp;
   const OperationHash: TRawBytes; var block: Cardinal;
   const OperationHash: TRawBytes; var block: Cardinal;
-  var operation_block_index: Integer): Boolean;
-  { With a OperationHash, search it }
+  var operation_block_index: Integer): TSearchOperationResult;
+{ With a OperationHash, search it }
 var account,n_operation : Cardinal;
 var account,n_operation : Cardinal;
   i : Integer;
   i : Integer;
   op : TPCOperation;
   op : TPCOperation;
   initial_block, aux_block : Cardinal;
   initial_block, aux_block : Cardinal;
   opHashValid, opHash_OLD : TRawBytes;
   opHashValid, opHash_OLD : TRawBytes;
+  md160 : TRawBytes;
 begin
 begin
-  Result := False;
+  Result := invalid_params;
   // Decode OperationHash
   // Decode OperationHash
-  If not TPCOperation.DecodeOperationHash(OperationHash,block,account,n_operation) then exit;
+  If not TPCOperation.DecodeOperationHash(OperationHash,block,account,n_operation,md160) then exit;
   initial_block := block;
   initial_block := block;
   //
   //
   If (account>=Bank.AccountsCount) then exit; // Invalid account number
   If (account>=Bank.AccountsCount) then exit; // Invalid account number
@@ -780,7 +934,7 @@ begin
             ((FBank.BlocksCount<CT_Protocol_Upgrade_v2_MinBlock) And (opHash_OLD=OperationHash)) then begin
             ((FBank.BlocksCount<CT_Protocol_Upgrade_v2_MinBlock) And (opHash_OLD=OperationHash)) then begin
             operation_block_index:=i;
             operation_block_index:=i;
             OperationComp.CopyFrom(FOperations);
             OperationComp.CopyFrom(FOperations);
-            Result := true;
+            Result := found;
             exit;
             exit;
           end;
           end;
         end;
         end;
@@ -794,9 +948,12 @@ begin
   end;
   end;
   if (block=0) or (block>=Bank.BlocksCount) then exit;
   if (block=0) or (block>=Bank.BlocksCount) then exit;
   // Search in previous blocks
   // Search in previous blocks
-  While (Not Result) And (block>0) do begin
+  While (block>0) do begin
     aux_block := block;
     aux_block := block;
-    If Not Bank.LoadOperations(OperationComp,block) then exit;
+    If Not Bank.LoadOperations(OperationComp,block) then begin
+      Result := blockchain_block_not_found;
+      exit;
+    end;
     For i:=OperationComp.Count-1 downto 0 do begin
     For i:=OperationComp.Count-1 downto 0 do begin
       op := OperationComp.Operation[i];
       op := OperationComp.Operation[i];
       if (op.SignerAccount=account) then begin
       if (op.SignerAccount=account) then begin
@@ -806,13 +963,13 @@ begin
           opHashValid := TPCOperation.OperationHashValid(op,initial_block);
           opHashValid := TPCOperation.OperationHashValid(op,initial_block);
           If (opHashValid=OperationHash) then begin
           If (opHashValid=OperationHash) then begin
             operation_block_index:=i;
             operation_block_index:=i;
-            Result := true;
+            Result := found;
             exit;
             exit;
           end else if (block<CT_Protocol_Upgrade_v2_MinBlock) then begin
           end else if (block<CT_Protocol_Upgrade_v2_MinBlock) then begin
             opHash_OLD := TPCOperation.OperationHash_OLD(op,initial_block);
             opHash_OLD := TPCOperation.OperationHash_OLD(op,initial_block);
             if (opHash_OLD=OperationHash) then begin
             if (opHash_OLD=OperationHash) then begin
               operation_block_index:=i;
               operation_block_index:=i;
-              Result := true;
+              Result := found;
               exit;
               exit;
             end else exit; // Not found!
             end else exit; // Not found!
           end else exit; // Not found!
           end else exit; // Not found!
@@ -832,6 +989,16 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TNode.FindOperation(const OperationComp: TPCOperationsComp;
+  const OperationHash: TRawBytes; var block: Cardinal;
+  var operation_block_index: Integer): Boolean;
+  { With a OperationHash, search it }
+var sor : TSearchOperationResult;
+begin
+  sor := FindOperationExt(OperationComp,OperationHash,block,operation_block_index);
+  Result := sor = found;
+end;
+
 procedure TNode.NotifyNetClientMessage(Sender: TNetConnection; const TheMessage: AnsiString);
 procedure TNode.NotifyNetClientMessage(Sender: TNetConnection; const TheMessage: AnsiString);
 Var i : Integer;
 Var i : Integer;
   s : AnsiString;
   s : AnsiString;

+ 105 - 9
Units/PascalCoin/URPC.pas

@@ -517,6 +517,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     jsonObject.GetAsVariant('subtype').Value:=OPR.OpSubtype;
     jsonObject.GetAsVariant('subtype').Value:=OPR.OpSubtype;
     jsonObject.GetAsVariant('account').Value:=OPR.AffectedAccount;
     jsonObject.GetAsVariant('account').Value:=OPR.AffectedAccount;
     jsonObject.GetAsVariant('signer_account').Value:=OPR.SignerAccount;
     jsonObject.GetAsVariant('signer_account').Value:=OPR.SignerAccount;
+    jsonObject.GetAsVariant('n_operation').Value:=OPR.n_operation;
     jsonObject.GetAsVariant('optxt').Value:=OPR.OperationTxt;
     jsonObject.GetAsVariant('optxt').Value:=OPR.OperationTxt;
     jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
     jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
     jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OPR.Fee);
     jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OPR.Fee);
@@ -2041,7 +2042,52 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := True;
     Result := True;
   end;
   end;
 
 
-Var c,c2 : Cardinal;
+  function FindNOperations : Boolean;
+  Var oprl : TOperationsResumeList;
+    start_block, account, n_operation_min, n_operation_max : Cardinal;
+    sor : TSearchOperationResult;
+    jsonarr : TPCJSONArray;
+    i : Integer;
+  begin
+    Result := False;
+    oprl := TOperationsResumeList.Create;
+    try
+      account := params.AsCardinal('account',MaxInt);
+      If (params.IndexOfName('n_operation_min')<0) Or (params.IndexOfName('n_operation_max')<0) then begin
+        ErrorNum:=CT_RPC_ErrNum_NotFound;
+        ErrorDesc:='Need n_operation_min and n_operation_max params';
+        exit;
+      end;
+      n_operation_min := params.AsCardinal('n_operation_min',0);
+      n_operation_max := params.AsCardinal('n_operation_max',0);
+      start_block := params.AsCardinal('start_block',0); // Optional: 0 = Search all
+      sor := FNode.FindNOperations(account,start_block,true,n_operation_min,n_operation_max,oprl);
+      Case sor of
+        found : Result := True;
+        invalid_params : begin
+            ErrorNum:=CT_RPC_ErrNum_NotFound;
+            ErrorDesc:='Not found using block/account/n_operation';
+            exit;
+          end;
+        blockchain_block_not_found : begin
+            ErrorNum := CT_RPC_ErrNum_InvalidBlock;
+            ErrorDesc:='Blockchain file does not contain all blocks to find';
+            exit;
+          end;
+      else Raise Exception.Create('ERROR DEV 20171120-7');
+      end;
+      jsonarr := jsonresponse.GetAsArray('result');
+      if oprl.Count>0 then begin;
+        for i:=0 to oprl.Count-1 do begin
+          FillOperationResumeToJSONObject(oprl.OperationResume[i],jsonarr.GetAsObject(jsonarr.Count));
+        end;
+      end;
+    finally
+      oprl.Free;
+    end;
+  end;
+
+Var c,c2,c3 : Cardinal;
   i,j,k,l : Integer;
   i,j,k,l : Integer;
   account : TAccount;
   account : TAccount;
   senderpubkey,destpubkey : TAccountKey;
   senderpubkey,destpubkey : TAccountKey;
@@ -2050,7 +2096,7 @@ Var c,c2 : Cardinal;
   pcops : TPCOperationsComp;
   pcops : TPCOperationsComp;
   ecpkey : TECPrivateKey;
   ecpkey : TECPrivateKey;
   opr : TOperationResume;
   opr : TOperationResume;
-  r : TRawBytes;
+  r1,r2 : TRawBytes;
   ocl : TOrderedCardinalList;
   ocl : TOrderedCardinalList;
   jsonarr : TPCJSONArray;
   jsonarr : TPCJSONArray;
   jso : TPCJSONObject;
   jso : TPCJSONObject;
@@ -2361,20 +2407,47 @@ begin
       FillOperationResumeToJSONObject(opr,GetResultArray.GetAsObject( FNode.Operations.Count-1-i ));
       FillOperationResumeToJSONObject(opr,GetResultArray.GetAsObject( FNode.Operations.Count-1-i ));
     end;
     end;
     Result := true;
     Result := true;
+  end else if (method='decodeophash') then begin
+    // Search for an operation based on "ophash"
+    r1 := TCrypto.HexaToRaw(params.AsString('ophash',''));
+    if (r1='') then begin
+      ErrorNum:=CT_RPC_ErrNum_NotFound;
+      ErrorDesc:='param ophash not found or invalid hexadecimal value "'+params.AsString('ophash','')+'"';
+      exit;
+    end;
+    If not TPCOperation.DecodeOperationHash(r1,c,c2,c3,r2) then begin
+      ErrorNum:=CT_RPC_ErrNum_NotFound;
+      ErrorDesc:='invalid ophash param value';
+      exit;
+    end;
+    GetResultObject.GetAsVariant('block').Value:=c;
+    GetResultObject.GetAsVariant('account').Value:=c2;
+    GetResultObject.GetAsVariant('n_operation').Value:=c3;
+    GetResultObject.GetAsVariant('md160hash').Value:=TCrypto.ToHexaString(r2);
+    Result := true;
   end else if (method='findoperation') then begin
   end else if (method='findoperation') then begin
     // Search for an operation based on "ophash"
     // Search for an operation based on "ophash"
-    r := TCrypto.HexaToRaw(params.AsString('ophash',''));
-    if (r='') then begin
+    r1 := TCrypto.HexaToRaw(params.AsString('ophash',''));
+    if (r1='') then begin
       ErrorNum:=CT_RPC_ErrNum_NotFound;
       ErrorNum:=CT_RPC_ErrNum_NotFound;
-      ErrorDesc:='param ophash not found or invalid value "'+params.AsString('ophash','')+'"';
+      ErrorDesc:='param ophash not found or invalid hexadecimal value "'+params.AsString('ophash','')+'"';
       exit;
       exit;
     end;
     end;
     pcops := TPCOperationsComp.Create(Nil);
     pcops := TPCOperationsComp.Create(Nil);
     try
     try
-      If not FNode.FindOperation(pcops,r,c,i) then begin
-        ErrorNum:=CT_RPC_ErrNum_NotFound;
-        ErrorDesc:='ophash not found: "'+params.AsString('ophash','')+'"';
-        exit;
+      Case FNode.FindOperationExt(pcops,r1,c,i) of
+        found : ;
+        invalid_params : begin
+            ErrorNum:=CT_RPC_ErrNum_NotFound;
+            ErrorDesc:='ophash not found: "'+params.AsString('ophash','')+'"';
+            exit;
+          end;
+        blockchain_block_not_found : begin
+            ErrorNum := CT_RPC_ErrNum_InternalError;
+            ErrorDesc:='Blockchain block '+IntToStr(c)+' not found to search ophash: "'+params.AsString('ophash','')+'"';
+            exit;
+          end;
+      else Raise Exception.Create('ERROR DEV 20171120-4');
       end;
       end;
       If not TPCOperation.OperationToOperationResume(c,pcops.Operation[i],pcops.Operation[i].SignerAccount,opr) then begin
       If not TPCOperation.OperationToOperationResume(c,pcops.Operation[i],pcops.Operation[i].SignerAccount,opr) then begin
         ErrorNum := CT_RPC_ErrNum_InternalError;
         ErrorNum := CT_RPC_ErrNum_InternalError;
@@ -2388,6 +2461,29 @@ begin
     finally
     finally
       pcops.Free;
       pcops.Free;
     end;
     end;
+  end else if (method='findnoperation') then begin
+    // Search for an operation signed by "account" and with "n_operation", start searching "block" (0=all)
+    // "block" = 0 search in all blocks, pending operations included
+    Case FNode.FindNOperation(params.AsCardinal('block',0),params.AsCardinal('account',MaxInt),params.AsCardinal('n_operation',0),opr) of
+      found : ;
+      invalid_params : begin
+          ErrorNum:=CT_RPC_ErrNum_NotFound;
+          ErrorDesc:='Not found using block/account/n_operation';
+          exit;
+        end;
+      blockchain_block_not_found : begin
+          ErrorNum := CT_RPC_ErrNum_InvalidBlock;
+          ErrorDesc:='Blockchain file does not contain all blocks to find';
+          exit;
+        end;
+    else Raise Exception.Create('ERROR DEV 20171120-5');
+    end;
+    FillOperationResumeToJSONObject(opr,GetResultObject);
+    Result := True;
+  end else if (method='findnoperations') then begin
+    // Search for all operations signed by "account" and n_operation value between "n_operation_min" and "n_operation_max", start searching at "block" (0=all)
+    // "block" = 0 search in all blocks, pending operations included
+    Result := findNOperations;
   end else if (method='sendto') then begin
   end else if (method='sendto') then begin
     // Sends "amount" coins from "sender" to "target" with "fee"
     // Sends "amount" coins from "sender" to "target" with "fee"
     // If "payload" is present, it will be encoded using "payload_method"
     // If "payload" is present, it will be encoded using "payload_method"

+ 1 - 1
Units/PascalCoin/upcdaemon.pas

@@ -205,7 +205,7 @@ begin
         FNode.Bank.StorageClass := TFileStorage;
         FNode.Bank.StorageClass := TFileStorage;
         TFileStorage(FNode.Bank.Storage).DatabaseFolder := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'Data';
         TFileStorage(FNode.Bank.Storage).DatabaseFolder := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'Data';
         // Reading database
         // Reading database
-        FNode.Node.Bank.DiskRestoreFromOperations(CT_MaxBlock);
+        FNode.InitSafeboxAndOperations;
         FWalletKeys.SafeBox := FNode.Node.Bank.SafeBox;
         FWalletKeys.SafeBox := FNode.Node.Bank.SafeBox;
         FNode.Node.NetServer.Port:=FIniFile.ReadInteger(CT_INI_SECTION_GLOBAL,CT_INI_IDENT_NODE_PORT,CT_NetServer_Port);
         FNode.Node.NetServer.Port:=FIniFile.ReadInteger(CT_INI_SECTION_GLOBAL,CT_INI_IDENT_NODE_PORT,CT_NetServer_Port);
         FNode.Node.NetServer.MaxConnections:=FIniFile.ReadInteger(CT_INI_SECTION_GLOBAL,CT_INI_IDENT_NODE_MAX_CONNECTIONS,CT_MaxClientsConnected);
         FNode.Node.NetServer.MaxConnections:=FIniFile.ReadInteger(CT_INI_SECTION_GLOBAL,CT_INI_IDENT_NODE_MAX_CONNECTIONS,CT_MaxClientsConnected);