Selaa lähdekoodia

New node network operations PIP-0013

-Pending operations: Similar implementation of PIP-0013
-Get account: Returns info of an account for third party apps native
communication with a node
PascalCoin 7 vuotta sitten
vanhempi
commit
efb638e29a
3 muutettua tiedostoa jossa 230 lisäystä ja 25 poistoa
  1. 5 2
      README.md
  2. 24 12
      src/core/UAccounts.pas
  3. 201 11
      src/core/UNetProtocol.pas

+ 5 - 2
README.md

@@ -38,8 +38,11 @@ Also, consider a donation at PascalCoin development account: "0-10"
 - TODO: Explore possible modification to sinoidial effect on blocks time (No PIP) 
 - TODO: PIP - 0010
 - TODO: Add new network operations
-  - Get account status
-  - Get penging pool
+- New Node network operations
+  - Get pending operations (code $0030)
+    - Implementation of the PIP-0013 (not exactly but with similar features)	
+  - Get account (code $0031)
+    - This call will allow a simple third party app communicate directly to a node to get account info (balance, n_operation, name, public key... )
 - MultiOperation: PIP-0017
   - Multioperation allows a transactional like operations, they can include transactions and change info operations in a signle multioperation
     - Allow to send coins from N accounts to M receivers in a transaction mixing, without knowledge of how many coins where sent from "Alice" to "Bob" if properly mixed

+ 24 - 12
src/core/UAccounts.pas

@@ -69,6 +69,18 @@ Type
     Class Function IsValidMinerBlockPayload(const newBlockPayload : TRawBytes) : Boolean;
   end;
 
+  TAccount = Record
+    account: Cardinal;        // FIXED value. Account number
+    accountInfo : TAccountInfo;
+    balance: UInt64;          // Balance, always >= 0
+    updated_block: Cardinal;  // Number of block where was updated
+    n_operation: Cardinal;    // count number of owner operations (when receive, this is not updated)
+    name : TRawBytes;         // Protocol 2. Unique name
+    account_type : Word;      // Protocol 2. Layer 2 use case
+    previous_updated_block : Cardinal; // New Build 1.0.8 -> Only used to store this info to storage. It helps App to search when an account was updated. NOT USED FOR HASH CALCULATIONS!
+  End;
+  PAccount = ^TAccount;
+
   { TAccountComp }
 
   TAccountComp = Class
@@ -99,6 +111,7 @@ Type
     Class Function AccountBlock(Const account_number : Cardinal) : Cardinal;
     Class Function AccountInfo2RawString(const AccountInfo : TAccountInfo) : TRawBytes; overload;
     Class procedure AccountInfo2RawString(const AccountInfo : TAccountInfo; var dest : TRawBytes); overload;
+    Class procedure SaveAccountToAStream(Stream: TStream; const Account : TAccount);
     Class Function RawString2AccountInfo(const rawaccstr: TRawBytes): TAccountInfo; overload;
     Class procedure RawString2AccountInfo(const rawaccstr: TRawBytes; var dest : TAccountInfo); overload;
     Class Function IsAccountLocked(const AccountInfo : TAccountInfo; blocks_count : Cardinal) : Boolean;
@@ -106,18 +119,6 @@ Type
     Class Function LoadTOperationBlockFromStream(const stream : TStream; var operationBlock:TOperationBlock) : Boolean;
   End;
 
-  TAccount = Record
-    account: Cardinal;        // FIXED value. Account number
-    accountInfo : TAccountInfo;
-    balance: UInt64;          // Balance, always >= 0
-    updated_block: Cardinal;  // Number of block where was updated
-    n_operation: Cardinal;    // count number of owner operations (when receive, this is not updated)
-    name : TRawBytes;         // Protocol 2. Unique name
-    account_type : Word;      // Protocol 2. Layer 2 use case
-    previous_updated_block : Cardinal; // New Build 1.0.8 -> Only used to store this info to storage. It helps App to search when an account was updated. NOT USED FOR HASH CALCULATIONS!
-  End;
-  PAccount = ^TAccount;
-
   {
     Protocol 2:
     Introducing OperationBlock info on the safebox, this will allow checkpointing a safebox because
@@ -761,6 +762,17 @@ begin
   end;
 end;
 
+class procedure TAccountComp.SaveAccountToAStream(Stream: TStream; const Account: TAccount);
+begin
+  Stream.Write(Account.account,Sizeof(Account.account));
+  TStreamOp.WriteAnsiString(Stream,AccountInfo2RawString(Account.accountInfo));
+  Stream.Write(Account.balance,Sizeof(Account.balance));
+  Stream.Write(Account.updated_block,Sizeof(Account.updated_block));
+  Stream.Write(Account.n_operation,Sizeof(Account.n_operation));
+  TStreamOp.WriteAnsiString(Stream,Account.name);
+  Stream.Write(Account.account_type,SizeOf(Account.account_type));
+end;
+
 class function TAccountComp.AccountKey2RawString(const account: TAccountKey): TRawBytes;
 begin
   AccountKey2RawString(account,Result);

+ 201 - 11
src/core/UNetProtocol.pas

@@ -35,14 +35,17 @@ Const
   CT_MagicResponse = $0002;
   CT_MagicAutoSend = $0003;
 
-  CT_NetOp_Hello = $0001;              // Sends my last operationblock + servers. Receive last operationblock + servers + same operationblock number of sender
-  CT_NetOp_Error = $0002;
-  CT_NetOp_Message = $0003;
-  CT_NetOp_GetBlocks = $0010;
-  CT_NetOp_GetOperationsBlock = $0005; // Sends from and to. Receive a number of OperationsBlock to check
-  CT_NetOp_NewBlock = $0011;
-  CT_NetOp_AddOperations = $0020;
-  CT_NetOp_GetSafeBox = $0021;         // V2 Protocol: Allows to send/receive Safebox in chunk parts
+  CT_NetOp_Hello                = $0001; // Sends my last operationblock + servers. Receive last operationblock + servers + same operationblock number of sender
+  CT_NetOp_Error                = $0002;
+  CT_NetOp_Message              = $0003;
+  CT_NetOp_GetBlockHeaders      = $0005; // Sends from and to. Receive a number of OperationsBlock to check
+  CT_NetOp_GetBlocks            = $0010;
+  CT_NetOp_NewBlock             = $0011;
+  CT_NetOp_AddOperations        = $0020;
+  CT_NetOp_GetSafeBox           = $0021; // V2 Protocol: Allows to send/receive Safebox in chunk parts
+
+  CT_NetOp_GetPendingOperations = $0030; // Obtain pending operations
+  CT_NetOp_GetAccount           = $0031; // Obtain account info
 
 
   CT_NetError_InvalidProtocolVersion = $0001;
@@ -358,6 +361,8 @@ Type
     Procedure DoProcess_NewBlock(HeaderData : TNetHeaderData; DataBuffer: TStream);
     Procedure DoProcess_AddOperations(HeaderData : TNetHeaderData; DataBuffer: TStream);
     Procedure DoProcess_GetSafeBox_Request(HeaderData : TNetHeaderData; DataBuffer: TStream);
+    Procedure DoProcess_GetPendingOperations_Request(HeaderData : TNetHeaderData; DataBuffer: TStream);
+    Procedure DoProcess_GetAccount_Request(HeaderData : TNetHeaderData; DataBuffer: TStream);
     Procedure SetClient(Const Value : TNetTcpIpClient);
     Function ReadTcpClientBuffer(MaxWaitMiliseconds : Cardinal; var HeaderData : TNetHeaderData; BufferData : TStream) : Boolean;
     Procedure DisconnectInvalidClient(ItsMyself : Boolean; Const why : AnsiString);
@@ -1399,7 +1404,7 @@ Const CT_LogSender = 'GetNewBlockChainFromClient';
     ReceiveData := TMemoryStream.Create;
     try
       if OnlyOperationBlock then begin
-        noperation := CT_NetOp_GetOperationsBlock;
+        noperation := CT_NetOp_GetBlockHeaders;
       end else begin
         noperation := CT_NetOp_GetBlocks;
       end;
@@ -1986,10 +1991,12 @@ begin
     CT_NetOp_Error : Result := 'ERROR';
     CT_NetOp_GetBlocks : Result := 'GET BLOCKS';
     CT_NetOp_Message : Result := 'MESSAGE';
-    CT_NetOp_GetOperationsBlock : Result := 'GET OPERATIONS BLOCK';
+    CT_NetOp_GetBlockHeaders : Result := 'GET BLOCK HEADERS';
     CT_NetOp_NewBlock : Result := 'NEW BLOCK';
     CT_NetOp_AddOperations : Result := 'ADD OPERATIONS';
     CT_NetOp_GetSafeBox : Result := 'GET SAFEBOX';
+    CT_NetOp_GetPendingOperations : Result := 'GET PENDING OPERATIONS';
+    CT_NetOp_GetAccount : Result := 'GET ACCOUNT';
   else Result := 'UNKNOWN OPERATION '+Inttohex(operation,4);
   end;
 end;
@@ -2692,6 +2699,179 @@ begin
   end;
 end;
 
+procedure TNetConnection.DoProcess_GetPendingOperations_Request(HeaderData: TNetHeaderData; DataBuffer: TStream);
+var responseStream : TMemoryStream;
+  i,start,max : Integer;
+  b : Byte;
+  c : Cardinal;
+  DoDisconnect : Boolean;
+  errors : AnsiString;
+  opht : TOperationsHashTree;
+begin
+  {
+  This call is used to obtain pending operations not included in blockchain
+  Request:
+  - Request type (1 byte) - Values
+    - Value 1:
+      Returns Count
+    - Value 2:
+      - start (4 bytes)
+      - max (4 bytes)
+      Returns Pending operations (from start to start+max) in a TOperationsHashTree Stream
+  }
+  errors := '';
+  DoDisconnect := true;
+  responseStream := TMemoryStream.Create;
+  try
+    if HeaderData.header_type<>ntp_request then begin
+      errors := 'Not request';
+      exit;
+    end;
+    DataBuffer.Read(b,1);
+    if (b=1) then begin
+      // Return count
+      c := TNode.Node.Operations.Count;
+      responseStream.Write(c,SizeOf(c));
+    end else if (b=2) then begin
+      // Return from start to start+max
+      DataBuffer.Read(c,SizeOf(c)); // Start 4 bytes
+      start:=c;
+      DataBuffer.Read(c,SizeOf(c)); // max 4 bytes
+      max:=c;
+      //
+      if (start<0) Or (max<0) then begin
+        errors := 'Invalid start/max value';
+        Exit;
+      end;
+      opht := TOperationsHashTree.Create;
+      Try
+        TNode.Node.Operations.Lock;
+        Try
+          if (start >= TNode.Node.Operations.Count) Or (max=0) then begin
+          end else begin
+            if (start + max >= TNode.Node.Operations.Count) then max := TNode.Node.Operations.Count - start;
+            for i:=start to (start + max -1) do begin
+              opht.AddOperationToHashTree(TNode.Node.Operations.OperationsHashTree.GetOperation(i));
+            end;
+          end;
+        finally
+          TNode.Node.Operations.Unlock;
+        end;
+        opht.SaveOperationsHashTreeToStream(responseStream,False);
+      Finally
+        opht.Free;
+      End;
+    end else begin
+      errors := 'Invalid call type '+inttostr(b);
+      Exit;
+    end;
+    DoDisconnect:=False;
+    Send(ntp_response,HeaderData.operation,0,HeaderData.request_id,responseStream);
+  finally
+    responseStream.Free;
+    if DoDisconnect then begin
+      DisconnectInvalidClient(false,errors+' > '+TNetData.HeaderDataToText(HeaderData)+' BuffSize: '+inttostr(DataBuffer.Size));
+    end;
+  end;
+end;
+
+procedure TNetConnection.DoProcess_GetAccount_Request(HeaderData: TNetHeaderData; DataBuffer: TStream);
+Const CT_Max_Accounts_per_call = 1000;
+var responseStream : TMemoryStream;
+  i,start,max : Integer;
+  b : Byte;
+  c : Cardinal;
+  acc : TAccount;
+  DoDisconnect : Boolean;
+  errors : AnsiString;
+begin
+  {
+  This call is used to obtain an Account data
+  Request:
+  Request type (1 byte) - Values
+    - Value 1: Single account
+    - Value 2: From account start to start+max  LIMITED AT MAX 1000
+    - Value 3: Multiple accounts LIMITED AT MAX 1000
+  On 1:
+    - account (4 bytes)
+  On 2:
+    - start (4 bytes)
+    - max (4 bytes)
+  On 3:
+    - count (4 bytes)
+    - for 1 to count read account (4 bytes)
+  Returns:
+  - count (4 bytes)
+  - for 1 to count:  TAccountComp.SaveAccountToAStream
+  }
+  errors := '';
+  DoDisconnect := true;
+  responseStream := TMemoryStream.Create;
+  try
+    if HeaderData.header_type<>ntp_request then begin
+      errors := 'Not request';
+      exit;
+    end;
+    if (DataBuffer.Size-DataBuffer.Position<5) then begin
+      errors := 'Invalid structure';
+      exit;
+    end;
+    DataBuffer.Read(b,1);
+    if (b in [1,2]) then begin
+      if (b=1) then begin
+        DataBuffer.Read(c,SizeOf(c));
+        start:=c;
+        max:=c;
+      end else begin
+        DataBuffer.Read(c,SizeOf(c));
+        start:=c;
+        DataBuffer.Read(c,SizeOf(c));
+        max:=c;
+      end;
+      If max>CT_Max_Accounts_per_call then max := CT_Max_Accounts_per_call;
+      if (start<0) Or (max<0) then begin
+        errors := 'Invalid start/max value';
+        Exit;
+      end;
+      if (start >= TNode.Node.Bank.AccountsCount) Or (max=0) then begin
+        c := 0;
+        responseStream.Write(c,SizeOf(c));
+      end else begin
+        if (start + max >= TNode.Node.Bank.AccountsCount) then max := TNode.Node.Bank.AccountsCount - start;
+        c := max;
+        responseStream.Write(c,SizeOf(c));
+        for i:=start to (start + max -1) do begin
+          acc := TNode.Node.Bank.SafeBox.Account(i);
+          TAccountComp.SaveAccountToAStream(responseStream,acc);
+        end;
+      end;
+    end else if (b=3) then begin
+      DataBuffer.Read(c,SizeOf(c));
+      responseStream.Write(c,SizeOf(c));
+      for i:=1 to b do begin
+        DataBuffer.Read(c,SizeOf(c));
+        if (c>=0) And (c<TNode.Node.Bank.AccountsCount) then begin
+          acc := TNode.Node.Bank.SafeBox.Account(c);
+          TAccountComp.SaveAccountToAStream(responseStream,acc);
+        end else begin
+          errors := 'Invalid account number '+Inttostr(c);
+          Exit;
+        end;
+      end;
+    end else begin
+      errors := 'Invalid call type '+inttostr(b);
+      Exit;
+    end;
+    DoDisconnect:=False;
+    Send(ntp_response,HeaderData.operation,0,HeaderData.request_id,responseStream);
+  finally
+    responseStream.Free;
+    if DoDisconnect then begin
+      DisconnectInvalidClient(false,errors+' > '+TNetData.HeaderDataToText(HeaderData)+' BuffSize: '+inttostr(DataBuffer.Size));
+    end;
+  end;
+end;
+
 procedure TNetConnection.DoProcess_Hello(HeaderData: TNetHeaderData; DataBuffer: TStream);
 var op, myLastOp : TPCOperationsComp;
     errors : AnsiString;
@@ -2993,7 +3173,7 @@ begin
                       DoProcess_GetBlocks_Response(HeaderData,ReceiveDataBuffer)
                     else DisconnectInvalidClient(false,'Not resquest or response: '+TNetData.HeaderDataToText(HeaderData));
                   End;
-                  CT_NetOp_GetOperationsBlock : Begin
+                  CT_NetOp_GetBlockHeaders : Begin
                     if HeaderData.header_type=ntp_request then
                       DoProcess_GetOperationsBlock_Request(HeaderData,ReceiveDataBuffer)
                     else TLog.NewLog(ltdebug,Classname,'Received old response of: '+TNetData.HeaderDataToText(HeaderData));
@@ -3008,6 +3188,16 @@ begin
                     if HeaderData.header_type=ntp_request then
                       DoProcess_GetSafeBox_Request(HeaderData,ReceiveDataBuffer)
                     else DisconnectInvalidClient(false,'Received '+TNetData.HeaderDataToText(HeaderData));
+                  end;
+                  CT_NetOp_GetPendingOperations : Begin
+                    if (HeaderData.header_type=ntp_request) then
+                      DoProcess_GetPendingOperations_Request(HeaderData,ReceiveDataBuffer)
+                    else TLog.NewLog(ltdebug,Classname,'Received old response of: '+TNetData.HeaderDataToText(HeaderData));
+                  end;
+                  CT_NetOp_GetAccount : Begin
+                    if (HeaderData.header_type=ntp_request) then
+                      DoProcess_GetAccount_Request(HeaderData,ReceiveDataBuffer)
+                    else TLog.NewLog(ltdebug,Classname,'Received old response of: '+TNetData.HeaderDataToText(HeaderData));
                   end
                 else
                   DisconnectInvalidClient(false,'Invalid operation: '+TNetData.HeaderDataToText(HeaderData));