Herman Schoenfeld před 7 roky
rodič
revize
9c37873d63

+ 25 - 25
README.md

@@ -55,36 +55,36 @@ Also, consider a donation at PascalCoin development account: "0-10"
 - JSON-RPC changes:
   - Added param "startblock" to "getaccountoperations" in order to start searching backwards on a specific block. Note: Balance will not be returned on each operation due cannot be calculated. Default value "0" means start searching on current block as usual
   - Operation Object changes:
+    New fields:
+    - "senders" : ARRAY of objects - When is a transaction, this array contains each sender
+      - "account" : Sending Account 
+      - "n_operation"
+      - "amount" : PASCURRENCY - In negative value, due it's outgoing from "account"
+      - "payload" : HEXASTRING
+    - "receivers" : ARRAY of objects - When is a transaction, this array contains each receiver
+      - "account" : Receiving Account 
+      - "amount" : PASCURRENCY - In positive value, due it's incoming from a sender to "account"
+      - "payload" : HEXASTRING
+    - "changers" : ARRAY of objects - When accounts changed state
+      - "account" : changing Account 
+      - "n_operation"
+      - "new_enc_pubkey" : If public key is changed
+      - "new_name" : If name is changed
+      - "new_type" : If type is changed
+      - "fee" : PASCURRENCY - In negative value, due it's outgoing from "account"
+    Modified fields / DEPRECATED FIELDS
+    Caused by multioperation introduction, search in "senders"/"receivers"/"changers" instead
     - "balance" will not be included when is not possible to calc previous balance of account searching at the past
-    - Fields not included when in Multioperation:
-      - "account" will not be included in Multioperations
-      - "signer_account" will not be included in Multioperations
-      - "n_operation" will not be included in Multioperations
-      - "amount" will not be included in Multioperations, need search on each field
-      - "payload" will not be included in Multioperations, need search on each field
-    - On Multioperations, will include those new fields:
-      - "totalamount" will be the total amount equal to SUM each "receivers"."amount" field
-      - "senders" : Will return an Array with Objects
-        - "account" : Sending Account 
-        - "n_operation"
-        - "amount" : In negative value, due it's outgoing form "account"
-        - "payload"
-      - "receivers"
-        - "account" : Receiving Account 
-        - "amount" : In positive value, due it's incoming from a sender to "account"
-        - "payload"
-      - "changers" : Will return an Array with Objects
-        - "account" : changing Account 
-        - "n_operation"
-        - "new_enc_pubkey" : If public key is changed
-        - "new_name" : If name is changed
-        - "new_type" : If type is changed
+    - "signer_account" will not be included in Multioperations
+    - "account" : will not be included in Multioperations, use fields in "senders"/"receivers"/"changers" instead    
+    - "n_operation" will not be included in Multioperations, use fields in "senders"/"receivers"/"changers" instead
+    - "payload" will not be included in Multioperations, use fields in "senders"/"receivers"/"changers" instead
   - New object "MultiOperation Object" : Will return info about a MultiOperation
     - "rawoperations" : HEXASTRING with this single MultiOperation in RAW format
     - "senders" : Will return an Array with Objects
       - "account" : Sending Account 
       - "n_operation"
-      - "amount" : In negative value, due it's outgoing form "account"
+      - "amount" : In negative value, due it's outgoing from "account"
       - "payload"
     - "receivers"
       - "account" : Receiving Account 
@@ -98,7 +98,7 @@ Also, consider a donation at PascalCoin development account: "0-10"
       - "new_type" : If type is changed
     - "amount" : PASCURRENCY Amount received by receivers
     - "fee" : PASCURRENCY Equal to "total send" - "total received"
-	- "signed_count" : Integer with info about how many accounts are signed 
+	- "signed_count" : Integer with info about how many accounts are signed. Does not check if signature is valid for a multioperation not included in blockchain 
 	- "not_signed_count" : Integer with info about how many accounts are pending to be signed
     - "signed_can_execute"	: Boolean. True if everybody signed. Does not check if MultiOperation is well formed or can be added to Network because is an offline call
   - New method "signmessage": Signs a digest message using a public key

+ 253 - 90
src/core/UAccounts.pas

@@ -62,7 +62,7 @@ Type
     Class Function GetRewardForNewLine(line_index: Cardinal): UInt64;
     Class Function TargetToCompact(target: TRawBytes): Cardinal;
     Class Function TargetFromCompact(encoded: Cardinal): TRawBytes;
-    Class Function GetNewTarget(vteorical, vreal: Cardinal; Const actualTarget: TRawBytes): TRawBytes;
+    Class Function GetNewTarget(vteorical, vreal: Cardinal; protocol_version : Integer; isSlowMovement : Boolean; Const actualTarget: TRawBytes): TRawBytes;
     Class Procedure CalcProofOfWork_Part1(const operationBlock : TOperationBlock; out Part1 : TRawBytes);
     Class Procedure CalcProofOfWork_Part3(const operationBlock : TOperationBlock; out Part3 : TRawBytes);
     Class Procedure CalcProofOfWork(const operationBlock : TOperationBlock; out PoW : TRawBytes);
@@ -193,12 +193,17 @@ Type
   TAccountKeyArray = array of TAccountKey;
 
   // This is a class to quickly find accountkeys and their respective account number/s
+
+  { TOrderedAccountKeysList }
+
   TOrderedAccountKeysList = Class
   Private
     FAutoAddAll : Boolean;
     FAccountList : TPCSafeBox;
-    FOrderedAccountKeysList : TList; // An ordered list of pointers to quickly find account keys in account list
-    Function Find(Const AccountKey: TAccountKey; var Index: Integer): Boolean;
+    FOrderedAccountKeysList : TPCThreadList; // An ordered list of pointers to quickly find account keys in account list
+    FTotalChanges : Integer;
+    Function Find(lockedList : TList; Const AccountKey: TAccountKey; var Index: Integer): Boolean;
+    function GetAccountKeyChanges(index : Integer): Integer;
     function GetAccountKeyList(index: Integer): TOrderedCardinalList;
     function GetAccountKey(index: Integer): TAccountKey;
   protected
@@ -213,10 +218,15 @@ Type
     Function IndexOfAccountKey(Const AccountKey : TAccountKey) : Integer;
     Property AccountKeyList[index : Integer] : TOrderedCardinalList read GetAccountKeyList;
     Property AccountKey[index : Integer] : TAccountKey read GetAccountKey;
+    Property AccountKeyChanges[index : Integer] : Integer read GetAccountKeyChanges;
+    procedure ClearAccountKeyChanges;
     Function Count : Integer;
     Property SafeBox : TPCSafeBox read FAccountList;
     Procedure Clear;
     function ToArray : TAccountKeyArray;
+    function Lock : TList;
+    procedure Unlock;
+    function HasAccountKeyChanged : Boolean;
   End;
 
   // Maintans a Cardinal ordered (without duplicates) list with TRawData each
@@ -578,10 +588,10 @@ end;
 
 { TPascalCoinProtocol }
 
-class function TPascalCoinProtocol.GetNewTarget(vteorical, vreal: Cardinal; const actualTarget: TRawBytes): TRawBytes;
+class function TPascalCoinProtocol.GetNewTarget(vteorical, vreal: Cardinal; protocol_version : Integer; isSlowMovement : Boolean; const actualTarget: TRawBytes): TRawBytes;
 Var
   bnact, bnaux: TBigNum;
-  tsTeorical, tsReal, factor1000, factor1000Min, factor1000Max: Int64;
+  tsTeorical, tsReal, factor, factorMin, factorMax, factorDivider: Int64;
 begin
   { Given a teorical time in seconds (vteorical>0) and a real time in seconds (vreal>0)
     and an actual target, calculates a new target
@@ -594,22 +604,48 @@ begin
     }
   tsTeorical := vteorical;
   tsReal := vreal;
-  factor1000 := (((tsTeorical - tsReal) * 1000) DIV (tsTeorical)) * (-1);
-
-  { Important: Note that a -500 is the same that divide by 2 (-100%), and
-    1000 is the same that multiply by 2 (+100%), so we limit increase
-    in a limit [-500..+1000] for a complete (CT_CalcNewTargetBlocksAverage DIV 2) round }
-  if CT_CalcNewTargetBlocksAverage>1 then begin
-    factor1000Min := (-500) DIV (CT_CalcNewTargetBlocksAverage DIV 2);
-    factor1000Max := (1000) DIV (CT_CalcNewTargetBlocksAverage DIV 2);
+
+  { On protocol 1,2 the increment was limited in a integer value between -10..20
+    On protocol 3 we increase decimals, so increment could be a integer
+    between -1000..2000, using 2 more decimals for percent. Also will introduce
+    a "isSlowMovement" variable that will limit to a maximum +-0.5% increment}
+  if (protocol_version<CT_PROTOCOL_3) then begin
+    factorDivider := 1000;
+    factor := (((tsTeorical - tsReal) * 1000) DIV (tsTeorical)) * (-1);
+
+    { Important: Note that a -500 is the same that divide by 2 (-100%), and
+      1000 is the same that multiply by 2 (+100%), so we limit increase
+      in a limit [-500..+1000] for a complete (CT_CalcNewTargetBlocksAverage DIV 2) round }
+    if CT_CalcNewTargetBlocksAverage>1 then begin
+      factorMin := (-500) DIV (CT_CalcNewTargetBlocksAverage DIV 2);
+      factorMax := (1000) DIV (CT_CalcNewTargetBlocksAverage DIV 2);
+    end else begin
+      factorMin := (-500);
+      factorMax := (1000);
+    end;
   end else begin
-    factor1000Min := (-500);
-    factor1000Max := (1000);
+    // Protocol 3:
+    factorDivider := 100000;
+    If (isSlowMovement) then begin
+      // Limit to 0.5% instead of 2% (When CT_CalcNewTargetBlocksAverage = 100)
+      factorMin := (-50000) DIV (CT_CalcNewTargetBlocksAverage * 2);
+      factorMax := (100000) DIV (CT_CalcNewTargetBlocksAverage * 2);
+    end else begin
+      if CT_CalcNewTargetBlocksAverage>1 then begin
+        factorMin := (-50000) DIV (CT_CalcNewTargetBlocksAverage DIV 2);
+        factorMax := (100000) DIV (CT_CalcNewTargetBlocksAverage DIV 2);
+      end else begin
+        factorMin := (-50000);
+        factorMax := (100000);
+      end;
+    end;
   end;
 
-  if factor1000 < factor1000Min then factor1000 := factor1000Min
-  else if factor1000 > factor1000Max then factor1000 := factor1000Max
-  else if factor1000=0 then begin
+  factor := (((tsTeorical - tsReal) * factorDivider) DIV (tsTeorical)) * (-1);
+
+  if factor < factorMin then factor := factorMin
+  else if factor > factorMax then factor := factorMax
+  else if factor=0 then begin
     Result := actualTarget;
     exit;
   end;
@@ -620,7 +656,7 @@ begin
     bnact.RawValue := actualTarget;
     bnaux := bnact.Copy;
     try
-      bnact.Multiply(factor1000).Divide(1000).Add(bnaux);
+      bnact.Multiply(factor).Divide(factorDivider).Add(bnaux);
     finally
       bnaux.Free;
     end;
@@ -3266,7 +3302,7 @@ begin
     tsTeorical := (CalcBack * CT_NewLineSecondsAvg);
     tsReal := (ts1 - ts2);
     If (protocolVersion=CT_PROTOCOL_1) then begin
-      Result := TPascalCoinProtocol.GetNewTarget(tsTeorical, tsReal,TPascalCoinProtocol.TargetFromCompact(lastBlock.compact_target));
+      Result := TPascalCoinProtocol.GetNewTarget(tsTeorical, tsReal,protocolVersion,False,TPascalCoinProtocol.TargetFromCompact(lastBlock.compact_target));
     end else if (protocolVersion<=CT_PROTOCOL_3) then begin
       CalcBack := CalcBack DIV CT_CalcNewTargetLimitChange_SPLIT;
       If CalcBack=0 then CalcBack := 1;
@@ -3280,15 +3316,15 @@ begin
       If ((tsTeorical>tsReal) and (tsTeoricalStop>tsRealStop))
          Or
          ((tsTeorical<tsReal) and (tsTeoricalStop<tsRealStop)) then begin
-        Result := TPascalCoinProtocol.GetNewTarget(tsTeorical, tsReal,TPascalCoinProtocol.TargetFromCompact(lastBlock.compact_target));
+        Result := TPascalCoinProtocol.GetNewTarget(tsTeorical, tsReal,protocolVersion,False,TPascalCoinProtocol.TargetFromCompact(lastBlock.compact_target));
       end else begin
         if (protocolVersion=CT_PROTOCOL_2) then begin
           // Nothing to do!
           Result:=TPascalCoinProtocol.TargetFromCompact(lastBlock.compact_target);
         end else begin
           // New on V3 protocol:
-          // Harmonization of the sinusoidal effect modifying the rise / fall by 50% calculating over the "stop" area
-          Result := TPascalCoinProtocol.GetNewTarget(tsTeoricalStop, (tsTeoricalStop + tsRealStop) DIV 2,TPascalCoinProtocol.TargetFromCompact(lastBlock.compact_target));
+          // Harmonization of the sinusoidal effect modifying the rise / fall over the "stop" area
+          Result := TPascalCoinProtocol.GetNewTarget(tsTeoricalStop,tsRealStop,protocolVersion,True,TPascalCoinProtocol.TargetFromCompact(lastBlock.compact_target));
         end;
       end;
     end else begin
@@ -4313,8 +4349,11 @@ Type
   TOrderedAccountKeyList = Record
     rawaccountkey : TRawBytes;
     accounts_number : TOrderedCardinalList;
+    changes_counter : Integer;
   end;
   POrderedAccountKeyList = ^TOrderedAccountKeyList;
+Const
+  CT_TOrderedAccountKeyList_NUL : TOrderedAccountKeyList = (rawaccountkey:'';accounts_number:Nil;changes_counter:0);
 
 function SortOrdered(Item1, Item2: Pointer): Integer;
 begin
@@ -4324,78 +4363,136 @@ end;
 procedure TOrderedAccountKeysList.AddAccountKey(const AccountKey: TAccountKey);
 Var P : POrderedAccountKeyList;
   i,j : Integer;
+  lockedList : TList;
 begin
-  if Not Find(AccountKey,i) then begin
-    New(P);
-    P^.rawaccountkey := TAccountComp.AccountKey2RawString(AccountKey);
-    P^.accounts_number := TOrderedCardinalList.Create;
-    FOrderedAccountKeysList.Insert(i,P);
-    // Search this key in the AccountsList and add all...
-    j := 0;
-    if Assigned(FAccountList) then begin
-      For i:=0 to FAccountList.AccountsCount-1 do begin
-        If TAccountComp.EqualAccountKeys(FAccountList.Account(i).accountInfo.accountkey,AccountKey) then begin
-          // Note: P^.accounts will be ascending ordered due to "for i:=0 to ..."
-          P^.accounts_number.Add(i);
+  lockedList := Lock;
+  Try
+    if Not Find(lockedList,AccountKey,i) then begin
+      New(P);
+      P^ := CT_TOrderedAccountKeyList_NUL;
+      P^.rawaccountkey := TAccountComp.AccountKey2RawString(AccountKey);
+      P^.accounts_number := TOrderedCardinalList.Create;
+      inc(P^.changes_counter);
+      inc(FTotalChanges);
+      lockedList.Insert(i,P);
+      // Search this key in the AccountsList and add all...
+      j := 0;
+      if Assigned(FAccountList) then begin
+        For i:=0 to FAccountList.AccountsCount-1 do begin
+          If TAccountComp.EqualAccountKeys(FAccountList.Account(i).accountInfo.accountkey,AccountKey) then begin
+            // Note: P^.accounts will be ascending ordered due to "for i:=0 to ..."
+            P^.accounts_number.Add(i);
+          end;
         end;
+        TLog.NewLog(ltdebug,Classname,Format('Adding account key (%d of %d) %s',[j,FAccountList.AccountsCount,TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(AccountKey))]));
+      end else begin
+        TLog.NewLog(ltdebug,Classname,Format('Adding account key (no Account List) %s',[TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(AccountKey))]));
       end;
-      TLog.NewLog(ltdebug,Classname,Format('Adding account key (%d of %d) %s',[j,FAccountList.AccountsCount,TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(AccountKey))]));
-    end else begin
-      TLog.NewLog(ltdebug,Classname,Format('Adding account key (no Account List) %s',[TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(AccountKey))]));
     end;
+  finally
+    Unlock;
   end;
 end;
 
 procedure TOrderedAccountKeysList.AddAccounts(const AccountKey: TAccountKey; const accounts: array of Cardinal);
 Var P : POrderedAccountKeyList;
   i,i2 : Integer;
+  lockedList : TList;
 begin
-  if Find(AccountKey,i) then begin
-    P :=  POrderedAccountKeyList(FOrderedAccountKeysList[i]);
-  end else if (FAutoAddAll) then begin
-    New(P);
-    P^.rawaccountkey := TAccountComp.AccountKey2RawString(AccountKey);
-    P^.accounts_number := TOrderedCardinalList.Create;
-    FOrderedAccountKeysList.Insert(i,P);
-  end else exit;
-  for i := Low(accounts) to High(accounts) do begin
-    P^.accounts_number.Add(accounts[i]);
+  lockedList := Lock;
+  Try
+    if Find(lockedList,AccountKey,i) then begin
+      P :=  POrderedAccountKeyList(lockedList[i]);
+    end else if (FAutoAddAll) then begin
+      New(P);
+      P^ := CT_TOrderedAccountKeyList_NUL;
+      P^.rawaccountkey := TAccountComp.AccountKey2RawString(AccountKey);
+      P^.accounts_number := TOrderedCardinalList.Create;
+      lockedList.Insert(i,P);
+    end else exit;
+    for i := Low(accounts) to High(accounts) do begin
+      P^.accounts_number.Add(accounts[i]);
+    end;
+    inc(P^.changes_counter);
+    inc(FTotalChanges);
+  finally
+    Unlock;
   end;
 end;
 
 procedure TOrderedAccountKeysList.Clear;
 begin
-  ClearAccounts(true);
+  Lock;
+  Try
+    ClearAccounts(true);
+    FTotalChanges := 1; // 1 = At least 1 change
+  finally
+    Unlock;
+  end;
 end;
 
 function TOrderedAccountKeysList.ToArray : TAccountKeyArray;
 var i : Integer;
 begin
-  SetLength(Result, Count);
-  for i := 0 to Count - 1 do Result[i] := Self.AccountKey[i];
+  Lock;
+  Try
+    SetLength(Result, Count);
+    for i := 0 to Count - 1 do Result[i] := Self.AccountKey[i];
+  finally
+    Unlock;
+  end;
+end;
+
+function TOrderedAccountKeysList.Lock: TList;
+begin
+  Result := FOrderedAccountKeysList.LockList;
+end;
+
+procedure TOrderedAccountKeysList.Unlock;
+begin
+  FOrderedAccountKeysList.UnlockList;
+end;
+
+function TOrderedAccountKeysList.HasAccountKeyChanged: Boolean;
+begin
+  Result := FTotalChanges>0;
 end;
 
 procedure TOrderedAccountKeysList.ClearAccounts(RemoveAccountList : Boolean);
 Var P : POrderedAccountKeyList;
   i : Integer;
+  lockedList : TList;
 begin
-  for i := 0 to FOrderedAccountKeysList.Count - 1 do begin
-    P := FOrderedAccountKeysList[i];
+  lockedList := Lock;
+  Try
+    for i := 0 to  lockedList.Count - 1 do begin
+      P := lockedList[i];
+      inc(P^.changes_counter);
+      if RemoveAccountList then begin
+        P^.accounts_number.Free;
+        Dispose(P);
+      end else begin
+        P^.accounts_number.Clear;
+      end;
+    end;
     if RemoveAccountList then begin
-      P^.accounts_number.Free;
-      Dispose(P);
-    end else begin
-      P^.accounts_number.Clear;
+      lockedList.Clear;
     end;
-  end;
-  if RemoveAccountList then begin
-    FOrderedAccountKeysList.Clear;
+    FTotalChanges:=lockedList.Count + 1; // At least 1 change
+  finally
+    Unlock;
   end;
 end;
 
 function TOrderedAccountKeysList.Count: Integer;
+var lockedList : TList;
 begin
-  Result := FOrderedAccountKeysList.Count;
+  lockedList := Lock;
+  Try
+    Result := lockedList.Count;
+  finally
+    Unlock;
+  end;
 end;
 
 constructor TOrderedAccountKeysList.Create(AccountList : TPCSafeBox; AutoAddAll : Boolean);
@@ -4404,13 +4501,19 @@ begin
   TLog.NewLog(ltdebug,Classname,'Creating an Ordered Account Keys List adding all:'+CT_TRUE_FALSE[AutoAddAll]);
   FAutoAddAll := AutoAddAll;
   FAccountList := AccountList;
-  FOrderedAccountKeysList := TList.Create;
+  FTotalChanges:=0;
+  FOrderedAccountKeysList := TPCThreadList.Create(ClassName);
   if Assigned(AccountList) then begin
-    AccountList.FListOfOrderedAccountKeysList.Add(Self);
-    if AutoAddAll then begin
-      for i := 0 to AccountList.AccountsCount - 1 do begin
-        AddAccountKey(AccountList.Account(i).accountInfo.accountkey);
+    Lock;
+    Try
+      AccountList.FListOfOrderedAccountKeysList.Add(Self);
+      if AutoAddAll then begin
+        for i := 0 to AccountList.AccountsCount - 1 do begin
+          AddAccountKey(AccountList.Account(i).accountInfo.accountkey);
+        end;
       end;
+    finally
+      Unlock;
     end;
   end;
 end;
@@ -4426,18 +4529,18 @@ begin
   inherited;
 end;
 
-function TOrderedAccountKeysList.Find(const AccountKey: TAccountKey; var Index: Integer): Boolean;
+function TOrderedAccountKeysList.Find(lockedList : TList; const AccountKey: TAccountKey; var Index: Integer): Boolean;
 var L, H, I, C: Integer;
   rak : TRawBytes;
 begin
   Result := False;
   rak := TAccountComp.AccountKey2RawString(AccountKey);
   L := 0;
-  H := FOrderedAccountKeysList.Count - 1;
+  H := lockedList.Count - 1;
   while L <= H do
   begin
     I := (L + H) shr 1;
-    C := CompareStr( POrderedAccountKeyList(FOrderedAccountKeysList[I]).rawaccountkey, rak );
+    C := TBaseType.BinStrComp( POrderedAccountKeyList(lockedList[I]).rawaccountkey, rak );
     if C < 0 then L := I + 1 else
     begin
       H := I - 1;
@@ -4451,52 +4554,112 @@ begin
   Index := L;
 end;
 
+function TOrderedAccountKeysList.GetAccountKeyChanges(index : Integer): Integer;
+var lockedList : TList;
+begin
+  lockedList := Lock;
+  Try
+    Result :=  POrderedAccountKeyList(lockedList[index])^.changes_counter;
+  finally
+    Unlock;
+  end;
+end;
+
 function TOrderedAccountKeysList.GetAccountKey(index: Integer): TAccountKey;
 Var raw : TRawBytes;
+  lockedList : TList;
 begin
-  raw := POrderedAccountKeyList(FOrderedAccountKeysList[index]).rawaccountkey;
+  lockedList := Lock;
+  Try
+    raw := POrderedAccountKeyList(lockedList[index]).rawaccountkey;
+  finally
+    Unlock;
+  end;
   Result := TAccountComp.RawString2Accountkey(raw);
 end;
 
 function TOrderedAccountKeysList.GetAccountKeyList(index: Integer): TOrderedCardinalList;
+var lockedList : TList;
 begin
-  Result := POrderedAccountKeyList(FOrderedAccountKeysList[index]).accounts_number;
+  lockedList := Lock;
+  Try
+    Result := POrderedAccountKeyList(lockedList[index]).accounts_number;
+  finally
+    Unlock;
+  end;
 end;
 
 function TOrderedAccountKeysList.IndexOfAccountKey(const AccountKey: TAccountKey): Integer;
+var lockedList : TList;
+begin
+  lockedList := Lock;
+  Try
+    If Not Find(lockedList,AccountKey,Result) then Result := -1;
+  finally
+    Unlock;
+  end;
+end;
+
+procedure TOrderedAccountKeysList.ClearAccountKeyChanges;
+var i : Integer;
+  lockedList : TList;
 begin
-  If Not Find(AccountKey,Result) then Result := -1;
+  lockedList := Lock;
+  Try
+    for i:=0 to lockedList.Count-1 do begin
+      POrderedAccountKeyList(lockedList[i])^.changes_counter:=0;
+    end;
+    FTotalChanges:=0;
+  finally
+    Unlock;
+  end;
 end;
 
 procedure TOrderedAccountKeysList.RemoveAccounts(const AccountKey: TAccountKey; const accounts: array of Cardinal);
 Var P : POrderedAccountKeyList;
   i,j : Integer;
+  lockedList : TList;
 begin
-  if Not Find(AccountKey,i) then exit; // Nothing to do
-  P :=  POrderedAccountKeyList(FOrderedAccountKeysList[i]);
-  for j := Low(accounts) to High(accounts) do begin
-    P^.accounts_number.Remove(accounts[j]);
-  end;
-  if (P^.accounts_number.Count=0) And (FAutoAddAll) then begin
-    // Remove from list
-    FOrderedAccountKeysList.Delete(i);
-    // Free it
-    P^.accounts_number.free;
-    Dispose(P);
+  lockedList := Lock;
+  Try
+    if Not Find(lockedList,AccountKey,i) then exit; // Nothing to do
+    P :=  POrderedAccountKeyList(lockedList[i]);
+    inc(P^.changes_counter);
+    inc(FTotalChanges);
+    for j := Low(accounts) to High(accounts) do begin
+      P^.accounts_number.Remove(accounts[j]);
+    end;
+    if (P^.accounts_number.Count=0) And (FAutoAddAll) then begin
+      // Remove from list
+      lockedList.Delete(i);
+      // Free it
+      P^.accounts_number.free;
+      Dispose(P);
+    end;
+  finally
+    Unlock;
   end;
 end;
 
 procedure TOrderedAccountKeysList.RemoveAccountKey(const AccountKey: TAccountKey);
 Var P : POrderedAccountKeyList;
   i,j : Integer;
+  lockedList : TList;
 begin
-  if Not Find(AccountKey,i) then exit; // Nothing to do
-  P :=  POrderedAccountKeyList(FOrderedAccountKeysList[i]);
-  // Remove from list
-  FOrderedAccountKeysList.Delete(i);
-  // Free it
-  P^.accounts_number.free;
-  Dispose(P);
+  lockedList := Lock;
+  Try
+    if Not Find(lockedList,AccountKey,i) then exit; // Nothing to do
+    P :=  POrderedAccountKeyList(lockedList[i]);
+    inc(P^.changes_counter);
+    inc(FTotalChanges);
+    // Remove from list
+    lockedList.Delete(i);
+    // Free it
+    P^.accounts_number.free;
+    Dispose(P);
+  finally
+    Unlock;
+  end;
 end;
 
 { TAccountPreviousBlockInfo }

+ 3 - 2
src/core/UBlockChain.pas

@@ -132,6 +132,7 @@ Type
     New_Accountkey: TAccountKey;  // If (changes_mask and $0001)=$0001 then change account key
     New_Name: TRawBytes;          // If (changes_mask and $0002)=$0002 then change name
     New_Type: Word;               // If (changes_mask and $0004)=$0004 then change type
+    Fee: Int64;
     Signature: TECDSA_SIG;
   end;
   TMultiOpChangesInfo = Array of TMultiOpChangeInfo;
@@ -471,7 +472,7 @@ Const
   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:'';isMultiOperation:False;Senders:Nil;Receivers:Nil;changers:Nil);
   CT_TMultiOpSender_NUL : TMultiOpSender =  (Account:0;Amount:0;N_Operation:0;Payload:'';Signature:(r:'';s:''));
   CT_TMultiOpReceiver_NUL : TMultiOpReceiver = (Account:0;Amount:0;Payload:'');
-  CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:'';y:'');New_Name:'';New_Type:0;Signature:(r:'';s:''));
+  CT_TMultiOpChangeInfo_NUL : TMultiOpChangeInfo = (Account:0;N_Operation:0;Changes_type:[];New_Accountkey:(EC_OpenSSL_NID:0;x:'';y:'');New_Name:'';New_Type:0;Fee:0;Signature:(r:'';s:''));
   CT_TOpChangeAccountInfoType_Txt : Array[Low(TOpChangeAccountInfoType)..High(TOpChangeAccountInfoType)] of AnsiString = ('public_key','account_name','account_type');
 
 implementation
@@ -2616,7 +2617,7 @@ begin
   If TCrypto.IsHumanReadable(OperationResume.OriginalPayload) then OperationResume.PrintablePayload := OperationResume.OriginalPayload
   else OperationResume.PrintablePayload := TCrypto.ToHexaString(OperationResume.OriginalPayload);
   OperationResume.OperationHash:=TPCOperation.OperationHashValid(Operation,Block);
-  if (Block<CT_Protocol_Upgrade_v2_MinBlock) then begin
+  if (Block>0) And (Block<CT_Protocol_Upgrade_v2_MinBlock) then begin
     OperationResume.OperationHash_OLD:=TPCOperation.OperationHash_OLD(Operation,Block);
   end;
   OperationResume.valid := true;

+ 5 - 5
src/core/UConst.pas

@@ -100,7 +100,7 @@ Const
   CT_Protocol_Upgrade_v3_MinBlock = {$IFDEF PRODUCTION}210000{$ELSE}250{$ENDIF};
 
 
-  CT_MagicNetIdentification = {$IFDEF PRODUCTION}$0A043580{$ELSE}$03000030{$ENDIF}; // Unix timestamp 168048000 ... It's Albert birthdate!
+  CT_MagicNetIdentification = {$IFDEF PRODUCTION}$0A043580{$ELSE}$03000040{$ENDIF}; // Unix timestamp 168048000 ... It's Albert birthdate!
 
   CT_NetProtocol_Version: Word = $0006; // Version 2.1.2 only allows net protocol 6 (Introduced on 2.0.0)
   // IMPORTANT NOTE!!!
@@ -150,7 +150,7 @@ Const
   CT_OpSubtype_MultiOperation_Global      = 91;
   CT_OpSubtype_MultiOperation_AccountInfo = 92;
 
-  CT_ClientAppVersion : AnsiString = {$IFDEF PRODUCTION}'2.1.6'{$ELSE}{$IFDEF TESTNET}'TESTNET 3.0 BETA'{$ELSE}{$ENDIF}{$ENDIF};
+  CT_ClientAppVersion : AnsiString = {$IFDEF PRODUCTION}'2.1.9'{$ELSE}{$IFDEF TESTNET}'TESTNET 3.0 BETA'{$ELSE}{$ENDIF}{$ENDIF};
 
   CT_Discover_IPs =  'bpascal1.dynamic-dns.net;bpascal2.dynamic-dns.net;pascalcoin1.dynamic-dns.net;pascalcoin2.dynamic-dns.net;pascalcoin1.dns1.us;pascalcoin2.dns1.us;pascalcoin1.dns2.us;pascalcoin2.dns2.us';
 
@@ -159,9 +159,9 @@ Const
   CT_MAX_0_fee_operations_per_block_by_miner = {$IFDEF PRODUCTION}2000{$ELSE}{$IFDEF TESTNET}2000{$ELSE}{$ENDIF}{$ENDIF};
   CT_MAX_Operations_per_block_by_miner =  {$IFDEF PRODUCTION}10000{$ELSE}{$IFDEF TESTNET}50000{$ELSE}{$ENDIF}{$ENDIF};
 
-  CT_MAX_MultiOperation_Senders = 1000;
-  CT_MAX_MultiOperation_Receivers = 10000;
-  CT_MAX_MultiOperation_Changers = 1000;
+  CT_MAX_MultiOperation_Senders = 100;
+  CT_MAX_MultiOperation_Receivers = 1000;
+  CT_MAX_MultiOperation_Changers = 100;
 
   CT_DEFAULT_MaxSafeboxSnapshots = 10;
 

+ 7 - 7
src/core/UNetProtocol.pas

@@ -139,7 +139,7 @@ Type
     Destructor Destroy; Override;
     Procedure Clear;
     Function Count : Integer;
-    Function CleanBlackList : Integer;
+    Function CleanBlackList(forceCleanAll : Boolean) : Integer;
     procedure CleanNodeServersList;
     Function LockList : TList;
     Procedure UnlockList;
@@ -473,7 +473,7 @@ Const
 
 { TOrderedServerAddressListTS }
 
-function TOrderedServerAddressListTS.CleanBlackList : Integer;
+function TOrderedServerAddressListTS.CleanBlackList(forceCleanAll : Boolean) : Integer;
 Var P : PNodeServerAddress;
   i : Integer;
 begin
@@ -485,7 +485,8 @@ begin
     for i := FListByIp.Count - 1 downto 0 do begin
       P := FListByIp[i];
       // Is an old blacklisted IP? (More than 1 hour)
-      If (P^.is_blacklisted) AND ((P^.last_connection+(CT_LAST_CONNECTION_MAX_MINUTES)) < (UnivDateTimeToUnix(DateTime2UnivDateTime(now)))) then begin
+      If (P^.is_blacklisted) AND
+        ((forceCleanAll) OR ((P^.last_connection+(CT_LAST_CONNECTION_MAX_MINUTES)) < (UnivDateTimeToUnix(DateTime2UnivDateTime(now))))) then begin
         SecuredDeleteFromListByIp(i);
         inc(Result);
       end;
@@ -1264,7 +1265,7 @@ begin
     TLog.NewLog(ltInfo,ClassName,'Already discovering servers...');
     exit;
   end;
-  FNodeServersAddresses.CleanBlackList;
+  FNodeServersAddresses.CleanBlackList(False);
   If NetStatistics.ClientsConnections>0 then begin
     j := CT_MinServersConnected - NetStatistics.ServersConnectionsWithResponse;
   end else begin
@@ -1541,10 +1542,9 @@ Const CT_LogSender = 'GetNewBlockChainFromClient';
     Result := (OperationBlock.proof_of_work <> CT_OperationBlock_NUL.proof_of_work);
   End;
 
-  Function GetNewBank(start_block : Int64) : Boolean;
+  procedure GetNewBank(start_block : Int64);
   Var BlocksList : TList;
     i : Integer;
-    tempfolder : AnsiString;
     OpComp,OpExecute : TPCOperationsComp;
     oldBlockchainOperations : TOperationsHashTree;
     opsResume : TOperationsResumeList;
@@ -2154,7 +2154,7 @@ begin
       DebugStep := 'Assigning client';
       n.SetClient(Client);
       TNetData.NetData.IncStatistics(1,1,0,0,0,0);
-      TNetData.NetData.NodeServersAddresses.CleanBlackList;
+      TNetData.NetData.NodeServersAddresses.CleanBlackList(False);
       DebugStep := 'Checking blacklisted';
       if (TNetData.NetData.NodeServersAddresses.IsBlackListed(Client.RemoteHost)) then begin
         // Invalid!

+ 26 - 0
src/core/UNode.pas

@@ -116,15 +116,18 @@ Type
   TNodeNotifyEvents = Class(TComponent)
   private
     FNode: TNode;
+    FOnKeyActivity: TNotifyEvent;
     FPendingNotificationsList : TPCThreadList;
     FThreadSafeNodeNotifyEvent : TThreadSafeNodeNotifyEvent;
     FOnBlocksChanged: TNotifyEvent;
     FOnOperationsChanged: TNotifyEvent;
     FMessages : TStringList;
     FOnNodeMessageEvent: TNodeMessageEvent;
+    FWatchKeys: TOrderedAccountKeysList;
     procedure SetNode(const Value: TNode);
     Procedure NotifyBlocksChanged;
     Procedure NotifyOperationsChanged;
+    procedure SetWatchKeys(AValue: TOrderedAccountKeysList);
   protected
     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
   public
@@ -134,6 +137,8 @@ Type
     Property OnBlocksChanged : TNotifyEvent read FOnBlocksChanged write FOnBlocksChanged;
     Property OnOperationsChanged : TNotifyEvent read FOnOperationsChanged write FOnOperationsChanged;
     Property OnNodeMessageEvent : TNodeMessageEvent read FOnNodeMessageEvent write FOnNodeMessageEvent;
+    Property WatchKeys : TOrderedAccountKeysList read FWatchKeys write SetWatchKeys;
+    Property OnKeyActivity : TNotifyEvent read FOnKeyActivity write FOnKeyActivity;
   End;
 
   TThreadNodeNotifyNewBlock = Class(TPCThread)
@@ -1099,6 +1104,8 @@ begin
   FOnOperationsChanged := Nil;
   FOnBlocksChanged := Nil;
   FOnNodeMessageEvent := Nil;
+  FWatchKeys := Nil;
+  FOnKeyActivity:=Nil;
   FMessages := TStringList.Create;
   FPendingNotificationsList := TPCThreadList.Create('TNodeNotifyEvents_PendingNotificationsList');
   FThreadSafeNodeNotifyEvent := TThreadSafeNodeNotifyEvent.Create(Self);
@@ -1134,6 +1141,12 @@ begin
   if Assigned(FThreadSafeNodeNotifyEvent) then FThreadSafeNodeNotifyEvent.FNotifyOperationsChanged := true;
 end;
 
+procedure TNodeNotifyEvents.SetWatchKeys(AValue: TOrderedAccountKeysList);
+begin
+  if FWatchKeys=AValue then Exit;
+  FWatchKeys:=AValue;
+end;
+
 procedure TNodeNotifyEvents.SetNode(const Value: TNode);
 begin
   if FNode=Value then exit;
@@ -1166,17 +1179,21 @@ end;
 
 procedure TThreadSafeNodeNotifyEvent.SynchronizedProcess;
 Var i : Integer;
+  can_alert_keys : Boolean;
 begin
   Try
     If (Terminated) Or (Not Assigned(FNodeNotifyEvents)) then exit;
+    can_alert_keys := False;
     if FNotifyBlocksChanged then begin
       FNotifyBlocksChanged := false;
+      can_alert_keys := True;
       DebugStep:='Notify OnBlocksChanged';
       if Assigned(FNodeNotifyEvents) And (Assigned(FNodeNotifyEvents.FOnBlocksChanged)) then
         FNodeNotifyEvents.FOnBlocksChanged(FNodeNotifyEvents);
     end;
     if FNotifyOperationsChanged then begin
       FNotifyOperationsChanged := false;
+      can_alert_keys := True;
       DebugStep:='Notify OnOperationsChanged';
       if Assigned(FNodeNotifyEvents) And (Assigned(FNodeNotifyEvents.FOnOperationsChanged)) then
         FNodeNotifyEvents.FOnOperationsChanged(FNodeNotifyEvents);
@@ -1191,6 +1208,15 @@ begin
       end;
       FNodeNotifyEvents.FMessages.Clear;
     end;
+    if (can_alert_keys) And Assigned(FNodeNotifyEvents) And (Assigned(FNodeNotifyEvents.FWatchKeys)) then begin
+      DebugStep:='Notify WatchKeys';
+      If FNodeNotifyEvents.FWatchKeys.HasAccountKeyChanged then begin
+        FNodeNotifyEvents.FWatchKeys.ClearAccountKeyChanges;
+        if Assigned(FNodeNotifyEvents.FOnKeyActivity) then begin
+          FNodeNotifyEvents.FOnKeyActivity(FNodeNotifyEvents);
+        end;
+      end;
+    end;
   Except
     On E:Exception do begin
       TLog.NewLog(lterror,ClassName,'Exception inside a Synchronized process: '+E.ClassName+':'+E.Message+' Step:'+DebugStep);

+ 98 - 0
src/core/UOpTransaction.pas

@@ -73,6 +73,7 @@ Type
     procedure InitializeData; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
+    procedure FillOperationResume(Block : Cardinal; getInfoForAllAccounts : Boolean; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; override;
     function DoOperation(AccountPreviousUpdatedBlock : TAccountPreviousBlockInfo; AccountTransaction : TPCSafeBoxTransaction; var errors : AnsiString) : Boolean; override;
@@ -103,6 +104,7 @@ Type
     procedure InitializeData; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
+    procedure FillOperationResume(Block : Cardinal; getInfoForAllAccounts : Boolean; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
     Class Function GetOperationHashToSign(const op : TOpChangeKeyData) : TRawBytes;
     Class Function DoSignOperation(key : TECPrivateKey; var op : TOpChangeKeyData) : Boolean;
@@ -139,6 +141,7 @@ Type
     procedure InitializeData; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
+    procedure FillOperationResume(Block : Cardinal; getInfoForAllAccounts : Boolean; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
     class function OpType : Byte; override;
 
@@ -203,6 +206,7 @@ Type
     procedure InitializeData; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
+    procedure FillOperationResume(Block : Cardinal; getInfoForAllAccounts : Boolean; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
     Class Function GetOperationHashToSign(const operation : TOpListAccountData) : TRawBytes;
     Class Function DoSignOperation(key : TECPrivateKey; var operation : TOpListAccountData) : Boolean;
@@ -257,6 +261,7 @@ Type
     procedure InitializeData; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
     function LoadOpFromStream(Stream: TStream; LoadExtendedData : Boolean): Boolean; override;
+    procedure FillOperationResume(Block : Cardinal; getInfoForAllAccounts : Boolean; Affected_account_number : Cardinal; var OperationResume : TOperationResume); override;
   public
     Class Function GetOperationHashToSign(const op : TOpChangeAccountInfoData) : TRawBytes;
     Class Function DoSignOperation(key : TECPrivateKey; var op : TOpChangeAccountInfoData) : Boolean;
@@ -356,6 +361,30 @@ begin
   Result := true;
 end;
 
+procedure TOpChangeAccountInfo.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
+begin
+  inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
+  SetLength(OperationResume.Changers,1);
+  OperationResume.Changers[0] := CT_TMultiOpChangeInfo_NUL;
+  OperationResume.Changers[0].Account := FData.account_target;
+  OperationResume.Changers[0].Changes_type := FData.changes_type;
+  OperationResume.Changers[0].New_Accountkey := FData.new_accountkey;
+  OperationResume.Changers[0].New_Name := FData.new_name;
+  OperationResume.Changers[0].New_Type := FData.new_type;
+  If (FData.account_signer=FData.account_target) then begin
+    OperationResume.Changers[0].N_Operation := FData.n_operation;
+    OperationResume.Changers[0].Signature := FData.sign;
+    OperationResume.Changers[0].Fee := FData.fee;
+  end else begin
+    SetLength(OperationResume.Changers,2);
+    OperationResume.Changers[1] := CT_TMultiOpChangeInfo_NUL;
+    OperationResume.Changers[1].Account := FData.account_signer;
+    OperationResume.Changers[1].N_Operation := FData.n_operation;
+    OperationResume.Changers[1].Fee := FData.fee;
+    OperationResume.Changers[1].Signature := FData.sign;
+  end;
+end;
+
 class function TOpChangeAccountInfo.GetOperationHashToSign(const op: TOpChangeAccountInfoData): TRawBytes;
 var Stream : TMemoryStream;
   b : Byte;
@@ -938,6 +967,23 @@ begin
   Result := true;
 end;
 
+procedure TOpTransaction.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
+begin
+  inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
+  SetLength(OperationResume.Senders,1);
+  OperationResume.Senders[0] := CT_TMultiOpSender_NUL;
+  OperationResume.Senders[0].Account:=FData.sender;
+  OperationResume.Senders[0].Amount:=Int64(FData.amount + FData.fee) * (-1);
+  OperationResume.Senders[0].N_Operation:=FData.n_operation;
+  OperationResume.Senders[0].Payload:=FData.payload;
+  OperationResume.Senders[0].Signature:=FData.sign;
+  SetLength(OperationResume.Receivers,1);
+  OperationResume.Receivers[0] := CT_TMultiOpReceiver_NUL;
+  OperationResume.Receivers[0].Account:=FData.target;
+  OperationResume.Receivers[0].Amount:=FData.amount;
+  OperationResume.Receivers[0].Payload:=FData.payload;
+end;
+
 function TOpTransaction.OperationAmount: Int64;
 begin
   Result := FData.amount;
@@ -1282,6 +1328,28 @@ begin
   Result := true;
 end;
 
+procedure TOpChangeKey.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
+begin
+  inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
+  SetLength(OperationResume.Changers,1);
+  OperationResume.Changers[0] := CT_TMultiOpChangeInfo_NUL;
+  OperationResume.Changers[0].Account := FData.account_target;
+  OperationResume.Changers[0].Changes_type := [public_key];
+  OperationResume.Changers[0].New_Accountkey := FData.new_accountkey;
+  if (FData.account_signer=FData.account_target) then begin
+    OperationResume.Changers[0].N_Operation := FData.n_operation;
+    OperationResume.Changers[0].Fee := FData.fee;
+    OperationResume.Changers[0].Signature := FData.sign;
+  end else begin
+    SetLength(OperationResume.Changers,2);
+    OperationResume.Changers[1] := CT_TMultiOpChangeInfo_NUL;
+    OperationResume.Changers[1].Account := FData.account_signer;
+    OperationResume.Changers[1].N_Operation := FData.n_operation;
+    OperationResume.Changers[1].Fee := FData.fee;
+    OperationResume.Changers[1].Signature := FData.sign;
+  end;
+end;
+
 function TOpChangeKey.OperationAmount: Int64;
 begin
   Result := 0;
@@ -1437,6 +1505,16 @@ begin
   Result := true;
 end;
 
+procedure TOpRecoverFounds.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
+begin
+  inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
+  SetLength(OperationResume.Changers,1);
+  OperationResume.Changers[0] := CT_TMultiOpChangeInfo_NUL;
+  OperationResume.Changers[0].Account := FData.account;
+  OperationResume.Changers[0].Fee := FData.fee;
+  OperationResume.Changers[0].N_Operation := FData.n_operation;
+end;
+
 function TOpRecoverFounds.OperationAmount: Int64;
 begin
   Result := 0;
@@ -1720,6 +1798,26 @@ begin
   Result := true;
 end;
 
+procedure TOpListAccount.FillOperationResume(Block: Cardinal; getInfoForAllAccounts: Boolean; Affected_account_number: Cardinal; var OperationResume: TOperationResume);
+begin
+  inherited FillOperationResume(Block, getInfoForAllAccounts, Affected_account_number, OperationResume);
+  SetLength(OperationResume.Changers,1);
+  OperationResume.Changers[0] := CT_TMultiOpChangeInfo_NUL;
+  OperationResume.Changers[0].Account:=FData.account_target;
+  if (FData.account_signer = FData.account_target) then begin
+    OperationResume.Changers[0].Fee:=FData.fee;
+    OperationResume.Changers[0].N_Operation:=FData.n_operation;
+    OperationResume.Changers[0].Signature:=FData.sign;
+  end else begin
+    SetLength(OperationResume.Changers,2);
+    OperationResume.Changers[1] := CT_TMultiOpChangeInfo_NUL;
+    OperationResume.Changers[1].Account := FData.account_signer;
+    OperationResume.Changers[1].N_Operation := FData.n_operation;
+    OperationResume.Changers[1].Fee := FData.fee;
+    OperationResume.Changers[1].Signature := FData.sign;
+  end;
+end;
+
 function TOpListAccount.N_Operation: Cardinal;
 begin
   Result := FData.n_operation;

+ 10 - 7
src/core/URPC.pas

@@ -190,7 +190,8 @@ Begin
     jsonObject.GetAsVariant('account').Value:=OPR.AffectedAccount;
     jsonObject.GetAsVariant('signer_account').Value:=OPR.SignerAccount;
     jsonObject.GetAsVariant('n_operation').Value:=OPR.n_operation;
-  end else begin
+  end;
+  // New V3: Will include senders[], receivers[] and changers[]
     jsonArr := jsonObject.GetAsArray('senders');
     for i:=Low(OPR.senders) to High(OPR.Senders) do begin
       auxObj := jsonArr.GetAsObject(jsonArr.Count);
@@ -221,18 +222,17 @@ Begin
       If account_type in OPR.Changers[i].Changes_type then begin
         auxObj.GetAsVariant('new_type').Value := OPR.Changers[i].New_Type;
       end;
+      if (OPR.Changers[i].Fee<>0) then begin
+        auxObj.GetAsVariant('fee').Value := ToJSONCurrency(OPR.Changers[i].Fee * (-1));
+      end;
     end;
-  end;
   jsonObject.GetAsVariant('optxt').Value:=OPR.OperationTxt;
   jsonObject.GetAsVariant('fee').Value:=ToJSONCurrency(OPR.Fee);
+  jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
   if (Not OPR.isMultiOperation) then begin
-    jsonObject.GetAsVariant('amount').Value:=ToJSONCurrency(OPR.Amount);
-    if (OPR.Balance>=0) And (OPR.valid) then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
     jsonObject.GetAsVariant('payload').Value:=TCrypto.ToHexaString(OPR.OriginalPayload);
-  end else begin
-    jsonObject.GetAsVariant('totalamount').Value:=ToJSONCurrency(OPR.Amount);
-    if (OPR.Balance>=0) And (OPR.valid) then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
   end;
+  if (OPR.Balance>=0) And (OPR.valid) then jsonObject.GetAsVariant('balance').Value:=ToJSONCurrency(OPR.Balance);
   If (OPR.OpType = CT_Op_Transaction) then begin
     If OPR.SignerAccount>=0 then begin
       jsonObject.GetAsVariant('sender_account').Value:=OPR.SignerAccount;
@@ -3383,6 +3383,9 @@ begin
     TNetData.NetData.NetConnectionsActive:=true;
     jsonresponse.GetAsVariant('result').Value := true;
     Result := true;
+  end else if (method='cleanblacklist') then begin
+    jsonresponse.GetAsVariant('result').Value := TNetData.NetData.NodeServersAddresses.CleanBlackList(True);
+    Result := True;
   end else begin
     ErrorNum := CT_RPC_ErrNum_MethodNotFound;
     ErrorDesc := 'Method not found: "'+method+'"';

+ 33 - 0
src/core/UTxMultiOperation.pas

@@ -101,6 +101,7 @@ Type
     FTotalAmount : Int64;
     FTotalFee : Int64;
     Function IndexOfAccountChangeNameTo(const newName : AnsiString) : Integer;
+    procedure ClearSignatures;
   protected
     procedure InitializeData; override;
     function SaveOpToStream(Stream: TStream; SaveExtendedData : Boolean): Boolean; override;
@@ -249,6 +250,17 @@ begin
   Result := -1;
 end;
 
+procedure TOpMultiOperation.ClearSignatures;
+var i : Integer;
+begin
+  for i:=0 to High(FData.txSenders) do begin
+    FData.txSenders[i].Signature := CT_TECDSA_SIG_Nul;
+  end;
+  for i:=0 to High(FData.changesInfo) do begin
+    FData.changesInfo[i].Signature := CT_TECDSA_SIG_Nul;
+  end;
+end;
+
 procedure TOpMultiOperation.InitializeData;
 begin
   inherited InitializeData;
@@ -881,6 +893,11 @@ begin
     // Allow receivers as a duplicate!
     If (receivers[i].Amount<=0) then Exit; // Must always receive >0
   end;
+  // Max Senders
+  If (length(senders)+length(FData.txSenders)) > CT_MAX_MultiOperation_Senders then Exit;
+  // Max Receivers
+  If (length(receivers)+length(FData.txReceivers)) > CT_MAX_MultiOperation_Receivers then Exit;
+
   // Ok, let's go
   FHasValidSignature:=False;
   If setInRandomOrder then begin
@@ -908,6 +925,7 @@ begin
       FData.txReceivers[j] := receivers[i];
       inc(total_receive,receivers[i].Amount);
     end;
+    ClearSignatures;
   end else begin
     j := length(FData.txSenders);
     SetLength(FData.txSenders,length(FData.txSenders)+length(senders));
@@ -929,6 +947,8 @@ end;
 
 function TOpMultiOperation.AddChangeInfos(const changes: TMultiOpChangesInfo; setInRandomOrder : Boolean): Boolean;
 Var i,j,k : Integer;
+  ct : TOpChangeAccountInfoType;
+  ctypes : TOpChangeAccountInfoTypes;
 begin
   Result := False;
   // Check not duplicate / invalid data
@@ -937,7 +957,19 @@ begin
     If IndexOfAccountChanger(changes[i].Account)>=0 then Exit;
     If IndexOfAccountChanger(changes[i].Account,i+1,changes)>=0 then Exit;
     If (changes[i].Changes_type=[]) then Exit; // Must change something
+    // check valid Change type
+    for ct:=Low(TOpChangeAccountInfoType) to High(TOpChangeAccountInfoType) do begin
+      case ct of
+        public_key,account_name,account_type : ; // Allowed
+      else
+        if (ct in changes[i].Changes_type) then begin
+          Exit; // Not allowed multioperation change type
+        end;
+      end;
+    end;
   end;
+  // Max Changers
+  If (length(changes)+length(FData.changesInfo)) > CT_MAX_MultiOperation_Changers then Exit;
   // Ok, let's go
   FHasValidSignature:=False;
   // Important:
@@ -957,6 +989,7 @@ begin
         j := Random(length(FData.changesInfo)); // Find random position 0..n-1
       end else j:=0;
       for k:=High(FData.changesInfo) downto (j+1) do FData.changesInfo[k] := FData.changesInfo[k-1];
+      ClearSignatures;
     end else j := High(FData.changesInfo);
     FData.changesInfo[j] := changes[i];
   end;

+ 1 - 1
src/core/UWallet.pas

@@ -258,7 +258,7 @@ begin
   while L <= H do
   begin
     I := (L + H) shr 1;
-    C := CompareStr( PWalletKey(FSearchableKeys[I]).SearchableAccountKey, rak );
+    C := TBaseType.BinStrComp( PWalletKey(FSearchableKeys[I]).SearchableAccountKey, rak );
     if C < 0 then L := I + 1 else
     begin
       H := I - 1;