Browse Source

Build 2.1.4

PascalCoin 7 years ago
parent
commit
fb3ed96682

+ 30 - 0
README.txt

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

+ 2 - 2
Units/Forms/UFRMMemoText.lfm

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

+ 3 - 3
Units/Forms/UFRMPayloadDecoder.lfm

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

+ 46 - 18
Units/Forms/UFRMPayloadDecoder.pas

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

+ 1 - 1
Units/Forms/UFRMWallet.lfm

@@ -16,7 +16,7 @@ object FRMWallet: TFRMWallet
   OnCreate = FormCreate
   OnCreate = FormCreate
   OnDestroy = FormDestroy
   OnDestroy = FormDestroy
   Position = poOwnerFormCenter
   Position = poOwnerFormCenter
-  LCLVersion = '1.6.0.4'
+  LCLVersion = '1.6.4.0'
   object pnlTop: TPanel
   object pnlTop: TPanel
     Left = 0
     Left = 0
     Height = 91
     Height = 91

+ 1 - 1
Units/Forms/UFRMWallet.pas

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

+ 171 - 0
Units/PascalCoin/UAccountKeyStorage.pas

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

+ 87 - 11
Units/PascalCoin/UAccounts.pas

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

+ 42 - 7
Units/PascalCoin/UBlockChain.pas

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

+ 1 - 1
Units/PascalCoin/UConst.pas

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

+ 60 - 1
Units/PascalCoin/UFileStorage.pas

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

+ 5 - 2
Units/PascalCoin/UNetProtocol.pas

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

+ 202 - 35
Units/PascalCoin/UNode.pas

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

+ 105 - 9
Units/PascalCoin/URPC.pas

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

+ 1 - 1
Units/PascalCoin/upcdaemon.pas

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