Browse Source

Fixed final bugs for TESTNET 5 Beta 3

PascalCoin 5 years ago
parent
commit
e55385ffcc

+ 3 - 1
README.md

@@ -46,7 +46,9 @@ Also, consider a donation at PascalCoin development account: "0-10"
 - Implementation of PIP-0027 (E-PASA Inifine Address-Space) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0027.md
 - Implementation of PIP-0027 (E-PASA Inifine Address-Space) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0027.md
   - Third party apps/implementations of hard coded operations need to pay attention of an extra byte added on each operation using Payload
   - Third party apps/implementations of hard coded operations need to pay attention of an extra byte added on each operation using Payload
   - TODO: Explain "in core" changes
   - TODO: Explain "in core" changes
-- Implementation of PIP-0012 (Recover Accounts option after 10 years) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0012.md
+- Partial implementation of PIP-0012 (Recover Accounts option after 4 years) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0012.md
+  - Accounts updated counter will only update when executing operations in active mode (sender or signer)
+  - If account is a receiver (passive mode) then n_operation_update_block will not update value and can be recovered after 4 years (as defined on original PascalCoin v1 WhitePaper)
 - Fixed important security issue related to PIP-0003 caused by possible "parallelization" of the Proof-of-work
 - Fixed important security issue related to PIP-0003 caused by possible "parallelization" of the Proof-of-work
   - Discovered by Herman Schoenfeld <[email protected]>
   - Discovered by Herman Schoenfeld <[email protected]>
   - Modified length of the digest to be mined, adding previous proof-of-work to avoid parallelization
   - Modified length of the digest to be mined, adding previous proof-of-work to avoid parallelization

+ 27 - 16
src/core/UAccounts.pas

@@ -152,7 +152,7 @@ Type
     Class function IsAccountForAccountSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class function IsAccountForAccountSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class Function IsAccountForSaleOrSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class Function IsAccountForSaleOrSwap(const AAccountInfo: TAccountInfo) : Boolean;
     Class Function IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; ACurrentProtocol : Word; const APayload : TRawBytes) : Boolean;
     Class Function IsAccountForSaleOrSwapAcceptingTransactions(const AAccount: TAccount; ACurrentBlock : Integer; ACurrentProtocol : Word; const APayload : TRawBytes) : Boolean;
-    Class Function IsOperationRecipientSignable(const ASender, ATarget : TAccount; AIncomingFunds : UInt64; ACurrentBlock : Integer; ACurrentProtocol : Word) : Boolean;
+    Class Function IsOperationRecipientSignable(const ASender, ATarget : TAccount; ACurrentBlock : Integer; ACurrentProtocol : Word) : Boolean;
     Class Function GetECInfoTxt(Const EC_OpenSSL_NID: Word) : String;
     Class Function GetECInfoTxt(Const EC_OpenSSL_NID: Word) : String;
     Class Procedure ValidsEC_OpenSSL_NID(list : TList<Word>);
     Class Procedure ValidsEC_OpenSSL_NID(list : TList<Word>);
     Class Function AccountKey2RawString(const account: TAccountKey): TRawBytes; overload;
     Class Function AccountKey2RawString(const account: TAccountKey): TRawBytes; overload;
@@ -1624,38 +1624,49 @@ begin
   if (ACurrentProtocol<CT_PROTOCOL_5) then begin
   if (ACurrentProtocol<CT_PROTOCOL_5) then begin
     // V4 and below only allows Private sales (No Swaps)
     // V4 and below only allows Private sales (No Swaps)
     if Not (IsAccountForPrivateSale(AAccount.accountInfo)) then Exit;
     if Not (IsAccountForPrivateSale(AAccount.accountInfo)) then Exit;
+  end else begin
+    // V5 only will allow PRIVATE SALES or SWAPS while locked
+    if (IsAccountForPublicSale(AAccount.accountInfo)) Then Exit; // Public sales not allowed
+    if (Not (IsAccountLocked(AAccount.accountInfo,ACurrentBlock))) then Exit; // Time lock expired
   end;
   end;
 
 
-  if (AAccount.accountInfo.state in [as_ForSale, as_ForAtomicAccountSwap]) then
+  if (AAccount.accountInfo.state in [as_ForSale, as_ForAtomicAccountSwap]) then begin
     if NOT IsValidAccountKey(AAccount.accountInfo.new_publicKey,errors) then
     if NOT IsValidAccountKey(AAccount.accountInfo.new_publicKey,errors) then
       exit;
       exit;
+  end;
 
 
-   if (AAccount.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) then
-     if NOT IsValidAccountInfoHashLockKey(AAccount.accountInfo, APayload) then
-       exit;
+  if (AAccount.accountInfo.state in [as_ForAtomicAccountSwap, as_ForAtomicCoinSwap]) then begin
+    if NOT IsValidAccountInfoHashLockKey(AAccount.accountInfo, APayload) then
+      exit;
+  end;
   Result := True;
   Result := True;
 end;
 end;
 
 
-Class Function TAccountComp.IsOperationRecipientSignable(const ASender, ATarget : TAccount; AIncomingFunds : UInt64; ACurrentBlock : Integer; ACurrentProtocol : Word) : Boolean;
+Class Function TAccountComp.IsOperationRecipientSignable(const ASender, ATarget : TAccount; ACurrentBlock : Integer; ACurrentProtocol : Word) : Boolean;
 begin
 begin
   // V5 - Allow recipient-signed operations under following conditions:
   // V5 - Allow recipient-signed operations under following conditions:
   //  - Sender Account = Target Account
   //  - Sender Account = Target Account
-  //  - Target Account is listed for SWAP (Atomic Swap)
-  //  - Target Account is time-locked to new-owner-key R and time-lock is active
-  //  - (Target.Balance + Operation.Quantity) >= Target.SalePrice
-  //  - Signed by new-owner-key R
+  //  - Target Account is listed:
+  //      - Listed for ACCOUNT SWAP
+  //      or
+  //      - Listed for PRIVATE SALE
+  //  - Target Account is time-locked and time-lock is active
   //
   //
   //  This allows following use-cases:
   //  This allows following use-cases:
   //  - Private account sale where buyer does not have existing account to initiate transaction
   //  - Private account sale where buyer does not have existing account to initiate transaction
   //  - Atomic account swap where counterparty does not have existing account to initiate transaction
   //  - Atomic account swap where counterparty does not have existing account to initiate transaction
+  //
+  // Note: this does not validate recipient signature, only determines if
+  // it is recipient signable
+  //
   Result := (ACurrentProtocol >= CT_PROTOCOL_5) AND
   Result := (ACurrentProtocol >= CT_PROTOCOL_5) AND
             (ASender.account = ATarget.account) AND
             (ASender.account = ATarget.account) AND
             TAccountComp.IsAccountLocked(ATarget.accountInfo, ACurrentBlock) AND
             TAccountComp.IsAccountLocked(ATarget.accountInfo, ACurrentBlock) AND
-            TAccountComp.IsAccountForSwap(ATarget.accountInfo) AND
-           ((ATarget.balance + AIncomingFunds) >= (ATarget.accountInfo.price));
-
- // Note: this does not validate recipient signature, only determines if
- // it is recipient signable
+            (
+              (TAccountComp.IsAccountForAccountSwap(ATarget.accountInfo))
+              OR
+              (TAccountComp.IsAccountForPrivateSale(ATarget.accountInfo))
+            );
 end;
 end;
 
 
 class function TAccountComp.IsAccountLocked(const AccountInfo: TAccountInfo; blocks_count: Cardinal): Boolean;
 class function TAccountComp.IsAccountLocked(const AccountInfo: TAccountInfo; blocks_count: Cardinal): Boolean;
@@ -1771,7 +1782,7 @@ end;
 class function TAccountComp.IsValidNewAccountKey(const AAccountInfo : TAccountInfo; const ANewKey : TAccountKey; AProtocolVersion : Integer) : Boolean;
 class function TAccountComp.IsValidNewAccountKey(const AAccountInfo : TAccountInfo; const ANewKey : TAccountKey; AProtocolVersion : Integer) : Boolean;
 begin
 begin
   Result :=False;
   Result :=False;
-  if AProtocolVersion <= CT_PROTOCOL_5 then begin
+  if AProtocolVersion < CT_PROTOCOL_5 then begin
     // V2 - V4 Rules
     // V2 - V4 Rules
     // - Private Sale: non-null and must match stored new-key in account_info
     // - Private Sale: non-null and must match stored new-key in account_info
     // - Public Sale: non-null
     // - Public Sale: non-null

+ 3 - 0
src/core/UBlockChain.pas

@@ -3425,6 +3425,9 @@ begin
         OperationResume.Fee := 0;
         OperationResume.Fee := 0;
         Result := true;
         Result := true;
       end else exit;
       end else exit;
+      if (TOpBuyAccount(Operation).Data.sender = TOpBuyAccount(Operation).Data.target) then begin
+        OperationResume.Amount := TOpBuyAccount(Operation).Data.AccountPrice;
+      end;
     End;
     End;
     CT_Op_ChangeAccountInfo : Begin
     CT_Op_ChangeAccountInfo : Begin
       OperationResume.DestAccount := Operation.DestinationAccount;
       OperationResume.DestAccount := Operation.DestinationAccount;

+ 1 - 1
src/core/UConst.pas

@@ -58,7 +58,7 @@ Const
 
 
   CT_WaitNewBlocksBeforeTransaction = {$IFDEF PRODUCTION}100{$ELSE}{$IFDEF TESTNET}10{$ENDIF}{$ENDIF};
   CT_WaitNewBlocksBeforeTransaction = {$IFDEF PRODUCTION}100{$ELSE}{$IFDEF TESTNET}10{$ENDIF}{$ENDIF};
 
 
-  CT_RecoverFoundsWaitInactiveCount = {$IFDEF PRODUCTION}1051200{$ELSE}{$IFDEF TESTNET}1000{$ENDIF}{$ENDIF};  // After 10 years... if an account has no operations, money will be a reward for a miner!
+  CT_RecoverFoundsWaitInactiveCount = {$IFDEF PRODUCTION}420480{$ELSE}{$IFDEF TESTNET}1000{$ENDIF}{$ENDIF};  // After 4 years... if an account has no operations, money will be a reward for a miner!
   CT_MaxFutureBlocksLockedAccount = {$IFDEF PRODUCTION}105120{$ELSE}{$IFDEF TESTNET}CT_RecoverFoundsWaitInactiveCount{$ENDIF}{$ENDIF}; // Maximum future blocks an account can be locked
   CT_MaxFutureBlocksLockedAccount = {$IFDEF PRODUCTION}105120{$ELSE}{$IFDEF TESTNET}CT_RecoverFoundsWaitInactiveCount{$ENDIF}{$ENDIF}; // Maximum future blocks an account can be locked
 
 
   CT_MaxTransactionAmount = 1000000000000; // ... can be deleted
   CT_MaxTransactionAmount = 1000000000000; // ... can be deleted

+ 39 - 14
src/core/UOpTransaction.pas

@@ -845,10 +845,16 @@ begin
   LSender := ASafeBoxTransaction.Account(FData.sender);
   LSender := ASafeBoxTransaction.Account(FData.sender);
   LTarget := ASafeBoxTransaction.Account(FData.target);
   LTarget := ASafeBoxTransaction.Account(FData.target);
 
 
-  // V5 - Allow recipient-signed transactions. This is defined as
+  // V5 - Allow recipient-signed OP_BUY operations. This is defined as
   //  - Sender Account = Target Account
   //  - Sender Account = Target Account
-  LRecipientSignable := TAccountComp.IsOperationRecipientSignable(LSender, LTarget, FData.Amount, LCurrentBlock, LCurrentProtocol);
-  LIsCoinSwap := TAccountComp.IsAccountForCoinSwap(LTarget.accountInfo);
+  //  - Account (sender = target) is for PRIVATE SALE or ACCOUNT SWAP
+  //  - TIME LOCK not expired
+  LRecipientSignable :=
+    ( FData.opTransactionStyle = buy_Account )
+    And (TAccountComp.IsOperationRecipientSignable(LSender, LTarget, LCurrentBlock, LCurrentProtocol));
+
+  LIsCoinSwap := TAccountComp.IsAccountForCoinSwap(LTarget.accountInfo)
+    And (TAccountComp.IsAccountForSaleOrSwapAcceptingTransactions(LTarget, LCurrentBlock, LCurrentProtocol, FData.payload.payload_raw));
 
 
   if (FData.sender=FData.target) AND (NOT LRecipientSignable) then begin
   if (FData.sender=FData.target) AND (NOT LRecipientSignable) then begin
     AErrors := Format('Sender=Target and Target is not recipient-signable. Account: %d',[FData.sender]);
     AErrors := Format('Sender=Target and Target is not recipient-signable. Account: %d',[FData.sender]);
@@ -869,7 +875,7 @@ begin
   end;
   end;
   LTotalAmount := FData.amount + FData.fee;
   LTotalAmount := FData.amount + FData.fee;
   if (LSender.balance<LTotalAmount) then begin
   if (LSender.balance<LTotalAmount) then begin
-    AErrors := Format('Insufficient funds %d < (%d + %d = %d)',[LSender.balance,FData.amount,FData.fee,LTotalAmount]);
+    AErrors := Format('Insufficient sender funds %d < (%d + %d = %d)',[LSender.balance,FData.amount,FData.fee,LTotalAmount]);
     Exit;
     Exit;
   end;
   end;
   if (LTarget.balance+FData.amount>CT_MaxWalletAmount) then begin
   if (LTarget.balance+FData.amount>CT_MaxWalletAmount) then begin
@@ -916,9 +922,22 @@ begin
       Exit;
       Exit;
     end;
     end;
 
 
-    if (TAccountComp.IsAccountForSwap(LTarget.accountInfo) AND (LCurrentProtocol<CT_PROTOCOL_5)) then begin
-      AErrors := 'Atomic swaps are not allowed until Protocol 5';
-      exit;
+    if (LCurrentProtocol < CT_PROTOCOL_5) then begin
+      if (TAccountComp.IsAccountForSwap(LTarget.accountInfo)) then begin
+        AErrors := 'Atomic swaps are not allowed until Protocol 5';
+        exit;
+      end;
+    end else begin
+      if (Not TAccountComp.IsAccountForPublicSale(LTarget.accountInfo)) then begin
+        // On V5 cannot BUY accounts with time-lock EXPIRED  (private sale or Swaps)
+        if (Not TAccountComp.IsAccountLocked(LTarget.accountInfo,LCurrentBlock)) then begin
+          AErrors := Format('Target %s time lock expired on block %d (Current %d)',
+            [TAccountComp.AccountNumberToAccountTxtNumber(LTarget.account),
+            LTarget.accountInfo.locked_until_block,
+            LCurrentBlock]);
+          Exit;
+        end;
+      end;
     end;
     end;
 
 
     LSeller := ASafeBoxTransaction.Account(FData.SellerAccount);
     LSeller := ASafeBoxTransaction.Account(FData.SellerAccount);
@@ -931,14 +950,20 @@ begin
       AErrors := Format('Seller account %d is not expected account %d',[FData.SellerAccount,LTarget.accountInfo.account_to_pay]);
       AErrors := Format('Seller account %d is not expected account %d',[FData.SellerAccount,LTarget.accountInfo.account_to_pay]);
       exit;
       exit;
     end;
     end;
-    LTotalAmount := LTarget.accountInfo.price;
-    if LRecipientSignable then
-      LTotalAmount := LTotalAmount + FData.fee;
-    
-    if (LTarget.balance + FData.amount) < LTotalAmount then begin
-      AErrors := Format('Account %d balance (%d) + amount (%d) < price (%d)',[LTarget.account,LTarget.balance,FData.amount,LTotalAmount]);
-      exit;
+
+    if (LSender.account = LTarget.account) then begin
+      // Self signed operation, amount is not used because has no effect
+      if (LSender.balance + FData.fee) < LTarget.accountInfo.price then begin
+        AErrors := Format('Self signed Account %d balance (%d) + fee (%d) < target price (%d)',[LTarget.account,LTarget.balance,FData.fee,LTarget.accountInfo.price]);
+        exit;
+      end;
+    end else begin
+      if (LTarget.balance + FData.amount) < LTarget.accountInfo.price then begin
+        AErrors := Format('Target Account %d balance (%d) + amount (%d) < target price (%d)',[LTarget.account,LTarget.balance,FData.amount,LTarget.accountInfo.price]);
+        exit;
+      end;
     end;
     end;
+
     if (FData.AccountPrice<>LTarget.accountInfo.price) then begin
     if (FData.AccountPrice<>LTarget.accountInfo.price) then begin
       AErrors := Format('Signed price (%d) is not the same of account price (%d)',[FData.AccountPrice,LTarget.accountInfo.price]);
       AErrors := Format('Signed price (%d) is not the same of account price (%d)',[FData.AccountPrice,LTarget.accountInfo.price]);
       exit;
       exit;

+ 1 - 1
src/gui-classic/UFRMAccountSelect.pas

@@ -173,7 +173,7 @@ procedure TSearchThread.BCExecute;
       If validAccKey then begin
       If validAccKey then begin
         isValid := TAccountComp.EqualAccountKeys(account.accountInfo.accountKey,FSearchValues.inAccountKey);
         isValid := TAccountComp.EqualAccountKeys(account.accountInfo.accountKey,FSearchValues.inAccountKey);
       end else if (Assigned(FSearchValues.inWalletKeys)) then begin
       end else if (Assigned(FSearchValues.inWalletKeys)) then begin
-        isValid := FSearchValues.inWalletKeys.IndexOfAccountKey(account.accountInfo.accountKey)>=0;
+        isValid := (FSearchValues.inWalletKeys.IndexOfAccountKey(account.accountInfo.accountKey)>=0) or (FSearchValues.onlyForPrivateSaleToMe);
       end;
       end;
       If isValid And (FSearchValues.onlyForSaleOrSwap) then begin
       If isValid And (FSearchValues.onlyForSaleOrSwap) then begin
         isValid := TAccountComp.IsAccountForSaleOrSwap(account.accountInfo);
         isValid := TAccountComp.IsAccountForSaleOrSwap(account.accountInfo);

+ 2 - 2
src/gui-classic/UFRMOperation.dfm

@@ -295,7 +295,7 @@ object FRMOperation: TFRMOperation
         Top = 7
         Top = 7
         Width = 524
         Width = 524
         Height = 204
         Height = 204
-        ActivePage = tsChangeInfo
+        ActivePage = tsBuyAccount
         TabOrder = 0
         TabOrder = 0
         OnChange = PageControlOpTypeChange
         OnChange = PageControlOpTypeChange
         object tsTransaction: TTabSheet
         object tsTransaction: TTabSheet
@@ -814,7 +814,7 @@ object FRMOperation: TFRMOperation
           object lblBuyAccountErrors: TLabel
           object lblBuyAccountErrors: TLabel
             Left = 13
             Left = 13
             Top = 10
             Top = 10
-            Width = 331
+            Width = 484
             Height = 13
             Height = 13
             AutoSize = False
             AutoSize = False
             Caption = 'Errors detected'
             Caption = 'Errors detected'

+ 30 - 5
src/gui-classic/UFRMOperation.pas

@@ -244,8 +244,18 @@ loop_start:
       account := FNode.GetMempoolAccount(_senderAccounts[iAcc]);
       account := FNode.GetMempoolAccount(_senderAccounts[iAcc]);
       If Not UpdatePayload(account, errors) then
       If Not UpdatePayload(account, errors) then
         raise Exception.Create('Error encoding payload of sender account '+TAccountComp.AccountNumberToAccountTxtNumber(account.account)+': '+errors);
         raise Exception.Create('Error encoding payload of sender account '+TAccountComp.AccountNumberToAccountTxtNumber(account.account)+': '+errors);
-      if NOT WalletKeys.TryGetKey(account.accountInfo.accountKey, LKey) then
-        Raise Exception.Create('Sender account private key not found in Wallet');
+      if NOT WalletKeys.TryGetKey(account.accountInfo.accountKey, LKey) then begin
+
+        if  (
+             (TAccountComp.IsAccountForPrivateSale(account.accountInfo)) or
+             (TAccountComp.IsAccountForAccountSwap(account.accountInfo))
+             )
+            and (Not TAccountComp.IsNullAccountKey(account.accountInfo.new_publicKey)) then begin
+
+          if NOT WalletKeys.TryGetKey(account.accountInfo.new_publicKey, LKey) then
+            Raise Exception.Create('New sender account private key not found in Wallet');
+        end else Raise Exception.Create('Sender account private key not found in Wallet');
+      end;
       dooperation := true;
       dooperation := true;
       // Default fee
       // Default fee
       if account.balance > uint64(DefaultFee) then _fee := DefaultFee else _fee := account.balance;
       if account.balance > uint64(DefaultFee) then _fee := DefaultFee else _fee := account.balance;
@@ -864,7 +874,7 @@ begin
       exit;
       exit;
     end;
     end;
     AccountToBuy := FNode.GetMempoolAccount(c);
     AccountToBuy := FNode.GetMempoolAccount(c);
-    ARecipientSigned := TAccountComp.IsOperationRecipientSignable(SenderAccount, AccountToBuy, Amount, FNode.Bank.BlocksCount, FNode.Bank.SafeBox.CurrentProtocol);
+    ARecipientSigned := TAccountComp.IsOperationRecipientSignable(SenderAccount, AccountToBuy, FNode.Bank.BlocksCount, FNode.Bank.SafeBox.CurrentProtocol);
     if (SenderAccount.account = AccountToBuy.Account) AND (NOT ARecipientSigned) then begin
     if (SenderAccount.account = AccountToBuy.Account) AND (NOT ARecipientSigned) then begin
       errors := 'Not recipient signable';
       errors := 'Not recipient signable';
       exit;
       exit;
@@ -873,13 +883,21 @@ begin
     If not TAccountComp.IsAccountForSaleOrSwap(AccountToBuy.accountInfo) then begin
     If not TAccountComp.IsAccountForSaleOrSwap(AccountToBuy.accountInfo) then begin
       errors := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(c)+' is not for sale or swap';
       errors := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(c)+' is not for sale or swap';
       exit;
       exit;
+    end else if (TAccountComp.IsAccountForCoinSwap(AccountToBuy.accountInfo)) then begin
+      errors := 'Account '+TAccountComp.AccountNumberToAccountTxtNumber(c)+' is for '+TAccountComp.FormatMoney(AccountToBuy.accountInfo.price)+' COIN swap, cannot buy';
+      exit;
     end;
     end;
+
     If Not TAccountComp.TxtToMoney(ebBuyAmount.Text,amount) then begin
     If Not TAccountComp.TxtToMoney(ebBuyAmount.Text,amount) then begin
       errors := 'Invalid amount value';
       errors := 'Invalid amount value';
       exit;
       exit;
     end;
     end;
-     if (AccountToBuy.accountInfo.price>amount) AND (NOT TAccountComp.IsAccountForCoinSwap(AccountToBuy.accountInfo)) then begin
-      errors := 'Account price '+TAccountComp.FormatMoney(AccountToBuy.accountInfo.price);
+    if (AccountToBuy.accountInfo.price>amount) AND (NOT ARecipientSigned) then begin
+      errors := Format('Account price %s < (amount:%s + balance:%s = %s)',[TAccountComp.FormatMoney(AccountToBuy.accountInfo.price),
+        TAccountComp.FormatMoney(amount),
+        TAccountComp.FormatMoney(AccountToBuy.balance),
+        TAccountComp.FormatMoney(amount + AccountToBuy.balance)
+        ]);
       exit;
       exit;
     end;
     end;
     if TAccountComp.IsAccountForSale(AccountToBuy.accountInfo) AND (amount+DefaultFee > SenderAccount.balance) then begin
     if TAccountComp.IsAccountForSale(AccountToBuy.accountInfo) AND (amount+DefaultFee > SenderAccount.balance) then begin
@@ -1149,6 +1167,13 @@ begin
       for iAcc := 0 to SenderAccounts.Count - 1 do begin
       for iAcc := 0 to SenderAccounts.Count - 1 do begin
         sender_account := TNode.Node.Bank.SafeBox.Account(SenderAccounts.Get(iAcc));
         sender_account := TNode.Node.Bank.SafeBox.Account(SenderAccounts.Get(iAcc));
         iWallet := WalletKeys.IndexOfAccountKey(sender_account.accountInfo.accountKey);
         iWallet := WalletKeys.IndexOfAccountKey(sender_account.accountInfo.accountKey);
+        if (iWallet<0) and (
+             (TAccountComp.IsAccountForPrivateSale(sender_account.accountInfo)) or
+             (TAccountComp.IsAccountForAccountSwap(sender_account.accountInfo))
+             )
+            and (Not TAccountComp.IsNullAccountKey(sender_account.accountInfo.new_publicKey)) then begin
+          iWallet := WalletKeys.IndexOfAccountKey(sender_account.accountInfo.new_publicKey);
+        end;
         if (iWallet<0) then begin
         if (iWallet<0) then begin
           errors := 'Private key of account '+TAccountComp.AccountNumberToAccountTxtNumber(sender_account.account)+' not found in wallet';
           errors := 'Private key of account '+TAccountComp.AccountNumberToAccountTxtNumber(sender_account.account)+' not found in wallet';
           lblGlobalErrors.Caption := errors;
           lblGlobalErrors.Caption := errors;

+ 28 - 0
src/gui-classic/UFRMWallet.dfm

@@ -671,6 +671,10 @@ object FRMWallet: TFRMWallet
         object tsMultiSelectAccounts: TTabSheet
         object tsMultiSelectAccounts: TTabSheet
           Caption = 'Selected Accounts For Batch Operation'
           Caption = 'Selected Accounts For Batch Operation'
           ImageIndex = 1
           ImageIndex = 1
+          ExplicitLeft = 0
+          ExplicitTop = 0
+          ExplicitWidth = 0
+          ExplicitHeight = 0
           object dgSelectedAccounts: TDrawGrid
           object dgSelectedAccounts: TDrawGrid
             Left = 41
             Left = 41
             Top = 31
             Top = 31
@@ -862,6 +866,10 @@ object FRMWallet: TFRMWallet
     object tsPendingOperations: TTabSheet
     object tsPendingOperations: TTabSheet
       Caption = 'Pending Operations'
       Caption = 'Pending Operations'
       ImageIndex = 5
       ImageIndex = 5
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       object dgPendingOperations: TDrawGrid
       object dgPendingOperations: TDrawGrid
         Left = 0
         Left = 0
         Top = 86
         Top = 86
@@ -908,6 +916,10 @@ object FRMWallet: TFRMWallet
     object tsBlockChain: TTabSheet
     object tsBlockChain: TTabSheet
       Caption = 'Block Explorer'
       Caption = 'Block Explorer'
       ImageIndex = 1
       ImageIndex = 1
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       object Panel2: TPanel
       object Panel2: TPanel
         Left = 0
         Left = 0
         Top = 0
         Top = 0
@@ -1000,6 +1012,10 @@ object FRMWallet: TFRMWallet
     object tsOperations: TTabSheet
     object tsOperations: TTabSheet
       Caption = 'Operations Explorer'
       Caption = 'Operations Explorer'
       ImageIndex = 1
       ImageIndex = 1
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       object Panel1: TPanel
       object Panel1: TPanel
         Left = 0
         Left = 0
         Top = 0
         Top = 0
@@ -1048,6 +1064,10 @@ object FRMWallet: TFRMWallet
     object tsLogs: TTabSheet
     object tsLogs: TTabSheet
       Caption = 'Logs'
       Caption = 'Logs'
       ImageIndex = 2
       ImageIndex = 2
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       object pnlTopLogs: TPanel
       object pnlTopLogs: TPanel
         Left = 0
         Left = 0
         Top = 0
         Top = 0
@@ -1079,6 +1099,10 @@ object FRMWallet: TFRMWallet
     object tsNodeStats: TTabSheet
     object tsNodeStats: TTabSheet
       Caption = 'Node Stats'
       Caption = 'Node Stats'
       ImageIndex = 3
       ImageIndex = 3
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       DesignSize = (
       DesignSize = (
         857
         857
         438)
         438)
@@ -1148,6 +1172,10 @@ object FRMWallet: TFRMWallet
     object tsMessages: TTabSheet
     object tsMessages: TTabSheet
       Caption = 'Messages'
       Caption = 'Messages'
       ImageIndex = 6
       ImageIndex = 6
+      ExplicitLeft = 0
+      ExplicitTop = 0
+      ExplicitWidth = 0
+      ExplicitHeight = 0
       DesignSize = (
       DesignSize = (
         857
         857
         438)
         438)

+ 34 - 8
src/gui-classic/UFRMWallet.pas

@@ -243,8 +243,8 @@ type
     Procedure FillAccountInformation(Const Strings : TStrings; Const AccountNumber : Cardinal);
     Procedure FillAccountInformation(Const Strings : TStrings; Const AccountNumber : Cardinal);
     Procedure FillOperationInformation(Const Strings : TStrings; Const OperationResume : TOperationResume);
     Procedure FillOperationInformation(Const Strings : TStrings; Const OperationResume : TOperationResume);
     Procedure InitMacOSMenu;
     Procedure InitMacOSMenu;
-    {$IFDEF TESTNET}
     Procedure InitMenuForTesting;
     Procedure InitMenuForTesting;
+    {$IFDEF TESTNET}
     Procedure Test_RandomOperations(Sender: TObject);
     Procedure Test_RandomOperations(Sender: TObject);
     Procedure Test_AskForFreeAccount(Sender: TObject);
     Procedure Test_AskForFreeAccount(Sender: TObject);
     {$IFDEF TESTING_NO_POW_CHECK}
     {$IFDEF TESTING_NO_POW_CHECK}
@@ -252,6 +252,7 @@ type
     {$ENDIF}
     {$ENDIF}
     {$ENDIF}
     {$ENDIF}
     Procedure Test_ShowPublicKeys(Sender: TObject);
     Procedure Test_ShowPublicKeys(Sender: TObject);
+    Procedure Test_ShowOperationsInMemory(Sender: TObject);
     procedure OnAccountsGridUpdatedData(Sender : TObject);
     procedure OnAccountsGridUpdatedData(Sender : TObject);
   protected
   protected
     { Private declarations }
     { Private declarations }
@@ -1057,13 +1058,13 @@ begin
 end;
 end;
 
 
 
 
-{$IFDEF TESTNET}
 procedure TFRMWallet.InitMenuForTesting;
 procedure TFRMWallet.InitMenuForTesting;
 var mi : TMenuItem;
 var mi : TMenuItem;
 begin
 begin
   mi := TMenuItem.Create(MainMenu);
   mi := TMenuItem.Create(MainMenu);
   mi.Caption:='-';
   mi.Caption:='-';
   miAbout.Add(mi);
   miAbout.Add(mi);
+{$IFDEF TESTNET}
   {$IFDEF TESTING_NO_POW_CHECK}
   {$IFDEF TESTING_NO_POW_CHECK}
   mi := TMenuItem.Create(MainMenu);
   mi := TMenuItem.Create(MainMenu);
   mi.Caption:='Create a block';
   mi.Caption:='Create a block';
@@ -1071,10 +1072,6 @@ begin
   miAbout.Add(mi);
   miAbout.Add(mi);
   {$ENDIF}
   {$ENDIF}
   mi := TMenuItem.Create(MainMenu);
   mi := TMenuItem.Create(MainMenu);
-  mi.Caption:='Show public keys state';
-  mi.OnClick:=Test_ShowPublicKeys;
-  miAbout.Add(mi);
-  mi := TMenuItem.Create(MainMenu);
   mi.Caption:='Create Random operations';
   mi.Caption:='Create Random operations';
   mi.OnClick:=Test_RandomOperations;
   mi.OnClick:=Test_RandomOperations;
   miAbout.Add(mi);
   miAbout.Add(mi);
@@ -1086,6 +1083,15 @@ begin
   mi.Caption:='Diagnostic Tool';
   mi.Caption:='Diagnostic Tool';
   mi.OnClick:=Test_ShowDiagnosticTool;
   mi.OnClick:=Test_ShowDiagnosticTool;
   miAbout.Add(mi);
   miAbout.Add(mi);
+{$ENDIF}
+  mi := TMenuItem.Create(MainMenu);
+  mi.Caption:='Show public keys state';
+  mi.OnClick:=Test_ShowPublicKeys;
+  miAbout.Add(mi);
+  mi := TMenuItem.Create(MainMenu);
+  mi.Caption:='Show operations in memory';
+  mi.OnClick:=Test_ShowOperationsInMemory;
+  miAbout.Add(mi);
 
 
 end;
 end;
 
 
@@ -1113,6 +1119,7 @@ begin
 end;
 end;
 {$ENDIF}
 {$ENDIF}
 
 
+{$IFDEF TESTNET}
 procedure TFRMWallet.Test_RandomOperations(Sender: TObject);
 procedure TFRMWallet.Test_RandomOperations(Sender: TObject);
 Var FRM : TFRMRandomOperations;
 Var FRM : TFRMRandomOperations;
 begin
 begin
@@ -1136,6 +1143,27 @@ end;
 
 
 {$ENDIF}
 {$ENDIF}
 
 
+procedure TFRMWallet.Test_ShowOperationsInMemory(Sender: TObject);
+var LFRM : TFRMMemoText;
+  i, nOps : Integer;
+  Lslist : TStrings;
+begin
+  Lslist := TStringList.Create;
+  try
+    TPCOperationsStorage.PCOperationsStorage.GetStats(Lslist);
+    nOps := TPCOperationsStorage.PCOperationsStorage.Count;
+    LFRM := TFRMMemoText.Create(Self);
+    try
+      LFRM.InitData('Operations in Memory '+IntToStr(nOps),Lslist.Text);
+      LFRM.ShowModal;
+    finally
+      LFRM.Free;
+    end;
+  finally
+    Lslist.Free;
+  end;
+end;
+
 procedure TFRMWallet.Test_ShowPublicKeys(Sender: TObject);
 procedure TFRMWallet.Test_ShowPublicKeys(Sender: TObject);
 var F : TFRMMemoText;
 var F : TFRMMemoText;
   i : Integer;
   i : Integer;
@@ -1322,10 +1350,8 @@ begin
   cbHashRateUnits.Items.Add('Th/s');
   cbHashRateUnits.Items.Add('Th/s');
   cbHashRateUnits.Items.Add('Ph/s');
   cbHashRateUnits.Items.Add('Ph/s');
   cbHashRateUnits.Items.Add('Eh/s');
   cbHashRateUnits.Items.Add('Eh/s');
-  {$IFDEF TESTNET}
   // Things for testing purposes only
   // Things for testing purposes only
   InitMenuForTesting;
   InitMenuForTesting;
-  {$ENDIF}
   {$ifdef DARWIN}
   {$ifdef DARWIN}
   // this is macOS specific menu layout
   // this is macOS specific menu layout
   InitMacOSMenu;
   InitMacOSMenu;