Browse Source

Merge pull request #42 from PascalCoinDev/master

Code ready for PayToKey and E-Pasa implementation on "sendto" JSON-RPC api call
Pascal Coin 4 years ago
parent
commit
85ea96f43e

+ 24 - 1
src/core/UAccounts.pas

@@ -219,6 +219,7 @@ Type
     // OrderedAccountKeysList (Added after Build 3.0.1) allows an indexed search of public keys in the safebox with mem optimization
     FOrderedAccountKeysList : TSafeboxPubKeysAndAccounts;
     FAccountsOrderedByUpdatedBlock : TAccountsOrderedByUpdatedBlock;
+    FAccountsOrderedBySalePrice : TAccountsOrderedBySalePrice;
     {$ENDIF}
     FModifiedBlocksSeparatedChain : TOrderedBlockAccountList; // Used when has PreviousSafebox (Used if we are on a Separated chain)
     //
@@ -327,6 +328,7 @@ Type
     property PCAbstractMem : TPCAbstractMem read FPCAbstractMem;
     {$ENDIF}
     Function AccountsOrderedByUpdatedBlock : TAccountsOrderedByUpdatedBlock;
+    Function AccountsOrderedBySalePrice : TAccountsOrderedBySalePrice;
   End;
 
 
@@ -2413,6 +2415,15 @@ begin
   end;
 end;
 
+function TPCSafeBox.AccountsOrderedBySalePrice: TAccountsOrderedBySalePrice;
+begin
+  {$IFDEF USE_ABSTRACTMEM}
+  Result := FPCAbstractMem.AccountsOrderedBySalePrice;
+  {$ELSE}
+  Result := FAccountsOrderedBySalePrice;
+  {$ENDIF}
+end;
+
 function TPCSafeBox.AccountsOrderedByUpdatedBlock: TAccountsOrderedByUpdatedBlock;
 begin
   {$IFDEF USE_ABSTRACTMEM}
@@ -2838,6 +2849,7 @@ begin
   FAggregatedHashrate := TBigNum.Create(0);
   FOrderedByName := TOrderedRawList.Create;
   FAccountsOrderedByUpdatedBlock := TAccountsOrderedByUpdatedBlock.Create(GetAccount);
+  FAccountsOrderedBySalePrice := TAccountsOrderedBySalePrice.Create(GetAccount);
   {$ENDIF}
   FListOfOrderedAccountKeysList := TList<TOrderedAccountKeysList>.Create;
   FCurrentProtocol := CT_PROTOCOL_1;
@@ -2883,6 +2895,7 @@ begin
   FreeAndNil(FSubChains);
   {$IFnDEF USE_ABSTRACTMEM}
   FreeAndNil(FAccountsOrderedByUpdatedBlock);
+  FreeAndNil(FAccountsOrderedBySalePrice);
   {$ENDIF}
 
   If Assigned(FPreviousSafeBox) then begin
@@ -3638,8 +3651,13 @@ begin
         for j := low(LBlock.accounts) to High(LBlock.accounts) do begin
           FAccountsOrderedByUpdatedBlock.Update(
             LBlock.accounts[j].account,
-            LBlock.accounts[j].updated_on_block_active_mode,
+            0,
             LBlock.accounts[j].updated_on_block_active_mode);
+          FAccountsOrderedBySalePrice.UpdateAccountBySalePrice(
+            LBlock.accounts[j].account,
+            CT_AccountInfo_NUL,
+            LBlock.accounts[j].accountInfo
+            );
         end;
         {$ENDIF}
         for j := low(LBlock.accounts) to High(LBlock.accounts) do begin
@@ -4758,6 +4776,11 @@ begin
     blockAccount.accounts[iAccount].updated_on_block_active_mode,
     newUpdated_block_active_mode
    );
+  FAccountsOrderedBySalePrice.UpdateAccountBySalePrice(
+    account_number,
+    blockAccount.accounts[iAccount].accountInfo,
+    newAccountInfo
+   );
   {$ENDIF}
 
   if (NOT TAccountComp.EqualAccountKeys(blockAccount.accounts[iAccount].accountInfo.accountKey,newAccountInfo.accountKey)) then begin

+ 32 - 7
src/core/UPCAbstractMem.pas

@@ -121,6 +121,7 @@ type
     FSavingOldGridCache : Boolean;
     FSavingOldDefaultCacheDataBlocksSize : Integer;
     FAccountsOrderedByUpdatedBlock : TAccountsOrderedByUpdatedBlock;
+    FAccountsOrderedBySalePrice : TAccountsOrderedBySalePrice;
 
     function IsChecking : Boolean;
     procedure DoCheck;
@@ -180,6 +181,7 @@ type
     Property MaxAccountKeysCache : Integer read GetMaxAccountKeysCache write SetMaxAccountKeysCache;
     Property SavingNewSafeboxMode : Boolean read FSavingNewSafeboxMode write SetSavingNewSafeboxMode;
     Property AccountsOrderedByUpdatedBlock : TAccountsOrderedByUpdatedBlock read FAccountsOrderedByUpdatedBlock;
+    Property AccountsOrderedBySalePrice : TAccountsOrderedBySalePrice read FAccountsOrderedBySalePrice;
   end;
 
 implementation
@@ -188,7 +190,7 @@ uses UAccounts;
 
 const
   CT_PCAbstractMem_FileVersion = 100;
-  CT_PCAbstractMem_HeaderVersion = 2;
+  CT_PCAbstractMem_HeaderVersion = 3;
 
 function _AccountCache_Comparision(const Left, Right: TAccountCache.PAVLCacheMemData): Integer;
 begin
@@ -313,6 +315,7 @@ const
   [32..35] 4 bytes: FZoneAggregatedHashrate.position
   [36..39] 4 bytes: LZoneBuffersBlockHash
   [40..43] 4 bytes: LZoneAccountsOrderedByUpdatedBlock.position
+  [44..47] 4 bytes: LZoneAccountsOrderedBySalePrice.position
   ...
   [96..99] 4 bytes: Header version
   }
@@ -321,7 +324,8 @@ var LZone,
   LZoneAccounts,
   LZoneAccountsNames,
   LZoneAccountKeys,
-  LZoneAccountsOrderedByUpdatedBlock : TAMZone;
+  LZoneAccountsOrderedByUpdatedBlock,
+  LZoneAccountsOrderedBySalePrice : TAMZone;
   LZoneBuffersBlockHash : TAbstractMemPosition;
   LHeader, LBuffer, LBigNum : TBytes;
   LIsGood : Boolean;
@@ -336,6 +340,7 @@ begin
   FreeAndNil(FAccountKeys);
   FreeAndNil(FBufferBlocksHash);
   FreeAndNil(FAccountsOrderedByUpdatedBlock);
+  FreeAndNil(FAccountsOrderedBySalePrice);
   //
   Result := False;
   AIsNewStructure := True;
@@ -347,6 +352,7 @@ begin
   FZoneAggregatedHashrate.Clear;
   LZoneBuffersBlockHash := 0;
   LZoneAccountsOrderedByUpdatedBlock.Clear;
+  LZoneAccountsOrderedBySalePrice.Clear;
 
   if (FAbstractMem.ReadFirstData(LZone,LHeader)) then begin
     // Check if header is valid:
@@ -370,6 +376,7 @@ begin
         Move(LHeader[32], FZoneAggregatedHashrate.position, 4);
         LZoneBuffersBlockHash := LZone.position + 36;
         Move(LHeader[40], LZoneAccountsOrderedByUpdatedBlock.position, 4);
+        Move(LHeader[44], LZoneAccountsOrderedBySalePrice.position, 4);
         //
         Move(LHeader[96], LHeaderVersion, 4);
         if (LHeaderVersion>CT_PCAbstractMem_HeaderVersion) then begin
@@ -377,12 +384,17 @@ begin
         end else begin
           AIsNewStructure := False;
           //
-          if (LZoneAccountsOrderedByUpdatedBlock.position=0) then begin
-            if (Not FAbstractMem.ReadOnly) then begin
+          if (Not FAbstractMem.ReadOnly) then begin
+            if (LZoneAccountsOrderedByUpdatedBlock.position=0) then begin
               LZoneAccountsOrderedByUpdatedBlock := FAbstractMem.New(TAbstractMemBTree.MinAbstractMemInitialPositionSize);
               Move(LZoneAccountsOrderedByUpdatedBlock.position,LHeader[40],4);
               FAbstractMem.Write(LZone.position,LHeader[0],Length(LHeader));
             end;
+            if (LZoneAccountsOrderedBySalePrice.position=0) then begin
+              LZoneAccountsOrderedBySalePrice := FAbstractMem.New(TAbstractMemBTree.MinAbstractMemInitialPositionSize);
+              Move(LZoneAccountsOrderedBySalePrice.position,LHeader[44],4);
+              FAbstractMem.Write(LZone.position,LHeader[0],Length(LHeader));
+            end;
           end;
         end;
       end;
@@ -407,6 +419,8 @@ begin
     LZoneBuffersBlockHash := LZone.position+36;
     LZoneAccountsOrderedByUpdatedBlock := FAbstractMem.New(
       TAbstractMemBTree.MinAbstractMemInitialPositionSize);
+    LZoneAccountsOrderedBySalePrice := FAbstractMem.New(
+      TAbstractMemBTree.MinAbstractMemInitialPositionSize);
 
     Move(LZoneBlocks.position,       LHeader[16],4);
     Move(LZoneAccounts.position,     LHeader[20],4);
@@ -415,6 +429,7 @@ begin
     Move(FZoneAggregatedHashrate.position,LHeader[32],4);
     LHeaderVersion := CT_PCAbstractMem_HeaderVersion;
     Move(LZoneAccountsOrderedByUpdatedBlock, LHeader[40],4);
+    Move(LZoneAccountsOrderedBySalePrice, LHeader[44],4);
     Move(LHeaderVersion,             LHeader[96],4);
 
     FAbstractMem.Write(LZone.position,LHeader[0],Length(LHeader));
@@ -442,11 +457,12 @@ begin
   end;
   FBufferBlocksHash := TPCAbstractMemBytesBuffer32Safebox.Create(FAbstractMem,LZoneBuffersBlockHash,FBlocks.Count);
 
-  if (LZoneAccountsOrderedByUpdatedBlock.position<>0) then begin
-    FAccountsOrderedByUpdatedBlock := TAccountsOrderedByUpdatedBlock.Create(FAbstractMem,LZoneAccountsOrderedByUpdatedBlock,DoGetAccount);
-  end;
+  FAccountsOrderedByUpdatedBlock := TAccountsOrderedByUpdatedBlock.Create(FAbstractMem,LZoneAccountsOrderedByUpdatedBlock,DoGetAccount);
   FAccounts.AccountsOrderedByUpdatedBlock := FAccountsOrderedByUpdatedBlock;
 
+  FAccountsOrderedBySalePrice := TAccountsOrderedBySalePrice.Create(FAbstractMem,LZoneAccountsOrderedBySalePrice,DoGetAccount);
+  FAccounts.AccountsOrderedBySalePrice := FAccountsOrderedBySalePrice;
+
   FAccountCache.Clear;
 
   if (Not AIsNewStructure) And (Not FAbstractMem.ReadOnly) And (LHeaderVersion<CT_PCAbstractMem_HeaderVersion) then begin
@@ -792,9 +808,18 @@ procedure TPCAbstractMem.UpgradeAbstractMemVersion(const ACurrentHeaderVersion:
 var LFirstTC, LTC : TTickCount;
   i : integer;
   LAccount : TAccount;
+  LaccInfoNul : TAccountInfo;
 begin
   LFirstTC := TPlatform.GetTickCount;
   LTC := LFirstTC;
+  if (ACurrentHeaderVersion=2) then begin
+    // Set accounts price
+    LaccInfoNul.Clear;
+    for i := 0 to AccountsCount-1 do begin
+      LAccount := GetAccount(i);
+      AccountsOrderedBySalePrice.UpdateAccountBySalePrice(LAccount.account,LaccInfoNul,LAccount.accountInfo);
+    end;
+  end;
   TLog.NewLog(ltinfo,ClassName,Format('Finalized upgrade AbstractMem file from %d to %d in %.2f seconds',[ACurrentHeaderVersion,CT_PCAbstractMem_HeaderVersion, TPlatform.GetElapsedMilliseconds(LFirstTC)/1000]));
 end;
 

+ 16 - 2
src/core/UPCAbstractMemAccounts.pas

@@ -22,13 +22,16 @@ type
   private
     FAccountKeys: TPCAbstractMemAccountKeys;
     FAccountsOrderedByUpdatedBlock : TAccountsOrderedByUpdatedBlock;
+    FAccountsOrderedBySalePrice : TAccountsOrderedBySalePrice;
   protected
     procedure LoadFrom(const ABytes: TBytes; var AItem: TAccount); override;
     procedure SaveTo(const AItem: TAccount; AIsAddingItem : Boolean; var ABytes: TBytes); override;
   public
+    Constructor Create(AAbstractMem : TAbstractMem; const AInitialZone : TAMZone; ADefaultElementsPerBlock : Integer; AUseCache : Boolean); override;
     class procedure LoadAccountFromTBytes(const ABytes: TBytes; const AAccountKeys : TPCAbstractMemAccountKeys; var AItem: TAccount);
     property AccountKeys: TPCAbstractMemAccountKeys read FAccountKeys write FAccountKeys;
     property AccountsOrderedByUpdatedBlock: TAccountsOrderedByUpdatedBlock read FAccountsOrderedByUpdatedBlock write FAccountsOrderedByUpdatedBlock;
+    property AccountsOrderedBySalePrice: TAccountsOrderedBySalePrice read FAccountsOrderedBySalePrice write FAccountsOrderedBySalePrice;
   end;
 
   EAbsctractMemAccounts = Class(Exception);
@@ -39,6 +42,16 @@ uses UAccounts;
 
 { TPCAbstractMemListAccounts }
 
+constructor TPCAbstractMemListAccounts.Create(AAbstractMem: TAbstractMem;
+  const AInitialZone: TAMZone; ADefaultElementsPerBlock: Integer;
+  AUseCache: Boolean);
+begin
+  inherited;
+  FAccountKeys := Nil;
+  FAccountsOrderedByUpdatedBlock := Nil;
+  FAccountsOrderedBySalePrice := Nil;
+end;
+
 class procedure TPCAbstractMemListAccounts.LoadAccountFromTBytes(
   const ABytes: TBytes; const AAccountKeys: TPCAbstractMemAccountKeys;
   var AItem: TAccount);
@@ -136,9 +149,10 @@ begin
     if LPrevious.updated_on_block_active_mode<>AItem.updated_on_block_active_mode then begin
       FAccountsOrderedByUpdatedBlock.Update(AItem.account,LPrevious.updated_on_block_active_mode,AItem.updated_on_block_active_mode);
     end;
-
+    FAccountsOrderedBySalePrice.UpdateAccountBySalePrice(AItem.account,LPrevious.accountInfo,AItem.accountInfo);
   end else begin
-    FAccountsOrderedByUpdatedBlock.Update(AItem.account,LPrevious.updated_on_block_active_mode,AItem.updated_on_block_active_mode);
+    FAccountsOrderedByUpdatedBlock.Update(AItem.account,0,AItem.updated_on_block_active_mode);
+    FAccountsOrderedBySalePrice.UpdateAccountBySalePrice(AItem.account,CT_AccountInfo_NUL,AItem.accountInfo);
   end;
 
   LStream := TMemoryStream.Create;

+ 81 - 1
src/core/UPCAccountsOrdenations.pas

@@ -62,9 +62,21 @@ type
     function Update(const AAccountNumber, AOldUpdatedBlock, ANewUpdatedBlock : Integer) : Boolean;
   End;
 
+  TAccountsOrderedBySalePrice = Class({$IFDEF USE_ABSTRACTMEM}TAbstractMemBTree{$ELSE}TMemoryBTree<Integer>{$ENDIF})
+  protected
+    FCallReturnAccount : TCallReturnAccount;
+    FSearching_AccountNumber : Integer;
+    FSearching_AccountInfo : TAccountInfo;
+    function DoCompareData(const ALeftData, ARightData: TAbstractMemPosition): Integer; override;
+  public
+    function NodeDataToString(const AData : TAbstractMemPosition) : String; override;
+    function UpdateAccountBySalePrice(const AAccountNumber : Integer; const AOldAccountInfo, ANewAccountInfo : TAccountInfo) : Boolean;
+    constructor Create({$IFDEF USE_ABSTRACTMEM}AAbstractMem : TAbstractMem; const AInitialZone: TAMZone; {$ENDIF}ACallReturnAccount : TCallReturnAccount);
+  End;
+
 implementation
 
-Uses UPCAbstractMemAccounts;
+Uses UPCAbstractMemAccounts, UAccounts;
 
 { TAccountsOrderedByUpdatedBlock }
 
@@ -162,4 +174,72 @@ begin
   end else Result := Format('(Pos:%d not found)',[AData]);
 end;
 
+{ TAccounstBySalePrice }
+
+constructor TAccountsOrderedBySalePrice.Create({$IFDEF USE_ABSTRACTMEM}AAbstractMem: TAbstractMem;
+  const AInitialZone: TAMZone; {$ENDIF}ACallReturnAccount: TCallReturnAccount);
+begin
+  {$IFDEF USE_ABSTRACTMEM}
+  inherited Create(AAbstractMem,AInitialZone,False,15);
+  {$ELSE}
+  inherited Create(Nil,False,15);
+  {$ENDIF}
+  FCallReturnAccount := ACallReturnAccount;
+  FSearching_AccountNumber := -1;
+  FSearching_AccountInfo.Clear;
+end;
+
+function TAccountsOrderedBySalePrice.DoCompareData(const ALeftData,
+  ARightData: TAbstractMemPosition): Integer;
+var LLeftAccount, LRightAccount : TAccount;
+  LopResult : Int64;
+begin
+  if (ALeftData = ARightData) then Exit(0);
+
+  FCallReturnAccount(ARightData,LRightAccount);
+  if ((FSearching_AccountNumber>=0) And (ALeftData=FSearching_AccountNumber)) then begin
+    LopResult := FSearching_AccountInfo.price - LRightAccount.accountInfo.price;
+  end else begin
+    FCallReturnAccount(ALeftData,LLeftAccount);
+    LopResult := LLeftAccount.accountInfo.price - LRightAccount.accountInfo.price;
+  end;
+  if LopResult<0 then Result := -1
+  else if LopResult>0 then Result := 1
+  else Result := ALeftData - ARightData;
+end;
+
+function TAccountsOrderedBySalePrice.NodeDataToString(
+  const AData: TAbstractMemPosition): String;
+var LAccount : TAccount;
+begin
+  if FCallReturnAccount(AData,LAccount) then begin
+    Result := Format('(Acc:%d price:%s)',[LAccount.account,TAccountComp.FormatMoney(LAccount.accountInfo.price)]);
+  end else Result := Format('(Pos:%d not found)',[AData]);
+end;
+
+function TAccountsOrderedBySalePrice.UpdateAccountBySalePrice(const AAccountNumber: Integer;
+  const AOldAccountInfo, ANewAccountInfo: TAccountInfo): Boolean;
+var Ldone : Boolean;
+begin
+  if (TAccountComp.IsAccountForSale(AOldAccountInfo)=TAccountComp.IsAccountForSale(ANewAccountInfo)) and
+    (AOldAccountInfo.price = ANewAccountInfo.price) then Exit(True); // No updates, no need to change
+  Lock;
+  Try
+    FSearching_AccountNumber := AAccountNumber;
+    FSearching_AccountInfo := AOldAccountInfo;
+    Ldone := Delete(AAccountNumber);
+    if (Ldone) and (Not TAccountComp.IsAccountForSale(AOldAccountInfo)) then raise EAbsctractMemAccounts.Create('ERROR DEV 20210126-1');
+    if (Not Ldone) and (TAccountComp.IsAccountForSale(AOldAccountInfo)) then raise EAbsctractMemAccounts.Create('ERROR DEV 20210126-2');
+    FSearching_AccountInfo := ANewAccountInfo;
+    if (TAccountComp.IsAccountForSale(ANewAccountInfo)) then begin
+      if Not Add(AAccountNumber) then raise EAbsctractMemAccounts.Create('ERROR DEV 20210126-3');
+    end;
+    FSearching_AccountNumber := -1;
+    FSearching_AccountInfo.Clear;
+  Finally
+    Unlock;
+  End;
+  Result := True;
+end;
+
 end.

+ 267 - 0
src/core/UPCRPCSend.pas

@@ -0,0 +1,267 @@
+unit UPCRPCSend;
+
+{ Copyright (c) 2021 by PascalCoin developers, orignal code by Albert Molina
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  This unit is a part of the PascalCoin Project, an infinitely scalable
+  cryptocurrency. Find us here:
+  Web: https://www.pascalcoin.org
+  Source: https://github.com/PascalCoin/PascalCoin
+
+  If you like it, consider a donation using Bitcoin:
+  16K3HCZRhFUtM8GdWRcfKeaa6KsuyxZaYk
+
+  THIS LICENSE HEADER MUST NOT BE REMOVED.
+}
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+interface
+
+{$I ./../config.inc}
+
+Uses classes, SysUtils,
+  UJSONFunctions, UAccounts, UBaseTypes, UOpTransaction, UConst,
+  {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
+  URPC, UCrypto, UWallet, UBlockChain, ULog, UPCOrderedLists, UPCDataTypes;
+
+
+Type
+  TRPCSend = Class
+  private
+  public
+    class function CreateOperationTransaction(const ARPCProcess : TRPCProcess;
+      ACurrent_protocol : Word; ASender, ATarget, ASender_last_n_operation : Cardinal; AAmount, AFee : UInt64;
+      Const ASenderAccounKey, ATargetAccountKey : TAccountKey; Const ARawPayload : TRawBytes;
+      Const APayload_method, AEncodePwd : String; var AErrorNum: Integer; var AErrorDesc: String) : TOpTransaction;
+    //
+    class function SendTo(const ASender : TRPCProcess; const AMethodName : String; AInputParams, AJSONResponse : TPCJSONObject; var AErrorNum : Integer; var AErrorDesc : String) : Boolean;
+    class function SignSendTo(const ASender : TRPCProcess; const AMethodName : String; AInputParams, AJSONResponse : TPCJSONObject; var AErrorNum : Integer; var AErrorDesc : String) : Boolean;
+  End;
+
+implementation
+
+{ TRPCFindAccounts }
+
+class function TRPCSend.CreateOperationTransaction(const ARPCProcess : TRPCProcess;
+  ACurrent_protocol: Word;
+  ASender, ATarget, ASender_last_n_operation: Cardinal; AAmount, AFee: UInt64;
+  const ASenderAccounKey, ATargetAccountKey: TAccountKey;
+  const ARawPayload: TRawBytes; const APayload_method,
+  AEncodePwd: String; var AErrorNum: Integer; var AErrorDesc: String): TOpTransaction;
+
+Var LOpPayload : TOperationPayload;
+  LPrivateKey : TECPrivateKey;
+Begin
+  Result := Nil;
+  if Not ARPCProcess.RPCServer.CheckAndGetPrivateKeyInWallet(ASenderAccounKey,LPrivateKey,AErrorNum,AErrorDesc) then Exit(Nil);
+  if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(ARawPayload,APayload_method,AEncodePwd,ASenderAccounKey,ATargetAccountKey,LOpPayload,AErrorNum,AErrorDesc) then Exit(Nil);
+  Result := TOpTransaction.CreateTransaction(ACurrent_protocol, ASender,ASender_last_n_operation+1, ATarget, LPrivateKey, AAmount, AFee, LOpPayload);
+  if Not Result.HasValidSignature then begin
+    FreeAndNil(Result);
+    AErrorNum:=CT_RPC_ErrNum_InternalError;
+    AErrorDesc:='Invalid signature';
+    exit;
+  end;
+end;
+
+class function TRPCSend.SendTo(const ASender: TRPCProcess;
+  const AMethodName: String; AInputParams, AJSONResponse: TPCJSONObject;
+  var AErrorNum: Integer; var AErrorDesc: String): Boolean;
+
+{ JSON-RPC "sendto"
+
+### sendto
+Executes a transaction operation from "sender" to "target"
+
+##### Params
+- `sender` : Integer - Sender account
+- `target` : Integer - Destination account
+- `amount` : PASCURRENCY - Coins to be transferred
+- `fee` : PASCURRENCY - Fee of the operation
+- `payload` : HEXASTRING - Payload "item" that will be included in this operation
+- `payload_method` : String - Encode type of the item payload
+  - `none` : Not encoded. Will be visible for everybody
+  - `dest` (default) : Using Public key of "target" account. Only "target" will be able to decrypt this payload
+  - `sender` : Using sender Public key. Only "sender" will be able to decrypt this payload
+  - `aes` : Encrypted data using `pwd` param
+- `pwd` : String - Used to encrypt payload with `aes` as a `payload_method`. If none equals to empty password
+
+##### Result
+If transaction is successfull will return a JSON Object in "[Operation Object]" format.
+Otherwise, will return a JSON-RPC error code with description
+
+}
+
+var LSender, LTarget : TAccount;
+ LAmount, LFee : UInt64;
+ LRawPayload : TRawBytes;
+ LPayload_method, LEncodePwd, LErrors : String;
+ LOpt : TOpTransaction;
+ LOpr : TOperationResume;
+begin
+  // Get Parameters
+  Result := False;
+
+  if (Not ASender.RPCServer.AllowUsePrivateKeys) then begin
+    // Protection when server is locked to avoid private keys call
+    AErrorNum := CT_RPC_ErrNum_NotAllowedCall;
+    Exit;
+  end;
+  If Not ASender.RPCServer.WalletKeys.IsValidPassword then begin
+    AErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
+    AErrorDesc := 'Wallet is password protected. Unlock first';
+    exit;
+  end;
+
+  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'sender',ASender.Node.Bank,LSender.account,AErrorDesc) then begin
+    AErrorNum := CT_RPC_ErrNum_InvalidAccount;
+    Exit;
+  end else LSender := ASender.Node.GetMempoolAccount(LSender.account);
+
+  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'target',ASender.Node.Bank,LTarget.account,AErrorDesc) then begin
+    AErrorNum := CT_RPC_ErrNum_InvalidAccount;
+    Exit;
+  end else LTarget := ASender.Node.GetMempoolAccount(LTarget.account);
+
+  LAmount := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('amount',0));
+  LFee := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('fee',0));
+  LRawPayload := TCrypto.HexaToRaw(AInputParams.AsString('payload',''));
+  LPayload_method := AInputParams.AsString('payload_method','dest');
+  LEncodePwd := AInputParams.AsString('pwd','');
+
+  ASender.Node.OperationSequenceLock.Acquire;  // Use lock to prevent N_Operation race-condition on concurrent sends
+  try
+    LOpt := CreateOperationTransaction(ASender, ASender.Node.Bank.SafeBox.CurrentProtocol,
+      LSender.account, LTarget.account, LSender.n_operation, LAmount, LFee,
+      LSender.accountInfo.accountKey, LTarget.accountInfo.accountKey,
+      LRawPayload, LPayload_method, LEncodePwd, AErrorNum, AErrorDesc);
+    if Assigned(LOpt) then
+    try
+      If not ASender.Node.AddOperation(Nil,LOpt,LErrors) then begin
+        AErrorDesc := 'Error adding operation: '+LErrors;
+        AErrorNum := CT_RPC_ErrNum_InvalidOperation;
+        Exit;
+      end;
+      TPCOperation.OperationToOperationResume(0,LOpt,False,LSender.account,LOpr);
+      TPascalCoinJSONComp.FillOperationObject(LOpr,ASender.Node.Bank.BlocksCount,AJSONResponse.GetAsObject('result'));
+      Result := true;
+    finally
+      LOpt.free;
+    end;
+  finally
+    ASender.Node.OperationSequenceLock.Release;
+  end;
+end;
+
+class function TRPCSend.SignSendTo(const ASender: TRPCProcess;
+  const AMethodName: String; AInputParams, AJSONResponse: TPCJSONObject;
+  var AErrorNum: Integer; var AErrorDesc: String): Boolean;
+
+{ JSON-RPC "signsendto"
+
+### signsendto
+
+Creates and signs a "Send to" operation without checking information and without transfering to the network.
+It's usefull for "cold wallets" that are off-line (not synchronized with the network) and only holds private keys
+
+##### Params
+- `rawoperations` : HEXASTRING (optional) - If we want to add a sign operation with other previous operations, here we must put previous `rawoperations` result
+- `sender` : Integer - Sender account
+- `target` : Integer - Target account
+- `sender_enc_pubkey` or `sender_b58_pubkey` : HEXASTRING - Public key (in encoded format or b58 format) of the sender account
+- `target_enc_pubkey` or `target_b58_pubkey` : HEXASTRING - Public key (in encoded format or b58 format) of the target account
+- `last_n_operation` : Last value of `n_operation` obtained with an [Account object](#account-object), for example when called to [getaccount](#getaccount)
+- `amount`,`fee`,`payload`,`payload_method`,`pwd` : Same values that calling [sendto](#sendto)
+
+##### Result
+
+Wallet must be unlocked and sender private key (searched with provided public key) must be in wallet.
+No other checks are made (no checks for valid target, valid n_operation, valid amount or fee ...)
+Returns a [Raw Operations Object](#raw-operations-object)
+
+}
+var LSender, LTarget : Cardinal;
+ LSenderPubKey, LTargetPubKey : TAccountKey;
+ LHexaStringOperationsHashTree, LErrors : String;
+ LProtocol : Integer;
+ LOperationsHashTree : TOperationsHashTree;
+ LOpt : TOpTransaction;
+ LOpr : TOperationResume;
+ LRawPayload : TRawBytes;
+ LPayload_method, LEncodePwd : String;
+ LAmount, LFee : UInt64;
+begin
+  Result := False;
+
+  if (Not ASender.RPCServer.AllowUsePrivateKeys) then begin
+    // Protection when server is locked to avoid private keys call
+    AErrorNum := CT_RPC_ErrNum_NotAllowedCall;
+    Exit;
+  end;
+  If Not ASender.RPCServer.WalletKeys.IsValidPassword then begin
+    AErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
+    AErrorDesc := 'Wallet is password protected. Unlock first';
+    exit;
+  end;
+  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'sender',Nil,LSender,AErrorDesc) then begin
+    AErrorNum := CT_RPC_ErrNum_InvalidAccount;
+    Exit;
+  end;
+  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'target',Nil,LTarget,AErrorDesc) then begin
+    AErrorNum := CT_RPC_ErrNum_InvalidAccount;
+    Exit;
+  end;
+  If Not TPascalCoinJSONComp.CapturePubKey(AInputParams,'sender_',LSenderPubKey,AErrorDesc) then begin
+    AErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+    exit;
+  end;
+  If Not TPascalCoinJSONComp.CapturePubKey(AInputParams,'target_',LTargetPubKey,AErrorDesc) then begin
+    AErrorNum := CT_RPC_ErrNum_InvalidPubKey;
+    exit;
+  end;
+
+  LAmount := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('amount',0));
+  LFee := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('fee',0));
+  LRawPayload := TCrypto.HexaToRaw(AInputParams.AsString('payload',''));
+  LPayload_method := AInputParams.AsString('payload_method','dest');
+  LEncodePwd := AInputParams.AsString('pwd','');
+
+  LHexaStringOperationsHashTree := AInputParams.AsString('rawoperations','');
+  LProtocol := AInputParams.AsCardinal('protocol',CT_BUILD_PROTOCOL);
+
+  if Not TPascalCoinJSONComp.HexaStringToOperationsHashTree(LHexaStringOperationsHashTree,LProtocol,LOperationsHashTree,LErrors) then begin
+    AErrorNum:=CT_RPC_ErrNum_InvalidData;
+    AErrorDesc:= 'Error decoding param "rawoperations": '+LErrors;
+    Exit;
+  end;
+  Try
+    LOpt := CreateOperationTransaction(ASender,LProtocol,LSender,LTarget,
+      AInputParams.AsCardinal('last_n_operation',0),
+      LAmount, LFee,
+      LSenderPubKey, LTargetPubKey,
+      LRawPayload,LPayload_method,LEncodePwd, AErrorNum, AErrorDesc);
+    if Assigned(LOpt) then
+    try
+      LOperationsHashTree.AddOperationToHashTree(LOpt);
+      TPascalCoinJSONComp.FillOperationsHashTreeObject(LOperationsHashTree,AJSONResponse.GetAsObject('result'));
+      Result := true;
+    finally
+      LOpt.Free;
+    end;
+  Finally
+    LOperationsHashTree.Free;
+  End;
+end;
+
+initialization
+  TRPCProcess.RegisterProcessMethod('signsendto',TRPCSend.SignSendTo);
+  TRPCProcess.RegisterProcessMethod('sendto',TRPCSend.SendTo);
+finalization
+  TRPCProcess.UnregisterProcessMethod('signsendto');
+  TRPCProcess.UnregisterProcessMethod('sendto');
+end.

+ 25 - 165
src/core/URPC.pas

@@ -72,6 +72,7 @@ Type
     class Function HexaStringToOperationsHashTree(Const AHexaStringOperationsHashTree : String; ACurrentProtocol : Word; out AOperationsHashTree : TOperationsHashTree; var AErrors : String) : Boolean;
     class Function CapturePubKey(const AInputParams : TPCJSONObject; const APrefix : String; var APubKey : TAccountKey; var AErrortxt : String) : Boolean;
     class function CheckAndGetEncodedRAWPayload(Const ARawPayload : TRawBytes; Const APayload_method, AEncodePwdForAES : String; const ASenderAccounKey, ATargetAccountKey : TAccountKey; out AOperationPayload : TOperationPayload; Var AErrorNum : Integer; Var AErrorDesc : String) : Boolean;
+    class Function CaptureAccountNumber(const AInputParams : TPCJSONObject; const AParamName : String; const ABank : TPCBank; var AAccountNumber : Cardinal; var AErrorParam : String) : Boolean;
   end;
 
   TRPCServerThread = Class;
@@ -155,6 +156,7 @@ implementation
 
 Uses  {$IFNDEF FPC}windows,{$ENDIF}
   SysUtils, Synautil,
+  UPCRPCSend,
   UPCRPCOpData, UPCRPCFindAccounts, UPCRPCFindBlocks, UPCRPCFileUtils;
 
 Type
@@ -345,6 +347,28 @@ Begin
   end;
 end;
 
+class function TPascalCoinJSONComp.CaptureAccountNumber(const AInputParams : TPCJSONObject;
+  const AParamName: String; const ABank : TPCBank; var AAccountNumber: Cardinal;
+  var AErrorParam: String): Boolean;
+var LParamValue : String;
+Begin
+  LParamValue := AInputParams.AsString(AParamName,'');
+  if Length(LParamValue)>0 then begin
+    Result := TAccountComp.AccountTxtNumberToAccountNumber(LParamValue,AAccountNumber);
+    if Not Result then begin
+      AErrorParam := Format('"%s" is no valid Account number for Param "%s"',[LParamValue,AParamName]);
+    end else if Assigned(ABank) then begin
+      if (AAccountNumber<0) or (AAccountNumber>=ABank.AccountsCount) then begin
+        Result := False;
+        AErrorParam := Format('Account %d does not exist in safebox (param "%s")',[AAccountNumber,AParamName]);
+      end;
+    end;
+  end else begin
+    Result := False;
+    AErrorParam := Format('Param "%s" not provided or null',[AParamName]);
+  end;
+end;
+
 class function TPascalCoinJSONComp.CapturePubKey(
   const AInputParams: TPCJSONObject; const APrefix: String;
   var APubKey: TAccountKey; var AErrortxt: String): Boolean;
@@ -1175,98 +1199,9 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
   Function CaptureAccountNumber(const AParamName : String; const ACheckAccountNumberExistsInSafebox : Boolean; var AAccountNumber : Cardinal; var AErrorParam : String) : Boolean;
   var LParamValue : String;
   Begin
-    LParamValue := params.AsString(AParamName,'');
-    if Length(LParamValue)>0 then begin
-      Result := TAccountComp.AccountTxtNumberToAccountNumber(LParamValue,AAccountNumber);
-      if Not Result then begin
-        AErrorParam := Format('"%s" is no valid Account number for Param "%s"',[LParamValue,AParamName]);
-      end else if (ACheckAccountNumberExistsInSafebox) then begin
-        if (AAccountNumber<0) or (AAccountNumber>=FNode.Bank.AccountsCount) then begin
-          Result := False;
-          AErrorParam := Format('Account %d does not exist in safebox (param "%s")',[AAccountNumber,AParamName]);
-        end;
-      end;
-    end else begin
-      Result := False;
-      AErrorParam := Format('Param "%s" not provided or null',[AParamName]);
-    end;
+    Result := TPascalCoinJSONComp.CaptureAccountNumber(params,AParamName,FNode.Bank,AAccountNumber,AErrorParam);
   End;
 
-  Function OpSendTo(sender, target : Cardinal; amount, fee : UInt64; Const RawPayload : TRawBytes; Const Payload_method, EncodePwd : String) : Boolean;
-  // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
-  Var opt : TOpTransaction;
-    sacc,tacc : TAccount;
-    errors : String;
-    opr : TOperationResume;
-  begin
-    FNode.OperationSequenceLock.Acquire;  // Use lock to prevent N_Operation race-condition on concurrent sends
-    try
-      Result := false;
-      if (sender<0) or (sender>=FNode.Bank.AccountsCount) then begin
-        If (sender=CT_MaxAccount) then ErrorDesc := 'Need sender'
-        else ErrorDesc:='Invalid sender account '+Inttostr(sender);
-        ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
-        Exit;
-      end;
-      if (target<0) or (target>=FNode.Bank.AccountsCount) then begin
-        If (target=CT_MaxAccount) then ErrorDesc := 'Need target'
-        else ErrorDesc:='Invalid target account '+Inttostr(target);
-        ErrorNum:=CT_RPC_ErrNum_InvalidAccount;
-        Exit;
-      end;
-      sacc := FNode.GetMempoolAccount(sender);
-      tacc := FNode.GetMempoolAccount(target);
-
-      opt := CreateOperationTransaction(FNode.Bank.SafeBox.CurrentProtocol,sender,target,sacc.n_operation,amount,fee,sacc.accountInfo.accountKey,tacc.accountInfo.accountKey,RawPayload,Payload_method,EncodePwd);
-      if opt=nil then exit;
-      try
-        If not FNode.AddOperation(Nil,opt,errors) then begin
-          ErrorDesc := 'Error adding operation: '+errors;
-          ErrorNum := CT_RPC_ErrNum_InvalidOperation;
-          Exit;
-        end;
-        TPCOperation.OperationToOperationResume(0,opt,False,sender,opr);
-        FillOperationResumeToJSONObject(opr,GetResultObject);
-        Result := true;
-      finally
-        opt.free;
-      end;
-    finally
-      FNode.OperationSequenceLock.Release;
-    end;
-  end;
-
-  Function SignOpSendTo(Const HexaStringOperationsHashTree : String; current_protocol : Word;
-    sender, target : Cardinal;
-    Const senderAccounKey, targetAccountKey : TAccountKey;
-    last_sender_n_operation : Cardinal;
-    amount, fee : UInt64; Const RawPayload : TRawBytes; Const Payload_method, EncodePwd : String) : Boolean;
-  // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
-  var OperationsHashTree : TOperationsHashTree;
-    errors : String;
-    opt : TOpTransaction;
-  begin
-    Result := false;
-    if Not TPascalCoinJSONComp.HexaStringToOperationsHashTree(HexaStringOperationsHashTree,current_protocol,OperationsHashTree,errors) then begin
-      ErrorNum:=CT_RPC_ErrNum_InvalidData;
-      ErrorDesc:= 'Error decoding param "rawoperations": '+errors;
-      Exit;
-    end;
-    Try
-      opt := CreateOperationTransaction(current_protocol, sender,target,last_sender_n_operation,amount,fee,senderAccounKey,targetAccountKey,RawPayload,Payload_method,EncodePwd);
-      if opt=nil then exit;
-      try
-        OperationsHashTree.AddOperationToHashTree(opt);
-        TPascalCoinJSONComp.FillOperationsHashTreeObject(OperationsHashTree,GetResultObject);
-        Result := true;
-      finally
-        opt.Free;
-      end;
-    Finally
-      OperationsHashTree.Free;
-    End;
-  end;
-
   // This function creates a TOpChangeKey without looking for private key of account
   // It assumes that account_signer,account_last_n_operation, account_target and account_pubkey are correct
   Function CreateOperationChangeKey(current_protocol : Word; account_signer, account_last_n_operation, account_target : Cardinal; const account_pubkey, new_pubkey : TAccountKey; fee : UInt64; RawPayload : TRawBytes; Const Payload_method, EncodePwd : String) : TOpChangeKey;
@@ -3227,81 +3162,6 @@ begin
     // Search for all operations signed by "account" and n_operation value between "n_operation_min" and "n_operation_max", start searching at "block" (0=all)
     // "block" = 0 search in all blocks, pending operations included
     Result := findNOperations;
-  end else if (method='sendto') then begin
-    // Sends "amount" coins from "sender" to "target" with "fee"
-    // If "payload" is present, it will be encoded using "payload_method"
-    // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
-    // Returns a JSON "Operation Resume format" object when successfull
-    // Note: "ophash" will contain block "0" = "pending block"
-    if (Not _RPCServer.AllowUsePrivateKeys) then begin
-      // Protection when server is locked to avoid private keys call
-      ErrorNum := CT_RPC_ErrNum_NotAllowedCall;
-      Exit;
-    end;
-    If Not _RPCServer.WalletKeys.IsValidPassword then begin
-      ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
-      ErrorDesc := 'Wallet is password protected. Unlock first';
-      exit;
-    end;
-    if Not CaptureAccountNumber('sender',True,c2,ErrorDesc) then begin
-      ErrorNum := CT_RPC_ErrNum_InvalidAccount;
-      Exit;
-    end;
-    if Not CaptureAccountNumber('target',True,c3,ErrorDesc) then begin
-      ErrorNum := CT_RPC_ErrNum_InvalidAccount;
-      Exit;
-    end;
-    Result := OpSendTo(c2,c3,
-       ToPascalCoins(params.AsDouble('amount',0)),
-       ToPascalCoins(params.AsDouble('fee',0)),
-       TCrypto.HexaToRaw(params.AsString('payload','')),
-       params.AsString('payload_method','dest'),params.AsString('pwd',''));
-  end else if (method='signsendto') then begin
-    // Create a Transaction operation and adds it into a "rawoperations" (that can include
-    // previous operations). This RPC method is usefull ffor cold storage, because doesn't
-    // need to check or verify accounts status/public key, assuming that passed values
-    // are ok.
-    // Signs a transaction of "amount" coins from "sender" to "target" with "fee", using "sender_enc_pubkey" or "sender_b58_pubkey"
-    // and "last_n_operation" of sender. Also, needs "target_enc_pubkey" or "target_b58_pubkey"
-    // If "payload" is present, it will be encoded using "payload_method"
-    // "payload_method" types: "none","dest"(default),"sender","aes"(must provide "pwd" param)
-    // Returns a JSON "Operations info" containing old "rawoperations" plus new Transaction
-    if (Not _RPCServer.AllowUsePrivateKeys) then begin
-      // Protection when server is locked to avoid private keys call
-      ErrorNum := CT_RPC_ErrNum_NotAllowedCall;
-      Exit;
-    end;
-    If Not _RPCServer.WalletKeys.IsValidPassword then begin
-      ErrorNum := CT_RPC_ErrNum_WalletPasswordProtected;
-      ErrorDesc := 'Wallet is password protected. Unlock first';
-      exit;
-    end;
-    if Not CaptureAccountNumber('sender',False,c2,ErrorDesc) then begin
-      ErrorNum := CT_RPC_ErrNum_InvalidAccount;
-      Exit;
-    end;
-    if Not CaptureAccountNumber('target',False,c3,ErrorDesc) then begin
-      ErrorNum := CT_RPC_ErrNum_InvalidAccount;
-      Exit;
-    end;
-    If Not CapturePubKey('sender_',senderpubkey,ErrorDesc) then begin
-      ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
-      exit;
-    end;
-    If Not CapturePubKey('target_',destpubkey,ErrorDesc) then begin
-      ErrorNum := CT_RPC_ErrNum_InvalidPubKey;
-      exit;
-    end;
-    Result := SignOpSendTo(
-       params.AsString('rawoperations',''),
-       params.AsCardinal('protocol',CT_BUILD_PROTOCOL),
-       c2,c3,
-       senderpubkey,destpubkey,
-       params.AsCardinal('last_n_operation',0),
-       ToPascalCoins(params.AsDouble('amount',0)),
-       ToPascalCoins(params.AsDouble('fee',0)),
-       TCrypto.HexaToRaw(params.AsString('payload','')),
-       params.AsString('payload_method','dest'),params.AsString('pwd',''));
   end else if (method='changekey') then begin
     // Change key of "account" to "new_enc_pubkey" or "new_b58_pubkey" (encoded public key format) with "fee"
     // If "payload" is present, it will be encoded using "payload_method"