PascalCoin преди 8 години
родител
ревизия
8b6dfd0d65

BIN
PascalCoinWallet.res


+ 17 - 0
README.md

@@ -34,6 +34,23 @@ Also, consider a donation at PascalCoin development account: "0-10"
 
 ## History:  
 
+### Build 1.4.0.0 - 2016-12-30
+
+- JSON-RPC changes:
+  - Added method "signsendto" to allow a off-line wallet sign transaction operations without being syncrhonized
+  - Added method "signchangekey" to allow a off-line wallet sign change key operations without being syncrhonized
+  - Added method "executeoperations" to allow a on-line wallet execute operations signed off-line
+  - Added method "operationsinfo" that will decode operations signed off-line
+  - Added param "max" at JSON-RPC method "getblocks"
+  - Changed param name "deep" for "depht" in "getaccountoperations". Deep will be available for compatibility.
+  - Changed result for "changekeys". Now returns a "JSON operation object" array with each change key operation result
+  - Changes on "JSON Operation object": May return a "valid" param and "errors" param
+- Corrected a memory leak when processing JSON-RPC calls
+- Better performance in connection protocol
+- Updated protocol available to 1
+- Updated net protocol to 3-4
+- Important bug corrected
+
 ### Build 1.3.0.0 - 2016-11-24
 
 - JSON-RPC modifications:

+ 17 - 0
README.txt

@@ -34,6 +34,23 @@ Also, consider a donation at PascalCoin development account: "0-10"
 
 ## History:  
 
+### Build 1.4.0.0 - 2016-12-30
+
+- JSON-RPC changes:
+  - Added method "signsendto" to allow a off-line wallet sign transaction operations without being syncrhonized
+  - Added method "signchangekey" to allow a off-line wallet sign change key operations without being syncrhonized
+  - Added method "executeoperations" to allow a on-line wallet execute operations signed off-line
+  - Added method "operationsinfo" that will decode operations signed off-line
+  - Added param "max" at JSON-RPC method "getblocks"
+  - Changed param name "deep" for "depht" in "getaccountoperations". Deep will be available for compatibility.
+  - Changed result for "changekeys". Now returns a "JSON operation object" array with each change key operation result
+  - Changes on "JSON Operation object": May return a "valid" param and "errors" param
+- Corrected a memory leak when processing JSON-RPC calls
+- Better performance in connection protocol
+- Updated protocol available to 1
+- Updated net protocol to 3-4
+- Important bug corrected
+
 ### Build 1.3.0.0 - 2016-11-24
 
 - JSON-RPC modifications:

+ 4 - 0
Units/Forms/UFRMOperation.dfm

@@ -101,6 +101,10 @@ object FRMOperation: TFRMOperation
     TabOrder = 1
     object tsOperation: TTabSheet
       TabVisible = False
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       object gbOperation: TGroupBox
         Left = 13
         Top = 8

+ 1 - 1
Units/Forms/UFRMOperation.pas

@@ -237,7 +237,7 @@ begin
       if Application.MessageBox(PChar('Execute this operation:'+#10+#10+operation_to_string+#10+#10+'Note: This operation will be transmitted to the network!'),
         PChar(Application.Title),MB_YESNO+MB_ICONINFORMATION+MB_DEFBUTTON2)<>IdYes then exit;
     end;
-    i := FNode.AddOperations(nil,ops,errors);
+    i := FNode.AddOperations(nil,ops,Nil,errors);
     if (i=ops.OperationsCount) then begin
       Application.MessageBox(PChar('Successfully executed '+inttostr(i)+' operations!'+#10+#10+operation_to_string),PChar(Application.Title),MB_OK+MB_ICONINFORMATION);
       ModalResult := MrOk;

+ 1 - 13
Units/Forms/UFRMWallet.dfm

@@ -374,10 +374,6 @@ object FRMWallet: TFRMWallet
     OnChange = PageControlChange
     object tsMyAccounts: TTabSheet
       Caption = 'Accounts Explorer'
-      ExplicitLeft = 0
-      ExplicitTop = 0
-      ExplicitWidth = 0
-      ExplicitHeight = 0
       object Splitter1: TSplitter
         Left = 380
         Top = 66
@@ -981,10 +977,6 @@ object FRMWallet: TFRMWallet
     object tsNodeStats: TTabSheet
       Caption = 'Node Stats'
       ImageIndex = 3
-      ExplicitLeft = 0
-      ExplicitTop = 0
-      ExplicitWidth = 0
-      ExplicitHeight = 0
       DesignSize = (
         841
         404)
@@ -1047,10 +1039,6 @@ object FRMWallet: TFRMWallet
     object tsMessages: TTabSheet
       Caption = 'Messages'
       ImageIndex = 6
-      ExplicitLeft = 0
-      ExplicitTop = 0
-      ExplicitWidth = 0
-      ExplicitHeight = 0
       DesignSize = (
         841
         404)
@@ -1284,7 +1272,7 @@ object FRMWallet: TFRMWallet
     Left = 105
     Top = 180
     Bitmap = {
-      494C010102000800940110003000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
+      494C010102000800A40110003000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
       0000000000003600000028000000400000003000000001002000000000000030
       0000000000000000000000000000000000000000000000000000000000000000
       0000000000000000002A292929D60B0B0BF4111111EE0000006B000000000000

+ 8 - 8
Units/Forms/UFRMWallet.lfm

@@ -1,7 +1,7 @@
 object FRMWallet: TFRMWallet
-  Left = 360
+  Left = 389
   Height = 600
-  Top = 328
+  Top = 201
   Width = 865
   Caption = 'Pascal Coin Wallet, JSON-RPC Miner & Explorer'
   ClientHeight = 600
@@ -382,9 +382,9 @@ object FRMWallet: TFRMWallet
     Height = 488
     Top = 91
     Width = 865
-    ActivePage = tsMessages
+    ActivePage = tsMyAccounts
     Align = alClient
-    TabIndex = 6
+    TabIndex = 0
     TabOrder = 2
     OnChange = PageControlChange
     object tsMyAccounts: TTabSheet
@@ -523,11 +523,11 @@ object FRMWallet: TFRMWallet
             ParentColor = False
           end
           object Label19: TLabel
-            Left = 120
+            Left = 136
             Height = 13
             Top = 10
-            Width = 104
-            Caption = 'Accounts Balance:'
+            Width = 49
+            Caption = 'Balance:'
             ParentColor = False
           end
           object lblAccountsCount: TLabel
@@ -539,7 +539,7 @@ object FRMWallet: TFRMWallet
             ParentColor = False
           end
           object lblAccountsBalance: TLabel
-            Left = 214
+            Left = 200
             Height = 13
             Top = 10
             Width = 21

+ 1 - 0
Units/Forms/UFRMWallet.pas

@@ -345,6 +345,7 @@ begin
     // 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;

+ 65 - 1
Units/Forms/UFRMWalletKeys.dfm

@@ -63,7 +63,7 @@ object FRMWalletKeys: TFRMWalletKeys
   object lblKeysEncrypted: TLabel
     Left = 30
     Top = 15
-    Width = 346
+    Width = 274
     Height = 39
     AutoSize = False
     Caption = 'lblKeysEncrypted'
@@ -719,6 +719,70 @@ object FRMWalletKeys: TFRMWalletKeys
     TabOrder = 11
     OnClick = bbImportKeysFileClick
   end
+  object bbLock: TBitBtn
+    Left = 310
+    Top = 8
+    Width = 66
+    Height = 36
+    Caption = 'Lock'
+    DoubleBuffered = True
+    Glyph.Data = {
+      76060000424D7606000000000000360400002800000018000000180000000100
+      0800000000004002000000000000000000000001000000010000000000000101
+      0100020202000303030004040400050505000606060007070700080808000909
+      09000A0A0A000B0B0B000C0C0C000D0D0D000E0E0E000F0F0F00101010001111
+      1100121212001313130014141400151515001616160017171700181818001919
+      19001A1A1A001B1B1B001C1C1C001D1D1D001E1E1E001F1F1F00202020002121
+      2100222222002323230024242400252525002626260027272700282828002929
+      29002A2A2A002B2B2B002C2C2C002D2D2D002E2E2E002F2F2F00303030003131
+      3100323232003333330034343400353535003636360037373700383838003939
+      39003A3A3A003B3B3B003C3C3C003D3D3D003E3E3E003F3F3F00404040004141
+      4100424242004343430044444400454545004646460047474700484848004949
+      49004A4A4A004B4B4B004C4C4C004D4D4D004E4E4E004F4F4F00505050005151
+      5100525252005353530054545400555555006A4B6A00853F8500B626B600E30F
+      E300F803F800FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00F51CFE00E841FC00DE5FFC00D492FB00CDB6FA00CACDFA00CADCFA00CAE5
+      FA00CAEAFA00CDEEFA00CDF1FA00C7F0FA00BBEEF900B1EAF800AAE8F800A5E6
+      F8009EE2F7009BE0F70098E0F70095DCF70093DBF80091DAF80090DAF7008EDC
+      F6008BDDF50088DDF40085DDF40082DDF3007FDCF3007ADCF20079DAF10077D6
+      EE0076D1EB0074CCE8006FC6E40069C5E20063C4E1005CC1DF0056C1E00050C0
+      E0004DC1E10048C0E00045BEDF0042BBDE0040BBDE003DB9DD003AB9DD0037B8
+      DC0035B6DB0033B5DA0031B3D9002EB1D8002AAFD70025ACD4001EA9D30019A6
+      D10015A5D00013A3CF0011A2CE0010A0CC00109ECA00119ECA00139DC800169B
+      C4001999C1001B94BB001A91B8001C8DB4001E90B7002092B8008A8A8A8A8A8A
+      8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A
+      8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8AF6E7E4E7ECEFF78A8A8A8A8A8A8A8A8A
+      8A8A8A8A8A8A8AF5F0CECECECED0D4DCE7F68A8A8A8A8A8A8A8A8A8A8A8AF5F2
+      EFCDCDD0CFD0DDDACDD6E78A8A8A8A8A8A8A8A8A8A8AF3EEEECACACDD0D0E1DE
+      CECECEDF8A8A8A8A8A8A8A8A8A8AF1E9EDC8C8CACBDDF9E0D4CFD0CEF28A8A8A
+      8A8A8A8A8A8AF0E6EAC6C6C8C8DEFDE7DBCED0D0F28A8A8A8A8A8A8A8A8AEFE2
+      E7C4C4C6C7C9FDFED0D0D0D0F28A8A8A8A8A8A8A8A8AEDD9E3C4C4C4C4C6DFFF
+      CCCFD0D0F28A8A8A8A8A8A8A8A8AEDE2EDCBCDD2D2CDC9C9C8CACED0F28A8A8A
+      8A8A8A8A8A8AF2E3E2E7E6E6ECEFECE1D9CAC9E2F28A8A8A8A8A8A8A8A8AF3D3
+      DEF6F9D6D7E2E9F2F5EEDAE1F28A8A8A8A8A8A8A8A8A8AF1EFF6FACCD2D7E2EF
+      F8F9EBF1F28A8A8A8A8A8A8A8A8A8A8AFDF5FBDFD6D7D9FAF9FBEBF68A8A8A8A
+      8A8A8A8A8A8A8A8A8AF4FC8A8AF1EFFAF8FBF58A8A8A8A8A8A8A8A8A8A8A8A8A
+      8AE8F98A8A8A8AFDF6FD8A8A8A8A8A8A8A8A8A8A8A8A8A8A8AE0D9FD8A8A8AFD
+      F5FD8A8A8A8A8A8A8A8A8A8A8A8A8A8A8AFED2D9FF8AFDFAF68A8A8A8A8A8A8A
+      8A8A8A8A8A8A8A8A8A8AFFD7D1D7DAE6FC8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A
+      8A8A8A8AFFE5E7FD8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A
+      8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A
+      8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A8A}
+    ParentDoubleBuffered = False
+    TabOrder = 12
+    OnClick = bbLockClick
+  end
   object SaveDialog: TSaveDialog
     DefaultExt = 'dat'
     Filter = 'Wallet keys file|*.dat|All files|*.*'

+ 83 - 13
Units/Forms/UFRMWalletKeys.lfm

@@ -19,7 +19,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 30
     Height = 13
     Top = 298
-    Width = 80
+    Width = 93
     Caption = 'Encryption type:'
     ParentColor = False
   end
@@ -27,7 +27,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 125
     Height = 13
     Top = 298
-    Width = 54
+    Width = 63
     Caption = '000000000'
     ParentColor = False
   end
@@ -35,7 +35,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 30
     Height = 13
     Top = 317
-    Width = 51
+    Width = 60
     Caption = 'Key name:'
     ParentColor = False
   end
@@ -43,7 +43,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 125
     Height = 13
     Top = 317
-    Width = 329
+    Width = 313
     AutoSize = False
     Caption = '000000000'
     ParentColor = False
@@ -52,7 +52,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 30
     Height = 13
     Top = 336
-    Width = 58
+    Width = 67
     Caption = 'Private key:'
     ParentColor = False
   end
@@ -60,7 +60,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 30
     Height = 13
     Top = 355
-    Width = 80
+    Width = 92
     Caption = '(In hexa format)'
     ParentColor = False
   end
@@ -68,7 +68,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 30
     Height = 39
     Top = 15
-    Width = 346
+    Width = 259
     AutoSize = False
     Caption = 'lblKeysEncrypted'
     Font.Color = clBlack
@@ -86,7 +86,9 @@ object FRMWalletKeys: TFRMWalletKeys
     Width = 346
     ItemHeight = 0
     OnClick = lbWalletKeysClick
+    ScrollWidth = 344
     TabOrder = 0
+    TopIndex = -1
   end
   object bbExportPrivateKey: TBitBtn
     Left = 382
@@ -169,10 +171,10 @@ object FRMWalletKeys: TFRMWalletKeys
     TabStop = False
   end
   object bbChangeName: TBitBtn
-    Left = 460
+    Left = 440
     Height = 25
     Top = 305
-    Width = 81
+    Width = 101
     Caption = 'Change Name'
     OnClick = bbChangeNameClick
     TabOrder = 8
@@ -576,7 +578,7 @@ object FRMWalletKeys: TFRMWalletKeys
     Left = 30
     Height = 36
     Top = 425
-    Width = 201
+    Width = 225
     Caption = 'Export all Wallet Keys to a file'
     Glyph.Data = {
       F6060000424DF606000000000000360000002800000018000000180000000100
@@ -640,10 +642,10 @@ object FRMWalletKeys: TFRMWalletKeys
     TabOrder = 10
   end
   object bbImportKeysFile: TBitBtn
-    Left = 247
+    Left = 264
     Height = 36
-    Top = 425
-    Width = 174
+    Top = 424
+    Width = 209
     Caption = 'Import a Wallet Keys File'
     Glyph.Data = {
       F6060000424DF606000000000000360000002800000018000000180000000100
@@ -706,6 +708,74 @@ object FRMWalletKeys: TFRMWalletKeys
     OnClick = bbImportKeysFileClick
     TabOrder = 11
   end
+  object bbLock: TBitBtn
+    Left = 296
+    Height = 38
+    Top = 8
+    Width = 79
+    Caption = 'Lock'
+    Font.Color = clWindowText
+    Font.Height = -13
+    Font.Name = 'Tahoma'
+    Font.Style = [fsBold]
+    Glyph.Data = {
+      76060000424D7606000000000000360400002800000018000000180000000100
+      0800000000004002000000000000000000000001000000010000000000000101
+      0100020202000303030004040400050505000606060007070700080808000909
+      09000A0A0A000B0B0B000C0C0C000D0D0D000E0E0E000F0F0F00101010001111
+      1100121212001313130014141400151515001616160017171700181818001919
+      19001A1A1A001B1B1B001C1C1C001D1D1D001E1E1E001F1F1F00202020002121
+      2100222222002323230024242400252525002626260027272700282828002929
+      29002A2A2A002B2B2B002C2C2C002D2D2D002E2E2E002F2F2F00303030003131
+      3100323232003333330034343400353535003636360037373700383838003939
+      39003A3A3A003B3B3B003C3C3C003D3D3D003E3E3E003F3F3F00404040004141
+      4100424242004343430044444400454545004646460047474700484848004949
+      49004A4A4A004B4B4B004C4C4C004D4D4D004E4E4E004F4F4F00505050005151
+      5100525252005353530054545400555555005656560057575700585858005959
+      59005A5A5A005B5B5B005C5C5C005D5D5D005E5E5E00685968007C4F7C009441
+      9400B72CB700DD15DD00F506F500FD01FD00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00
+      FE00FE00FE00FE00FE00FE00FE00F008F900D517F000BD24E800A830E1007B4B
+      D0005065C1003774B7002B7DB100237FAC001F82AD001C83AD001785AE001687
+      AF001489B100128AB300108CB6000F8EB9000D91BB000C94BF000A97C3000A99
+      C5000A9AC7000B9BC8000B9CCA000C9ECC000C9FCD000DA0CE000DA1CE000EA2
+      CF0011A3CF0015A4CF0018A5CF001CA7D10021A9D1002AAAD00035AACD0035AD
+      D00035AFD30035B1D60037B5D8003AB8DB003EB9DC0040BBDD0045BEDE004CC1
+      E00055C6E3005BC8E40061CAE40066CBE3006BCCE30072D3E60078D8EB0080D8
+      EE0083D9F10085DCF40088DBF5008BDBF6008EDAF70091DAF70095DCF70097E0
+      F7009EE3F800A4E7F900A9E9F900AEE9F900B5EBF900B2EBF800919191919191
+      919191919191919191919191919191919191919191919191D5E7E8E7E3DED591
+      919191919191919191919191919191D6DDF7F8F8F7F6F4EEE6D5919191919191
+      919191919191D7DADEF8F8F6F7F6EDF1F8F1E69191919191919191919191DADE
+      DFFAFAF8F7F7E9EDF7F7F8EB91919191919191919191DDE5E3FCFCFAFAEEE0E9
+      F3F7F7F7DC919191919191919191DEE7E4FEFEFCFCEDC5E0EFF7F6F6DC919191
+      919191919191DEEAE7FEFEFEFDFBC4C4F5F7F6F6DC919191919191919191E3EC
+      E9FEFEFEFEFEE0C3F9F7F6F6DC919191919191919191E3E9E3F8F8F1F1F8FBFB
+      FCFAF7F6DC919191919191919191DCEAEAE0E1E9E5E3DEE4EBF2FAF9DC919191
+      919191919191DBF3EEC5C5EFF3EBE5DDD9D6E6F5DC91919191919191919191D5
+      E8EDF1FAF6F0EAE4DAD1D2DBDC919191919191919191919191DBE8F0F3F3F3E4
+      D0CFE4D8DC919191919191919191919191919191D5DEE4DED2CFD4D491919191
+      91919191919191919191919191919191D2CE9191919191919191919191919191
+      91C8C89191919191D2CE919191919191919191919191919191D9D09191919191
+      D2CE919191919191919191919191919191E6E39191919191D2CE919191919191
+      919191919191919191E0F3CE91919191D2CE9191919191919191919191919191
+      9191EFF6E1C9C8CDD9CC91919191919191919191919191919191C9EDF9F3F3EB
+      DC91919191919191919191919191919191919191CBE0E1CF9191919191919191
+      9191919191919191919191919191919191919191919191919191
+    }
+    OnClick = bbLockClick
+    ParentFont = False
+    TabOrder = 12
+  end
   object SaveDialog: TSaveDialog
     DefaultExt = '.dat'
     Filter = 'Wallet keys file|*.dat|All files|*.*'

+ 8 - 0
Units/Forms/UFRMWalletKeys.pas

@@ -51,6 +51,7 @@ type
     SaveDialog: TSaveDialog;
     bbImportKeysFile: TBitBtn;
     OpenDialog: TOpenDialog;
+    bbLock: TBitBtn;
     procedure FormCreate(Sender: TObject);
     procedure bbChangeNameClick(Sender: TObject);
     procedure bbExportPrivateKeyClick(Sender: TObject);
@@ -63,6 +64,7 @@ type
     procedure bbUpdatePasswordClick(Sender: TObject);
     procedure bbExportAllWalletKeysClick(Sender: TObject);
     procedure bbImportKeysFileClick(Sender: TObject);
+    procedure bbLockClick(Sender: TObject);
   private
     FOldOnChanged : TNotifyEvent;
     FWalletKeys: TWalletKeys;
@@ -341,6 +343,11 @@ begin
   end;
 end;
 
+procedure TFRMWalletKeys.bbLockClick(Sender: TObject);
+begin
+  FWalletKeys.LockWallet;
+end;
+
 procedure TFRMWalletKeys.bbUpdatePasswordClick(Sender: TObject);
 Var s,s2 : String;
 begin
@@ -492,6 +499,7 @@ begin
     lbWalletKeys.Items.Clear;
     lblKeysEncrypted.Caption := '';
     if not assigned(FWalletKeys) then exit;
+    bbLock.Enabled := (FWalletKeys.IsValidPassword) And (FWalletKeys.WalletPassword<>'');
     If FWalletKeys.IsValidPassword then begin
       if FWalletKeys.WalletPassword='' then lblKeysEncrypted.Caption := 'Wallet without password'
       else lblKeysEncrypted.Caption := 'Wallet is password protected';

+ 11 - 9
Units/PascalCoin/UAccounts.pas

@@ -51,7 +51,7 @@ Type
   TAccount = Record
     account: Cardinal;        // FIXED value. Account number
     accountkey: TAccountKey;  // Public EC
-    balance: UInt64;          // Balance, allways >= 0
+    balance: UInt64;          // Balance, always >= 0
     updated_block: Cardinal;  // Number of block where was updated
     n_operation: Cardinal;    // count number of owner operations (when receive, this is not updated)
     //
@@ -340,15 +340,17 @@ begin
   BNBase := TBigNum.Create(1);
   try
     for i := length(HumanReadable) downto 1 do begin
-      j := pos(HumanReadable[i],CT_Base58);
-      if j=0 then begin
-        errors := 'Invalid char "'+HumanReadable[i]+'" at pos '+inttostr(i)+'/'+inttostr(length(HumanReadable));
-        exit;
+      if (HumanReadable[i]<>' ') then begin
+        j := pos(HumanReadable[i],CT_Base58);
+        if j=0 then begin
+          errors := 'Invalid char "'+HumanReadable[i]+'" at pos '+inttostr(i)+'/'+inttostr(length(HumanReadable));
+          exit;
+        end;
+        BNAux.Value := j-1;
+        BNAux.Multiply(BNBase);
+        BN.Add(BNAux);
+        BNBase.Multiply(length(CT_Base58));
       end;
-      BNAux.Value := j-1;
-      BNAux.Multiply(BNBase);
-      BN.Add(BNAux);
-      BNBase.Multiply(length(CT_Base58));
     end;
     // Last 8 hexa chars are the checksum of others
     s1 := Copy(BN.HexaValue,3,length(BN.HexaValue));

+ 103 - 60
Units/PascalCoin/UBlockChain.pas

@@ -146,6 +146,7 @@ Type
     OriginalPayload : TRawBytes;
     PrintablePayload : AnsiString;
     OperationHash : TRawBytes;
+    errors : AnsiString;
   end;
 
   TOperationsResumeList = Class
@@ -170,6 +171,7 @@ Type
   Protected
     FPrevious_Sender_updated_block: Cardinal;
     FPrevious_Destination_updated_block : Cardinal;
+    FHasValidSignature : Boolean;
   public
     function GetOperationBufferToHash: TRawBytes; virtual; abstract;
     function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean; virtual; abstract;
@@ -188,6 +190,7 @@ Type
     function LoadFromStorage(Stream: TStream): Boolean;
     Property Previous_Sender_updated_block : Cardinal read FPrevious_Sender_updated_block;
     Property Previous_Destination_updated_block : Cardinal read FPrevious_Destination_updated_block;
+    Property HasValidSignature : Boolean read FHasValidSignature;
     Class function OperationHash(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function DecodeOperationHash(Const operationHash : TRawBytes; var block, account,n_operation : Cardinal) : Boolean;
   End;
@@ -211,6 +214,8 @@ Type
     Procedure CopyFromHashTree(Sender : TOperationsHashTree);
     Property TotalAmount : Int64 read FTotalAmount;
     Property TotalFee : Int64 read FTotalFee;
+    function SaveOperationsHashTreeToStream(Stream: TStream; SaveToStorage : Boolean): Boolean;
+    function LoadOperationsHashTreeFromStream(Stream: TStream; LoadingFromStorage : Boolean; var errors : AnsiString): Boolean;
   End;
 
   { TPCOperationsComp }
@@ -325,6 +330,9 @@ Type
     Function DoRestoreBank(max_block : Int64) : Boolean; virtual; abstract;
     Procedure DoDeleteBlockChainBlocks(StartingDeleteBlock : Cardinal); virtual; abstract;
     Function BlockExists(Block : Cardinal) : Boolean; virtual; abstract;
+    function GetFirstBlockNumber: Int64; virtual; abstract;
+    function GetLastBlockNumber: Int64; virtual; abstract;
+    function DoInitialize:Boolean; virtual; abstract;
   public
     Function LoadBlockChainBlock(Operations : TPCOperationsComp; Block : Cardinal) : Boolean;
     Function SaveBlockChainBlock(Operations : TPCOperationsComp) : Boolean;
@@ -337,6 +345,9 @@ Type
     Property ReadOnly : Boolean read FReadOnly write SetReadOnly;
     Property Bank : TPCBank read FBank write SetBank;
     Procedure CopyConfiguration(Const CopyFrom : TStorage); virtual;
+    Property FirstBlock : Int64 read GetFirstBlockNumber;
+    Property LastBlock : Int64 read GetLastBlockNumber;
+    Function Initialize : Boolean;
   End;
 
   TStorageClass = Class of TStorage;
@@ -390,7 +401,7 @@ Type
   End;
 
 Const
-  CT_TOperationResume_NUL : TOperationResume = (valid:false;Block:0;NOpInsideBlock:-1;OpType:0;time:0;AffectedAccount:0;SenderAccount:-1;DestAccount:-1;newKey:(EC_OpenSSL_NID:0;x:'';y:'');OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:'';PrintablePayload:'';OperationHash:'');
+  CT_TOperationResume_NUL : TOperationResume = (valid:false;Block:0;NOpInsideBlock:-1;OpType:0;time:0;AffectedAccount:0;SenderAccount:-1;DestAccount:-1;newKey:(EC_OpenSSL_NID:0;x:'';y:'');OperationTxt:'';Amount:0;Fee:0;Balance:0;OriginalPayload:'';PrintablePayload:'';OperationHash:'';errors:'');
 
   CT_OperationBlock_NUL : TOperationBlock = (block:0;account_key:(EC_OpenSSL_NID:0;x:'';y:'');reward:0;fee:0;protocol_version:0;
     protocol_available:0;timestamp:0;compact_target:0;nonce:0;block_payload:'';initial_safe_box_hash:'';operations_hash:'';proof_of_work:'');
@@ -612,7 +623,8 @@ begin
     FIsRestoringFromFile := true;
     try
       Clear;
-      Storage.RestoreBank(max_block);
+      Storage.Initialize;
+      Storage.RestoreBank(Storage.LastBlock);
       // Restore last blockchain
       if BlocksCount>0 then begin
         if Not Storage.LoadBlockChainBlock(FLastBlockCache,BlocksCount-1) then begin
@@ -966,8 +978,7 @@ end;
 var
   _OperationsClass: Array of TPCOperationClass;
 
-function TPCOperationsComp.AddOperation(Execute: Boolean; op: TPCOperation;
-  var errors: AnsiString): Boolean;
+function TPCOperationsComp.AddOperation(Execute: Boolean; op: TPCOperation; var errors: AnsiString): Boolean;
 Begin
   Lock;
   Try
@@ -1331,15 +1342,10 @@ end;
 
 function TPCOperationsComp.LoadBlockFromStreamExt(Stream: TStream;
   LoadingFromStorage: Boolean; var errors: AnsiString): Boolean;
-Var
-  c, i, lastfee: Cardinal;
+Var i: Cardinal;
+  lastfee : UInt64;
   soob : Byte;
-  OpType: Cardinal;
-  bcop: TPCOperation;
   m: AnsiString;
-  j: Integer;
-  OpClass: TPCOperationClass;
-  errors2 : AnsiString;
 begin
   Lock;
   Try
@@ -1366,6 +1372,10 @@ begin
     if (soob=2) or (soob=3) then begin
       Stream.Read(FOperationBlock.protocol_version, Sizeof(FOperationBlock.protocol_version));
       Stream.Read(FOperationBlock.protocol_available, Sizeof(FOperationBlock.protocol_available));
+    end else begin
+      // We assume that protocol_version is 1 and protocol_available is 0
+      FOperationBlock.protocol_version := 1;
+      FOperationBlock.protocol_available := 0;
     end;
 
     if Stream.Read(FOperationBlock.block, Sizeof(FOperationBlock.block))<0 then exit;
@@ -1388,37 +1398,13 @@ begin
     // Fee will be calculated for each operation. Set it to 0 and check later for integrity
     lastfee := OperationBlock.fee;
     FOperationBlock.fee := 0;
-    Stream.Read(c, 4);
-    // c = operations count
-    for i := 1 to c do begin
-      errors := 'Invalid operation structure ' + inttostr(i) + '/' + inttostr(c);
-      if Stream.Size - Stream.Position < 4 then exit;
-      Stream.Read(OpType, 4);
-      j := IndexOfOperationClassByOpType(OpType);
-      if j >= 0 then
-        OpClass := _OperationsClass[j]
-      else
-        OpClass := Nil;
-      if Not Assigned(OpClass) then begin
-        errors := errors + ' optype not valid:' + InttoHex(OpType, 4);
-        exit;
-      end;
-      errors := 'Invalid operation ' + inttostr(i) + '/' + inttostr(c)+' Class:'+OpClass.ClassName;
-      bcop := OpClass.Create;
-      Try
-        if LoadingFromStorage then begin
-          If not bcop.LoadFromStorage(Stream) then exit;
-        end else if not bcop.LoadFromStream(Stream) then begin
-          exit;
-        end;
-        if Not AddOperation(false,bcop, errors2) then begin
-          errors := errors + ' '+errors2+' '+bcop.ToString;
-          exit;
-        end;
-      Finally
-        FreeAndNil(bcop);
-      end;
+    Result := FOperationsHashTree.LoadOperationsHashTreeFromStream(Stream,LoadingFromStorage,errors);
+    if not Result then begin
+      exit;
     end;
+    FOperationBlock.fee := FOperationsHashTree.TotalFee;
+    FOperationBlock.operations_hash := FOperationsHashTree.HashTree;
+    Calc_Digest_Parts;
     // Validation control:
     if (lastfee<>OperationBlock.fee) then begin
       errors := 'Corrupted operations fee old:'+inttostr(lastfee)+' new:'+inttostr(OperationBlock.fee);
@@ -1537,11 +1523,7 @@ end;
 
 function TPCOperationsComp.SaveBlockToStreamExt(save_only_OperationBlock: Boolean;
   Stream: TStream; SaveToStorage: Boolean): Boolean;
-Var
-  c, opl, i, OpType: Cardinal;
-  soob : Byte;
-  bcop: TPCOperation;
-  bcops, errors: AnsiString;
+Var soob : Byte;
 begin
   Lock;
   Try
@@ -1572,19 +1554,8 @@ begin
     TStreamOp.WriteAnsiString(Stream, FOperationBlock.operations_hash);
     TStreamOp.WriteAnsiString(Stream, FOperationBlock.proof_of_work);
     if (Not save_only_OperationBlock) then begin
-      c := Count;
-      Stream.Write(c, 4);
-      // c = operations count
-      for i := 1 to c do
-      begin
-        bcop := Operation[i - 1];
-        OpType := bcop.OpType;
-        Stream.write(OpType, 4);
-        if SaveToStorage then bcop.SaveToStorage(Stream)
-        else bcop.SaveToStream(Stream);
-      end;
-    end;
-    Result := true;
+      Result := FOperationsHashTree.SaveOperationsHashTreeToStream(Stream,SaveToStorage);
+    end else Result := true;
   finally
     Unlock;
   end;
@@ -1921,6 +1892,49 @@ begin
   inc(FTotalFee,op.OperationFee);
 end;
 
+function TOperationsHashTree.LoadOperationsHashTreeFromStream(Stream: TStream; LoadingFromStorage: Boolean; var errors: AnsiString): Boolean;
+Var c, i: Cardinal;
+  OpType: Cardinal;
+  bcop: TPCOperation;
+  j: Integer;
+  OpClass: TPCOperationClass;
+begin
+  Result := false;
+  //
+  If Stream.Read(c, 4)<4 then begin
+    errors := 'Cannot read operations count';
+    exit;
+  end;
+  // c = operations count
+  for i := 1 to c do begin
+    errors := 'Invalid operation structure ' + inttostr(i) + '/' + inttostr(c);
+    if Stream.Size - Stream.Position < 4 then exit;
+    Stream.Read(OpType, 4);
+    j := TPCOperationsComp.IndexOfOperationClassByOpType(OpType);
+    if j >= 0 then
+      OpClass := _OperationsClass[j]
+    else
+      OpClass := Nil;
+    if Not Assigned(OpClass) then begin
+      errors := errors + ' optype not valid:' + InttoHex(OpType, 4);
+      exit;
+    end;
+    errors := 'Invalid operation load from stream ' + inttostr(i) + '/' + inttostr(c)+' Class:'+OpClass.ClassName;
+    bcop := OpClass.Create;
+    Try
+      if LoadingFromStorage then begin
+        If not bcop.LoadFromStorage(Stream) then exit;
+      end else if not bcop.LoadFromStream(Stream) then begin
+        exit;
+      end;
+      AddOperationToHashTree(bcop);
+    Finally
+      FreeAndNil(bcop);
+    end;
+  end;
+  Result := true;
+end;
+
 function TOperationsHashTree.OperationsCount: Integer;
 Var l : TList;
 begin
@@ -1932,6 +1946,29 @@ begin
   end;
 end;
 
+function TOperationsHashTree.SaveOperationsHashTreeToStream(Stream: TStream; SaveToStorage: Boolean): Boolean;
+Var c, i, OpType: Cardinal;
+  bcop: TPCOperation;
+  l : TList;
+begin
+  l := FHashTreeOperations.LockList;
+  Try
+    c := l.Count;
+    Stream.Write(c, 4);
+    // c = operations count
+    for i := 1 to c do begin
+      bcop := GetOperation(i - 1);
+      OpType := bcop.OpType;
+      Stream.write(OpType, 4);
+      if SaveToStorage then bcop.SaveToStorage(Stream)
+      else bcop.SaveToStream(Stream);
+    end;
+    Result := true;
+  Finally
+    FHashTreeOperations.UnlockList;
+  End;
+end;
+
 { TStorage }
 
 procedure TStorage.CopyConfiguration(const CopyFrom: TStorage);
@@ -1952,9 +1989,15 @@ begin
   DoDeleteBlockChainBlocks(StartingDeleteBlock);
 end;
 
+function TStorage.Initialize: Boolean;
+begin
+  Result := DoInitialize;
+end;
+
 function TStorage.LoadBlockChainBlock(Operations: TPCOperationsComp; Block: Cardinal): Boolean;
 begin
-   Result := DoLoadBlockChain(Operations,Block);
+  if (Block<FirstBlock) Or (Block>LastBlock) then result := false
+  else Result := DoLoadBlockChain(Operations,Block);
 end;
 
 function TStorage.MoveBlockChainBlocks(StartBlock: Cardinal; const DestOrphan: TOrphan; DestStorage : TStorage): Boolean;

+ 6 - 6
Units/PascalCoin/UConst.pas

@@ -85,15 +85,15 @@ Const
   CT_Default_EC_OpenSSL_NID = NID_secp256k1;
 
   CT_BlockChain_Protocol_Version: Word = $0001; // Version 1
-  CT_BlockChain_Protocol_Available: Word = $0000;
+  CT_BlockChain_Protocol_Available: Word = $0001; // Build 1.4 Protocol available changed 0->1
 
   CT_MagicNetIdentification = $0A043580; // Unix timestamp 168048000 ... It's Albert birthdate!
 
   // Build 1.0.4 - introducing NetProtocol versioning:
-  CT_NetProtocol_Version: Word = $0002;
+  CT_NetProtocol_Version: Word = $0003;
   // IMPORTANT NOTE!!!
-  // NetProtocol_Available MUST BE allways >= NetProtocol_version
-  CT_NetProtocol_Available: Word = $0003;  // Remember, >= NetProtocol_version !!!
+  // NetProtocol_Available MUST BE always >= NetProtocol_version
+  CT_NetProtocol_Available: Word = $0004;  // Remember, >= NetProtocol_version !!!
 
   CT_SafeBoxBankVersion : Word = 2;
 
@@ -104,9 +104,9 @@ Const
   CT_Op_Changekey = $02;
   CT_Op_Recover = $03;
 
-  CT_ClientAppVersion : AnsiString = {$IFDEF PRODUCTION}'1.3.1'{$ELSE}{$IFDEF TESTNET}'TESTNET'{$ELSE}{$ENDIF}{$ENDIF};
+  CT_ClientAppVersion : AnsiString = {$IFDEF PRODUCTION}'1.4.0'{$ELSE}{$IFDEF TESTNET}'TESTNET'{$ELSE}{$ENDIF}{$ENDIF};
 
-  CT_Discover_IPs =  'bpascal1.dynamic-dns.net;bpascal2.dynamic-dns.net;pascalcoin1.ddns.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';
 
   CT_TRUE_FALSE : Array[Boolean] Of AnsiString = ('FALSE','TRUE');
 

+ 4 - 0
Units/PascalCoin/UCrypto.pas

@@ -466,6 +466,10 @@ begin
   P := @Result[1];
   lc := LowerCase(HexaString);
   i := HexToBin(PAnsiChar(@lc[1]),P,length(Result));
+  if (i<>(length(HexaString) DIV 2)) then begin
+    TLog.NewLog(lterror,Classname,'Invalid HEXADECIMAL string result '+inttostr(i)+'<>'+inttostr(length(HexaString) DIV 2)+': '+HexaString);
+    Result := '';
+  end;
 end;
 
 class procedure TCrypto.InitCrypto;

+ 143 - 15
Units/PascalCoin/UFileStorage.pas

@@ -29,15 +29,19 @@ Type
     BlockSize : Cardinal;
   end; // 16 bytes
 
+  TArrayOfInt64 = Array of Int64;
+
   { TFileStorage }
 
   TFileStorage = Class(TStorage)
   private
     FStorageLock : TCriticalSection;
     FBlockChainStream : TFileStream;
-    FStreamFirstBlockNumber : Cardinal;
-    FBlockHeadersFirstBytePosition : Array of Int64;
+    FStreamFirstBlockNumber : Int64;
+    FStreamLastBlockNumber : Int64;
+    FBlockHeadersFirstBytePosition : TArrayOfInt64;
     FDatabaseFolder: AnsiString;
+    FBlockChainFileName : AnsiString;
     Function StreamReadBlockHeader(Stream: TStream; StreamBlockHeaderStartPos : Int64; BlockHeaderFirstBlock, Block: Cardinal; var BlockHeader : TBlockHeader): Boolean;
     Function StreamBlockRead(Stream : TStream; StreamBlockHeaderStartPos : Int64; BlockHeaderFirstBlock, Block : Cardinal; Operations : TPCOperationsComp) : Boolean;
     Function StreamBlockSave(Stream : TStream; StreamBlockHeaderStartPos : Int64; BlockHeaderFirstBlock : Cardinal; Operations : TPCOperationsComp) : Boolean;
@@ -59,12 +63,16 @@ Type
     Function LockBlockChainStream : TFileStream;
     Procedure UnlockBlockChainStream;
     Function LoadBankFileInfo(Const Filename : AnsiString; var BlocksCount : Cardinal) : Boolean;
+    function GetFirstBlockNumber: Int64; override;
+    function GetLastBlockNumber: Int64; override;
+    function DoInitialize : Boolean; override;
   public
     Constructor Create(AOwner : TComponent); Override;
     Destructor Destroy; Override;
     Class Function GetBankFileName(Const BaseDataFolder : AnsiString; block : Cardinal) : AnsiString;
     Property DatabaseFolder : AnsiString read FDatabaseFolder write SetDatabaseFolder;
     Procedure CopyConfiguration(Const CopyFrom : TStorage); override;
+    Procedure SetBlockChainFile(BlockChainFileName : AnsiString);
   End;
 
 implementation
@@ -128,6 +136,7 @@ procedure TFileStorage.ClearStream;
 begin
   FreeAndNil(FBlockChainStream);
   FStreamFirstBlockNumber := 0;
+  FStreamLastBlockNumber := -1;
   SetLength(FBlockHeadersFirstBytePosition,0);
 end;
 
@@ -143,9 +152,11 @@ constructor TFileStorage.Create(AOwner: TComponent);
 begin
   inherited;
   FDatabaseFolder := '';
+  FBlockChainFileName := '';
   FBlockChainStream := Nil;
   SetLength(FBlockHeadersFirstBytePosition,0);
   FStreamFirstBlockNumber := 0;
+  FStreamLastBlockNumber := -1;
   FStorageLock := TCriticalSection.Create;
 end;
 
@@ -190,6 +201,17 @@ begin
   End;
 end;
 
+function TFileStorage.DoInitialize: Boolean;
+Var stream : TStream;
+begin
+  stream := LockBlockChainStream;
+  Try
+    Result := true;
+  Finally
+    UnlockBlockChainStream;
+  End;
+end;
+
 function TFileStorage.DoLoadBlockChain(Operations: TPCOperationsComp; Block: Cardinal): Boolean;
 Var stream : TStream;
   StreamBlockHeaderStartPos : Int64; BlockHeaderFirstBlock : Cardinal;
@@ -280,6 +302,7 @@ begin
       FindClose(sr);
     end;
     if (filename<>'') then begin
+      TLog.NewLog(ltinfo,Self.ClassName,'Loading SafeBox with '+inttostr(blockscount)+' blocks from file '+filename);
       fs := TFileStream.Create(filename,fmOpenRead);
       try
         ms := TMemoryStream.Create;
@@ -348,7 +371,7 @@ begin
   Finally
     UnlockBlockChainStream;
   End;
-  SaveBank;
+  if Assigned(Bank) then SaveBank;
 end;
 
 class function TFileStorage.GetBankFileName(const BaseDataFolder: AnsiString;
@@ -409,6 +432,11 @@ begin
   Result := (CT_GroupBlockSize* CT_SizeOfBlockHeader);
 end;
 
+function TFileStorage.GetFirstBlockNumber: Int64;
+begin
+  Result := FStreamFirstBlockNumber;
+end;
+
 function TFileStorage.GetFolder(const AOrphan: TOrphan): AnsiString;
 begin
   if FDatabaseFolder = '' then raise Exception.Create('No Database Folder');
@@ -417,6 +445,11 @@ begin
   if not ForceDirectories(Result) then raise Exception.Create('Cannot create database folder: '+Result);
 end;
 
+function TFileStorage.GetLastBlockNumber: Int64;
+begin
+  Result := FStreamLastBlockNumber;
+end;
+
 function TFileStorage.LoadBankFileInfo(const Filename: AnsiString; var BlocksCount: Cardinal): Boolean;
 var fs: TFileStream;
 begin
@@ -433,23 +466,107 @@ begin
 end;
 
 function TFileStorage.LockBlockChainStream: TFileStream;
+  function InitStreamInfo(Stream : TStream; var errors : String) : Boolean;
+  Var mem : TStream;
+    iPos : Int64;
+    i,j,k : Integer;
+    bh,lastbh : TBlockHeader;
+  begin
+    errors := '';
+    FStreamFirstBlockNumber := 0;
+    FStreamLastBlockNumber := -1;
+    SetLength(FBlockHeadersFirstBytePosition,0);
+    //
+    if stream.Size<GetBlockHeaderFixedSize then begin
+      if (stream.Size=0) then begin
+        Result := true;
+        exit;
+      end else begin
+        // Invalid stream!
+        Result := false;
+        errors := Format('Invalid stream size %d. Lower than minimum %d',[stream.Size, GetBlockHeaderFixedSize]);
+        exit;
+      end;
+    end;
+    // Initialize it
+    if stream.Size>GetBlockHeaderFixedSize then begin
+      SetLength(FBlockHeadersFirstBytePosition,1);
+      FBlockHeadersFirstBytePosition[0] := 0;
+    end;
+    mem := TMemoryStream.Create;
+    Try
+      iPos := 0;
+      while (iPos + GetBlockHeaderFixedSize < Stream.Size) do begin
+        Stream.Position := iPos;
+        mem.Size := 0;
+        mem.CopyFrom(Stream,GetBlockHeaderFixedSize);
+        // Analize it:
+        mem.Position := 0;
+        for i := 0 to CT_GroupBlockSize-1 do begin
+          mem.Read(bh.BlockNumber,SizeOf(bh.BlockNumber));
+          mem.Read(bh.StreamBlockRelStartPos,SizeOf(bh.StreamBlockRelStartPos));
+          mem.Read(bh.BlockSize,sizeof(bh.BlockSize));
+          if (i=0) And (iPos=0) then begin
+              FStreamFirstBlockNumber := bh.BlockNumber;
+              FStreamLastBlockNumber := bh.BlockNumber;
+            if (0<>bh.StreamBlockRelStartPos) then begin
+              errors := Format('Invalid first block start rel pos %d',[bh.StreamBlockRelStartPos]);
+              result := false;
+              exit;
+            end;
+          end else begin
+            if (bh.BlockNumber=0) then begin
+              // End here
+              break;
+            end;
+            if (lastbh.BlockNumber+1<>bh.BlockNumber) or
+              ((lastbh.StreamBlockRelStartPos+lastbh.BlockSize<>bh.StreamBlockRelStartPos) And (i>0)) Or
+              ((0<>bh.StreamBlockRelStartPos) And (i=0)) then begin
+              errors := Format('Invalid check on block header. iPos=%d i=%d Number=%d relstart=%d size=%d',[iPos,i,bh.BlockNumber,bh.StreamBlockRelStartPos,bh.BlockSize]);
+              result := false;
+              exit;
+            end else begin
+              FStreamLastBlockNumber := bh.BlockNumber;
+            end;
+          end;
+          lastbh := bh;
+        end;
+        iPos := iPos + GetBlockHeaderFixedSize + lastbh.StreamBlockRelStartPos + lastBh.BlockSize;
+      end;
+      Result := true;
+    Finally
+      mem.Free;
+    End;
+  end;
+
 Var fn : TFileName;
   fm : Word;
+  exists : Boolean;
+  bh : TBlockHeader;
+  errors : String;
 begin
   TPCThread.ProtectEnterCriticalSection(Self,FStorageLock);
   Try
     if Not Assigned(FBlockChainStream) then begin
-      fn := GetFolder(Orphan)+PathDelim+'BlockChainStream.blocks';
+      if FBlockChainFileName<>'' then begin
+        fn := FBlockChainFileName
+      end else begin
+        fn := GetFolder(Orphan)+PathDelim+'BlockChainStream.blocks';
+      end;
+      exists := FileExists(fn);
       if ReadOnly then begin
-        if FileExists(fn) then fm := fmOpenRead+fmShareDenyNone
+        if exists then fm := fmOpenRead+fmShareDenyNone
         else raise Exception.Create('FileStorage not exists for open ReadOnly: '+fn);
       end else begin
-        if FileExists(fn) then fm := fmOpenReadWrite+fmShareDenyWrite  // DenyNone -> XXXXXXXXX Sure not to use fmShareDenyWrite ???
-        else fm := fmCreate+fmShareDenyWrite  // XXXXXXXXX Sure not to use fmShareDenyWrite too ???
+        if exists then fm := fmOpenReadWrite+fmShareDenyWrite
+        else fm := fmCreate+fmShareDenyWrite
       end;
       FBlockChainStream := TFileStream.Create(fn,fm);
-      // Read Headers:
-      SetLength(FBlockHeadersFirstBytePosition,0);
+      // Init stream
+      If Not InitStreamInfo(FBlockChainStream,errors) then begin
+        TLog.NewLog(lterror,ClassName,errors);
+        raise Exception.Create('Error reading File: '+errors);
+      end;
     end;
   Except
     FStorageLock.Release;
@@ -458,12 +575,17 @@ begin
   Result := FBlockChainStream;
 end;
 
+procedure TFileStorage.SetBlockChainFile(BlockChainFileName: AnsiString);
+begin
+  ClearStream;
+  FBlockChainFileName := BlockChainFileName;
+end;
+
 procedure TFileStorage.SetDatabaseFolder(const Value: AnsiString);
 begin
   if FDatabaseFolder=Value then exit;
   FDatabaseFolder := Value;
-  FreeAndNil(FBlockChainStream);
-  SetLength(FBlockHeadersFirstBytePosition,0);
+  ClearStream;
 end;
 
 procedure TFileStorage.SetOrphan(const Value: TOrphan);
@@ -525,14 +647,19 @@ end;
 
 function TFileStorage.StreamBlockSave(Stream : TStream; StreamBlockHeaderStartPos : Int64; BlockHeaderFirstBlock : Cardinal; Operations : TPCOperationsComp) : Boolean;
   Procedure GrowUntilPos(newPos : Int64; DeleteDataStartingAtCurrentPos : Boolean);
-  Var b : Byte;
+  Var null_buff : Array[1..CT_GroupBlockSize] of Byte;
+    i : Int64;
   begin
-    b := 0;
     if Not DeleteDataStartingAtCurrentPos then begin
       Stream.Position := Stream.Size;
     end;
-    While (Stream.Position<newPos) do begin
-      Stream.Write(b,1);
+    if (stream.Position<newPos) then begin
+      FillChar(null_buff,length(null_buff),0);
+      while (Stream.Position<newPos) do begin
+        i := newPos - Stream.Position;
+        if i>length(null_buff) then i := length(null_buff);
+        Stream.WriteBuffer(null_buff,i);
+      end;
     end;
     Stream.Position := newPos;
   end;
@@ -578,6 +705,7 @@ begin
     // Save Data
     _ops.Position := 0;
     Stream.CopyFrom(_ops,_ops.Size);
+    FStreamLastBlockNumber := Operations.OperationBlock.block;
   Finally
     _ops.Free;
   end;

+ 19 - 4
Units/PascalCoin/UNetProtocol.pas

@@ -266,6 +266,7 @@ Type
     FAlertedForNewProtocolAvailable : Boolean;
     FHasReceivedData : Boolean;
     FIsDownloadingBlocks : Boolean;
+    FRandomWaitSecondsSendHello : Cardinal;
     function GetConnected: Boolean;
     procedure SetConnected(const Value: Boolean);
     procedure TcpClient_OnConnect(Sender: TObject);
@@ -1513,6 +1514,7 @@ begin
   FNetLock := TCriticalSection.Create;
   FLastDataReceivedTS := 0;
   FLastDataSendedTS := 0;
+  FRandomWaitSecondsSendHello := 90 + Random(60);
   FTcpIpClient := Nil;
   FRemoteOperationBlock := CT_OperationBlock_NUL;
   SetClient( TBufferedNetTcpIpClient.Create(Self) );
@@ -1634,8 +1636,8 @@ begin
       ms.Free;
     end;
     If ((FLastDataReceivedTS>0) Or ( NOT (Self is TNetServerClient)))
-       AND ((FLastDataReceivedTS+(1000*120)<GetTickCount) AND (FLastDataSendedTS+(1000*120)<GetTickCount)) then begin
-       // Build 1.3 -> Changing wait time from 60 to 120 secs.
+       AND ((FLastDataReceivedTS+(1000*FRandomWaitSecondsSendHello)<GetTickCount) AND (FLastDataSendedTS+(1000*FRandomWaitSecondsSendHello)<GetTickCount)) then begin
+       // Build 1.4 -> Changing wait time from 120 secs to a random seconds value
       DebugStep := 'LastSend time old';
       If TNetData.NetData.PendingRequest(Self,ops)>=2 then begin
         TLog.NewLog(ltDebug,Classname,'Pending requests without response... closing connection to '+ClientRemoteAddr+' > '+ops);
@@ -1700,7 +1702,7 @@ begin
       if DoDisconnect then begin
         DisconnectInvalidClient(false,errors+' > '+TNetData.HeaderDataToText(HeaderData)+' BuffSize: '+inttostr(DataBuffer.Size));
       end else begin
-        TNode.Node.AddOperations(Self,operations,errors);
+        TNode.Node.AddOperations(Self,operations,Nil,errors);
       end;
     finally
       operations.Free;
@@ -1875,6 +1877,17 @@ begin
 
     DoDisconnect := false;
 
+    // Build 1.4
+    if b_start<TNode.Node.Bank.Storage.FirstBlock then begin
+      b_start := TNode.Node.Bank.Storage.FirstBlock;
+      if b_end<b_start then begin
+        errors := 'Block:'+inttostr(b_end)+' not found';
+        SendError(ntp_response,HeaderData.operation,HeaderData.request_id,CT_NetError_InternalServerError,errors);
+        exit;
+      end;
+    end;
+
+
     if (b_end>=TNode.Node.Bank.BlocksCount) then b_end := TNode.Node.Bank.BlocksCount-1;
     inc_b := ((b_end - b_start) DIV CT_Max_Positions)+1;
     msops := TMemoryStream.Create;
@@ -2313,6 +2326,7 @@ begin
           last_bytes_read := auxstream.size;
           if last_bytes_read>0 then begin
             FLastDataReceivedTS := GetTickCount;
+            FRandomWaitSecondsSendHello := 90 + Random(60);
 
             FClientBufferRead.Position := FClientBufferRead.size; // Go to the end
             auxstream.Position := 0;
@@ -2418,6 +2432,7 @@ begin
         ClientRemoteAddr);
       (Client as TBufferedNetTcpIpClient).WriteBufferToSend(Buffer);
       FLastDataSendedTS := GetTickCount;
+      FRandomWaitSecondsSendHello := 90 + Random(60);
     Finally
       FNetLock.Release;
     End;
@@ -2479,7 +2494,7 @@ begin
       if FRemoteOperationBlock.block>0 then c2 := FRemoteOperationBlock.block
       else c2 := c1+100;
     end else c2 := c1+quantity-1;
-    // Build 1.0.5 BUG - Allways query for ONLY 1 if Build is lower or equal to 1.0.5
+    // Build 1.0.5 BUG - Always query for ONLY 1 if Build is lower or equal to 1.0.5
     if ((FClientAppVersion='') Or ( (length(FClientAppVersion)=5) And (FClientAppVersion<='1.0.5') )) then begin
       c2 := c1;
     end;

+ 33 - 15
Units/PascalCoin/UNode.pas

@@ -72,13 +72,13 @@ Type
     Property Operations : TPCOperationsComp read FOperations;
     //
     Function AddNewBlockChain(SenderMiner : TMinerThread; SenderConnection : TNetConnection; NewBlockOperations: TPCOperationsComp; var newBlockAccount: TBlockAccount; var errors: AnsiString): Boolean;
-    Function AddOperations(SenderConnection : TNetConnection; Operations : TOperationsHashTree; var errors: AnsiString): Integer;
+    Function AddOperations(SenderConnection : TNetConnection; Operations : TOperationsHashTree; OperationsResult : TOperationsResumeList; var errors: AnsiString): Integer;
     Function AddOperation(SenderConnection : TNetConnection; Operation : TPCOperation; var errors: AnsiString): Boolean;
     Function SendNodeMessage(Target : TNetConnection; TheMessage : AnsiString; var errors : AnsiString) : Boolean;
     //
     Procedure NotifyBlocksChanged;
     //
-    procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDeep, MaxOperations : Integer);
+    procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, MaxOperations : Integer);
     Function FindOperation(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : Boolean;
     //
     Procedure AutoDiscoverNodes(Const ips : AnsiString);
@@ -291,13 +291,13 @@ begin
   ops := TOperationsHashTree.Create;
   Try
     ops.AddOperationToHashTree(Operation);
-    Result := AddOperations(SenderConnection,ops,errors)=1;
+    Result := AddOperations(SenderConnection,ops,Nil,errors)=1;
   Finally
     ops.Free;
   End;
 end;
 
-function TNode.AddOperations(SenderConnection : TNetConnection; Operations : TOperationsHashTree; var errors: AnsiString): Integer;
+function TNode.AddOperations(SenderConnection : TNetConnection; Operations : TOperationsHashTree; OperationsResult : TOperationsResumeList; var errors: AnsiString): Integer;
 Var
   i,j : Integer;
   operationscomp : TPCOperationsComp;
@@ -307,8 +307,11 @@ Var
   mtl : TList;
   netConnectionsList : TList;
   s : String;
+  OPR : TOperationResume;
+  ActOp : TPCOperation;
 begin
   Result := -1;
+  if Assigned(OperationsResult) then OperationsResult.Clear;
   if FDisabledsNewBlocksCount>0 then begin
     errors := Format('Cannot Add Operations due is adding disabled - OpCount:%d',[Operations.OperationsCount]);
     TLog.NewLog(ltinfo,Classname,errors);
@@ -327,15 +330,30 @@ begin
     valids_operations := TOperationsHashTree.Create;
     try
       for j := 0 to Operations.OperationsCount-1 do begin
-        if (FOperations.AddOperation(true,Operations.GetOperation(j),e)) then begin
+        ActOp := Operations.GetOperation(j);
+        if (FOperations.AddOperation(true,ActOp,e)) then begin
           inc(Result);
-          valids_operations.AddOperationToHashTree(Operations.GetOperation(j));
-          TLog.NewLog(ltdebug,Classname,Format('AddOperation %d/%d: %s',[(j+1),Operations.OperationsCount,Operations.GetOperation(j).ToString]));
+          valids_operations.AddOperationToHashTree(ActOp);
+          TLog.NewLog(ltdebug,Classname,Format('AddOperation %d/%d: %s',[(j+1),Operations.OperationsCount,ActOp.ToString]));
+          if Assigned(OperationsResult) then begin
+            TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SenderAccount,OPR);
+            OPR.NOpInsideBlock:=FOperations.Count-1;
+            OPR.Balance := FOperations.SafeBoxTransaction.Account(ActOp.SenderAccount).balance;
+            OperationsResult.Add(OPR);
+          end;
         end else begin
           if (errors<>'') then errors := errors+' ';
           errors := errors+'Op '+IntToStr(j+1)+'/'+IntToStr(Operations.OperationsCount)+':'+e;
           TLog.NewLog(ltdebug,Classname,Format('AddOperation failed %d/%d: %s  - Error:%s',
-            [(j+1),Operations.OperationsCount,Operations.GetOperation(j).ToString,e]));
+            [(j+1),Operations.OperationsCount,ActOp.ToString,e]));
+          if Assigned(OperationsResult) then begin
+            TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SenderAccount,OPR);
+            OPR.valid := false;
+            OPR.NOpInsideBlock:=-1;
+            OPR.OperationHash := '';
+            OPR.errors := e;
+            OperationsResult.Add(OPR);
+          end;
         end;
       end;
       if Result=0 then exit;
@@ -631,8 +649,8 @@ begin
   end;
 end;
 
-procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDeep, MaxOperations: Integer);
-  Procedure DoGetFromBlock(block_number : Cardinal; last_balance : Int64; act_deep : Integer);
+procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, MaxOperations: Integer);
+  Procedure DoGetFromBlock(block_number : Cardinal; last_balance : Int64; act_depth : Integer);
   var opc : TPCOperationsComp;
     op : TPCOperation;
     OPR : TOperationResume;
@@ -640,7 +658,7 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
     i : Integer;
     next_block_number : Cardinal;
   begin
-    if (act_deep<=0) Or ((block_number<=0) And (block_number>0)) then exit;
+    if (act_depth<=0) Or ((block_number<=0) And (block_number>0)) then exit;
 
     opc := TPCOperationsComp.Create(Nil);
     Try
@@ -682,10 +700,10 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
         end;
         //
         opc.Clear(true);
-        if (next_block_number>=0) And (next_block_number<block_number) And (act_deep>0)
+        if (next_block_number>=0) And (next_block_number<block_number) And (act_depth>0)
            And (next_block_number >= (account_number DIV CT_AccountsPerBlock))
            And ((OperationsResume.Count<MaxOperations) Or (MaxOperations<=0))
-           then DoGetFromBlock(next_block_number,last_balance,act_deep-1);
+           then DoGetFromBlock(next_block_number,last_balance,act_depth-1);
       finally
         l.Free;
       end;
@@ -696,10 +714,10 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
 
 Var acc : TAccount;
 begin
-  if MaxDeep<0 then exit;
+  if MaxDepth<0 then exit;
   if account_number>=Bank.SafeBox.AccountsCount then exit;
   acc := Bank.SafeBox.Account(account_number);
-  if (acc.updated_block>0) Or (acc.account=0) then DoGetFromBlock(acc.updated_block,acc.balance,MaxDeep);
+  if (acc.updated_block>0) Or (acc.account=0) then DoGetFromBlock(acc.updated_block,acc.balance,MaxDepth);
 end;
 
 function TNode.FindOperation(const OperationComp: TPCOperationsComp;

+ 32 - 10
Units/PascalCoin/UOpTransaction.pas

@@ -62,7 +62,7 @@ Type
     function LoadFromStream(Stream : TStream) : Boolean; override;
     procedure AffectedAccounts(list : TList); override;
     //
-    Class Function GetTransactionHasthToSign(const trans : TOpTransactionData) : TRawBytes;
+    Class Function GetTransactionHashToSign(const trans : TOpTransactionData) : TRawBytes;
     Class Function DoSignOperation(key : TECPrivateKey; var trans : TOpTransactionData) : Boolean;
     class function OpType : Byte; override;
     function OperationAmount : Int64; override;
@@ -158,7 +158,8 @@ begin
   FData.public_key := key.PublicKey;
   If Not DoSignOperation(key,FData) then begin
     TLog.NewLog(lterror,Classname,'Error signing a new Transaction');
-  end;
+    FHasValidSignature := false;
+  end else FHasValidSignature := true;
 end;
 
 function TOpTransaction.DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString): Boolean;
@@ -217,12 +218,21 @@ begin
     errors := Format('Target cannot accept this transaction due to max amount %d+%d=%d > %d',[target.balance,FData.amount,target.balance+FData.amount,CT_MaxWalletAmount]);
     Exit;
   end;
+  // Build 1.4
+  If (FData.public_key.EC_OpenSSL_NID<>CT_TECDSA_Public_Nul.EC_OpenSSL_NID) And (Not TAccountComp.Equal(FData.public_key,sender.accountkey)) then begin
+    errors := Format('Invalid sender public key for account %d. Distinct from SafeBox public key! %s <> %s',[
+      FData.sender,
+      TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(FData.public_key)),
+      TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(sender.accountkey))]);
+    exit;
+  end;
   // Check signature
-  _h := GetTransactionHasthToSign(FData);
-  if (Not TCrypto.ECDSAVerify(FData.public_key,_h,FData.sign)) then begin
+  _h := GetTransactionHashToSign(FData);
+  if (Not TCrypto.ECDSAVerify(sender.accountkey,_h,FData.sign)) then begin
     errors := 'Invalid sign';
+    FHasValidSignature := false;
     Exit;
-  end;
+  end else FHasValidSignature := true;
   //
   FPrevious_Sender_updated_block := sender.updated_block;
   FPrevious_Destination_updated_block := target.updated_block;
@@ -240,7 +250,7 @@ begin
     trans.sign.s:='';
     exit;
   end;
-  s := GetTransactionHasthToSign(trans);
+  s := GetTransactionHashToSign(trans);
   Try
     _sign := TCrypto.ECDSASign(key.PrivateKey,s);
     trans.sign := _sign;
@@ -277,7 +287,7 @@ begin
   end;
 end;
 
-class function TOpTransaction.GetTransactionHasthToSign(const trans: TOpTransactionData): TRawBytes;
+class function TOpTransaction.GetTransactionHashToSign(const trans: TOpTransactionData): TRawBytes;
 Var ms : TMemoryStream;
 begin
   ms := TMemoryStream.Create;
@@ -389,7 +399,8 @@ begin
   FData.new_accountkey := new_account_key;
   If Not DoSignOperation(key,FData) then begin
     TLog.NewLog(lterror,Classname,'Error signing a new Change key');
-  end;
+    FHasValidSignature := false;
+  end else FHasValidSignature := true;
 end;
 
 function TOpChangeKey.DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
@@ -423,10 +434,20 @@ begin
   If Not TAccountComp.IsValidAccountKey( FData.new_accountkey, errors ) then begin
     exit;
   end;
-  If Not TCrypto.ECDSAVerify(FData.public_key,GetOperationHasthToSign(FData),FData.sign) then begin
-    errors := 'Invalid sign';
+  // Build 1.4
+  If (FData.public_key.EC_OpenSSL_NID<>CT_TECDSA_Public_Nul.EC_OpenSSL_NID) And (Not TAccountComp.Equal(FData.public_key,account.accountkey)) then begin
+    errors := Format('Invalid public key for account %d. Distinct from SafeBox public key! %s <> %s',[
+      FData.account,
+      TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(FData.public_key)),
+      TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(account.accountkey))]);
     exit;
   end;
+
+  If Not TCrypto.ECDSAVerify(account.accountkey,GetOperationHasthToSign(FData),FData.sign) then begin
+    errors := 'Invalid sign';
+    FHasValidSignature := false;
+    exit;
+  end else FHasValidSignature := true;
   FPrevious_Sender_updated_block := account.updated_block;
   Result := AccountTransaction.UpdateAccountkey(FData.account,FData.n_operation,FData.new_accountkey,FData.fee,errors);
 end;
@@ -581,6 +602,7 @@ begin
   FData.account := account_number;
   FData.n_operation := n_operation;
   FData.fee := fee;
+  FHasValidSignature := true; // Recover founds doesn't need a signature
 end;
 
 function TOpRecoverFounds.DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean;

+ 436 - 109
Units/PascalCoin/URPC.pas

@@ -49,10 +49,16 @@ Type
     FJSON20Strict: Boolean;
     FIniFileName: AnsiString;
     FIniFile : TIniFile;
+    FRPCLog : TLog;
+    FCallsCounter : Int64;
     procedure SetActive(AValue: Boolean);
     procedure SetIniFileName(const Value: AnsiString);
+    procedure SetLogFileName(const Value: AnsiString);
+    Function GetLogFileName : AnsiString;
   protected
     Function IsValidClientIP(Const clientIp : String; clientPort : Word) : Boolean;
+    Procedure AddRPCLog(Const Sender : String; Const Message : String);
+    Function GetNewCallCounter : Int64;
   public
     Constructor Create;
     Destructor Destroy; override;
@@ -62,6 +68,7 @@ Type
     //
     Property JSON20Strict : Boolean read FJSON20Strict write FJSON20Strict;
     Property IniFileName : AnsiString read FIniFileName write SetIniFileName;
+    Property LogFileName : AnsiString read GetLogFileName write SetLogFileName;
   end;
 
   { TRPCServerThread }
@@ -91,12 +98,32 @@ Type
 
 implementation
 
-Uses SysUtils, Synautil;
+Uses  {$IFNDEF FPC}windows,{$ENDIF}
+  SysUtils, Synautil;
 
 var _RPCServer : TRPCServer = Nil;
 
 { TRPCServer }
 
+Procedure TRPCServer.AddRPCLog(Const Sender : String; Const Message : String);
+Begin
+  If Not Assigned(FRPCLog) then exit;
+  FRPCLog.NotifyNewLog(ltinfo,Sender+' '+Inttostr(FCallsCounter),Message);
+end;
+
+Function TRPCServer.GetLogFileName : AnsiString;
+Begin
+  If Assigned(FRPCLog) then
+    Result := FRPCLog.FileName
+  else Result := '';
+end;
+
+function TRPCServer.GetNewCallCounter: Int64;
+begin
+  inc(FCallsCounter);
+  Result := FCallsCounter;
+end;
+
 procedure TRPCServer.SetActive(AValue: Boolean);
 begin
   if FActive=AValue then Exit;
@@ -124,6 +151,18 @@ begin
   end;
 end;
 
+procedure TRPCServer.SetLogFileName(const Value: AnsiString);
+begin
+  If (Not Assigned(FRPCLog)) And (Trim(Value)<>'') then begin
+    FRPCLog := TLog.Create(Nil);
+    FRPCLog.ProcessGlobalLogs:=false;
+    FRPCLog.SaveTypes:=CT_TLogTypes_ALL;
+  end;
+  If (trim(Value)<>'') then begin
+    FRPCLog.FileName:=Value;
+  end else FreeAndNil(FRPCLog);
+end;
+
 function TRPCServer.IsValidClientIP(const clientIp: String; clientPort: Word): Boolean;
 begin
   Result := true;
@@ -133,17 +172,20 @@ end;
 
 constructor TRPCServer.Create;
 begin
+  FRPCLog := Nil;
   FIniFile := Nil;
   FIniFileName := '';
   FJSON20Strict := true;
   FWalletKeys := Nil;
   FRPCServerThread := Nil;
   FPort := CT_JSONRPC_Port;
+  FCallsCounter := 0;
   If Not assigned(_RPCServer) then _RPCServer := Self;
 end;
 
 destructor TRPCServer.Destroy;
 begin
+  FreeAndNil(FRPCLog);
   active := false;
   if _RPCServer=Self then _RPCServer:=Nil;
   inherited Destroy;
@@ -158,6 +200,7 @@ begin
   FreeOnTerminate:=true;
   //Priority:=tpNormal;
   inherited create(false);
+  FreeOnTerminate:=true;
 end;
 
 destructor TRPCProcess.Destroy;
@@ -179,21 +222,28 @@ var
   jsonobj,jsonresponse : TPCJSONObject;
   errNum : Integer; errDesc : String;
   jsonrequesttxt,
-  jsonresponsetxt : AnsiString;
+  jsonresponsetxt, methodName, paramsTxt : AnsiString;
   valid : Boolean;
   i : Integer;
   Headers : TStringList;
+  tc : Cardinal;
+  callcounter : Int64;
 begin
+  callcounter := _RPCServer.GetNewCallCounter;
+  tc := GetTickCount;
+  methodName := '';
+  paramsTxt := '';
   // IP Protection
   If (Not _RPCServer.IsValidClientIP(FSock.GetRemoteSinIP,FSock.GetRemoteSinPort)) then begin
     TLog.NewLog(lterror,Classname,FSock.GetRemoteSinIP+':'+inttostr(FSock.GetRemoteSinPort)+' INVALID IP');
+    _RPCServer.AddRPCLog(FSock.GetRemoteSinIP+':'+InttoStr(FSock.GetRemoteSinPort),' INVALID IP');
     exit;
   end;
-  Headers := TStringList.Create;
   errNum := CT_RPC_ErrNum_InternalError;
   errDesc := 'No data';
   valid := false;
   SetLength(inputdata,0);
+  Headers := TStringList.Create;
   jsonresponse := TPCJSONObject.Create;
   try
     timeout := 5000;
@@ -253,6 +303,8 @@ begin
             errNum := 0;
             errDesc := '';
             try
+              methodName := jsonobj.AsString('method','');
+              paramsTxt := jsonobj.GetAsObject('params').ToJSON(false);
               TLog.NewLog(ltinfo,Classname,FSock.GetRemoteSinIP+':'+inttostr(FSock.GetRemoteSinPort)+' Processing method '+jsonobj.AsString('method',''));
               Valid := ProcessMethod(jsonobj.AsString('method',''),jsonobj.GetAsObject('params'),jsonresponse,errNum,errDesc);
               if not Valid then begin
@@ -310,9 +362,10 @@ begin
           FSock.SendBuffer(addr(jsonresponsetxt[1]),Length(jsonresponsetxt));
         end;
       end;
+      _RPCServer.AddRPCLog(FSock.GetRemoteSinIP+':'+InttoStr(FSock.GetRemoteSinPort),'Method:'+methodName+' Params:'+paramsTxt+' '+Inttostr(errNum)+':'+errDesc+' Time:'+FormatFloat('0.000',(GetTickCount - tc)/1000));
     finally
       jsonresponse.free;
-      headers.free;
+      Headers.Free; // Memory leak on Build 1.3
     end;
   end;
 end;
@@ -332,7 +385,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
 
   Function GetResultArray : TPCJSONArray;
   begin
-    if not assigned(_ro) then begin
+    if not assigned(_ra) then begin
       _ra := jsonresponse.GetAsArray('result');
       _ro := Nil;
     end;
@@ -349,6 +402,49 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := Round(jsonCurr * 10000);
   End;
 
+  Function HexaStringToOperationsHashTree(Const HexaStringOperationsHashTree : AnsiString; out OperationsHashTree : TOperationsHashTree; var errors : AnsiString) : Boolean;
+  var raw : TRawBytes;
+    ms : TMemoryStream;
+  Begin
+    Result := False;
+    raw := TCrypto.HexaToRaw(HexaStringOperationsHashTree);
+    if (HexaStringOperationsHashTree<>'') And (raw='') then begin
+      errors := 'Invalid HexaString as operations';
+      exit;
+    end;
+    ms := TMemoryStream.Create;
+    Try
+      ms.WriteBuffer(raw[1],length(raw));
+      ms.Position := 0;
+      OperationsHashTree := TOperationsHashTree.Create;
+      if (raw<>'') then begin
+        If not OperationsHashTree.LoadOperationsHashTreeFromStream(ms,false,errors) then begin
+          FreeAndNil(OperationsHashTree);
+          exit;
+        end;
+      end;
+      Result := true;
+    Finally
+      ms.Free;
+    End;
+  End;
+
+  Function OperationsHashTreeToHexaString(Const OperationsHashTree : TOperationsHashTree) : AnsiString;
+  var ms : TMemoryStream;
+    raw : TRawBytes;
+  Begin
+    ms := TMemoryStream.Create;
+    Try
+      OperationsHashTree.SaveOperationsHashTreeToStream(ms,false);
+      ms.Position := 0;
+      SetLength(raw,ms.Size);
+      ms.ReadBuffer(raw[1],ms.Size);
+      Result := TCrypto.ToHexaString(raw);
+    Finally
+      ms.Free;
+    End;
+  End;
+
   Function GetBlock(nBlock : Cardinal; jsonObject : TPCJSONObject) : Boolean;
   var pcops : TPCOperationsComp;
   begin
@@ -384,15 +480,23 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
 
   Procedure FillOperationResumeToJSONObject(Const OPR : TOperationResume; jsonObject : TPCJSONObject);
   Begin
-    jsonObject.GetAsVariant('block').Value:=OPR.Block;
-    jsonObject.GetAsVariant('opblock').Value:=OPR.NOpInsideBlock;
+    if Not OPR.valid then begin
+      jsonObject.GetAsVariant('valid').Value := OPR.valid;
+    end;
+    if (OPR.errors<>'') And (Not OPR.valid) then begin
+      jsonObject.GetAsVariant('errors').Value := OPR.errors;
+    end;
+    if OPR.valid then begin
+      jsonObject.GetAsVariant('block').Value:=OPR.Block;
+      jsonObject.GetAsVariant('time').Value:=OPR.time;
+      jsonObject.GetAsVariant('opblock').Value:=OPR.NOpInsideBlock;
+    end;
     jsonObject.GetAsVariant('optype').Value:=OPR.OpType;
-    jsonObject.GetAsVariant('time').Value:=OPR.time;
     jsonObject.GetAsVariant('account').Value:=OPR.AffectedAccount;
     jsonObject.GetAsVariant('optxt').Value:=OPR.OperationTxt;
     jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
     jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OPR.Fee);
-    if OPR.Balance>=0 then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
+    if (OPR.Balance>=0) And (OPR.valid) then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
     jsonObject.GetAsVariant('payload').Value:=TCrypto.ToHexaString(OPR.OriginalPayload);
     If OPR.SenderAccount>=0 then begin
       jsonObject.GetAsVariant('sender_account').Value:=OPR.SenderAccount;
@@ -403,10 +507,20 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     If OPR.newKey.EC_OpenSSL_NID>0 then begin
       jsonObject.GetAsVariant('enc_pubkey').Value := TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(OPR.newKey));
     end;
-    jsonObject.GetAsVariant('ophash').Value := TCrypto.ToHexaString(OPR.OperationHash);
+    if (OPR.valid) And (OPR.OperationHash<>'') then begin
+      jsonObject.GetAsVariant('ophash').Value := TCrypto.ToHexaString(OPR.OperationHash);
+    end;
   end;
 
-  Function GetAccountOperations(AccountNumber : Cardinal; jsonArray : TPCJSONArray; MaxBlocksDeep,start,max : Integer) : Boolean;
+  Procedure FillOperationsHashTreeToJSONObject(Const OperationsHashTree : TOperationsHashTree; jsonObject : TPCJSONObject);
+  Begin
+    jsonObject.GetAsVariant('operations').Value:=OperationsHashTree.OperationsCount;
+    jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OperationsHashTree.TotalAmount);
+    jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OperationsHashTree.TotalFee);
+    jsonObject.GetAsVariant('rawoperations').Value:=OperationsHashTreeToHexaString(OperationsHashTree);
+  End;
+
+  Function GetAccountOperations(AccountNumber : Cardinal; jsonArray : TPCJSONArray; MaxBlocksDepht,start,max : Integer) : Boolean;
   var list : TList;
     Op : TPCOperation;
     OPR : TOperationResume;
@@ -432,7 +546,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         list.Free;
       End;
       if ((max<=0) Or (OperationsResume.Count<(max+start))) then begin
-        TNode.Node.GetStoredOperationsFromAccount(OperationsResume,AccountNumber,MaxBlocksDeep,max+start);
+        TNode.Node.GetStoredOperationsFromAccount(OperationsResume,AccountNumber,MaxBlocksDepht,max+start);
       end;
       //
       for i:=0 to OperationsResume.Count-1 do begin
@@ -474,35 +588,20 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     end;
   end;
 
-  Function OpSendTo(sender, target : Cardinal; amount, fee : UInt64; RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
+  // This function creates a TOpTransaction without looking for balance/private key of sender account
+  // It assumes that sender,target,sender_last_n_operation,senderAccountKey and targetAccountKey are correct
+  Function CreateOperationTransaction(sender, target, sender_last_n_operation : Cardinal; amount, fee : UInt64;
+    Const senderAccounKey, targetAccountKey : TAccountKey; Const RawPayload : TRawBytes;
+    Const Payload_method, EncodePwd : AnsiString) : TOpTransaction;
   // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
-  Var opt : TOpTransaction;
-    sacc,tacc : TAccount;
-    i : Integer;
-    errors : AnsiString;
-    opr : TOperationResume;
+  Var i : Integer;
     f_raw : TRawBytes;
-  begin
-    Result := false;
-    if (sender<0) or (sender>=TNode.Node.Bank.AccountsCount) then begin
-      If (sender=CT_MaxAccount) then ErrorDesc := 'Need sender'
-      else ErrorDesc:='Invalid sender account '+Inttostr(sender);
-      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
-      Exit;
-    end;
-    if (target<0) or (target>=TNode.Node.Bank.AccountsCount) then begin
-      If (target=CT_MaxAccount) then ErrorDesc := 'Need target'
-      else ErrorDesc:='Invalid target account '+Inttostr(target);
-      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
-      Exit;
-    end;
-    sacc := TNode.Node.Operations.SafeBoxTransaction.Account(sender);
-    tacc := TNode.Node.Operations.SafeBoxTransaction.Account(target);
-    _RPCServer.FWalletKeys.AccountsKeyList.IndexOfAccountKey(sacc.accountkey);
-    i := _RPCServer.FWalletKeys.IndexOfAccountKey(sacc.accountkey);
-    if (i<0) then begin
-      ErrorDesc:='Private key of sender account '+Inttostr(sender)+' not found in wallet';
-      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
+  Begin
+    Result := Nil;
+    i := _RPCServer.FWalletKeys.IndexOfAccountKey(senderAccounKey);
+    if i<0 then begin
+      ErrorDesc:='Sender Public Key not found in wallet: '+TAccountComp.AccountKeyToExport(senderAccounKey);
+      ErrorNum:=CT_RPC_ErrNum_InvalidPubKey;
       Exit;
     end;
     if (Not assigned(_RPCServer.FWalletKeys.Key[i].PrivateKey)) then begin
@@ -512,16 +611,17 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
       end else begin
         ErrorDesc := 'Wallet private key not found in Wallet';
-        ErrorNum := CT_RPC_ErrNum_InvalidAccount;
+        ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
       end;
       exit;
     end;
+    //
     if (length(RawPayload)>0) then begin
       if (Payload_method='none') then f_raw:=RawPayload
       else if (Payload_method='dest') then begin
-        f_raw := ECIESEncrypt(tacc.accountkey,RawPayload);
+        f_raw := ECIESEncrypt(targetAccountKey,RawPayload);
       end else if (Payload_method='sender') then begin
-        f_raw := ECIESEncrypt(sacc.accountkey,RawPayload);
+        f_raw := ECIESEncrypt(senderAccounKey,RawPayload);
       end else if (Payload_method='aes') then begin
         f_raw := TAESComp.EVP_Encrypt_AES256(RawPayload,EncodePwd);
       end else begin
@@ -530,7 +630,40 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         exit;
       end;
     end else f_raw := '';
-    opt := TOpTransaction.Create(sender,sacc.n_operation+1,target,_RPCServer.FWalletKeys.Key[i].PrivateKey,amount,fee,f_raw);
+    Result := TOpTransaction.Create(sender,sender_last_n_operation+1,target,_RPCServer.FWalletKeys.Key[i].PrivateKey,amount,fee,f_raw);
+    if Not Result.HasValidSignature then begin
+      FreeAndNil(Result);
+      ErrorNum:=CT_RPC_ErrNum_InternalError;
+      ErrorDesc:='Invalid signature';
+      exit;
+    end;
+  End;
+
+  Function OpSendTo(sender, target : Cardinal; amount, fee : UInt64; Const RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
+  // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
+  Var opt : TOpTransaction;
+    sacc,tacc : TAccount;
+    errors : AnsiString;
+    opr : TOperationResume;
+  begin
+    Result := false;
+    if (sender<0) or (sender>=TNode.Node.Bank.AccountsCount) then begin
+      If (sender=CT_MaxAccount) then ErrorDesc := 'Need sender'
+      else ErrorDesc:='Invalid sender account '+Inttostr(sender);
+      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
+      Exit;
+    end;
+    if (target<0) or (target>=TNode.Node.Bank.AccountsCount) then begin
+      If (target=CT_MaxAccount) then ErrorDesc := 'Need target'
+      else ErrorDesc:='Invalid target account '+Inttostr(target);
+      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
+      Exit;
+    end;
+    sacc := TNode.Node.Operations.SafeBoxTransaction.Account(sender);
+    tacc := TNode.Node.Operations.SafeBoxTransaction.Account(target);
+
+    opt := CreateOperationTransaction(sender,target,sacc.n_operation,amount,fee,sacc.accountkey,tacc.accountkey,RawPayload,Payload_method,EncodePwd);
+    if opt=nil then exit;
     try
       If not TNode.Node.AddOperation(Nil,opt,errors) then begin
         ErrorDesc := 'Error adding operation: '+errors;
@@ -545,27 +678,49 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     end;
   end;
 
-  Function ChangeAccountKey(account_number : Cardinal; new_pub_key : TAccountKey; fee : UInt64; RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
+  Function SignOpSendTo(Const HexaStringOperationsHashTree : TRawBytes; sender, target : Cardinal;
+    Const senderAccounKey, targetAccountKey : TAccountKey;
+    last_sender_n_operation : Cardinal;
+    amount, fee : UInt64; Const RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
   // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
-  Var opck : TOpChangeKey;
-    acc : TAccount;
-    i : Integer;
+  var OperationsHashTree : TOperationsHashTree;
     errors : AnsiString;
-    opr : TOperationResume;
-    f_raw : TRawBytes;
+    opt : TOpTransaction;
   begin
     Result := false;
-    if (account_number<0) or (account_number>=TNode.Node.Bank.AccountsCount) then begin
-      ErrorDesc:='Invalid account '+Inttostr(account_number);
-      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
+    if Not HexaStringToOperationsHashTree(HexaStringOperationsHashTree,OperationsHashTree,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param "rawoperations": '+errors;
       Exit;
     end;
-    acc := TNode.Node.Operations.SafeBoxTransaction.Account(account_number);
-    _RPCServer.FWalletKeys.AccountsKeyList.IndexOfAccountKey(acc.accountkey);
-    i := _RPCServer.FWalletKeys.IndexOfAccountKey(acc.accountkey);
+    Try
+      opt := CreateOperationTransaction(sender,target,last_sender_n_operation,amount,fee,senderAccounKey,targetAccountKey,RawPayload,Payload_method,EncodePwd);
+      if opt=nil then exit;
+      try
+        OperationsHashTree.AddOperationToHashTree(opt);
+        FillOperationsHashTreeToJSONObject(OperationsHashTree,GetResultObject);
+        Result := true;
+      finally
+        opt.Free;
+      end;
+    Finally
+      OperationsHashTree.Free;
+    End;
+  end;
+
+  // This function creates a TOpChangeKey without looking for private key of account
+  // It assumes that account_number,account_last_n_operation and account_pubkey are correct
+  Function CreateOperationChangeKey(account_number, account_last_n_operation : Cardinal; const account_pubkey, new_pubkey : TAccountKey; fee : UInt64; RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : TOpChangeKey;
+  // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
+  var i : Integer;
+    errors : AnsiString;
+    f_raw : TRawBytes;
+  Begin
+    Result := Nil;
+    i := _RPCServer.FWalletKeys.IndexOfAccountKey(account_pubkey);
     if (i<0) then begin
-      ErrorDesc:='Private key of account '+Inttostr(account_number)+' not found in wallet';
-      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
+      ErrorDesc:='Private key not found in wallet: '+TAccountComp.AccountKeyToExport(account_pubkey);
+      ErrorNum:=CT_RPC_ErrNum_InvalidPubKey;
       Exit;
     end;
     if (Not assigned(_RPCServer.FWalletKeys.Key[i].PrivateKey)) then begin
@@ -582,9 +737,9 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     if (length(RawPayload)>0) then begin
       if (Payload_method='none') then f_raw:=RawPayload
       else if (Payload_method='dest') then begin
-        f_raw := ECIESEncrypt(new_pub_key,RawPayload);
+        f_raw := ECIESEncrypt(new_pubkey,RawPayload);
       end else if (Payload_method='sender') then begin
-        f_raw := ECIESEncrypt(acc.accountkey,RawPayload);
+        f_raw := ECIESEncrypt(account_pubkey,RawPayload);
       end else if (Payload_method='aes') then begin
         f_raw := TAESComp.EVP_Encrypt_AES256(RawPayload,EncodePwd);
       end else begin
@@ -593,7 +748,32 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         exit;
       end;
     end else f_raw := '';
-    opck := TOpChangeKey.Create(account_number,acc.n_operation+1,_RPCServer.FWalletKeys.Key[i].PrivateKey,new_pub_key,fee,f_raw);
+    Result := TOpChangeKey.Create(account_number,account_last_n_operation+1,_RPCServer.FWalletKeys.Key[i].PrivateKey,new_pubkey,fee,f_raw);
+    if Not Result.HasValidSignature then begin
+      FreeAndNil(Result);
+      ErrorNum:=CT_RPC_ErrNum_InternalError;
+      ErrorDesc:='Invalid signature';
+      exit;
+    end;
+  End;
+
+  Function ChangeAccountKey(account_number : Cardinal; const new_pub_key : TAccountKey; fee : UInt64; const RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
+  // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
+  Var opck : TOpChangeKey;
+    acc : TAccount;
+    errors : AnsiString;
+    opr : TOperationResume;
+  begin
+    Result := false;
+    if (account_number<0) or (account_number>=TNode.Node.Bank.AccountsCount) then begin
+      ErrorDesc:='Invalid account '+Inttostr(account_number);
+      ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
+      Exit;
+    end;
+    acc := TNode.Node.Operations.SafeBoxTransaction.Account(account_number);
+
+    opck := CreateOperationChangeKey(account_number,acc.n_operation,acc.accountkey,new_pub_key,fee,RawPayload,Payload_method,EncodePwd);
+    if not assigned(opck) then exit;
     try
       If not TNode.Node.AddOperation(Nil,opck,errors) then begin
         ErrorDesc := 'Error adding operation: '+errors;
@@ -653,16 +833,16 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := true;
   end;
 
-  Function ChangeAccountsKey(accounts_txt : String; new_pub_key : TAccountKey; fee : UInt64; RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
+  Function ChangeAccountsKey(accounts_txt : String; const new_pub_key : TAccountKey; fee : UInt64; const RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
   // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
   Var opck : TOpChangeKey;
     acc : TAccount;
-    i,ian : Integer;
+    i, ian : Integer;
     errors : AnsiString;
     opr : TOperationResume;
-    f_raw : TRawBytes;
     accountsnumber : TOrderedCardinalList;
     operationsht : TOperationsHashTree;
+    OperationsResumeList : TOperationsResumeList;
   begin
     Result := false;
     accountsnumber := TOrderedCardinalList.Create;
@@ -682,39 +862,8 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
             Exit;
           end;
           acc := TNode.Node.Operations.SafeBoxTransaction.Account(accountsnumber.Get(ian));
-          _RPCServer.FWalletKeys.AccountsKeyList.IndexOfAccountKey(acc.accountkey);
-          i := _RPCServer.FWalletKeys.IndexOfAccountKey(acc.accountkey);
-          if (i<0) then begin
-            ErrorDesc:='Private key of account '+Inttostr(acc.account)+' not found in wallet';
-            ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
-            Exit;
-          end;
-          if (Not assigned(_RPCServer.FWalletKeys.Key[i].PrivateKey)) then begin
-            if _RPCServer.FWalletKeys.Key[i].CryptedKey<>'' then begin
-              // Wallet is password protected
-              ErrorDesc := 'Wallet is password protected for account '+IntToStr(acc.account);
-              ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
-            end else begin
-              ErrorDesc := 'Wallet private key not found in Wallet for account '+IntToStr(acc.account);
-              ErrorNum := CT_RPC_ErrNum_InvalidAccount;
-            end;
-            exit;
-          end;
-          if (length(RawPayload)>0) then begin
-            if (Payload_method='none') then f_raw:=RawPayload
-            else if (Payload_method='dest') then begin
-              f_raw := ECIESEncrypt(new_pub_key,RawPayload);
-            end else if (Payload_method='sender') then begin
-              f_raw := ECIESEncrypt(acc.accountkey,RawPayload);
-            end else if (Payload_method='aes') then begin
-              f_raw := TAESComp.EVP_Encrypt_AES256(RawPayload,EncodePwd);
-            end else begin
-              ErrorNum:=CT_RPC_ErrNum_InvalidOperation;
-              ErrorDesc:='Invalid encode payload method: '+Payload_method;
-              exit;
-            end;
-          end else f_raw := '';
-          opck := TOpChangeKey.Create(acc.account,acc.n_operation+1,_RPCServer.FWalletKeys.Key[i].PrivateKey,new_pub_key,fee,f_raw);
+          opck := CreateOperationChangeKey(acc.account,acc.n_operation,acc.accountkey,new_pub_key,fee,RawPayload,Payload_method,EncodePwd);
+          if not assigned(opck) then exit;
           try
             operationsht.AddOperationToHashTree(opck);
           finally
@@ -722,16 +871,21 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
           end;
         end; // For
         // Ready to execute...
-        i := TNode.Node.AddOperations(Nil,operationsht,errors);
-        if (i<0) then begin
-          ErrorNum:=CT_RPC_ErrNum_InternalError;
-          ErrorDesc:=errors;
-          exit;
-        end;
-        GetResultObject.GetAsVariant('accounts_total').Value := operationsht.OperationsCount;
-        GetResultObject.GetAsVariant('accounts_ok').Value := i;
-        GetResultObject.GetAsVariant('accounts_error').Value := operationsht.OperationsCount - i;
-        GetResultObject.GetAsVariant('errors').Value := errors;
+        OperationsResumeList := TOperationsResumeList.Create;
+        Try
+          i := TNode.Node.AddOperations(Nil,operationsht,OperationsResumeList, errors);
+          if (i<0) then begin
+            ErrorNum:=CT_RPC_ErrNum_InternalError;
+            ErrorDesc:=errors;
+            exit;
+          end;
+          GetResultArray.Clear; // Inits an array
+          for i := 0 to OperationsResumeList.Count - 1 do begin
+            FillOperationResumeToJSONObject(OperationsResumeList[i],GetResultArray.GetAsObject(i));
+          end;
+        Finally
+          OperationsResumeList.Free;
+        End;
         Result := true;
       finally
         operationsht.Free;
@@ -741,6 +895,100 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     end;
   end;
 
+  Function SignOpChangeKey(Const HexaStringOperationsHashTree : TRawBytes; account : Cardinal;
+    Const actualAccounKey, newAccountKey : TAccountKey;
+    last_n_operation : Cardinal;
+    fee : UInt64; Const RawPayload : TRawBytes; Const Payload_method, EncodePwd : AnsiString) : Boolean;
+  // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
+  var OperationsHashTree : TOperationsHashTree;
+    errors : AnsiString;
+    opck : TOpChangeKey;
+  begin
+    Result := false;
+    if Not HexaStringToOperationsHashTree(HexaStringOperationsHashTree,OperationsHashTree,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param "rawoperations": '+errors;
+      Exit;
+    end;
+    Try
+      opck := CreateOperationChangeKey(account,last_n_operation,actualAccounKey,newAccountKey,fee,RawPayload,Payload_method,EncodePwd);
+      if opck=nil then exit;
+      try
+        OperationsHashTree.AddOperationToHashTree(opck);
+        FillOperationsHashTreeToJSONObject(OperationsHashTree,GetResultObject);
+        Result := true;
+      finally
+        opck.Free;
+      end;
+    Finally
+      OperationsHashTree.Free;
+    End;
+  end;
+
+  Function OperationsInfo(Const HexaStringOperationsHashTree : TRawBytes; jsonArray : TPCJSONArray) : Boolean;
+  var OperationsHashTree : TOperationsHashTree;
+    errors : AnsiString;
+    OPR : TOperationResume;
+    Obj : TPCJSONObject;
+    Op : TPCOperation;
+    i : Integer;
+  Begin
+    if Not HexaStringToOperationsHashTree(HexaStringOperationsHashTree,OperationsHashTree,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param "rawoperations": '+errors;
+      Exit;
+    end;
+    Try
+      jsonArray.Clear;
+      for i := 0 to OperationsHashTree.OperationsCount - 1 do begin
+        Op := OperationsHashTree.GetOperation(i);
+        Obj := jsonArray.GetAsObject(i);
+        If TPCOperation.OperationToOperationResume(0,Op,Op.SenderAccount,OPR) then begin
+          OPR.NOpInsideBlock := i;
+          OPR.Balance := -1;
+        end else OPR := CT_TOperationResume_NUL;
+        FillOperationResumeToJSONObject(OPR,Obj);
+      end;
+      Result := true;
+    Finally
+      OperationsHashTree.Free;
+    End;
+  End;
+
+  Function ExecuteOperations(Const HexaStringOperationsHashTree : TRawBytes) : Boolean;
+  var OperationsHashTree : TOperationsHashTree;
+    errors : AnsiString;
+    i : Integer;
+    OperationsResumeList : TOperationsResumeList;
+  Begin
+    if Not HexaStringToOperationsHashTree(HexaStringOperationsHashTree,OperationsHashTree,errors) then begin
+      ErrorNum:=CT_RPC_ErrNum_InvalidData;
+      ErrorDesc:= 'Error decoding param "rawoperations": '+errors;
+      Exit;
+    end;
+    Try
+      errors := '';
+      OperationsResumeList := TOperationsResumeList.Create;
+      Try
+        i := TNode.Node.AddOperations(Nil,OperationsHashTree,OperationsResumeList,errors);
+        if (i<0) then begin
+          ErrorNum:=CT_RPC_ErrNum_InternalError;
+          ErrorDesc:=errors;
+          exit;
+        end;
+        GetResultArray.Clear; // Inits an array
+        for i := 0 to OperationsResumeList.Count - 1 do begin
+          FillOperationResumeToJSONObject(OperationsResumeList[i],GetResultArray.GetAsObject(i));
+        end;
+      Finally
+        OperationsResumeList.Free;
+      End;
+      Result := true;
+    Finally
+      OperationsHashTree.Free;
+    End;
+  End;
+
   Procedure FillAccountObject(Const account : TAccount; jsonObj : TPCJSONObject);
   Begin
     jsonObj.GetAsVariant('account').Value:=account.account;
@@ -859,6 +1107,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
 Var c,c2 : Cardinal;
   i,j,k,l : Integer;
   account : TAccount;
+  senderpubkey,destpubkey : TAccountKey;
   ansistr : AnsiString;
   nsaarr : TNodeServerAddressArray;
   pcops : TPCOperationsComp;
@@ -1051,6 +1300,11 @@ begin
     end else begin
       c := params.GetAsVariant('start').AsCardinal(CT_MaxBlock);
       c2 := params.GetAsVariant('end').AsCardinal(CT_MaxBlock);
+      i := params.AsInteger('max',0);
+      if (c<TNode.Node.Bank.BlocksCount) And (i>0) And (i<=1000) then begin
+        if (c+i<TNode.Node.Bank.BlocksCount) then c2 := c+i
+        else c2 := TNode.Node.Bank.BlocksCount-1;
+      end;
     end;
     if ((c>=0) And (c<TNode.Node.Bank.BlocksCount)) And (c2>=c) And (c2<TNode.Node.Bank.BlocksCount) then begin
       i := 0; Result := true;
@@ -1061,7 +1315,7 @@ begin
     end else begin
       ErrorNum := CT_RPC_ErrNum_InvalidBlock;
       if (c>c2) then ErrorDesc := 'Block start > block end'
-      else if (c=CT_MaxBlock) Or (c2=CT_MaxBlock) then ErrorDesc:='Need param "last" or "start" and "end"'
+      else if (c=CT_MaxBlock) Or (c2=CT_MaxBlock) then ErrorDesc:='Need param "last" or "start" and "end"/"max"'
       else if (c2>=TNode.Node.Bank.BlocksCount) then ErrorDesc := 'Block higher or equal to getblockccount: '+IntToStr(c2)
       else  ErrorDesc := 'Block not found: '+IntToStr(c);
     end;
@@ -1141,11 +1395,12 @@ begin
   end else if (method='getaccountoperations') then begin
     // Returns all the operations affecting an account in "Operation resume format" as an array
     // Param "account" contains account number
-    // Param "deep" (optional) contains max blocks deep to search (Default: 100)
+    // Param "depht" (optional or "deep") contains max blocks deep to search (Default: 100)
     // Param "start" and "max" contains starting index and max operations respectively
     c := params.GetAsVariant('account').AsCardinal(CT_MaxAccount);
     if ((c>=0) And (c<TNode.Node.Bank.AccountsCount)) then begin
-      Result := GetAccountOperations(c,GetResultArray,params.AsInteger('deep',100),params.AsInteger('start',0),params.AsInteger('max',100));
+      if (params.IndexOfName('depth')>=0) then i := params.AsInteger('depth',100) else i:=params.AsInteger('deep',100);
+      Result := GetAccountOperations(c,GetResultArray,i,params.AsInteger('start',0),params.AsInteger('max',100));
     end else begin
       ErrorNum := CT_RPC_ErrNum_InvalidAccount;
       If (c=CT_MaxAccount) then ErrorDesc := 'Need account param'
@@ -1209,6 +1464,38 @@ begin
        ToPascalCoins(params.AsDouble('fee',0)),
        TCrypto.HexaToRaw(params.AsString('payload','')),
        params.AsString('payload_method','dest'),params.AsString('pwd',''));
+  end else if (method='signsendto') then begin
+    // Create a Transaction operation and adds it into a "rawoperations" (that can include
+    // previous operations). This RPC method is usefull for cold storage, because doesn't
+    // need to check or verify accounts status/public key, assuming that passed values
+    // are ok.
+    // Signs a transaction of "amount" coins from "sender" to "target" with "fee", using "sender_enc_pubkey" or "sender_b58_pubkey"
+    // and "last_n_operation" of sender. Also, needs "target_enc_pubkey" or "target_b58_pubkey"
+    // If "payload" is present, it will be encoded using "payload_method"
+    // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
+    // Returns a JSON "Operations info" containing old "rawoperations" plus new Transaction
+    If Not _RPCServer.WalletKeys.IsValidPassword then begin
+      ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
+      ErrorDesc := 'Wallet is password protected. Unlock first';
+      exit;
+    end;
+    If Not CapturePubKey('sender_',senderpubkey,ErrorDesc) then begin
+      ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+      exit;
+    end;
+    If Not CapturePubKey('target_',destpubkey,ErrorDesc) then begin
+      ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+      exit;
+    end;
+    Result := SignOpSendTo(
+       params.AsString('rawoperations',''),
+       params.AsCardinal('sender',CT_MaxAccount),params.AsCardinal('target',CT_MaxAccount),
+       senderpubkey,destpubkey,
+       params.AsCardinal('last_n_operation',0),
+       ToPascalCoins(params.AsDouble('amount',0)),
+       ToPascalCoins(params.AsDouble('fee',0)),
+       TCrypto.HexaToRaw(params.AsString('payload','')),
+       params.AsString('payload_method','dest'),params.AsString('pwd',''));
   end else if (method='changekey') then begin
     // Change key of "account" to "new_enc_pubkey" or "new_b58_pubkey" (encoded public key format) with "fee"
     // If "payload" is present, it will be encoded using "payload_method"
@@ -1259,6 +1546,46 @@ begin
        ToPascalCoins(params.AsDouble('fee',0)),
        TCrypto.HexaToRaw(params.AsString('payload','')),
        params.AsString('payload_method','dest'),params.AsString('pwd',''));
+  end else if (method='signchangekey') then begin
+    // Create a Change Key operation and adds it into a "rawoperations" (that can include
+    // previous operations). This RPC method is usefull for cold storage, because doesn't
+    // need to check or verify accounts status/public key, assuming that passed values
+    // are ok.
+    // Signs a change key of "account" to "new_enc_pubkey" or "new_b58_pubkey" (encoded public key format) with "fee"
+    // needs "old_enc_pubkey" or "old_b58_pubkey" that will be used to find private key in wallet to sign
+    // and "last_n_operation" of account.
+    // If "payload" is present, it will be encoded using "payload_method"
+    // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
+    // Returns a JSON "Operations info" containing old "rawoperations" plus new Transaction
+    If Not _RPCServer.WalletKeys.IsValidPassword then begin
+      ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
+      ErrorDesc := 'Wallet is password protected. Unlock first';
+      exit;
+    end;
+    if params.IndexOfName('account')<0 then begin
+      ErrorNum := CT_RPC_ErrNum_InvalidAccount;
+      ErrorDesc := 'Need "account" param';
+      exit;
+    end;
+    If Not CapturePubKey('old_',senderpubkey,ErrorDesc) then begin
+      ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+      exit;
+    end;
+    If Not CapturePubKey('new_',destpubkey,ErrorDesc) then begin
+      ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+      exit;
+    end;
+    Result := SignOpChangeKey(params.AsString('rawoperations',''),
+       params.AsCardinal('account',CT_MaxAccount),
+       senderpubkey,destpubkey,
+       params.AsCardinal('last_n_operation',0),
+       ToPascalCoins(params.AsDouble('fee',0)),
+       TCrypto.HexaToRaw(params.AsString('payload','')),
+       params.AsString('payload_method','dest'),params.AsString('pwd',''));
+  end else if (method='operationsinfo') then begin
+    Result := OperationsInfo(params.AsString('rawoperations',''),GetResultArray);
+  end else if (method='executeoperations') then begin
+    Result := ExecuteOperations(params.AsString('rawoperations',''));
   end else if (method='nodestatus') then begin
     // Returns a JSON Object with Node status
     GetResultObject.GetAsVariant('ready').Value := False;

+ 23 - 30
Units/PascalCoin/UThread.pas

@@ -58,7 +58,7 @@ Type
   public
     constructor Create;
     destructor Destroy; override;
-    procedure Add(Item: Pointer);
+    function Add(Item: Pointer) : Integer;
     procedure Clear;
     procedure Remove(Item: Pointer); inline;
     function LockList: TList;
@@ -87,38 +87,30 @@ Var l : TList;
 begin
   FStartTickCount := GetTickCount;
   FDebugStep := '';
-  _threads.Add(Self);
+  i := _threads.Add(Self);
   try
-    TLog.NewLog(ltdebug,Classname,'Starting Thread');
+    TLog.NewLog(ltdebug,Classname,'Starting Thread in pos '+inttostr(i+1));
     Try
       Try
-        Try
-          BCExecute;
-          FDebugStep := 'Finalized BCExecute';
-        Finally
-          // Build 1.0.9 BUG-101 When BCExecute ends... must indicate to Thread Terminated=TRUE !
-          // ... because if not... nobody knows when a thread terminated !
-          Terminate;
-        End;
-      Except
-        On E:Exception do begin
-          TLog.NewLog(lterror,Classname,'Exception inside a Thread at step: '+FDebugStep+' ('+E.ClassName+'): '+E.Message);
-          Raise;
-        end;
+        BCExecute;
+        FDebugStep := 'Finalized BCExecute';
+      Finally
+        Terminate;
       End;
-    Finally
-      TLog.NewLog(ltdebug,Classname,'Finalizing Thread');
+    Except
+      On E:Exception do begin
+        TLog.NewLog(lterror,Classname,'Exception inside a Thread at step: '+FDebugStep+' ('+E.ClassName+'): '+E.Message);
+        Raise;
+      end;
     End;
   finally
-    if (Assigned(_threads)) then begin
-      l := _threads.LockList;
-      Try
-        i := l.Remove(Self);
-        TLog.NewLog(ltdebug,Classname,'Finalizing Thread in pos '+inttostr(i+1)+'/'+inttostr(l.Count+1)+' working time: '+FormatFloat('0.000',(GetTickCount-FStartTickCount) / 1000)+' sec');
-      Finally
-        _threads.UnlockList;
-      End;
-    end;
+    l := _threads.LockList;
+    Try
+      i := l.Remove(Self);
+      TLog.NewLog(ltdebug,Classname,'Finalizing Thread in pos '+inttostr(i+1)+'/'+inttostr(l.Count+1)+' working time: '+FormatFloat('0.000',(GetTickCount-FStartTickCount) / 1000)+' sec');
+    Finally
+      _threads.UnlockList;
+    End;
   end;
 end;
 
@@ -228,17 +220,17 @@ begin
   if Not Result then begin
     s := Format('Cannot Protect a critical section by %s after %d milis',
       [Sender.ClassName,MaxWaitMilliseconds]);
-    TLog.NewLog(lterror,Classname,s);
+    TLog.NewLog(ltdebug,Classname,s);
   end;
 end;
 
 { TPCThreadList }
 
-procedure TPCThreadList.Add(Item: Pointer);
+function TPCThreadList.Add(Item: Pointer) : Integer;
 begin
   LockList;
   Try
-    FList.Add(Item);
+    Result := FList.Add(Item);
   Finally
     UnlockList;
   End;
@@ -305,3 +297,4 @@ initialization
 finalization
   FreeAndNil(_threads);
 end.
+

+ 5 - 0
Units/PascalCoin/upcdaemon.pas

@@ -54,6 +54,7 @@ Type
   TPCDaemonMapper = Class(TCustomDaemonMapper)
   private
     FLog : TLog;
+    FLogRPCCalls : TLog;
     procedure OnPascalCoinInThreadLog(logtype : TLogType; Time : TDateTime; AThreadID : Cardinal; Const sender, logtext : AnsiString);
   public
     Constructor Create(AOwner : TComponent); override;
@@ -95,6 +96,10 @@ begin
       FNode.Node.AutoDiscoverNodes(CT_Discover_IPs);
       FNode.Node.NetServer.Active := true;
 
+      if (Application.HasOption('r','logrpc')) then begin
+        FRPC.LogFileName:= TFolderHelper.GetPascalCoinDataFolder+PathDelim+'pascalcoin_rpc.log';
+      end;
+
       Repeat
         Sleep(100);
       Until Terminated;

+ 11 - 2
Units/Utils/UGridUtils.pas

@@ -116,6 +116,8 @@ Type
   TBlockChainData = Record
     Block : Cardinal;
     Timestamp : Cardinal;
+    BlockProtocolVersion,
+    BlockProtocolAvailable : Word;
     OperationsCount : Cardinal;
     Volume : Int64;
     Reward, Fee : Int64;
@@ -162,7 +164,7 @@ Type
   End;
 
 Const
-  CT_TBlockChainData_NUL : TBlockChainData = (Block:0;Timestamp:0;OperationsCount:0;Volume:0;Reward:0;Fee:0;Target:0;HashRateKhs:0;MinerPayload:'';PoW:'';SafeBoxHash:'');
+  CT_TBlockChainData_NUL : TBlockChainData = (Block:0;Timestamp:0;BlockProtocolVersion:0;BlockProtocolAvailable:0;OperationsCount:0;Volume:0;Reward:0;Fee:0;Target:0;HashRateKhs:0;MinerPayload:'';PoW:'';SafeBoxHash:'');
 
 
 implementation
@@ -915,7 +917,7 @@ begin
   DrawGrid.FixedRows := 1;
   DrawGrid.DefaultDrawing := true;
   DrawGrid.FixedCols := 0;
-  DrawGrid.ColCount := 11;
+  DrawGrid.ColCount := 12;
   DrawGrid.ColWidths[0] := 50; // Block
   DrawGrid.ColWidths[1] := 110; // Time
   DrawGrid.ColWidths[2] := 30; // Ops
@@ -927,6 +929,7 @@ begin
   DrawGrid.ColWidths[8] := 190; // Miner Payload
   DrawGrid.ColWidths[9] := 190; // PoW
   DrawGrid.ColWidths[10] := 190; // SafeBox Hash
+  DrawGrid.ColWidths[11] := 50; // Protocol
   FDrawGrid.DefaultRowHeight := 18;
   FDrawGrid.Invalidate;
   DrawGrid.Options := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine,
@@ -970,6 +973,7 @@ begin
       8 : s := 'Miner Payload';
       9 : s := 'Proof of Work';
       10 : s := 'SafeBox Hash';
+      11 : s := 'Protocol';
     else s:= '';
     end;
     Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfCenter,tfVerticalCenter]);
@@ -1023,6 +1027,9 @@ begin
       end else if ACol=10 then begin
         s := TCrypto.ToHexaString(bcd.SafeBoxHash);
         Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfLeft,tfVerticalCenter]);
+      end else if ACol=11 then begin
+        s := Inttostr(bcd.BlockProtocolVersion)+'-'+IntToStr(bcd.BlockProtocolAvailable);
+        Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfCenter,tfVerticalCenter,tfSingleLine]);
       end;
     end;
   end;
@@ -1137,6 +1144,8 @@ begin
         if (Node.Bank.LoadOperations(opc,nend)) then begin
           bcd.Block := opc.OperationBlock.block;
           bcd.Timestamp := opc.OperationBlock.timestamp;
+          bcd.BlockProtocolVersion := opc.OperationBlock.protocol_version;
+          bcd.BlockProtocolAvailable := opc.OperationBlock.protocol_available;
           bcd.OperationsCount := opc.Count;
           bcd.Volume := opc.OperationsHashTree.TotalAmount + opc.OperationsHashTree.TotalFee;
           bcd.Reward := opc.OperationBlock.reward;