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:  
 
+### 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
 - 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

+ 2 - 2
Units/Forms/UFRMMemoText.lfm

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

+ 3 - 3
Units/Forms/UFRMPayloadDecoder.lfm

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

+ 46 - 18
Units/Forms/UFRMPayloadDecoder.pas

@@ -103,7 +103,7 @@ implementation
   {$R *.lfm}
 {$ENDIF}
 
-Uses UNode, UTime, UECIES, UAES, UAccounts;
+Uses UNode, UTime, UECIES, UAES, UAccounts, UCommon, UFRMMemoText;
 
 { TFRMPayloadDecoder }
 
@@ -135,11 +135,13 @@ end;
 
 procedure TFRMPayloadDecoder.DoFind(Const OpHash : String);
 Var
-  r : TRawBytes;
+  r,md160 : TRawBytes;
   pcops : TPCOperationsComp;
-  b : Cardinal;
+  nBlock,nAccount,nN_Operation : Cardinal;
   opbi : Integer;
   opr : TOperationResume;
+  strings : TStrings;
+  FRM : TFRMMemoText;
 begin
   // Search for an operation based on "ophash"
   if (trim(OpHash)='') then begin
@@ -147,23 +149,49 @@ begin
     exit;
   end;
   try
-    r := TCrypto.HexaToRaw(trim(ophash));
+    r := TCrypto.HexaToRaw(trim(OpHash));
     if (r='') then begin
       raise Exception.Create('Value is not an hexadecimal string');
     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;
-    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;
   Except
     OpResume := CT_TOperationResume_NUL;
@@ -270,8 +298,8 @@ begin
       exit;
     end;
     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
       lblDateTime.Caption := DateTimeToStr(UnivDateTime2LocalDateTime(UnixToUnivDateTime(Value.time)));
       lblDateTime.Font.Color := clBlack;

+ 1 - 1
Units/Forms/UFRMWallet.lfm

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

+ 1 - 1
Units/Forms/UFRMWallet.pas

@@ -308,7 +308,7 @@ Type
 procedure TThreadActivate.BCExecute;
 begin
   // 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.NetServer.Active := true;
   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
 
 uses
-  SysUtils, ULog, UOpenSSLdef, UOpenSSL;
+  SysUtils, ULog, UOpenSSLdef, UOpenSSL, UAccountKeyStorage;
 
 { TPascalCoinProtocol }
 
@@ -1149,9 +1149,21 @@ end;
 
 // New on version 2: To reduce mem usage
 {$DEFINE uselowmem}
+{$DEFINE useAccountKeyStorage}
 
 {$IFDEF uselowmem}
 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
     to store in RAM memory (better than to use original ones)
     This will reduce 10-15% of memory usage.
@@ -1159,7 +1171,11 @@ Type
     of originals, but}
   TMemAccount = Record // TAccount with less memory usage
     // account number is discarded (-4 bytes)
+    {$IFDEF useAccountKeyStorage}
+    accountInfoKS : TAccountInfoKS;
+    {$ELSE}
     accountInfo : TDynRawBytes;
+    {$ENDIF}
     balance: UInt64;
     updated_block: Cardinal;
     n_operation: Cardinal;
@@ -1170,7 +1186,11 @@ Type
 
   TMemOperationBlock = Record // TOperationBlock with less memory usage
     // block number is discarded (-4 bytes)
+    {$IFDEF useAccountKeyStorage}
+    account_keyKS: PAccountKey;
+    {$ELSE}
     account_key: TDynRawBytes;
+    {$ENDIF}
     reward: UInt64;
     fee: UInt64;
     protocol_version: Word;
@@ -1205,8 +1225,17 @@ Var raw : TRawBytes;
 {$ENDIF}
 begin
   {$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);
   TBaseType.To256RawBytes(raw,dest.accountInfo);
+  {$ENDIF}
   dest.balance := source.balance;
   dest.updated_block:=source.updated_block;
   dest.n_operation:=source.n_operation;
@@ -1225,8 +1254,17 @@ var raw : TRawBytes;
 begin
   {$IFDEF uselowmem}
   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);
   TAccountComp.RawString2AccountInfo(raw,dest.accountInfo);
+  {$ENDIF}
   dest.balance := source.balance;
   dest.updated_block:=source.updated_block;
   dest.n_operation:=source.n_operation;
@@ -1245,8 +1283,12 @@ var raw : TRawBytes;
 {$ENDIF}
 Begin
   {$IFDEF uselowmem}
+  {$IFDEF useAccountKeyStorage}
+  dest.blockchainInfo.account_keyKS:=TAccountKeyStorage.KS.AddAccountKey(source.blockchainInfo.account_key);
+  {$ELSE}
   TAccountComp.AccountKey2RawString(source.blockchainInfo.account_key,raw);
   TBaseType.To256RawBytes(raw,dest.blockchainInfo.account_key);
+  {$ENDIF}
   dest.blockchainInfo.reward:=source.blockchainInfo.reward;
   dest.blockchainInfo.fee:=source.blockchainInfo.fee;
   dest.blockchainInfo.protocol_version:=source.blockchainInfo.protocol_version;
@@ -1277,8 +1319,12 @@ var i : Integer;
 begin
   {$IFDEF uselowmem}
   dest.blockchainInfo.block:=block_number;
+  {$IFDEF useAccountKeyStorage}
+  dest.blockchainInfo.account_key := source.blockchainInfo.account_keyKS^;
+  {$ELSE}
   TBaseType.ToRawBytes(source.blockchainInfo.account_key,raw);
   TAccountComp.RawString2Accountkey(raw,dest.blockchainInfo.account_key);
+  {$ENDIF}
   dest.blockchainInfo.reward:=source.blockchainInfo.reward;
   dest.blockchainInfo.fee:=source.blockchainInfo.fee;
   dest.blockchainInfo.protocol_version:=source.blockchainInfo.protocol_version;
@@ -1304,9 +1350,14 @@ end;
 function TPCSafeBox.Account(account_number: Cardinal): TAccount;
 var b : Cardinal;
 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;
 
 
@@ -1370,13 +1421,23 @@ end;
 
 function TPCSafeBox.AccountsCount: Integer;
 begin
-  Result := BlocksCount * CT_AccountsPerBlock;
+  StartThreadSafe;
+  try
+    Result := BlocksCount * CT_AccountsPerBlock;
+  finally
+    EndThreadSave;
+  end;
 end;
 
 function TPCSafeBox.Block(block_number: Cardinal): TBlockAccount;
 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;
 
 class function TPCSafeBox.BlockAccountToText(const block: TBlockAccount): AnsiString;
@@ -1388,7 +1449,12 @@ end;
 
 function TPCSafeBox.BlocksCount: Integer;
 begin
-  Result := FBlockAccountsList.Count;
+  StartThreadSafe;
+  try
+    Result := FBlockAccountsList.Count;
+  finally
+    EndThreadSave;
+  end;
 end;
 
 class function TPCSafeBox.CalcBlockHash(const block : TBlockAccount; useProtocol2Method : Boolean): TRawBytes;
@@ -1484,9 +1550,14 @@ end;
 
 function TPCSafeBox.CalcSafeBoxHash: TRawBytes;
 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;
 
 function TPCSafeBox.CanUpgradeToProtocol2: Boolean;
@@ -2367,6 +2438,11 @@ begin
     AccountKeyListAddAccounts(newAccountInfo.accountKey,[account_number]);
   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;
   // Name:
   If acc.name<>newName then begin

+ 42 - 7
Units/PascalCoin/UBlockChain.pas

@@ -120,6 +120,7 @@ Type
     time : Cardinal;
     AffectedAccount : Cardinal;
     SignerAccount : Int64; // Is the account that executes this operation
+    n_operation : Cardinal;
     DestAccount : Int64;   //
     SellerAccount : Int64; // Protocol 2 - only used when is a pay to transaction
     newKey : TAccountKey;
@@ -187,7 +188,8 @@ Type
     Property HasValidSignature : Boolean read FHasValidSignature;
     Class function OperationHash_OLD(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;
     function Sha256 : TRawBytes;
   End;
@@ -342,6 +344,8 @@ Type
     function DoInitialize:Boolean; virtual; abstract;
     Function DoCreateSafeBoxStream(blockCount : Cardinal) : TStream; virtual; abstract;
     Procedure DoEraseStorage; virtual; abstract;
+    Procedure DoSavePendingBufferOperations(OperationsHashTree : TOperationsHashTree); virtual; abstract;
+    Procedure DoLoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree); virtual; abstract;
   public
     Function LoadBlockChainBlock(Operations : TPCOperationsComp; Block : Cardinal) : Boolean;
     Function SaveBlockChainBlock(Operations : TPCOperationsComp) : Boolean;
@@ -361,6 +365,8 @@ Type
     Function HasUpgradedToVersion2 : Boolean; virtual; abstract;
     Procedure CleanupVersion1Data; virtual; abstract;
     Procedure EraseStorage;
+    Procedure SavePendingBufferOperations(OperationsHashTree : TOperationsHashTree);
+    Procedure LoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree);
   End;
 
   TStorageClass = Class of TStorage;
@@ -406,7 +412,7 @@ Type
   End;
 
 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
 
@@ -415,7 +421,7 @@ uses
   SysUtils, Variants, {Graphics,}
   {Controls, Forms,}
   Dialogs, {StdCtrls,}
-  UTime, UConst, UOpTransaction;
+  UTime, UConst, UOpTransaction, UCommon;
 
 { TPCBank }
 
@@ -1793,6 +1799,7 @@ begin
     FOnChanged := lastNE;
   end;
   If Assigned(FOnChanged) then FOnChanged(Self);
+  errors := '';
   Result := true;
 end;
 
@@ -1866,6 +1873,16 @@ begin
   DoEraseStorage;
 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;
 begin
   if (Block<FirstBlock) Or (Block>LastBlock) then result := false
@@ -1928,7 +1945,6 @@ begin
   FReadOnly := Value;
 end;
 
-
 { TPCOperation }
 
 constructor TPCOperation.Create;
@@ -1962,13 +1978,15 @@ begin
 end;
 
 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 }
 var ms : TMemoryStream;
 begin
   Result := false;
-  account:=0;
-  n_operation:=0;
+  block :=0;
+  account :=0;
+  n_operation :=0;
+  md160Hash:='';
   if length(operationHash)<>32 then exit;
   ms := TMemoryStream.Create;
   try
@@ -1977,12 +1995,28 @@ begin
     ms.Read(block,4);
     ms.Read(account,4);
     ms.Read(n_operation,4);
+    SetLength(md160Hash, 20);
+    ms.ReadBuffer(md160Hash[1], 20);
     Result := true;
   finally
     ms.free;
   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;
 begin
   Result := TCrypto.ToHexaString(Copy(operationHash,5,28));
@@ -2092,6 +2126,7 @@ begin
   OperationResume.AffectedAccount := Affected_account_number;
   OperationResume.OpType:=Operation.OpType;
   OperationResume.SignerAccount := Operation.SignerAccount;
+  OperationResume.n_operation := Operation.N_Operation;
   Result := false;
   case Operation.OpType of
     CT_Op_Transaction : Begin

+ 1 - 1
Units/PascalCoin/UConst.pas

@@ -135,7 +135,7 @@ Const
   CT_OpSubtype_ChangeKeySigned            = 71;
   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';
 

+ 60 - 1
Units/PascalCoin/UFileStorage.pas

@@ -38,6 +38,7 @@ Type
   private
     FStorageLock : TPCCriticalSection;
     FBlockChainStream : TFileStream;
+    FPendingBufferOperationsStream : TFileStream;
     FStreamFirstBlockNumber : Int64;
     FStreamLastBlockNumber : Int64;
     FBlockHeadersFirstBytePosition : TArrayOfInt64;
@@ -52,6 +53,7 @@ Type
     procedure SetDatabaseFolder(const Value: AnsiString);
     Procedure ClearStream;
     Procedure GrowStreamUntilPos(Stream : TStream; newPos : Int64; DeleteDataStartingAtCurrentPos : Boolean);
+    Function GetPendingBufferOperationsStream : TFileStream;
   protected
     procedure SetReadOnly(const Value: Boolean); override;
     procedure SetOrphan(const Value: TOrphan); override;
@@ -70,6 +72,8 @@ Type
     function DoInitialize : Boolean; override;
     Function DoCreateSafeBoxStream(blockCount : Cardinal) : TStream; override;
     Procedure DoEraseStorage; override;
+    Procedure DoSavePendingBufferOperations(OperationsHashTree : TOperationsHashTree); override;
+    Procedure DoLoadPendingBufferOperations(OperationsHashTree : TOperationsHashTree); override;
   public
     Constructor Create(AOwner : TComponent); Override;
     Destructor Destroy; Override;
@@ -142,6 +146,7 @@ end;
 procedure TFileStorage.ClearStream;
 begin
   FreeAndNil(FBlockChainStream);
+  FreeAndNil(FPendingBufferOperationsStream);
   FStreamFirstBlockNumber := 0;
   FStreamLastBlockNumber := -1;
   SetLength(FBlockHeadersFirstBytePosition,0);
@@ -167,6 +172,27 @@ begin
   Stream.Position := newPos;
 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);
 begin
   inherited;
@@ -184,6 +210,7 @@ begin
   SetLength(FBlockHeadersFirstBytePosition,0);
   FStreamFirstBlockNumber := 0;
   FStreamLastBlockNumber := -1;
+  FPendingBufferOperationsStream := Nil;
   FStorageLock := TPCCriticalSection.Create('TFileStorage_StorageLock');
 end;
 
@@ -236,7 +263,7 @@ begin
   Result := Nil;
   fn := GetSafeboxCheckpointingFileName(GetFolder(Orphan),blockCount);
   If (fn<>'') and (FileExists(fn)) then begin
-    Result := TFileStream.Create(fn,fmOpenRead);
+    Result := TFileStream.Create(fn,fmOpenRead+fmShareDenyWrite);
   end;
   If Not Assigned(Result) then begin
     err := 'Cannot load SafeBoxStream (block:'+IntToStr(blockCount)+') file:'+fn;
@@ -256,6 +283,38 @@ begin
   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;
 Var stream : TStream;
   iBlockHeaders : Integer;

+ 5 - 2
Units/PascalCoin/UNetProtocol.pas

@@ -530,6 +530,8 @@ begin
            ((nsa.last_connection>0) Or (nsa.last_connection_by_server>0))
           ))
       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);
         dec(FNetStatistics.NodeServersListCount);
         inc(FNetStatistics.NodeServersDeleted);
@@ -2448,7 +2450,7 @@ Begin
       end;
       //
       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;
     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;
                 case HeaderData.operation of
                   CT_NetOp_Hello : Begin
+                    iDebugStep := 1100;
                     DoProcess_Hello(HeaderData,ReceiveDataBuffer);
                   End;
                   CT_NetOp_Message : Begin
@@ -2732,7 +2735,7 @@ begin
     end;
   Except
     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;
     end;
   End;

+ 202 - 35
Units/PascalCoin/UNode.pas

@@ -38,6 +38,8 @@ Type
 
   { TNode }
 
+  TSearchOperationResult = (found, invalid_params, blockchain_block_not_found);
+
   TNode = Class(TComponent)
   private
     FNodeLog : TLog;
@@ -72,7 +74,7 @@ Type
     Property Operations : TPCOperationsComp read FOperations;
     //
     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 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);
     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);
     Function IsBlockChainValid(var WhyNot : AnsiString) : Boolean;
     Function IsReady(Var CurrentProcess : AnsiString) : Boolean;
@@ -162,6 +168,8 @@ Var i,j : Integer;
   errors2 : AnsiString;
   OpBlock : TOperationBlock;
   opsht : TOperationsHashTree;
+  minBlockResend : Cardinal;
+  resendOp : TPCOperation;
 begin
   Result := false;
   errors := '';
@@ -225,10 +233,22 @@ begin
     if Result then begin
       opsht := TOperationsHashTree.Create;
       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
-          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;
         if opsht.OperationsCount>0 then begin
           TLog.NewLog(ltinfo,classname,'Resending '+IntToStr(opsht.OperationsCount)+' operations for new block');
@@ -245,7 +265,7 @@ begin
           end;
         end;
         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;
         TLog.NewLog(ltdebug,ClassName,'Buffer Sent operations: '+IntToStr(FSentOperations.Count));
         // Notify to clients
@@ -284,7 +304,7 @@ begin
   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}
   Procedure Process_BufferOfFutureOperations(valids_operations : TOperationsHashTree);
   Var i,j, nAdded, nDeleted : Integer;
@@ -320,20 +340,18 @@ function TNode.AddOperations(SenderConnection : TNetConnection; Operations : TOp
   {$ENDIF}
 Var
   i,j : Integer;
-  operationscomp : TPCOperationsComp;
   valids_operations : TOperationsHashTree;
   nc : TNetConnection;
   e : AnsiString;
-  mtl : TList;
   s : String;
   OPR : TOperationResume;
   ActOp : TPCOperation;
-  sAcc : TAccount;
+  {$IFDEF BufferOfFutureOperations}sAcc : TAccount;{$ENDIF}
 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]);
+    errors := Format('Cannot Add Operations due is adding disabled - OpCount:%d',[OperationsHashTree.OperationsCount]);
     TLog.NewLog(ltinfo,Classname,errors);
     exit;
   end;
@@ -342,7 +360,7 @@ begin
   valids_operations := TOperationsHashTree.Create;
   try
     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
       s := 'Cannot AddOperations due blocking lock operations node';
       TLog.NewLog(lterror,Classname,s);
@@ -352,17 +370,17 @@ begin
       {$IFDEF BufferOfFutureOperations}
       Process_BufferOfFutureOperations(valids_operations);
       {$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
           If (ActOp.OperationFee=0) And (Bank.SafeBox.CurrentProtocol>=CT_PROTOCOL_2) And
              (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]);
             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',
-              [(j+1),Operations.OperationsCount,ActOp.ToString,e]));
+              [(j+1),OperationsHashTree.OperationsCount,ActOp.ToString,e]));
             if Assigned(OperationsResult) then begin
               TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
               OPR.valid := false;
@@ -372,12 +390,11 @@ begin
               OperationsResult.Add(OPR);
             end;
           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
               inc(Result);
+              FSentOperations.Add(ActOp.Sha256,FOperations.OperationBlock.block);
               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
                 TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
                 OPR.NOpInsideBlock:=FOperations.Count-1;
@@ -386,9 +403,9 @@ begin
               end;
             end else begin
               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',
-                [(j+1),Operations.OperationsCount,ActOp.ToString,e]));
+                [(j+1),OperationsHashTree.OperationsCount,ActOp.ToString,e]));
               if Assigned(OperationsResult) then begin
                 TPCOperation.OperationToOperationResume(0,ActOp,ActOp.SignerAccount,OPR);
                 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
                   If FBufferAuxWaitingOperations.IndexOfOperation(ActOp)<0 then begin
                     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;
@@ -413,15 +430,29 @@ begin
             end;
           end;
         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;
+      // Save operations buffer
+      If Result<>0 then begin
+        Bank.Storage.SavePendingBufferOperations(Self.Operations.OperationsHashTree);
+      end;
     finally
       FLockNodeOperations.Release;
       if Result<>0 then begin
         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;
     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);
 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;
-  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;
   i : Integer;
   op : TPCOperation;
   initial_block, aux_block : Cardinal;
   opHashValid, opHash_OLD : TRawBytes;
+  md160 : TRawBytes;
 begin
-  Result := False;
+  Result := invalid_params;
   // 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;
   //
   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
             operation_block_index:=i;
             OperationComp.CopyFrom(FOperations);
-            Result := true;
+            Result := found;
             exit;
           end;
         end;
@@ -796,9 +950,12 @@ begin
   end;
   if (block=0) or (block>=Bank.BlocksCount) then exit;
   // Search in previous blocks
-  While (Not Result) And (block>0) do begin
+  While (block>0) do begin
     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
       op := OperationComp.Operation[i];
       if (op.SignerAccount=account) then begin
@@ -808,13 +965,13 @@ begin
           opHashValid := TPCOperation.OperationHashValid(op,initial_block);
           If (opHashValid=OperationHash) then begin
             operation_block_index:=i;
-            Result := true;
+            Result := found;
             exit;
           end else if (block<CT_Protocol_Upgrade_v2_MinBlock) then begin
             opHash_OLD := TPCOperation.OperationHash_OLD(op,initial_block);
             if (opHash_OLD=OperationHash) then begin
               operation_block_index:=i;
-              Result := true;
+              Result := found;
               exit;
             end else exit; // Not found!
           end else exit; // Not found!
@@ -834,6 +991,16 @@ begin
   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);
 Var i : Integer;
   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('account').Value:=OPR.AffectedAccount;
     jsonObject.GetAsVariant('signer_account').Value:=OPR.SignerAccount;
+    jsonObject.GetAsVariant('n_operation').Value:=OPR.n_operation;
     jsonObject.GetAsVariant('optxt').Value:=OPR.OperationTxt;
     jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
     jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OPR.Fee);
@@ -2044,7 +2045,52 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := True;
   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;
   account : TAccount;
   senderpubkey,destpubkey : TAccountKey;
@@ -2053,7 +2099,7 @@ Var c,c2 : Cardinal;
   pcops : TPCOperationsComp;
   ecpkey : TECPrivateKey;
   opr : TOperationResume;
-  r : TRawBytes;
+  r1,r2 : TRawBytes;
   ocl : TOrderedCardinalList;
   jsonarr : TPCJSONArray;
   jso : TPCJSONObject;
@@ -2364,20 +2410,47 @@ begin
       FillOperationResumeToJSONObject(opr,GetResultArray.GetAsObject( FNode.Operations.Count-1-i ));
     end;
     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
     // 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;
-      ErrorDesc:='param ophash not found or invalid value "'+params.AsString('ophash','')+'"';
+      ErrorDesc:='param ophash not found or invalid hexadecimal value "'+params.AsString('ophash','')+'"';
       exit;
     end;
     pcops := TPCOperationsComp.Create(Nil);
     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;
       If not TPCOperation.OperationToOperationResume(c,pcops.Operation[i],pcops.Operation[i].SignerAccount,opr) then begin
         ErrorNum := CT_RPC_ErrNum_InternalError;
@@ -2391,6 +2464,29 @@ begin
     finally
       pcops.Free;
     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
     // Sends "amount" coins from "sender" to "target" with "fee"
     // 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;
         TFileStorage(FNode.Bank.Storage).DatabaseFolder := TFolderHelper.GetPascalCoinDataFolder+PathDelim+'Data';
         // Reading database
-        FNode.Node.Bank.DiskRestoreFromOperations(CT_MaxBlock);
+        FNode.InitSafeboxAndOperations;
         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.MaxConnections:=FIniFile.ReadInteger(CT_INI_SECTION_GLOBAL,CT_INI_IDENT_NODE_MAX_CONNECTIONS,CT_MaxClientsConnected);