Browse Source

PIP-0041: PayToKey implementation for sendto/signsendto RPC

Herman Schoenfeld 4 years ago
parent
commit
39d0f075f3

+ 0 - 68
src/core/UAccounts.pas

@@ -295,8 +295,6 @@ Type
     Function FindAccountByName(const aName : String) : Integer; overload;
     Function FindAccountByName(const aName : String) : Integer; overload;
     Function FindAccountByName(const aName : TRawBytes) : Integer; overload;
     Function FindAccountByName(const aName : TRawBytes) : Integer; overload;
     Function FindAccountsStartingByName(const AStartName : TRawBytes; const ARawList : TOrderedRawList; const AMax : Integer = 0) : Integer;
     Function FindAccountsStartingByName(const AStartName : TRawBytes; const ARawList : TOrderedRawList; const AMax : Integer = 0) : Integer;
-    Function TryResolveAccountByEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean): Boolean; overload;
-    Function TryResolveAccountByEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean; out AErrorMessage: String): Boolean; overload;
     
     
     Procedure Clear;
     Procedure Clear;
     Function Account(account_number : Cardinal) : TAccount;
     Function Account(account_number : Cardinal) : TAccount;
@@ -4702,72 +4700,6 @@ begin
   end;
   end;
 end;
 end;
 
 
-Function TPCSafeBox.TryResolveAccountByEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean): Boolean;
-var LErrMsg : String;
-begin
-  Result := TryResolveAccountByEPASA(AEPasa, AResolvedAccount, AResolvedKey, ARequiresPurchase, LErrMsg);
-end;
-
-Function TPCSafeBox.TryResolveAccountByEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean; out AErrorMessage: String): Boolean;
-var
-  LKey : TAccountKey;
-  LErrMsg : String;
-begin
-  if (AEPasa.IsPayToKey) then begin
-    // Parse account key in EPASA
-    if NOT TAccountComp.AccountPublicKeyImport(AEPasa.Payload, LKey, LErrMsg) then begin
-      AResolvedAccount := CT_AccountNo_NUL;
-      AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
-      ARequiresPurchase := False;
-      AErrorMessage := Format('Invalid key specified in PayToKey EPASA "%s". %s',[AEPasa.ToString(), LErrMsg]);
-      Exit(False);
-    end;
-    
-    // Try to find key in safebox 
-
-    // If key is found, then do not purchase new account and send to first account with key
-
-    // If no key found, find optimal public purchase account
-
-    // WIP (not implemented)
-    AResolvedAccount := CT_AccountNo_NUL;    
-    AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
-    ARequiresPurchase := False;
-    AErrorMessage := 'Not implemented';
-    Result := False;     
-  end else if (AEPasa.IsAddressedByName) then begin
-    // Find account by name
-
-    // WIP (not implemented)
-    AResolvedAccount := CT_AccountNo_NUL;    
-    AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
-    ARequiresPurchase := False;
-    AErrorMessage := 'Not implemented';
-    Result := False; 
-    
-  end else begin
-    // addressed by number
-    if NOT AEPasa.IsAddressedByNumber then
-      raise Exception.Create('Internal Error c8ecd69d-3621-4f5e-b4f1-9926ab2f5013');
-    if NOT AEPasa.Account.HasValue then raise Exception.Create('Internal Error 544c8cb9-b700-4b5f-93ca-4d045d0a06ae');
-
-    if (AEPasa.Account.Value < 0) or (AEPasa.Account.Value >= Self.AccountsCount) then begin
-      AResolvedAccount := CT_AccountNo_NUL;
-      AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
-      ARequiresPurchase := False;
-      AErrorMessage := Format('Account number %d does not exist in safebox',[AEPasa.Account.Value]);
-      Exit(False);
-    end;
-    
-    AResolvedAccount := AEPasa.Account.Value;
-    AResolvedKey := CT_Account_NUL.accountInfo.accountKey;      
-    ARequiresPurchase := False;    
-    Result := true;
-  end;
-
-
-end;
-
 procedure TPCSafeBox.SearchBlockWhenOnSeparatedChain(blockNumber: Cardinal; out blockAccount: TBlockAccount);
 procedure TPCSafeBox.SearchBlockWhenOnSeparatedChain(blockNumber: Cardinal; out blockAccount: TBlockAccount);
   Function WasUpdatedBeforeOrigin : Boolean;
   Function WasUpdatedBeforeOrigin : Boolean;
   var j, maxUB : Integer;
   var j, maxUB : Integer;

+ 0 - 1
src/core/UEPasa.pas

@@ -349,7 +349,6 @@ begin
   Result := PayloadType.HasTrait(ptPublic) OR PayloadType.HasTrait(ptRecipientKeyEncrypted) OR PayloadType.HasTrait(ptSenderKeyEncrypted);
   Result := PayloadType.HasTrait(ptPublic) OR PayloadType.HasTrait(ptRecipientKeyEncrypted) OR PayloadType.HasTrait(ptSenderKeyEncrypted);
 end;
 end;
 
 
-
 function TEPasa.GetRawPayloadBytes: TArray<Byte>;
 function TEPasa.GetRawPayloadBytes: TArray<Byte>;
 begin
 begin
   if (PayloadType.HasTrait(ptAsciiFormatted)) then
   if (PayloadType.HasTrait(ptAsciiFormatted)) then

+ 74 - 2
src/core/UNode.pas

@@ -36,7 +36,7 @@ interface
 uses
 uses
   Classes, SysUtils,
   Classes, SysUtils,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF}, UPCDataTypes,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF}, UPCDataTypes,
-  UBlockChain, UNetProtocol, UAccounts, UCrypto, UThread, SyncObjs, ULog, UBaseTypes, UPCOrderedLists;
+  UBlockChain, UNetProtocol, UAccounts, UCrypto, UEPasa, UThread, SyncObjs, ULog, UBaseTypes, UPCOrderedLists;
 
 
 {$I ./../config.inc}
 {$I ./../config.inc}
 
 
@@ -131,6 +131,9 @@ Type
     //
     //
     function TryFindAccountByKey(const APubKey : TAccountKey; out AAccountNumber : Cardinal) : Boolean;
     function TryFindAccountByKey(const APubKey : TAccountKey; out AAccountNumber : Cardinal) : Boolean;
     function TryFindPublicSaleAccount(AMaximumPrice : Int64; APreventRaceCondition : Boolean; out AAccountNumber : Cardinal) : Boolean;
     function TryFindPublicSaleAccount(AMaximumPrice : Int64; APreventRaceCondition : Boolean; out AAccountNumber : Cardinal) : Boolean;
+    Function TryResolveEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean): Boolean; overload;
+    Function TryResolveEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean; out AErrorMessage: String): Boolean; overload;
+
   End;
   End;
 
 
   TThreadSafeNodeNotifyEvent = Class(TPCThread)
   TThreadSafeNodeNotifyEvent = Class(TPCThread)
@@ -212,7 +215,7 @@ Type
 implementation
 implementation
 
 
 Uses UOpTransaction, UConst, UTime, UCommon, UPCOperationsSignatureValidator,
 Uses UOpTransaction, UConst, UTime, UCommon, UPCOperationsSignatureValidator,
-  UFolderHelper;
+  UFolderHelper, USettings;
 
 
 var _Node : TNode;
 var _Node : TNode;
   _PascalCoinDataFolder : String;
   _PascalCoinDataFolder : String;
@@ -840,6 +843,75 @@ begin
   end;
   end;
 end;
 end;
 
 
+Function TNode.TryResolveEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean): Boolean;
+var LErrMsg : String;
+begin
+  Result := TryResolveEPASA(AEPasa, AResolvedAccount, AResolvedKey, ARequiresPurchase, LErrMsg);
+end;
+
+Function TNode.TryResolveEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : boolean; out AErrorMessage: String): Boolean;
+var
+  LErrMsg : String;
+begin
+  if (AEPasa.IsPayToKey) then begin
+    // Parse account key in EPASA
+    if NOT TAccountComp.AccountPublicKeyImport(AEPasa.Payload, AResolvedKey, LErrMsg) then begin
+      AResolvedAccount := CT_AccountNo_NUL;
+      AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
+      ARequiresPurchase := False;
+      AErrorMessage := Format('Invalid key specified in PayToKey EPASA "%s". %s',[AEPasa.ToString(), LErrMsg]);
+      Exit(False);
+    end;
+
+    // Try to find key in safebox
+    if TryFindAccountByKey(AResolvedKey, AResolvedAccount) then begin
+      // Key already exists in SafeBox, so send to that account
+      ARequiresPurchase := False;
+      Exit(True);
+    end;
+
+    // If no key found, find optimal public purchase account
+    if TryFindPublicSaleAccount(TSettings.MaxPayToKeyPurchasePrice, True, AResolvedAccount) then begin
+      // Account needs to be purchased
+      ARequiresPurchase := True;
+      Exit(True);
+    end;
+
+    // Account could not be resolved
+    AResolvedAccount := CT_AccountNo_NUL;
+    AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
+    ARequiresPurchase := False;
+    AErrorMessage := 'No account could be resolved for pay to key EPASA';
+    Exit(False);
+
+  end else if (AEPasa.IsAddressedByName) then begin
+    // Find account by name
+    AResolvedAccount := Bank.SafeBox.FindAccountByName(AEPasa.AccountName);
+    if AResolvedAccount < 0 then begin
+      // No account with name found
+      AResolvedAccount := CT_AccountNo_NUL;
+      AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
+      ARequiresPurchase := False;
+      AErrorMessage := Format('No account with name "%s" was found', [AEPasa.AccountName]);
+      Exit(False);
+    end;
+    Exit(True);
+  end;
+  // addressed by number
+  if NOT AEPasa.IsAddressedByNumber then raise Exception.Create('Internal Error c8ecd69d-3621-4f5e-b4f1-9926ab2f5013');
+  if NOT AEPasa.Account.HasValue then raise Exception.Create('Internal Error 544c8cb9-b700-4b5f-93ca-4d045d0a06ae');
+  AResolvedAccount := AEPasa.Account.Value;
+  if (AResolvedAccount < 0) or (AResolvedAccount >= Self.Bank.AccountsCount) then begin
+    AResolvedAccount := CT_AccountNo_NUL;
+    AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
+    ARequiresPurchase := False;
+    AErrorMessage := Format('Account number %d does not exist in safebox',[AResolvedAccount]);
+    Exit(False);
+  end;
+  Result := true;
+end;
+
+
 function TNode.TryLockNode(MaxWaitMilliseconds: Cardinal): Boolean;
 function TNode.TryLockNode(MaxWaitMilliseconds: Cardinal): Boolean;
 begin
 begin
   Result := TPCThread.TryProtectEnterCriticalSection(Self,MaxWaitMilliseconds,FLockMempool);
   Result := TPCThread.TryProtectEnterCriticalSection(Self,MaxWaitMilliseconds,FLockMempool);

+ 3 - 1
src/core/UPCRPCOpData.pas

@@ -27,7 +27,7 @@ interface
 Uses classes, SysUtils,
 Uses classes, SysUtils,
   UJSONFunctions, UAccounts, UBaseTypes, UOpTransaction, UConst, UPCDataTypes,
   UJSONFunctions, UAccounts, UBaseTypes, UOpTransaction, UConst, UPCDataTypes,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
-  URPC, UCrypto, UWallet, UBlockChain, ULog;
+  URPC, UCrypto, UEPasa, UWallet, UBlockChain, ULog;
 
 
 
 
 Type
 Type
@@ -335,6 +335,7 @@ begin
 
 
   if not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
   if not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
     TCrypto.HexaToRaw(AInputParams.AsString('payload','')),
     TCrypto.HexaToRaw(AInputParams.AsString('payload','')),
+    [ptNonDeterministic],
     AInputParams.AsString('payload_method','none'),
     AInputParams.AsString('payload_method','none'),
     AInputParams.AsString('pwd',''),
     AInputParams.AsString('pwd',''),
     LSender.accountInfo.accountKey,
     LSender.accountInfo.accountKey,
@@ -409,6 +410,7 @@ begin
 
 
     if not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
     if not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
       TCrypto.HexaToRaw(AInputParams.AsString('payload','')),
       TCrypto.HexaToRaw(AInputParams.AsString('payload','')),
+      [ptNonDeterministic],
       AInputParams.AsString('payload_method','dest'),
       AInputParams.AsString('payload_method','dest'),
       AInputParams.AsString('pwd',''),
       AInputParams.AsString('pwd',''),
       LPayloadPubkey,
       LPayloadPubkey,

+ 104 - 35
src/core/UPCRPCSend.pas

@@ -27,40 +27,55 @@ interface
 Uses classes, SysUtils,
 Uses classes, SysUtils,
   UJSONFunctions, UAccounts, UBaseTypes, UOpTransaction, UConst,
   UJSONFunctions, UAccounts, UBaseTypes, UOpTransaction, UConst,
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
   {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF},
-  URPC, UCrypto, UWallet, UBlockChain, ULog, UPCOrderedLists, UPCDataTypes;
+  URPC, UCrypto, UWallet, UBlockChain, UEPasa, ULog, UPCOrderedLists, UPCDataTypes;
 
 
 
 
 Type
 Type
   TRPCSend = Class
   TRPCSend = Class
   private
   private
   public
   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 CreateOperationTransaction(const ARPCProcess : TRPCProcess; ACurrentProtocol : Word; ASender, ATarget : TAccount; AAmount, AFee : UInt64; const ARawPayload : TRawBytes; const APayloadMethod, AEncodePwd : String; const APayloadType : TPayloadType; var AErrorNum: Integer; var AErrorDesc: String) : TOpTransaction;
+    class function CreatePayToKeyTransaction(const ARPCProcess : TRPCProcess; ACurrentProtocol: Word; ASender, APurchaseAccount : TAccount; const ANewKey : TAccountKey; AAmount, AFee: UInt64; const ARawPayload: TRawBytes;  const APayloadMethod, AEncodePwd: String; const APayloadType : TPayloadType; 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 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;
     class function SignSendTo(const ASender : TRPCProcess; const AMethodName : String; AInputParams, AJSONResponse : TPCJSONObject; var AErrorNum : Integer; var AErrorDesc : String) : Boolean;
   End;
   End;
 
 
 implementation
 implementation
 
 
-{ TRPCFindAccounts }
+{ TRPCSend }
 
 
-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;
+class function TRPCSend.CreateOperationTransaction(const ARPCProcess : TRPCProcess; ACurrentProtocol : Word; ASender, ATarget : TAccount; AAmount, AFee : UInt64; const ARawPayload : TRawBytes; const APayloadMethod, AEncodePwd : String; const APayloadType : TPayloadType; var AErrorNum: Integer; var AErrorDesc: String): TOpTransaction;
+var
+  LOpPayload : TOperationPayload;
+  LPrivateKey : TECPrivateKey;
+Begin
+  Result := Nil;
+  if Not ARPCProcess.RPCServer.CheckAndGetPrivateKeyInWallet(ASender.accountInfo.accountKey, LPrivateKey, AErrorNum, AErrorDesc) then Exit(Nil);
+  if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(ARawPayload, APayloadType, APayloadMethod, AEncodePwd, ASender.accountInfo.accountKey, ATarget.accountInfo.accountKey, LOpPayload, AErrorNum, AErrorDesc) then Exit(Nil);
+  Result := TOpTransaction.CreateTransaction(ACurrentProtocol, ASender.account, ASender.n_operation+1, ATarget.account, LPrivateKey, AAmount, AFee, LOpPayload);
+  if Not Result.HasValidSignature then begin
+    FreeAndNil(Result);
+    AErrorNum:=CT_RPC_ErrNum_InternalError;
+    AErrorDesc:='Invalid signature';
+    exit;
+  end;
+end;
 
 
-Var LOpPayload : TOperationPayload;
+class function TRPCSend.CreatePayToKeyTransaction(const ARPCProcess : TRPCProcess; ACurrentProtocol: Word; ASender, APurchaseAccount : TAccount; const ANewKey : TAccountKey; AAmount, AFee: UInt64; const ARawPayload: TRawBytes;  const APayloadMethod, AEncodePwd: String; const APayloadType : TPayloadType; var AErrorNum: Integer; var AErrorDesc: String): TOpTransaction;
+Var
+  LOpPayload : TOperationPayload;
   LPrivateKey : TECPrivateKey;
   LPrivateKey : TECPrivateKey;
 Begin
 Begin
   Result := Nil;
   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 (AAmount < APurchaseAccount.accountInfo.price) then begin
+    AErrorNum := CT_RPC_ErrNum_InternalError;
+    AErrorDesc := 'Insufficient funds to purchase account for pay-to-key transaction';
+    Exit(Nil);
+  end;
+
+  if Not ARPCProcess.RPCServer.CheckAndGetPrivateKeyInWallet(ASender.accountInfo.accountKey, LPrivateKey, AErrorNum, AErrorDesc) then Exit(Nil);
+  if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(ARawPayload, APayloadType, APayloadMethod, AEncodePwd, ASender.accountInfo.accountKey, ANewKey, LOpPayload, AErrorNum, AErrorDesc) then Exit(Nil);
+  Result := TOpBuyAccount.CreateBuy(ACurrentProtocol, ASender.account, ASender.n_operation + 1, APurchaseAccount.account, APurchaseAccount.accountInfo.account_to_pay, APurchaseAccount.accountInfo.price, AAmount, AFee, ANewKey, LPrivateKey, LOpPayload);
   if Not Result.HasValidSignature then begin
   if Not Result.HasValidSignature then begin
     FreeAndNil(Result);
     FreeAndNil(Result);
     AErrorNum:=CT_RPC_ErrNum_InternalError;
     AErrorNum:=CT_RPC_ErrNum_InternalError;
@@ -97,7 +112,11 @@ Otherwise, will return a JSON-RPC error code with description
 
 
 }
 }
 
 
-var LSender, LTarget : TAccount;
+var
+ LSender, LTarget : TAccount;
+ LTargetEPASA : TEPasa;
+ LTargetKey : TAccountKey;
+ LTargetRequiresPurchase : Boolean;
  LAmount, LFee : UInt64;
  LAmount, LFee : UInt64;
  LRawPayload : TRawBytes;
  LRawPayload : TRawBytes;
  LPayload_method, LEncodePwd, LErrors : String;
  LPayload_method, LEncodePwd, LErrors : String;
@@ -118,28 +137,47 @@ begin
     exit;
     exit;
   end;
   end;
 
 
-  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'sender',ASender.Node.Bank,LSender.account,AErrorDesc) then begin
+  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'sender',ASender.Node,LSender.account,AErrorDesc) then begin
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     Exit;
     Exit;
   end else LSender := ASender.Node.GetMempoolAccount(LSender.account);
   end else LSender := ASender.Node.GetMempoolAccount(LSender.account);
 
 
-  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'target',ASender.Node.Bank,LTarget.account,AErrorDesc) then begin
+  if Not TPascalCoinJSONComp.CaptureEPASA(AInputParams,'target',ASender.Node, LTargetEPASA, LTarget.account, LTargetKey, LTargetRequiresPurchase, AErrorDesc) then begin
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     Exit;
     Exit;
   end else LTarget := ASender.Node.GetMempoolAccount(LTarget.account);
   end else LTarget := ASender.Node.GetMempoolAccount(LTarget.account);
 
 
+  if Not TPascalCoinJSONComp.OverridePayloadParams(AInputParams, LTargetEPASA) then begin
+    AErrorNum := CT_RPC_ErrNum_AmbiguousPayload;
+    Exit;
+  end;
+
   LAmount := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('amount',0));
   LAmount := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('amount',0));
   LFee := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('fee',0));
   LFee := TPascalCoinJSONComp.ToPascalCoins(AInputParams.AsDouble('fee',0));
   LRawPayload := TCrypto.HexaToRaw(AInputParams.AsString('payload',''));
   LRawPayload := TCrypto.HexaToRaw(AInputParams.AsString('payload',''));
   LPayload_method := AInputParams.AsString('payload_method','dest');
   LPayload_method := AInputParams.AsString('payload_method','dest');
   LEncodePwd := AInputParams.AsString('pwd','');
   LEncodePwd := AInputParams.AsString('pwd','');
 
 
+  // Do new operation
   ASender.Node.OperationSequenceLock.Acquire;  // Use lock to prevent N_Operation race-condition on concurrent sends
   ASender.Node.OperationSequenceLock.Acquire;  // Use lock to prevent N_Operation race-condition on concurrent sends
   try
   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);
+    // Create operation
+    if LTargetRequiresPurchase then begin
+      // Buy Account
+      LOpt := CreatePayToKeyTransaction(
+          ASender, ASender.Node.Bank.SafeBox.CurrentProtocol,
+          LSender, LTarget, LTargetKey, LAmount, LFee,
+          LRawPayload, LPayload_method, LEncodePwd, LTargetEPASA.PayloadType,
+          AErrorNum, AErrorDesc);
+    end else begin
+      // Transaction
+      LOpt := CreateOperationTransaction(
+        ASender, ASender.Node.Bank.SafeBox.CurrentProtocol,
+        LSender, LTarget, LAmount, LFee,
+        LRawPayload, LPayload_method, LEncodePwd, LTargetEPASA.PayloadType,
+        AErrorNum, AErrorDesc);
+    end;
+    // Execute operation
     if Assigned(LOpt) then
     if Assigned(LOpt) then
     try
     try
       If not ASender.Node.AddOperation(Nil,LOpt,LErrors) then begin
       If not ASender.Node.AddOperation(Nil,LOpt,LErrors) then begin
@@ -185,8 +223,11 @@ No other checks are made (no checks for valid target, valid n_operation, valid a
 Returns a [Raw Operations Object](#raw-operations-object)
 Returns a [Raw Operations Object](#raw-operations-object)
 
 
 }
 }
-var LSender, LTarget : Cardinal;
- LSenderPubKey, LTargetPubKey : TAccountKey;
+var
+ LSender, LTarget : TAccount;
+ LTargetEPASA : TEPasa;
+ LTargetKey : TAccountKey;
+ LTargetRequiresPurchase : Boolean;
  LHexaStringOperationsHashTree, LErrors : String;
  LHexaStringOperationsHashTree, LErrors : String;
  LProtocol : Integer;
  LProtocol : Integer;
  LOperationsHashTree : TOperationsHashTree;
  LOperationsHashTree : TOperationsHashTree;
@@ -208,19 +249,31 @@ begin
     AErrorDesc := 'Wallet is password protected. Unlock first';
     AErrorDesc := 'Wallet is password protected. Unlock first';
     exit;
     exit;
   end;
   end;
-  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'sender',Nil,LSender,AErrorDesc) then begin
+  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'sender',Nil,LSender.account,AErrorDesc) then begin
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     Exit;
     Exit;
   end;
   end;
-  if Not TPascalCoinJSONComp.CaptureAccountNumber(AInputParams,'target',Nil,LTarget,AErrorDesc) then begin
+  if Not TPascalCoinJSONComp.CaptureNOperation(AInputParams,'last_n_operation',Nil,LSender.n_operation,AErrorDesc) then begin
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     AErrorNum := CT_RPC_ErrNum_InvalidAccount;
     Exit;
     Exit;
   end;
   end;
-  If Not TPascalCoinJSONComp.CapturePubKey(AInputParams,'sender_',LSenderPubKey,AErrorDesc) then begin
+
+  if Not TPascalCoinJSONComp.CaptureEPASA(AInputParams,'target', nil, LTargetEPASA, LTarget.account, LTargetKey, LTargetRequiresPurchase, AErrorDesc) then begin
+    AErrorNum := CT_RPC_ErrNum_InvalidEPASA;
+    Exit;
+  end;
+
+  if Not TPascalCoinJSONComp.OverridePayloadParams(AInputParams, LTargetEPASA) then begin
+    AErrorNum := CT_RPC_ErrNum_AmbiguousPayload;
+    Exit;
+  end;
+
+  If Not TPascalCoinJSONComp.CapturePubKey(AInputParams,'sender_',LSender.accountInfo.accountKey,AErrorDesc) then begin
     AErrorNum := CT_RPC_ErrNum_InvalidPubKey;
     AErrorNum := CT_RPC_ErrNum_InvalidPubKey;
     exit;
     exit;
   end;
   end;
-  If Not TPascalCoinJSONComp.CapturePubKey(AInputParams,'target_',LTargetPubKey,AErrorDesc) then begin
+
+  If Not TPascalCoinJSONComp.CapturePubKey(AInputParams,'target_',LTarget.accountInfo.accountKey,AErrorDesc) then begin
     AErrorNum := CT_RPC_ErrNum_InvalidPubKey;
     AErrorNum := CT_RPC_ErrNum_InvalidPubKey;
     exit;
     exit;
   end;
   end;
@@ -239,12 +292,27 @@ begin
     AErrorDesc:= 'Error decoding param "rawoperations": '+LErrors;
     AErrorDesc:= 'Error decoding param "rawoperations": '+LErrors;
     Exit;
     Exit;
   end;
   end;
+
+
   Try
   Try
-    LOpt := CreateOperationTransaction(ASender,LProtocol,LSender,LTarget,
-      AInputParams.AsCardinal('last_n_operation',0),
-      LAmount, LFee,
-      LSenderPubKey, LTargetPubKey,
-      LRawPayload,LPayload_method,LEncodePwd, AErrorNum, AErrorDesc);
+    // Create operation
+    if LTargetRequiresPurchase then begin
+      // Buy Account
+      LOpt := CreatePayToKeyTransaction(
+          ASender, ASender.Node.Bank.SafeBox.CurrentProtocol,
+          LSender, LTarget, LTargetKey, LAmount, LFee,
+          LRawPayload, LPayload_method, LEncodePwd, LTargetEPASA.PayloadType,
+          AErrorNum, AErrorDesc);
+    end else begin
+      // Transaction
+      LOpt := CreateOperationTransaction(
+        ASender, ASender.Node.Bank.SafeBox.CurrentProtocol,
+        LSender, LTarget, LAmount, LFee,
+        LRawPayload, LPayload_method, LEncodePwd, LTargetEPASA.PayloadType,
+        AErrorNum, AErrorDesc);
+    end;
+
+    // Execute operation
     if Assigned(LOpt) then
     if Assigned(LOpt) then
     try
     try
       LOperationsHashTree.AddOperationToHashTree(LOpt);
       LOperationsHashTree.AddOperationToHashTree(LOpt);
@@ -253,6 +321,7 @@ begin
     finally
     finally
       LOpt.Free;
       LOpt.Free;
     end;
     end;
+
   Finally
   Finally
     LOperationsHashTree.Free;
     LOperationsHashTree.Free;
   End;
   End;

+ 85 - 21
src/core/URPC.pas

@@ -45,12 +45,15 @@ Const
   CT_RPC_ErrNum_InvalidOperation = 1004;
   CT_RPC_ErrNum_InvalidOperation = 1004;
   CT_RPC_ErrNum_InvalidPubKey = 1005;
   CT_RPC_ErrNum_InvalidPubKey = 1005;
   CT_RPC_ErrNum_InvalidAccountName = 1006;
   CT_RPC_ErrNum_InvalidAccountName = 1006;
+  CT_RPC_ErrNum_InvalidEPASA = 1007;
   CT_RPC_ErrNum_NotFound = 1010;
   CT_RPC_ErrNum_NotFound = 1010;
   CT_RPC_ErrNum_WalletPasswordProtected = 1015;
   CT_RPC_ErrNum_WalletPasswordProtected = 1015;
   CT_RPC_ErrNum_InvalidData = 1016;
   CT_RPC_ErrNum_InvalidData = 1016;
+  CT_RPC_ErrNum_AmbiguousPayload = 1017;
   CT_RPC_ErrNum_InvalidSignature = 1020;
   CT_RPC_ErrNum_InvalidSignature = 1020;
   CT_RPC_ErrNum_NotAllowedCall = 1021;
   CT_RPC_ErrNum_NotAllowedCall = 1021;
 
 
+
 Type
 Type
 
 
   { TRPCServer }
   { TRPCServer }
@@ -60,6 +63,7 @@ Type
   TPascalCoinJSONComp = Class
   TPascalCoinJSONComp = Class
   private
   private
     class function OperationsHashTreeToHexaString(Const OperationsHashTree : TOperationsHashTree) : String;
     class function OperationsHashTreeToHexaString(Const OperationsHashTree : TOperationsHashTree) : String;
+    class function TryResolveOfflineEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AErrorMessage: String): Boolean;
   public
   public
     class procedure FillAccountObject(Const account : TAccount; jsonObj : TPCJSONObject);
     class procedure FillAccountObject(Const account : TAccount; jsonObj : TPCJSONObject);
     class procedure FillBlockObject(nBlock : Cardinal; ANode : TNode; jsonObject: TPCJSONObject);
     class procedure FillBlockObject(nBlock : Cardinal; ANode : TNode; jsonObject: TPCJSONObject);
@@ -71,9 +75,11 @@ Type
     //
     //
     class Function HexaStringToOperationsHashTree(Const AHexaStringOperationsHashTree : String; ACurrentProtocol : Word; out AOperationsHashTree : TOperationsHashTree; var AErrors : String) : Boolean;
     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 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; out AResolvedAccount: Cardinal; var AErrorParam : String) : Boolean;
-    class Function CaptureEPASA(const AInputParams : TPCJSONObject; const AParamName : String; const ABank : TPCBank; out AEPasa: TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : Boolean; var AErrorParam : String) : Boolean;
+    class function CheckAndGetEncodedRAWPayload(Const ARawPayload : TRawBytes; const APayloadType : TPayloadType; Const APayload_method, AEncodePwdForAES : String; const ASenderAccounKey, ATargetAccountKey : TAccountKey; out AOperationPayload : TOperationPayload; Var AErrorNum : Integer; Var AErrorDesc : String) : Boolean;
+    class Function CaptureNOperation(const AInputParams : TPCJSONObject; const AParamName : String; const ANode : TNode; out ALastNOp: Cardinal; var AErrorParam : String) : Boolean;
+    class Function CaptureAccountNumber(const AInputParams : TPCJSONObject; const AParamName : String; const ANode : TNode; out AResolvedAccount: Cardinal; var AErrorParam : String) : Boolean;
+    class Function CaptureEPASA(const AInputParams : TPCJSONObject; const AParamName : String; const ANode : TNode; out AEPasa: TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : Boolean; var AErrorParam : String) : Boolean;
+    class Function OverridePayloadParams(const AInputParams : TPCJSONObject; const AEPASA : TEPasa) : Boolean;
   end;
   end;
 
 
   TRPCServerThread = Class;
   TRPCServerThread = Class;
@@ -349,7 +355,19 @@ Begin
   end;
   end;
 end;
 end;
 
 
-class Function TPascalCoinJSONComp.CaptureAccountNumber(const AInputParams : TPCJSONObject; const AParamName : String; const ABank : TPCBank; out AResolvedAccount: Cardinal; var AErrorParam : String) : Boolean;
+class Function TPascalCoinJSONComp.CaptureNOperation(const AInputParams : TPCJSONObject; const AParamName : String; const ANode : TNode; out ALastNOp: Cardinal; var AErrorParam : String) : Boolean;
+var
+  LParamValue : String;
+begin
+  if NOT AInputParams.HasName(AParamName) then begin
+    AErrorParam := Format('Missing n-operation value for Param "%s"',[AParamName]);
+    Exit(False);
+  end;
+  // TODO: add type checking?
+  ALastNOp := AInputParams.AsCardinal(AParamName,0);
+end;
+
+class Function TPascalCoinJSONComp.CaptureAccountNumber(const AInputParams : TPCJSONObject; const AParamName : String; const ANode : TNode; out AResolvedAccount: Cardinal; var AErrorParam : String) : Boolean;
 var
 var
   LEPasa : TEPasa;
   LEPasa : TEPasa;
   LKey : TAccountKey;
   LKey : TAccountKey;
@@ -357,17 +375,17 @@ var
   LParamValue : String;
   LParamValue : String;
 begin
 begin
   LParamValue := AInputParams.AsString(AParamName,'');
   LParamValue := AInputParams.AsString(AParamName,'');
-  Result := CaptureEPASA(AInputParams, AParamName, ABank, LEPasa, AResolvedAccount, LKey, LPurchase, AErrorParam);
+  Result := CaptureEPASA(AInputParams, AParamName, ANode, LEPasa, AResolvedAccount, LKey, LPurchase, AErrorParam);
   if Result AND (NOT LEPasa.IsStandard) then begin
   if Result AND (NOT LEPasa.IsStandard) then begin
       AErrorParam := Format('"%s" is not valid Account Number for Param "%s"',[LParamValue,AParamName]);
       AErrorParam := Format('"%s" is not valid Account Number for Param "%s"',[LParamValue,AParamName]);
       Exit(False);
       Exit(False);
   end;
   end;
 end;
 end;
 
 
-class function TPascalCoinJSONComp.CaptureEPASA(const AInputParams : TPCJSONObject; const AParamName : String; const ABank : TPCBank; out AEPasa: TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : Boolean; var AErrorParam : String): Boolean;
+class function TPascalCoinJSONComp.CaptureEPASA(const AInputParams : TPCJSONObject; const AParamName : String; const ANode : TNode; out AEPasa: TEPasa; out AResolvedAccount: Cardinal; out AResolvedKey : TAccountKey; out ARequiresPurchase : Boolean; var AErrorParam : String): Boolean;
 var LParamValue : String;
 var LParamValue : String;
 Begin
 Begin
-  if not Assigned(ABank) then raise EArgumentNilException.Create('ABank');
+
   LParamValue := AInputParams.AsString(AParamName,'');
   LParamValue := AInputParams.AsString(AParamName,'');
   if Length(LParamValue)>0 then begin
   if Length(LParamValue)>0 then begin
     if Not TEPasa.TryParse(LParamValue, AEPasa) then begin
     if Not TEPasa.TryParse(LParamValue, AEPasa) then begin
@@ -377,7 +395,14 @@ Begin
       AErrorParam := Format('"%s" is not valid Account EPASA for Param "%s"',[LParamValue,AParamName]);
       AErrorParam := Format('"%s" is not valid Account EPASA for Param "%s"',[LParamValue,AParamName]);
       Exit(False);
       Exit(False);
     end;
     end;
-    Result := ABank.SafeBox.TryResolveAccountByEPASA(AEPasa, AResolvedAccount, AResolvedKey, ARequiresPurchase, AErrorParam);
+    if Assigned(ANode) then begin
+      Result := ANode.TryResolveEPASA(AEPasa, AResolvedAccount, AResolvedKey, ARequiresPurchase, AErrorParam);
+    end else begin
+      // Offline EPASA
+      Result := TryResolveOfflineEPASA(AEPasa, AResolvedAccount, AErrorParam);
+      AResolvedKey := CT_Account_NUL.accountInfo.accountKey;
+      ARequiresPurchase := False;
+    end;
   end else begin
   end else begin
     AEPasa := TEPasa.Empty;
     AEPasa := TEPasa.Empty;
     AResolvedAccount := CT_AccountNo_NUL;
     AResolvedAccount := CT_AccountNo_NUL;
@@ -387,6 +412,28 @@ Begin
   end;
   end;
 end;
 end;
 
 
+class function TPascalCoinJSONComp.OverridePayloadParams(const AInputParams : TPCJSONObject; const AEPASA : TEPasa) : Boolean;
+begin
+   // none, dest, sender, aes, payload, pwd
+   if (NOT AEPASA.IsStandard) AND (AInputParams.HasValue('payload') OR AInputParams.HasValue('payload_method') OR AInputParams.HasValue('pwd')) then
+     Exit(False);
+
+   if AEPASA.PayloadType.HasTrait(ptPublic) then begin
+     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('none'));
+     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes().ToHexaString()));
+   end else if AEPASA.PayloadType.HasTrait(ptSenderKeyEncrypted) then begin
+     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('sender'));
+     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes().ToHexaString()));
+   end else if AEPASA.PayloadType.HasTrait(ptRecipientKeyEncrypted) then begin
+     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('dest'));
+     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes().ToHexaString()));
+   end else if AEPASA.PayloadType.HasTrait(ptPasswordEncrypted) then begin
+     AInputParams.SetAs('payload_method', TPCJSONVariantValue.CreateFromVariant('aes'));
+     AInputParams.SetAs('payload', TPCJSONVariantValue.CreateFromVariant(AEPASA.GetRawPayloadBytes().ToHexaString()));
+     AInputParams.SetAs('pwd', TPCJSONVariantValue.CreateFromVariant(AEPASA.Password));
+   end;
+end;
+
 class function TPascalCoinJSONComp.CapturePubKey(
 class function TPascalCoinJSONComp.CapturePubKey(
   const AInputParams: TPCJSONObject; const APrefix: String;
   const AInputParams: TPCJSONObject; const APrefix: String;
   var APubKey: TAccountKey; var AErrortxt: String): Boolean;
   var APubKey: TAccountKey; var AErrortxt: String): Boolean;
@@ -424,7 +471,7 @@ end;
 
 
 class function TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
 class function TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
   const ARawPayload: TRawBytes;
   const ARawPayload: TRawBytes;
-//  const APayloadType : TPayloadType;
+  const APayloadType : TPayloadType;
   const APayload_method, AEncodePwdForAES: String;
   const APayload_method, AEncodePwdForAES: String;
   const ASenderAccounKey, ATargetAccountKey: TAccountKey;
   const ASenderAccounKey, ATargetAccountKey: TAccountKey;
   out AOperationPayload : TOperationPayload;
   out AOperationPayload : TOperationPayload;
@@ -432,11 +479,7 @@ class function TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(
   var AErrorDesc: String): Boolean;
   var AErrorDesc: String): Boolean;
 begin
 begin
   AOperationPayload := CT_TOperationPayload_NUL;
   AOperationPayload := CT_TOperationPayload_NUL;
-   // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-  // TODO:
-  // Needs to assign AOperationPayload.payload_type based on PIP-0027
-  // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-  //AOperationPayload.payload_type := APayloadType.ProtocolValue;
+  AOperationPayload.payload_type := APayloadType.ProtocolValue;
   if (Length(ARawPayload)>0) then begin
   if (Length(ARawPayload)>0) then begin
     if (APayload_method='none') then begin
     if (APayload_method='none') then begin
       AOperationPayload.payload_raw:=ARawPayload;
       AOperationPayload.payload_raw:=ARawPayload;
@@ -643,6 +686,27 @@ Begin
   End;
   End;
 end;
 end;
 
 
+
+class function TPascalCoinJSONComp.TryResolveOfflineEPASA(const AEPasa : TEPasa; out AResolvedAccount: Cardinal; out AErrorMessage: String): Boolean;
+begin
+  if (AEPasa.IsPayToKey) then begin
+    // PayToKey not supported in offline signing
+    AResolvedAccount := CT_AccountNo_NUL;
+    AErrorMessage := 'PayToKey not supported in offline signing';
+    Exit(False);
+ end else if (AEPasa.IsAddressedByName) then begin
+    // PayToKey not supported in offline signing
+    AResolvedAccount := CT_AccountNo_NUL;
+    AErrorMessage := 'Addressed-by-name EPASA not supported in offline signing';
+    Exit(False);
+ end;
+  // addressed by number
+  if NOT AEPasa.IsAddressedByNumber then raise Exception.Create('Internal Error 0293f104-fce6-46a5-853f-e91fb501b452');
+  if NOT AEPasa.Account.HasValue then raise Exception.Create('Internal Error b569cd90-8dd7-4fac-95c4-6508179dac03');
+  AResolvedAccount := AEPasa.Account.Value;
+  Result := true;
+end;
+
 class function TPascalCoinJSONComp.ToPascalCoins(jsonCurr: Real): Int64;
 class function TPascalCoinJSONComp.ToPascalCoins(jsonCurr: Real): Int64;
 begin
 begin
   Result := Round(jsonCurr * 10000);
   Result := Round(jsonCurr * 10000);
@@ -1208,7 +1272,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
   Begin
   Begin
     Result := Nil;
     Result := Nil;
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(senderAccounKey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(senderAccounKey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
-    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,Payload_method,EncodePwd,senderAccounKey,targetAccountKey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
+    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,[ptNonDeterministic],Payload_method,EncodePwd,senderAccounKey,targetAccountKey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
     Result := TOpTransaction.CreateTransaction(current_protocol, sender,sender_last_n_operation+1,target,privateKey,amount,fee,LOpPayload);
     Result := TOpTransaction.CreateTransaction(current_protocol, sender,sender_last_n_operation+1,target,privateKey,amount,fee,LOpPayload);
     if Not Result.HasValidSignature then begin
     if Not Result.HasValidSignature then begin
       FreeAndNil(Result);
       FreeAndNil(Result);
@@ -1221,7 +1285,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
   Function CaptureAccountNumber(const AParamName : String; const ACheckAccountNumberExistsInSafebox : Boolean; var AAccountNumber : Cardinal; var AErrorParam : String) : Boolean;
   Function CaptureAccountNumber(const AParamName : String; const ACheckAccountNumberExistsInSafebox : Boolean; var AAccountNumber : Cardinal; var AErrorParam : String) : Boolean;
   var LParamValue : String;
   var LParamValue : String;
   Begin
   Begin
-    Result := TPascalCoinJSONComp.CaptureAccountNumber(params,AParamName,FNode.Bank,AAccountNumber,AErrorParam);
+    Result := TPascalCoinJSONComp.CaptureAccountNumber(params,AParamName,FNode,AAccountNumber,AErrorParam);
   End;
   End;
 
 
   // This function creates a TOpChangeKey without looking for private key of account
   // This function creates a TOpChangeKey without looking for private key of account
@@ -1236,7 +1300,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := Nil;
     Result := Nil;
     LOpPayload := CT_TOperationPayload_NUL;
     LOpPayload := CT_TOperationPayload_NUL;
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(account_pubkey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(account_pubkey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
-    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,Payload_method,EncodePwd,account_pubkey,new_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
+    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,[ptNonDeterministic],Payload_method,EncodePwd,account_pubkey,new_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
     If account_signer=account_target then begin
     If account_signer=account_target then begin
       Result := TOpChangeKey.Create(current_protocol,account_signer,account_last_n_operation+1,account_target,privateKey,new_pubkey,fee,LOpPayload);
       Result := TOpChangeKey.Create(current_protocol,account_signer,account_last_n_operation+1,account_target,privateKey,new_pubkey,fee,LOpPayload);
     end else begin
     end else begin
@@ -1303,7 +1367,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         // If using 'dest', only will apply if there is a fixed new public key, otherwise will use current public key of account
         // If using 'dest', only will apply if there is a fixed new public key, otherwise will use current public key of account
        aux_target_pubkey := new_account_pubkey;
        aux_target_pubkey := new_account_pubkey;
     end else aux_target_pubkey := account_signer_pubkey;
     end else aux_target_pubkey := account_signer_pubkey;
-    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,Payload_method,EncodePwd,account_signer_pubkey,aux_target_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
+    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,[ptNonDeterministic],Payload_method,EncodePwd,account_signer_pubkey,aux_target_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
     Result := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(
     Result := TOpListAccountForSaleOrSwap.CreateListAccountForSaleOrSwap(
       current_protocol,
       current_protocol,
       ANewAccountState,
       ANewAccountState,
@@ -1338,7 +1402,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
     Result := Nil;
     Result := Nil;
     LOpPayload := CT_TOperationPayload_NUL;
     LOpPayload := CT_TOperationPayload_NUL;
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(account_signer_pubkey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(account_signer_pubkey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
-    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,Payload_method,EncodePwd,account_signer_pubkey,account_signer_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
+    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload, [ptNonDeterministic],Payload_method,EncodePwd,account_signer_pubkey,account_signer_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
     Result := TOpDelistAccountForSale.CreateDelistAccountForSale(current_protocol,account_signer,account_last_n_operation+1,account_delisted,fee,privateKey,LOpPayload);
     Result := TOpDelistAccountForSale.CreateDelistAccountForSale(current_protocol,account_signer,account_last_n_operation+1,account_delisted,fee,privateKey,LOpPayload);
     if Not Result.HasValidSignature then begin
     if Not Result.HasValidSignature then begin
       FreeAndNil(Result);
       FreeAndNil(Result);
@@ -1361,7 +1425,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
   Begin
   Begin
     Result := Nil;
     Result := Nil;
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(account_pubkey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
     if Not RPCServer.CheckAndGetPrivateKeyInWallet(account_pubkey,privateKey,ErrorNum,ErrorDesc) then Exit(Nil);
-    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,Payload_method,EncodePwd,account_pubkey,new_account_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
+    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,[ptNonDeterministic],Payload_method,EncodePwd,account_pubkey,new_account_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
     Result := TOpBuyAccount.CreateBuy(current_protocol,account_number,account_last_n_operation+1,account_to_buy,account_to_pay,account_price,amount,fee,new_account_pubkey,privateKey,LOpPayload);
     Result := TOpBuyAccount.CreateBuy(current_protocol,account_number,account_last_n_operation+1,account_to_buy,account_to_pay,account_price,amount,fee,new_account_pubkey,privateKey,LOpPayload);
     if Not Result.HasValidSignature then begin
     if Not Result.HasValidSignature then begin
       FreeAndNil(Result);
       FreeAndNil(Result);
@@ -1866,7 +1930,7 @@ function TRPCProcess.ProcessMethod(const method: String; params: TPCJSONObject;
         // If using 'dest', only will apply if there is a fixed new public key, otherwise will use current public key of account
         // If using 'dest', only will apply if there is a fixed new public key, otherwise will use current public key of account
        aux_target_pubkey := new_account_pubkey;
        aux_target_pubkey := new_account_pubkey;
     end else aux_target_pubkey := account_signer_pubkey;
     end else aux_target_pubkey := account_signer_pubkey;
-    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,Payload_method,EncodePwd,account_signer_pubkey,aux_target_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
+    if Not TPascalCoinJSONComp.CheckAndGetEncodedRAWPayload(RawPayload,[ptNonDeterministic],Payload_method,EncodePwd,account_signer_pubkey,aux_target_pubkey,LOpPayload,ErrorNum,ErrorDesc) then Exit(Nil);
     Result := TOpChangeAccountInfo.CreateChangeAccountInfo(current_protocol,
     Result := TOpChangeAccountInfo.CreateChangeAccountInfo(current_protocol,
       account_signer,account_last_n_operation+1,account_target,
       account_signer,account_last_n_operation+1,account_target,
       privateKey,
       privateKey,

+ 17 - 0
src/core/USettings.pas

@@ -39,6 +39,7 @@ const
   CT_PARAM_SaveDebugLogs = 'SaveDebugLogs';
   CT_PARAM_SaveDebugLogs = 'SaveDebugLogs';
   CT_PARAM_ShowLogs = 'ShowLogs';
   CT_PARAM_ShowLogs = 'ShowLogs';
   CT_PARAM_MinerName = 'MinerName';
   CT_PARAM_MinerName = 'MinerName';
+  CT_PARAM_MaxPayToKeyPurchasePrice = 'MaxPayToKeyPurchasePrice';
   CT_PARAM_RunCount = 'RunCount';
   CT_PARAM_RunCount = 'RunCount';
   CT_PARAM_FirstTime = 'FirstTime';
   CT_PARAM_FirstTime = 'FirstTime';
   CT_PARAM_ShowModalMessages = 'ShowModalMessages';
   CT_PARAM_ShowModalMessages = 'ShowModalMessages';
@@ -95,6 +96,8 @@ type
       class procedure SetMinerName(AName: string); static;
       class procedure SetMinerName(AName: string); static;
       class function GetRunCount : Integer; static;
       class function GetRunCount : Integer; static;
       class procedure SetRunCount(AInt: Integer); static;
       class procedure SetRunCount(AInt: Integer); static;
+      class function GetMaxPayToKeyPurchasePrice : UInt64; static;
+      class procedure SetMaxPayToKeyPurchasePrice(AVal: UInt64); static;
       class function GetShowModalMessages : boolean; static;
       class function GetShowModalMessages : boolean; static;
       class procedure SetShowModalMessages(ABool: boolean); static;
       class procedure SetShowModalMessages(ABool: boolean); static;
       class function GetRpcAllowedIPs : string; static;
       class function GetRpcAllowedIPs : string; static;
@@ -122,6 +125,7 @@ type
       class property SaveDebugLogs : boolean read GetSaveDebugLogs write SetSaveDebugLogs;
       class property SaveDebugLogs : boolean read GetSaveDebugLogs write SetSaveDebugLogs;
       class property MinerName : string read GetMinerName write SetMinerName;
       class property MinerName : string read GetMinerName write SetMinerName;
       class property RunCount : Integer read GetRunCount write SetRunCount;
       class property RunCount : Integer read GetRunCount write SetRunCount;
+      class property MaxPayToKeyPurchasePrice : UInt64 read GetMaxPayToKeyPurchasePrice write SetMaxPayToKeyPurchasePrice;
       class property ShowModalMessages : boolean read GetShowModalMessages write SetShowModalMessages;
       class property ShowModalMessages : boolean read GetShowModalMessages write SetShowModalMessages;
       class property PeerCache : string read GetPeerCache write SetPeerCache;
       class property PeerCache : string read GetPeerCache write SetPeerCache;
       class property TryConnectOnlyWithThisFixedServers : string read GetTryConnectOnlyWithThisFixedServers write SetTryConnectOnlyWithThisFixedServers;
       class property TryConnectOnlyWithThisFixedServers : string read GetTryConnectOnlyWithThisFixedServers write SetTryConnectOnlyWithThisFixedServers;
@@ -329,6 +333,19 @@ begin
   FAppParams.ParamByName[CT_PARAM_RunCount].SetAsInteger(AInt)
   FAppParams.ParamByName[CT_PARAM_RunCount].SetAsInteger(AInt)
 end;
 end;
 
 
+
+class function TSettings.GetMaxPayToKeyPurchasePrice : UInt64;
+begin
+  CheckLoaded;
+  Result := FAppParams.ParamByName[CT_PARAM_MaxPayToKeyPurchasePrice].GetAsUInt64(0);
+end;
+
+class procedure TSettings.SetMaxPayToKeyPurchasePrice(AVal: UInt64);
+begin
+  CheckLoaded;
+  FAppParams.ParamByName[CT_PARAM_MaxPayToKeyPurchasePrice].SetAsUInt64(AVal);
+end;
+
 class function TSettings.GetShowModalMessages : boolean;
 class function TSettings.GetShowModalMessages : boolean;
 begin
 begin
   CheckLoaded;
   CheckLoaded;

+ 21 - 0
src/libraries/pascalcoin/UAppParams.pas

@@ -57,6 +57,7 @@ Type
     Procedure SetAsCardinal(CardValue : Cardinal);
     Procedure SetAsCardinal(CardValue : Cardinal);
     Procedure SetAsString(StringValue : String);
     Procedure SetAsString(StringValue : String);
     Procedure SetAsInt64(Int64Value : Int64);
     Procedure SetAsInt64(Int64Value : Int64);
+    Procedure SetAsUInt64(UInt64Value : UInt64);
     Procedure SetAsBoolean(BoolValue : Boolean);
     Procedure SetAsBoolean(BoolValue : Boolean);
     Procedure SetAsStream(Stream : TStream);
     Procedure SetAsStream(Stream : TStream);
     Procedure SetAsTBytes(Bytes : TBytes);
     Procedure SetAsTBytes(Bytes : TBytes);
@@ -65,6 +66,7 @@ Type
     function GetAsBoolean(Const DefValue : Boolean): Boolean;
     function GetAsBoolean(Const DefValue : Boolean): Boolean;
     function GetAsInteger(Const DefValue : Integer): Integer;
     function GetAsInteger(Const DefValue : Integer): Integer;
     function GetAsInt64(Const DefValue : Int64): Int64;
     function GetAsInt64(Const DefValue : Int64): Int64;
+    function GetAsUInt64(Const DefValue : UInt64): UInt64;
     function GetAsStream(Stream : TStream) : Integer;
     function GetAsStream(Stream : TStream) : Integer;
     function GetAsTBytes(Const DefValue : TBytes) : TBytes;
     function GetAsTBytes(Const DefValue : TBytes) : TBytes;
   End;
   End;
@@ -186,6 +188,18 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TAppParam.GetAsUInt64(const DefValue: UInt64): UInt64;
+begin
+  if IsNull then Result := DefValue
+  else begin
+    Try
+      Result := FValue;
+    Except
+      Result := DefValue;
+    End;
+  end;
+end;
+
 function TAppParam.GetAsInteger(const DefValue: Integer): Integer;
 function TAppParam.GetAsInteger(const DefValue: Integer): Integer;
 begin
 begin
   if IsNull then Result := DefValue
   if IsNull then Result := DefValue
@@ -357,6 +371,13 @@ begin
   If Assigned(FAppParams) then FAppParams.Save;
   If Assigned(FAppParams) then FAppParams.Save;
 end;
 end;
 
 
+procedure TAppParam.SetAsUInt64(UInt64Value: UInt64);
+begin
+  FParamType := ptInt64;
+  FValue := UInt64Value;
+  If Assigned(FAppParams) then FAppParams.Save;
+end;
+
 procedure TAppParam.SetAsInteger(IntValue: Integer);
 procedure TAppParam.SetAsInteger(IntValue: Integer);
 begin
 begin
   FParamType := ptInteger;
   FParamType := ptInteger;

+ 20 - 0
src/libraries/pascalcoin/UJSONFunctions.pas

@@ -76,6 +76,7 @@ Type
     Function ToJSONFormatted(pretty:Boolean;const prefix : String) : String; override;
     Function ToJSONFormatted(pretty:Boolean;const prefix : String) : String; override;
   public
   public
     Constructor Create; override;
     Constructor Create; override;
+    Constructor CreateFromVariant(const Value: Variant);
     Constructor CreateFromJSONValue(JSONValue : TJSONValue);
     Constructor CreateFromJSONValue(JSONValue : TJSONValue);
     Property Value : Variant read FValue write SetValue;
     Property Value : Variant read FValue write SetValue;
     Function AsString(DefValue : String) : String;
     Function AsString(DefValue : String) : String;
@@ -158,6 +159,8 @@ Type
     Destructor Destroy; override;
     Destructor Destroy; override;
     Function FindName(Name : String) : TPCJSONNameValue;
     Function FindName(Name : String) : TPCJSONNameValue;
     Function IndexOfName(Name : String) : Integer;
     Function IndexOfName(Name : String) : Integer;
+    Function HasName(Name: String): Boolean;
+    Function HasValue(const AParamName : String) : Boolean;
     Procedure DeleteName(Name : String);
     Procedure DeleteName(Name : String);
     Function GetAsVariant(Name : String) : TPCJSONVariantValue;
     Function GetAsVariant(Name : String) : TPCJSONVariantValue;
     Function GetAsObject(Name : String) : TPCJSONObject;
     Function GetAsObject(Name : String) : TPCJSONObject;
@@ -496,6 +499,12 @@ begin
   FWritable := False;
   FWritable := False;
 end;
 end;
 
 
+Constructor TPCJSONVariantValue.CreateFromVariant(const Value: Variant);
+begin
+  Create;
+  SetValue(Value);
+end;
+
 constructor TPCJSONVariantValue.CreateFromJSONValue(JSONValue: TJSONValue);
 constructor TPCJSONVariantValue.CreateFromJSONValue(JSONValue: TJSONValue);
 {$IFnDEF FPC}
 {$IFnDEF FPC}
 Var d : Double;
 Var d : Double;
@@ -878,6 +887,17 @@ begin
   Result := -1;
   Result := -1;
 end;
 end;
 
 
+function TPCJSONObject.HasName(Name: String): Boolean;
+begin
+  Result := IndexOfName(Name) >= 0;
+end;
+
+Function TPCJSONObject.HasValue(const AParamName : String) : Boolean;
+begin
+  Result := HasName(AParamName) AND (NOT AsString(AParamName, String.Empty).IsEmpty);
+end;
+
+
 function TPCJSONObject.LoadAsStream(ParamName: String; Stream: TStream): Integer;
 function TPCJSONObject.LoadAsStream(ParamName: String; Stream: TStream): Integer;
 Var s : RawByteString;
 Var s : RawByteString;
 begin
 begin