Ver Fonte

PIP-0017 beta 2

Final implementation of PIP-0017
Not tested!
PascalCoin há 7 anos atrás
pai
commit
3e011dc344
4 ficheiros alterados com 472 adições e 68 exclusões
  1. 186 7
      src/core/UAccounts.pas
  2. 8 3
      src/core/UBlockChain.pas
  3. 2 0
      src/core/UConst.pas
  4. 276 58
      src/core/UTxMultiOperation.pas

+ 186 - 7
src/core/UAccounts.pas

@@ -253,6 +253,7 @@ Type
   protected
     Function AddNew(Const blockChain : TOperationBlock) : TBlockAccount;
     function DoUpgradeToProtocol2 : Boolean;
+    function DoUpgradeToProtocol3 : Boolean;
   public
     Constructor Create;
     Destructor Destroy; override;
@@ -288,7 +289,7 @@ Type
     Property SafeBoxHash : TRawBytes read FSafeBoxHash;
     Property WorkSum : UInt64 read FWorkSum;
     Property CurrentProtocol : Integer read FCurrentProtocol;
-    function CanUpgradeToProtocol2 : Boolean;
+    function CanUpgradeToProtocol(newProtocolVersion : Word) : Boolean;
     procedure CheckMemory;
   End;
 
@@ -323,6 +324,7 @@ Type
     Constructor Create(SafeBox : TPCSafeBox);
     Destructor Destroy; override;
     Function TransferAmount(sender,target : Cardinal; n_operation : Cardinal; amount, fee : UInt64; var errors : AnsiString) : Boolean;
+    Function TransferAmounts(const senders, n_operations : Array of Cardinal; const sender_amounts : Array of UInt64; const receivers : Array of Cardinal; const receivers_amounts : Array of UInt64; var errors : AnsiString) : Boolean;
     Function UpdateAccountInfo(signer_account, signer_n_operation, target_account: Cardinal; accountInfo: TAccountInfo; newName : TRawBytes; newType : Word; fee: UInt64; var errors : AnsiString) : Boolean;
     Function BuyAccount(buyer,account_to_buy,seller: Cardinal; n_operation : Cardinal; amount, account_price, fee : UInt64; const new_account_key : TAccountKey; var errors : AnsiString) : Boolean;
     Function Commit(Const operationBlock : TOperationBlock; var errors : AnsiString) : Boolean;
@@ -336,6 +338,7 @@ Type
     Procedure CleanTransaction;
     Function ModifiedCount : Integer;
     Function Modified(index : Integer) : TAccount;
+    Function FindAccountByNameInTransaction(const findName : TRawBytes; out isAddedInThisTransaction, isDeletedInThisTransaction : Boolean) : Integer;
   End;
 
   TStreamOp = Class
@@ -1581,9 +1584,13 @@ begin
   end;
 end;
 
-function TPCSafeBox.CanUpgradeToProtocol2: Boolean;
+function TPCSafeBox.CanUpgradeToProtocol(newProtocolVersion : Word) : Boolean;
 begin
-  Result := (FCurrentProtocol<CT_PROTOCOL_2) and (BlocksCount >= CT_Protocol_Upgrade_v2_MinBlock);
+  If (newProtocolVersion=CT_PROTOCOL_2) then begin
+    Result := (FCurrentProtocol<CT_PROTOCOL_2) and (BlocksCount >= CT_Protocol_Upgrade_v2_MinBlock);
+  end else if (newProtocolVersion=CT_PROTOCOL_3) then begin
+    Result := (FCurrentProtocol=CT_PROTOCOL_2) And (BlocksCount >= CT_Protocol_Upgrade_v3_MinBlock);
+  end else Result := False;
 end;
 
 procedure TPCSafeBox.CheckMemory;
@@ -1709,7 +1716,7 @@ var block_number : Cardinal;
 begin
   // Upgrade process to protocol 2
   Result := false;
-  If Not CanUpgradeToProtocol2 then exit;
+  If Not CanUpgradeToProtocol(CT_PROTOCOL_2) then exit;
   // Recalc all BlockAccounts block_hash value
   aux := CalcSafeBoxHash;
   TLog.NewLog(ltInfo,ClassName,'Start Upgrade to protocol 2 - Old Safeboxhash:'+TCrypto.ToHexaString(FSafeBoxHash)+' calculated: '+TCrypto.ToHexaString(aux)+' Blocks: '+IntToStr(BlocksCount));
@@ -1729,6 +1736,18 @@ begin
   TLog.NewLog(ltInfo,ClassName,'End Upgraded to protocol 2 - New safeboxhash:'+TCrypto.ToHexaString(FSafeBoxHash));
 end;
 
+function TPCSafeBox.DoUpgradeToProtocol3: Boolean;
+begin
+  // XXXXXXXXXXX
+  // XXXXXXXXXXX
+  // TODO
+  // XXXXXXXXXXX
+  // XXXXXXXXXXX
+  FCurrentProtocol := CT_PROTOCOL_3;
+  Result := True;
+  TLog.NewLog(ltInfo,ClassName,'End Upgraded to protocol 3 - New safeboxhash:'+TCrypto.ToHexaString(FSafeBoxHash));
+end;
+
 procedure TPCSafeBox.EndThreadSave;
 begin
   FLock.Release;
@@ -2297,7 +2316,12 @@ begin
         errors := 'Invalid PascalCoin protocol version: '+IntToStr( newOperationBlock.protocol_version )+' Current: '+IntToStr(CurrentProtocol)+' Previous:'+IntToStr(lastBlock.protocol_version);
         exit;
       end;
-      If (newOperationBlock.protocol_version=CT_PROTOCOL_2) then begin
+      If (newOperationBlock.protocol_version=CT_PROTOCOL_3) then begin
+        If (newOperationBlock.block<CT_Protocol_Upgrade_v3_MinBlock) then begin
+          errors := 'Upgrade to protocol version 3 available at block: '+IntToStr(CT_Protocol_Upgrade_v3_MinBlock);
+          exit;
+        end;
+      end else If (newOperationBlock.protocol_version=CT_PROTOCOL_2) then begin
         If (newOperationBlock.block<CT_Protocol_Upgrade_v2_MinBlock) then begin
           errors := 'Upgrade to protocol version 2 available at block: '+IntToStr(CT_Protocol_Upgrade_v2_MinBlock);
           exit;
@@ -2336,7 +2360,8 @@ begin
   Result := IsValidOperationBlock(newOperationBlock,errors);
 end;
 
-class function TPCSafeBox.IsValidOperationBlock(Const newOperationBlock : TOperationBlock; var errors : AnsiString) : Boolean;
+class function TPCSafeBox.IsValidOperationBlock(
+  const newOperationBlock: TOperationBlock; var errors: AnsiString): Boolean;
   { This class function will check a OperationBlock basic info as a valid info
 
     Creted at Build 2.1.7 as a division of IsValidNewOperationsBlock for easily basic check TOperationBlock
@@ -2696,13 +2721,22 @@ begin
     //
     if (FFreezedAccounts.FCurrentProtocol<CT_PROTOCOL_2) And (operationBlock.protocol_version=CT_PROTOCOL_2) then begin
       // First block with new protocol!
-      if FFreezedAccounts.CanUpgradeToProtocol2 then begin
+      if FFreezedAccounts.CanUpgradeToProtocol(CT_PROTOCOL_2) then begin
         TLog.NewLog(ltInfo,ClassName,'Protocol upgrade to v2');
         If not FFreezedAccounts.DoUpgradeToProtocol2 then begin
           raise Exception.Create('Cannot upgrade to protocol v2 !');
         end;
       end;
     end;
+    if (FFreezedAccounts.FCurrentProtocol<CT_PROTOCOL_3) And (operationBlock.protocol_version=CT_PROTOCOL_3) then begin
+      // First block with V3 protocol
+      if FFreezedAccounts.CanUpgradeToProtocol(CT_PROTOCOL_3) then begin
+        TLog.NewLog(ltInfo,ClassName,'Protocol upgrade to v3');
+        If not FFreezedAccounts.DoUpgradeToProtocol3 then begin
+          raise Exception.Create('Cannot upgrade to protocol v3 !');
+        end;
+      end;
+    end;
     Result := true;
   finally
     FFreezedAccounts.EndThreadSave;
@@ -2761,6 +2795,36 @@ begin
   Result := FOrderedList.Get(index);
 end;
 
+function TPCSafeBoxTransaction.FindAccountByNameInTransaction(const findName: TRawBytes; out isAddedInThisTransaction, isDeletedInThisTransaction : Boolean) : Integer;
+Var nameLower : AnsiString;
+  iSafeBox, iAdded, iDeleted : Integer;
+begin
+  Result := -1;
+  isAddedInThisTransaction := False;
+  isDeletedInThisTransaction := False;
+  nameLower := LowerCase(findName);
+  If (nameLower)='' then begin
+    Exit; // No name, no found
+  end;
+  iSafeBox := FFreezedAccounts.FindAccountByName(nameLower);
+  iAdded := FAccountNames_Added.IndexOf(nameLower);
+  iDeleted := FAccountNames_Deleted.IndexOf(nameLower);
+  isAddedInThisTransaction := (iAdded >= 0);
+  isDeletedInThisTransaction := (iDeleted >= 0);
+  if (iSafeBox<0) then begin
+    // Not found previously, check added in current trans?
+    If iAdded>=0 then begin
+      Result := FAccountNames_Added.GetTag(iAdded);
+    end;
+  end else begin
+    // Was found previously, check if deleted
+    if iDeleted<0 then begin
+      // Not deleted! "iSafebox" value contains account number using name
+      Result := iSafeBox;
+    end;
+  end;
+end;
+
 function TPCSafeBoxTransaction.ModifiedCount: Integer;
 begin
   Result := FOrderedList.Count;
@@ -2838,6 +2902,117 @@ begin
   Result := true;
 end;
 
+function TPCSafeBoxTransaction.TransferAmounts(const senders,
+  n_operations: array of Cardinal; const sender_amounts: array of UInt64;
+  const receivers: array of Cardinal; const receivers_amounts: array of UInt64;
+  var errors: AnsiString): Boolean;
+Var i : Integer;
+  PaccSender, PaccTarget : PAccount;
+  nTotalAmountSent, nTotalAmountReceived, nTotalFee : Int64;
+begin
+  Result := false;
+  errors := '';
+  nTotalAmountReceived:=0;
+  nTotalAmountSent:=0;
+  if not CheckIntegrity then begin
+    errors := 'Invalid integrity in transfer amounts transaction';
+    Exit;
+  end;
+  if (Length(senders)<>Length(n_operations)) Or
+     (Length(senders)<>Length(sender_amounts)) Or
+     (Length(senders)=0)
+     then begin
+    errors := 'Invalid senders/n_operations/amounts arrays length';
+    Exit;
+  end;
+  if (Length(receivers)<>Length(receivers_amounts)) Or
+     (Length(receivers)=0) then begin
+    errors := 'Invalid receivers/amounts arrays length';
+    Exit;
+  end;
+  // Check sender
+  for i:=Low(senders) to High(senders) do begin
+    if (senders[i]<0) Or (senders[i]>=(FFreezedAccounts.BlocksCount*CT_AccountsPerBlock)) then begin
+       errors := 'Invalid sender on transfer';
+       exit;
+    end;
+    if TAccountComp.IsAccountBlockedByProtocol(senders[i],FFreezedAccounts.BlocksCount) then begin
+      errors := 'Sender account is blocked for protocol';
+      Exit;
+    end;
+    if (sender_amounts[i]<=0) then begin
+      errors := 'Invalid amount for multiple sender';
+      Exit;
+    end;
+    PaccSender := GetInternalAccount(senders[i]);
+    if (PaccSender^.n_operation+1<>n_operations[i]) then begin
+      errors := 'Incorrect multisender n_operation';
+      Exit;
+    end;
+    if (PaccSender^.balance < sender_amounts[i]) then begin
+      errors := 'Insuficient funds';
+      Exit;
+    end;
+    if (TAccountComp.IsAccountLocked(PaccSender^.accountInfo,FFreezedAccounts.BlocksCount)) then begin
+      errors := 'Multi sender account is locked until block '+Inttostr(PaccSender^.accountInfo.locked_until_block);
+      Exit;
+    end;
+    inc(nTotalAmountSent,sender_amounts[i]);
+  end;
+  //
+  for i:=Low(receivers) to High(receivers) do begin
+    if (receivers[i]<0) Or (receivers[i]>=(FFreezedAccounts.BlocksCount*CT_AccountsPerBlock)) then begin
+       errors := 'Invalid receiver on transfer';
+       exit;
+    end;
+    if TAccountComp.IsAccountBlockedByProtocol(receivers[i],FFreezedAccounts.BlocksCount) then begin
+      errors := 'Receiver account is blocked for protocol';
+      Exit;
+    end;
+    if (receivers_amounts[i]<=0) then begin
+      errors := 'Invalid amount for multiple receivers';
+      Exit;
+    end;
+    inc(nTotalAmountReceived,receivers_amounts[i]);
+    PaccTarget := GetInternalAccount(receivers[i]);
+    if ((PaccTarget^.balance + receivers_amounts[i])>CT_MaxWalletAmount) then begin
+      errors := 'Max receiver balance';
+      Exit;
+    end;
+  end;
+  //
+  nTotalFee := nTotalAmountSent - nTotalAmountReceived;
+  If (nTotalAmountSent<nTotalAmountReceived) then begin
+    errors := 'Total amount sent < total amount received';
+    Exit;
+  end;
+  if (nTotalFee>CT_MaxTransactionFee) then begin
+    errors := 'Max fee';
+    Exit;
+  end;
+  // Ok, execute!
+  for i:=Low(senders) to High(senders) do begin
+    PaccSender := GetInternalAccount(senders[i]);
+    If PaccSender^.updated_block<>FFreezedAccounts.BlocksCount then begin
+      PaccSender^.previous_updated_block := PaccSender^.updated_block;
+      PaccSender^.updated_block := FFreezedAccounts.BlocksCount;
+    end;
+    Inc(PaccSender^.n_operation);
+    PaccSender^.balance := PaccSender^.balance - (sender_amounts[i]);
+  end;
+  for i:=Low(receivers) to High(receivers) do begin
+    PaccTarget := GetInternalAccount(receivers[i]);
+    If PaccTarget^.updated_block<>FFreezedAccounts.BlocksCount then begin
+      PaccTarget^.previous_updated_block := PaccTarget.updated_block;
+      PaccTarget^.updated_block := FFreezedAccounts.BlocksCount;
+    end;
+    PaccTarget^.balance := PaccTarget^.balance + receivers_amounts[i];
+  end;
+  Dec(FTotalBalance,nTotalFee);
+  inc(FTotalFee,nTotalFee);
+  Result := true;
+end;
+
 function TPCSafeBoxTransaction.UpdateAccountInfo(signer_account, signer_n_operation, target_account: Cardinal;
   accountInfo: TAccountInfo; newName: TRawBytes; newType: Word; fee: UInt64; var errors: AnsiString): Boolean;
 Var i : Integer;
@@ -2845,6 +3020,10 @@ Var i : Integer;
 begin
   Result := false;
   errors := '';
+  if not CheckIntegrity then begin
+    errors := 'Invalid integrity on Update account info';
+    Exit;
+  end;
   if (signer_account<0) Or (signer_account>=(FFreezedAccounts.BlocksCount*CT_AccountsPerBlock)) Or
      (target_account<0) Or (target_account>=(FFreezedAccounts.BlocksCount*CT_AccountsPerBlock)) Then begin
      errors := 'Invalid account';

+ 8 - 3
src/core/UBlockChain.pas

@@ -457,7 +457,7 @@ Type
   End;
 
 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:'';Senders:Nil;Receivers:Nil);
+  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:'';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:''));
@@ -913,8 +913,10 @@ begin
     FOperationBlock.timestamp := UnivDateTimeToUnix(DateTime2UnivDateTime(now));
     if Assigned(FBank) then begin
       FOperationBlock.protocol_version := bank.SafeBox.CurrentProtocol;
-      If (FOperationBlock.protocol_version=CT_PROTOCOL_1) And (FBank.SafeBox.CanUpgradeToProtocol2) then begin
+      If (FOperationBlock.protocol_version=CT_PROTOCOL_1) And (FBank.SafeBox.CanUpgradeToProtocol(CT_PROTOCOL_2)) then begin
         FOperationBlock.protocol_version := CT_PROTOCOL_2; // If minting... upgrade to Protocol 2
+      end else if (FOperationBlock.protocol_version=CT_PROTOCOL_2) And (FBank.SafeBox.CanUpgradeToProtocol(CT_PROTOCOL_3)) then begin
+        FOperationBlock.protocol_version := CT_PROTOCOL_3; // If minting... upgrade to Protocol 3
       end;
       FOperationBlock.block := bank.BlocksCount;
       FOperationBlock.reward := TPascalCoinProtocol.GetRewardForNewLine(bank.BlocksCount);
@@ -1250,9 +1252,12 @@ begin
     FOperationBlock.timestamp := UnivDateTimeToUnix(DateTime2UnivDateTime(now));
     if Assigned(FBank) then begin
       FOperationBlock.protocol_version := bank.SafeBox.CurrentProtocol;
-      If (FOperationBlock.protocol_version=CT_PROTOCOL_1) And (FBank.SafeBox.CanUpgradeToProtocol2) then begin
+      If (FOperationBlock.protocol_version=CT_PROTOCOL_1) And (FBank.SafeBox.CanUpgradeToProtocol(CT_PROTOCOL_2)) then begin
         TLog.NewLog(ltinfo,ClassName,'New miner protocol version to 2 at sanitize');
         FOperationBlock.protocol_version := CT_PROTOCOL_2;
+      end else if (FOperationBlock.protocol_version=CT_PROTOCOL_2) And (FBank.SafeBox.CanUpgradeToProtocol(CT_PROTOCOL_3)) then begin
+        TLog.NewLog(ltinfo,ClassName,'New miner protocol version to 3 at sanitize');
+        FOperationBlock.protocol_version := CT_PROTOCOL_3;
       end;
       FOperationBlock.block := bank.BlocksCount;
       FOperationBlock.reward := TPascalCoinProtocol.GetRewardForNewLine(bank.BlocksCount);

+ 2 - 0
src/core/UConst.pas

@@ -94,8 +94,10 @@ Const
 
   CT_PROTOCOL_1 = 1;
   CT_PROTOCOL_2 = 2;
+  CT_PROTOCOL_3 = 3;
   CT_BlockChain_Protocol_Available: Word = $0002; // Protocol 2 flag
   CT_Protocol_Upgrade_v2_MinBlock = {$IFDEF PRODUCTION}115000{$ELSE}600{$ENDIF};
+  CT_Protocol_Upgrade_v3_MinBlock = {$IFDEF PRODUCTION}210000{$ELSE}800{$ENDIF};
 
 
   CT_MagicNetIdentification = {$IFDEF PRODUCTION}$0A043580{$ELSE}$0A04FFFF{$ENDIF}; // Unix timestamp 168048000 ... It's Albert birthdate!

+ 276 - 58
src/core/UTxMultiOperation.pas

@@ -45,11 +45,49 @@ Type
     Also, can be signed off-line by all senders/signers, knowing previously the OpHash because
     the OpHash algo will not include the signature (due it's checked separately)
 
+    ALLOWED:
+    - N senders to M receivers
+    - Each sender can add a Payload
+    - Each receiver can receive a Payload
+    - Allow receive multiple incomes in a single receiver. For example:
+      - Account A sends X coins to account B three times (X1,X2,X3) using Payload to discriminate (3 operations with only 1 signer, account A).
+        - Example: Pool sending rewards to an exchange. Only 1 sender and to 1 receiver but multiple times, each time with a distinct amount/payload
+        - Sender: Account A, amount X
+        - Receivers (three times same account):
+           - Account B, amount X1, Payload value P1
+           - Account B, amount X2, Payload value P2
+           - Account B, amount X3, Payload value P3
+        - X1+X2+X3 <= X  (Fee is X - (X1+X2+X3))
+        - Affected accounts are only A and B
+        - There is only 1 signature: Account A (less space!)
+
+    LIMITATIONS:
+    - At least 1 operation must be made (1 change info, or 1 send + 1 receive)
+    - Obvious: Total amount sent >= total amount received
+      - Operation fee will be (total sent - total received)
+      - If no send/receive operation, then there is no fee in multioperation
+    - Senders cannot be duplicated (sender A can send only 1 time)
+    - Senders must have previously enough amount to send: Example
+      - Account A had 0 coins previously
+      - Account A receive X coins in this multioperation
+      - Account A sends X coins in this multioperation: NOT POSSIBLE, no previous funds
+    - Change account op cannot be duplicated
+    - Cannot change name between 2 accounts: Example
+      - Account A has name X  (X not null)
+      - Account B has name Y  (Y not null)
+      - Cannot change name X to B and Y to A at same multioperation
+    - Senders cannot be both in change account info and viceversa: Example
+      - Account A sends X
+      - Account A changes info
+      - Not allowed (same sender and same change info account)
   }
 
   TOpMultiOperationData = Record
+    // senders are unique and not duplicable
     txSenders : TMultiOpSenders;
+    // receivers can be duplicated
     txReceivers : TMultiOpReceivers;
+    // changers are unique and not duplicable
     changesInfo : TMultiOpChangesInfo;
   end;
 
@@ -60,9 +98,6 @@ Type
     FSaveSignatureValue : Boolean;
     FTotalAmount : Int64;
     FTotalFee : Int64;
-    Function IndexOfAccountSender(nAccount : Cardinal) : Integer;
-    Function IndexOfAccountReceiver(nAccount : Cardinal) : Integer;
-    Function IndexOfAccountChanger(nAccount : Cardinal) : Integer;
     Function IndexOfAccountChangeNameTo(const newName : AnsiString) : Integer;
   protected
     procedure InitializeData; override;
@@ -88,9 +123,14 @@ Type
     function N_Operation : Cardinal; override;
     //
     Constructor CreateMultiOperation(const senders : TMultiOpSenders; const receivers : TMultiOpReceivers; const changes : TMultiOpChangesInfo; const senders_keys, changes_keys: Array of TECPrivateKey);
+    Destructor Destroy; override;
     Function AddTx(const senders : TMultiOpSenders; const receivers : TMultiOpReceivers; setInRandomOrder : Boolean) : Boolean;
     Function AddChangeInfo(const changes : TMultiOpChangesInfo; setInRandomOrder : Boolean) : Boolean;
-    Destructor Destroy; override;
+    //
+    Function IndexOfAccountSender(nAccount : Cardinal) : Integer;
+    Function IndexOfAccountReceiver(nAccount : Cardinal; startPos : Integer) : Integer;
+    Function IndexOfAccountChanger(nAccount : Cardinal) : Integer;
+    //
     Function toString : String; Override;
   End;
 
@@ -109,9 +149,10 @@ begin
   Result := -1;
 end;
 
-function TOpMultiOperation.IndexOfAccountReceiver(nAccount: Cardinal): Integer;
+function TOpMultiOperation.IndexOfAccountReceiver(nAccount: Cardinal; startPos : Integer): Integer;
 begin
-  for Result:=0 to high(FData.txReceivers) do begin
+  if startPos<Low(FData.txReceivers) then startPos := Low(FData.txReceivers);
+  for Result:=startPos to high(FData.txReceivers) do begin
     If (FData.txReceivers[Result].Account = nAccount) then exit;
   end;
   Result := -1;
@@ -156,7 +197,7 @@ var i : Integer;
   b : Byte;
 begin
   // Will save protocol info
-  w := CT_PROTOCOL_2;
+  w := CT_PROTOCOL_3;
   stream.Write(w,SizeOf(w));
   // Save senders count
   w := Length(FData.txSenders);
@@ -232,7 +273,7 @@ begin
   Try
     // Read protocol info
     stream.Read(w,SizeOf(w));
-    If w<>CT_PROTOCOL_2 then Raise Exception.Create('Invalid protocol found');
+    If w<>CT_PROTOCOL_3 then Raise Exception.Create('Invalid protocol found');
     // Load senders
     stream.Read(w,SizeOf(w));
     If w>CT_MAX_MultiOperation_Senders then Raise Exception.Create('Max senders');
@@ -360,35 +401,197 @@ begin
 end;
 
 function TOpMultiOperation.DoOperation(AccountTransaction: TPCSafeBoxTransaction; var errors: AnsiString): Boolean;
+var i,j : Integer;
+  txs : TMultiOpSender;
+  txr : TMultiOpReceiver;
+  chi : TMultiOpChangeInfo;
+  sender,receiver,changer : TAccount;
+  newNameWasAdded, newNameWasDeleted : Boolean;
+  senders,senders_n_operation,receivers : Array of Cardinal;
+  senders_amount : Array of UInt64;
+  receivers_amount : Array of UInt64;
 begin
-  // TODO
-  { XXXXXXXXXXXXXXXXXXXXXXXXXX
-
-  Implementation as expected and explained at PIP-0017
-
-  Note: I've added "payload", that must be checked too
-
-
-
-  TODO:
-  - If a destination account is for a PRIVATE SALE... must work same as working currently for TOpTransaction
-    when opTransactionStyle is in transaction_with_auto_buy_account?
-    - IMPORTANT: If Yes, then is possible that a future change operation in same multioperation
-      does not work due changed PUBLIC KEY when executing transaction_with_auto_buy_account
-    - We can limit multioperation to not be able to "auto buy" account, simply add coins to target,
-      this solves possible bad checking
-
-  - When changing name of accounts in a multioperation, is possible that 2 accounts wants
-    to set same name for account. Must prevent this! <-- Partially Prevented thanks to "IndexOfAccountChangeNameTo"
-    - Must prevent that can set new names in real safebox
-
-  - Conclusion:
-    - Prior to execute each "setAccount", must check ALL is ok
-    - HARD JOB!
-
-  }
-  Raise Exception.Create('NOT IMPLEMENTED ERROR DEV 20180308-1');
+  // Check valid info:
   Result := False;
+  errors := '';
+  if (AccountTransaction.FreezedSafeBox.CurrentProtocol<CT_PROTOCOL_3) then begin
+    errors := 'NEED PROTOCOL 3';
+    exit;
+  end;
+  if (FTotalAmount<0) Or (FTotalFee<0) then begin
+    errors := 'Invalid Amount or Fee';
+    Exit;
+  end;
+  if ((length(FData.txReceivers)=0) And (length(FData.txSenders)=0) And (length(FData.changesInfo)=0))
+     Or
+     ((length(FData.txSenders)=0) XOR (length(FData.txReceivers)=0))  // Both must be 0 length or length>0
+    then begin
+    errors := 'Invalid receivers/senders/changesinfo length';
+    Exit;
+  end;
+  SetLength(senders,Length(FData.txSenders));
+  SetLength(senders_amount,Length(FData.txSenders));
+  SetLength(senders_n_operation,Length(FData.txSenders));
+  SetLength(receivers,Length(FData.txReceivers));
+  SetLength(receivers_amount,Length(FData.txReceivers));
+  // Check senders accounts:
+  for i:=low(FData.txSenders) to high(FData.txSenders) do begin
+    txs := FData.txSenders[i];
+    senders[i] := txs.Account;
+    senders_amount[i] := txs.Amount;
+    senders_n_operation[i] := txs.N_Operation;
+    if (txs.Account>=AccountTransaction.FreezedSafeBox.AccountsCount) then begin
+      errors := Format('Invalid sender %d',[txs.Account]);
+      Exit;
+    end;
+    if TAccountComp.IsAccountBlockedByProtocol(txs.Account,AccountTransaction.FreezedSafeBox.BlocksCount) then begin
+      errors := Format('sender (%d) is blocked for protocol',[txs.Account]);
+      Exit;
+    end;
+    if (txs.Amount<=0) Or (txs.Amount>CT_MaxTransactionAmount) then begin
+      errors := Format('Invalid amount %d (0 or max: %d)',[txs.Amount,CT_MaxTransactionAmount]);
+      Exit;
+    end;
+    if (length(txs.Payload)>CT_MaxPayloadSize) then begin
+      errors := 'Invalid Payload size:'+inttostr(length(txs.Payload))+' (Max: '+inttostr(CT_MaxPayloadSize)+')';
+      Exit;
+    end;
+    //
+    sender := AccountTransaction.Account(txs.Account);
+    if ((sender.n_operation+1)<>txs.N_Operation) then begin
+      errors := Format('Invalid n_operation %d (expected %d)',[txs.N_Operation,sender.n_operation+1]);
+      Exit;
+    end;
+    if (sender.balance<txs.Amount) then begin
+      errors := Format('Insufficient funds account %d %d < %d',[sender.account, sender.balance,txs.Amount]);
+      Exit;
+    end;
+    // Is locked? Protocol 2 check
+    if (TAccountComp.IsAccountLocked(sender.accountInfo,AccountTransaction.FreezedSafeBox.BlocksCount)) then begin
+      errors := 'Sender Account is currently locked';
+      exit;
+    end;
+  end;
+  // Check receivers accounts:
+  for i:=Low(FData.txReceivers) to High(FData.txReceivers) do begin
+    txr := FData.txReceivers[i];
+    receivers[i] := txr.Account;
+    receivers_amount[i] := txr.Amount;
+    if (txr.Account>=AccountTransaction.FreezedSafeBox.AccountsCount) then begin
+      errors := Format('Invalid receiver %d',[txr.Account]);
+      Exit;
+    end;
+    if TAccountComp.IsAccountBlockedByProtocol(txr.Account,AccountTransaction.FreezedSafeBox.BlocksCount) then begin
+      errors := Format('receiver (%d) is blocked for protocol',[txr.Account]);
+      Exit;
+    end;
+    if (txr.Amount<=0) Or (txr.Amount>CT_MaxTransactionAmount) then begin
+      errors := Format('Invalid amount %d (0 or max: %d)',[txr.Amount,CT_MaxTransactionAmount]);
+      Exit;
+    end;
+    if (length(txr.Payload)>CT_MaxPayloadSize) then begin
+      errors := 'Invalid Payload size:'+inttostr(length(txr.Payload))+' (Max: '+inttostr(CT_MaxPayloadSize)+')';
+      Exit;
+    end;
+    //
+    receiver := AccountTransaction.Account(txr.Account);
+    if (receiver.balance+txr.Amount>CT_MaxWalletAmount) then begin
+      errors := Format('Target cannot accept this transaction due to max amount %d+%d=%d > %d',[receiver.balance,txr.Amount,receiver.balance+txr.Amount,CT_MaxWalletAmount]);
+      Exit;
+    end;
+  end;
+  // Check change info accounts:
+  for i:=Low(FData.changesInfo) to High(FData.changesInfo) do begin
+    chi := FData.changesInfo[i];
+    if (chi.Account>=AccountTransaction.FreezedSafeBox.AccountsCount) then begin
+      errors := 'Invalid change info account number';
+      Exit;
+    end;
+    if TAccountComp.IsAccountBlockedByProtocol(chi.Account, AccountTransaction.FreezedSafeBox.BlocksCount) then begin
+      errors := 'change info account is blocked for protocol';
+      Exit;
+    end;
+
+    changer := AccountTransaction.Account(chi.Account);
+    if ((changer.n_operation+1)<>chi.N_Operation) then begin
+      errors := 'Invalid changer n_operation';
+      Exit;
+    end;
+    // Is locked? Protocol 2 check
+    if (TAccountComp.IsAccountLocked(changer.accountInfo,AccountTransaction.FreezedSafeBox.BlocksCount)) then begin
+      errors := 'Account changer is currently locked';
+      exit;
+    end;
+    If (public_key in chi.Changes_type) then begin
+      If Not TAccountComp.IsValidAccountKey( chi.New_Accountkey, errors ) then begin
+        Exit;
+      end;
+    end;
+    If (account_name in chi.changes_type) then begin
+      If (chi.New_Name<>'') then begin
+        If Not TPCSafeBox.ValidAccountName(chi.New_Name,errors) then Exit;
+        // Check name not found!
+        j := AccountTransaction.FindAccountByNameInTransaction(chi.New_Name,newNameWasAdded, newNameWasDeleted);
+        If (j>=0) Or (newNameWasAdded) or (newNameWasDeleted) then begin
+          errors := 'New name is in use or was added or deleted at same transaction';
+          Exit;
+        end;
+      end;
+    end else begin
+      If (chi.New_Name<>'') then begin
+        errors := 'Invalid data in new_name field';
+        Exit;
+      end;
+    end;
+    If (chi.changes_type=[]) then begin
+      errors := 'No change';
+      Exit;
+    end;
+  end;
+  // Check signatures!
+  If Not FSignatureChecked then begin
+    If Not CheckSignatures(AccountTransaction,errors) then Exit;
+  end else begin
+    If Not FHasValidSignature then begin
+      Errors := 'Not valid signatures found!';
+      Exit;
+    end;
+  end;
+  // Execute!
+  If Not AccountTransaction.TransferAmounts(senders,senders_n_operation,senders_amount,
+    receivers,receivers_amount,errors) then Begin
+    TLog.NewLog(ltError,ClassName,'FATAL ERROR DEV 20180312-1 '+errors); // This must never happen!
+    Raise Exception.Create('FATAL ERROR DEV 20180312-1 '+errors); // This must never happen!
+    Exit;
+  end;
+  for i:=Low(FData.changesInfo) to High(FData.changesInfo) do begin
+    chi := FData.changesInfo[i];
+    changer := AccountTransaction.Account(chi.Account);
+    If (public_key in chi.Changes_type) then begin
+      changer.accountInfo.accountKey := chi.New_Accountkey;
+      // Set to normal:
+      changer.accountInfo.state := as_Normal;
+      changer.accountInfo.locked_until_block := 0;
+      changer.accountInfo.price := 0;
+      changer.accountInfo.account_to_pay := 0;
+      changer.accountInfo.new_publicKey := CT_TECDSA_Public_Nul;
+    end;
+    If (account_name in chi.Changes_type) then begin
+      changer.name := chi.New_Name;
+    end;
+    If (account_type in chi.Changes_type) then begin
+      changer.account_type := chi.New_Type;
+    end;
+    If Not AccountTransaction.UpdateAccountInfo(chi.Account,chi.N_Operation,chi.Account,
+           changer.accountInfo,
+           changer.name,
+           changer.account_type,
+           0,errors) then begin
+      TLog.NewLog(ltError,ClassName,'FATAL ERROR DEV 20180312-2 '+errors); // This must never happen!
+      Raise Exception.Create('FATAL ERROR DEV 20180312-2 '+errors); // This must never happen!
+    end;
+  end;
+  Result := True;
 end;
 
 procedure TOpMultiOperation.AffectedAccounts(list: TList);
@@ -537,9 +740,8 @@ begin
       Exit;
     end;
   end;
-  // Check as a Valid after everybody signed properly
-  FSignatureChecked:=True;
-  FHasValidSignature:=True;
+  FSignatureChecked:=False;
+  FHasValidSignature:=False;
 end;
 
 function TOpMultiOperation.AddTx(const senders: TMultiOpSenders; const receivers: TMultiOpReceivers; setInRandomOrder : Boolean) : Boolean;
@@ -549,43 +751,57 @@ begin
   Result := False;
   total_spend:=0;
   total_receive:=0;
-  // Check not duplicate
+  // Check not duplicate and invalid data
   For i:=Low(senders) to High(senders) do begin
     If IndexOfAccountSender(senders[i].Account)>=0 then Exit;
+    If IndexOfAccountChanger(senders[i].Account)>=0 then Exit;
+    If (senders[i].Amount<=0) then Exit; // Must always sender >0
   end;
   For i:=Low(receivers) to High(receivers) do begin
-    If IndexOfAccountReceiver(receivers[i].Account)>=0 then Exit;
+    // Allow receivers as a duplicate!
+    If (receivers[i].Amount<=0) then Exit; // Must always receive >0
   end;
   // Ok, let's go
   FHasValidSignature:=False;
   FSignatureChecked:=False;
-  // Important:
-  // When a sender/receiver is added, everybody must sign again
-  // In order to create high anonymity, will add senders/receivers in random order
-  // to difficult know who was the first or last to add
-  For i:=Low(senders) to High(senders) do begin
-    SetLength(FData.txSenders,length(FData.txSenders)+1);
-    If setInRandomOrder then begin
+  If setInRandomOrder then begin
+    // Important:
+    // When a sender/receiver is added, everybody must sign again
+    // In order to create high anonymity, will add senders/receivers in random order
+    // to difficult know who was the first or last to add
+    For i:=Low(senders) to High(senders) do begin
+      SetLength(FData.txSenders,length(FData.txSenders)+1);
       // Set sender in a random order
       If (length(FData.txSenders)>0) then begin
         j := Random(length(FData.txSenders)); // Find random position 0..n-1
       end else j:=0;
       for k:=High(FData.txSenders) downto (j+1) do FData.txSenders[k] := FData.txSenders[k-1];
-    end else j:=High(FData.txSenders);
-    FData.txSenders[j] := senders[i];
-    inc(total_spend,senders[i].Amount);
-  end;
-  For i:=Low(receivers) to High(receivers) do begin
-    SetLength(FData.txReceivers,length(FData.txReceivers)+1);
-    If setInRandomOrder then begin
+      FData.txSenders[j] := senders[i];
+      inc(total_spend,senders[i].Amount);
+    end;
+    For i:=Low(receivers) to High(receivers) do begin
+      SetLength(FData.txReceivers,length(FData.txReceivers)+1);
       // Set receiver in a random order
       If (length(FData.txReceivers)>0) then begin
         j := Random(length(FData.txReceivers)); // Find random position 0..n-1
       end else j:=0;
       for k:=High(FData.txReceivers) downto (j+1) do FData.txReceivers[k] := FData.txReceivers[k-1];
-    end else j:=High(FData.txReceivers);
-    FData.txReceivers[j] := receivers[i];
-    inc(total_receive,receivers[i].Amount);
+      FData.txReceivers[j] := receivers[i];
+      inc(total_receive,receivers[i].Amount);
+    end;
+  end else begin
+    j := length(FData.txSenders);
+    SetLength(FData.txSenders,length(FData.txSenders)+length(senders));
+    For i:=Low(senders) to High(senders) do begin
+      FData.txSenders[j+i] := senders[i];
+      inc(total_spend,senders[i].Amount);
+    end;
+    j := length(FData.txReceivers);
+    SetLength(FData.txReceivers,length(FData.txReceivers)+length(receivers));
+    For i:=Low(receivers) to High(receivers) do begin
+      FData.txReceivers[j+i] := receivers[i];
+      inc(total_receive,receivers[i].Amount);
+    end;
   end;
   inc(FTotalAmount,total_receive);
   inc(FTotalFee,total_spend - total_receive);
@@ -596,9 +812,11 @@ function TOpMultiOperation.AddChangeInfo(const changes: TMultiOpChangesInfo; set
 Var i,j,k : Integer;
 begin
   Result := False;
-  // Check not duplicate
+  // Check not duplicate / invalid data
   For i:=Low(changes) to High(changes) do begin
+    If IndexOfAccountSender(changes[i].Account)>=0 then Exit;
     If IndexOfAccountChanger(changes[i].Account)>=0 then Exit;
+    If (changes[i].Changes_type=[]) then Exit; // Must change something
   end;
   // Ok, let's go
   FHasValidSignature:=False;