Browse Source

Limit 0-fee operation by signer per block

PascalCoin 6 years ago
parent
commit
3c09702fb9
2 changed files with 86 additions and 19 deletions
  1. 1 0
      README.md
  2. 85 19
      src/core/UBlockChain.pas

+ 1 - 0
README.md

@@ -39,6 +39,7 @@ TODO: Bug in Lazarus optimization cause Access Violation on "getpendings" call (
 - Implementation of Hard fork on block XXXXXXX (PENDING... TODO !)
 - Implementation of Hard fork on block XXXXXXX (PENDING... TODO !)
   - PIP - 0015: Fast Block Propagation
   - PIP - 0015: Fast Block Propagation
   - PIP - 0016: Layer-2 protocol support
   - PIP - 0016: Layer-2 protocol support
+  - Limit blockchain to allow max only one 0-fee operation by signer per block (prior was limited by network, not by core)
   - New digest hash for signature verifications
   - New digest hash for signature verifications
   - Added OrderedAccountKeysList that allows an indexed search of public keys in the safebox with mem optimization
   - Added OrderedAccountKeysList that allows an indexed search of public keys in the safebox with mem optimization
 - JSON-RPC changes:
 - JSON-RPC changes:

+ 85 - 19
src/core/UBlockChain.pas

@@ -268,15 +268,17 @@ Type
     FOnChanged: TNotifyEvent;
     FOnChanged: TNotifyEvent;
     FTotalAmount : Int64;
     FTotalAmount : Int64;
     FTotalFee : Int64;
     FTotalFee : Int64;
-    Procedure InternalAddOperationToHashTree(list : TList; op : TPCOperation; CalcNewHashTree : Boolean);
+    FMax0feeOperationsBySigner : Integer;
+    function InternalAddOperationToHashTree(list : TList; op : TPCOperation; CalcNewHashTree : Boolean) : Boolean;
     Function FindOrderedByOpReference(lockedThreadList : TList; const Value: TOpReference; var Index: Integer): Boolean;
     Function FindOrderedByOpReference(lockedThreadList : TList; const Value: TOpReference; var Index: Integer): Boolean;
     Function FindOrderedBySha(lockedThreadList : TList; const Value: TRawBytes; var Index: Integer): Boolean;
     Function FindOrderedBySha(lockedThreadList : TList; const Value: TRawBytes; var Index: Integer): Boolean;
     Function FindOrderedByAccountData(lockedThreadList : TList; const account_number : Cardinal; var Index: Integer): Boolean;
     Function FindOrderedByAccountData(lockedThreadList : TList; const account_number : Cardinal; var Index: Integer): Boolean;
     function GetHashTree: TRawBytes;
     function GetHashTree: TRawBytes;
+    procedure SetMax0feeOperationsBySigner(const Value: Integer);
   public
   public
     Constructor Create;
     Constructor Create;
     Destructor Destroy; Override;
     Destructor Destroy; Override;
-    Procedure AddOperationToHashTree(op : TPCOperation);
+    function AddOperationToHashTree(op : TPCOperation) : Boolean;
     Procedure ClearHastThree;
     Procedure ClearHastThree;
     Property HashTree : TRawBytes read GetHashTree;
     Property HashTree : TRawBytes read GetHashTree;
     Function OperationsCount : Integer;
     Function OperationsCount : Integer;
@@ -293,6 +295,7 @@ Type
     function IndexOfOpReference(const opReference : TOpReference) : Integer;
     function IndexOfOpReference(const opReference : TOpReference) : Integer;
     procedure RemoveByOpReference(const opReference : TOpReference);
     procedure RemoveByOpReference(const opReference : TOpReference);
     Property OnChanged : TNotifyEvent read FOnChanged write FOnChanged;
     Property OnChanged : TNotifyEvent read FOnChanged write FOnChanged;
+    Property Max0feeOperationsBySigner : Integer Read FMax0feeOperationsBySigner write SetMax0feeOperationsBySigner;
   End;
   End;
 
 
   { TPCOperationsComp }
   { TPCOperationsComp }
@@ -886,6 +889,8 @@ var
   _OperationsClass: Array of TPCOperationClass;
   _OperationsClass: Array of TPCOperationClass;
 
 
 function TPCOperationsComp.AddOperation(Execute: Boolean; op: TPCOperation; var errors: AnsiString): Boolean;
 function TPCOperationsComp.AddOperation(Execute: Boolean; op: TPCOperation; var errors: AnsiString): Boolean;
+var i : Integer;
+  auxs : AnsiString;
 Begin
 Begin
   Lock;
   Lock;
   Try
   Try
@@ -904,15 +909,27 @@ Begin
       Result := op.DoOperation(FPreviousUpdatedBlocks, FSafeBoxTransaction, errors);
       Result := op.DoOperation(FPreviousUpdatedBlocks, FSafeBoxTransaction, errors);
     end else Result := true;
     end else Result := true;
     if Result then begin
     if Result then begin
-      if FIsOnlyOperationBlock then begin
-        // Clear fee values and put to False
-        FIsOnlyOperationBlock := False;
-        FOperationBlock.fee := 0;
+      if FOperationsHashTree.AddOperationToHashTree(op) then begin
+        if FIsOnlyOperationBlock then begin
+          // Clear fee values and put to False
+          FIsOnlyOperationBlock := False;
+          FOperationBlock.fee := op.OperationFee;
+        end else begin
+          FOperationBlock.fee := FOperationBlock.fee + op.OperationFee;
+        end;
+        FOperationBlock.operations_hash := FOperationsHashTree.HashTree;
+        if FDisableds<=0 then Calc_Digest_Parts;
+      end else begin
+        errors := 'Cannot add operation. Limits reached';
+        if (Execute) then begin
+          // Undo execute
+          TLog.NewLog(lterror,ClassName,Format('Undo operation.DoExecute due limits reached. Executing %d operations',[FOperationsHashTree.OperationsCount]));
+          FPreviousUpdatedBlocks.Clear;
+          FSafeBoxTransaction.Rollback;
+          for i := 0 to FOperationsHashTree.OperationsCount-1 do FOperationsHashTree.GetOperation(i).DoOperation(FPreviousUpdatedBlocks, FSafeBoxTransaction, auxs);
+        end;
+        Result := False;
       end;
       end;
-      FOperationsHashTree.AddOperationToHashTree(op);
-      FOperationBlock.fee := FOperationBlock.fee + op.OperationFee;
-      FOperationBlock.operations_hash := FOperationsHashTree.HashTree;
-      if FDisableds<=0 then Calc_Digest_Parts;
     end;
     end;
   finally
   finally
     Unlock;
     Unlock;
@@ -1004,6 +1021,9 @@ begin
         resetNewTarget := True; // RandomHash algo will reset new target on V4
         resetNewTarget := True; // RandomHash algo will reset new target on V4
         {$ENDIF}
         {$ENDIF}
       end;
       end;
+      if (FOperationBlock.protocol_version>=CT_PROTOCOL_4) then begin
+        FOperationsHashTree.Max0feeOperationsBySigner := 1; // Limit to 1 0-fee operation by signer
+      end;
       FOperationBlock.block := FBank.BlocksCount;
       FOperationBlock.block := FBank.BlocksCount;
       FOperationBlock.reward := TPascalCoinProtocol.GetRewardForNewLine(FBank.BlocksCount);
       FOperationBlock.reward := TPascalCoinProtocol.GetRewardForNewLine(FBank.BlocksCount);
       if (resetNewTarget) then begin
       if (resetNewTarget) then begin
@@ -1283,6 +1303,9 @@ begin
     // Fee will be calculated for each operation. Set it to 0 and check later for integrity
     // Fee will be calculated for each operation. Set it to 0 and check later for integrity
     lastfee := OperationBlock.fee;
     lastfee := OperationBlock.fee;
     FOperationBlock.fee := 0;
     FOperationBlock.fee := 0;
+    if FOperationBlock.protocol_version>=CT_PROTOCOL_4 then begin
+      FOperationsHashTree.Max0feeOperationsBySigner := 1;
+    end;
     Result := FOperationsHashTree.LoadOperationsHashTreeFromStream(Stream,LoadingFromStorage,load_protocol_version,FPreviousUpdatedBlocks,errors);
     Result := FOperationsHashTree.LoadOperationsHashTreeFromStream(Stream,LoadingFromStorage,load_protocol_version,FPreviousUpdatedBlocks,errors);
     if not Result then begin
     if not Result then begin
       exit;
       exit;
@@ -1350,9 +1373,9 @@ procedure TPCOperationsComp.SanitizeOperations;
     Finally calculates new operation pow
     Finally calculates new operation pow
     It's used when a new account has beed found by other chanels (miners o nodes...)
     It's used when a new account has beed found by other chanels (miners o nodes...)
     }
     }
-Var i,n,lastn : Integer;
+Var i,n,lastn, iUndo : Integer;
   op : TPCOperation;
   op : TPCOperation;
-  errors : AnsiString;
+  errors, auxs : AnsiString;
   aux,aux2 : TOperationsHashTree;
   aux,aux2 : TOperationsHashTree;
   resetNewTarget : Boolean;
   resetNewTarget : Boolean;
 begin
 begin
@@ -1376,6 +1399,7 @@ begin
         {$ENDIF}
         {$ENDIF}
       end;
       end;
       FOperationBlock.block := FBank.BlocksCount;
       FOperationBlock.block := FBank.BlocksCount;
+
       FOperationBlock.reward := TPascalCoinProtocol.GetRewardForNewLine(FBank.BlocksCount);
       FOperationBlock.reward := TPascalCoinProtocol.GetRewardForNewLine(FBank.BlocksCount);
       if (resetNewTarget) then begin
       if (resetNewTarget) then begin
         FOperationBlock.compact_target := TPascalCoinProtocol.MinimumTarget(FOperationBlock.protocol_version);
         FOperationBlock.compact_target := TPascalCoinProtocol.MinimumTarget(FOperationBlock.protocol_version);
@@ -1401,14 +1425,23 @@ begin
     FPreviousUpdatedBlocks.Clear;
     FPreviousUpdatedBlocks.Clear;
     aux := TOperationsHashTree.Create;
     aux := TOperationsHashTree.Create;
     Try
     Try
+      if (FOperationBlock.protocol_version>=CT_PROTOCOL_4) then begin
+        aux.Max0feeOperationsBySigner := 1;
+      end;
       lastn := FOperationsHashTree.OperationsCount;
       lastn := FOperationsHashTree.OperationsCount;
       for i:=0 to lastn-1 do begin
       for i:=0 to lastn-1 do begin
         op := FOperationsHashTree.GetOperation(i);
         op := FOperationsHashTree.GetOperation(i);
         if (op.DoOperation(FPreviousUpdatedBlocks, SafeBoxTransaction,errors)) then begin
         if (op.DoOperation(FPreviousUpdatedBlocks, SafeBoxTransaction,errors)) then begin
-          inc(n);
-          aux.AddOperationToHashTree(op);
-          inc(FOperationBlock.fee,op.OperationFee);
-          {$IFDEF HIGHLOG}TLog.NewLog(ltdebug,Classname,'Sanitizing (pos:'+inttostr(i+1)+'/'+inttostr(lastn)+'): '+op.ToString){$ENDIF};
+          if aux.AddOperationToHashTree(op) then begin
+            inc(n);
+            inc(FOperationBlock.fee,op.OperationFee);
+            {$IFDEF HIGHLOG}TLog.NewLog(ltdebug,Classname,'Sanitizing (pos:'+inttostr(i+1)+'/'+inttostr(lastn)+'): '+op.ToString){$ENDIF};
+          end else begin
+            TLog.NewLog(lterror,ClassName,Format('Undo operation.DoExecute at Sanitize due limits reached. Executing %d operations',[FOperationsHashTree.OperationsCount]));
+            FPreviousUpdatedBlocks.Clear;
+            FSafeBoxTransaction.Rollback;
+            for iUndo := 0 to FOperationsHashTree.OperationsCount-1 do FOperationsHashTree.GetOperation(iUndo).DoOperation(FPreviousUpdatedBlocks, FSafeBoxTransaction, auxs);
+          end;
         end;
         end;
       end;
       end;
     Finally
     Finally
@@ -1736,12 +1769,12 @@ Type TOperationHashTreeReg = Record
      end;
      end;
      POperationsHashAccountsData = ^TOperationsHashAccountsData;
      POperationsHashAccountsData = ^TOperationsHashAccountsData;
 
 
-procedure TOperationsHashTree.AddOperationToHashTree(op: TPCOperation);
+function TOperationsHashTree.AddOperationToHashTree(op: TPCOperation) : Boolean;
 Var l : TList;
 Var l : TList;
 begin
 begin
   l := FHashTreeOperations.LockList;
   l := FHashTreeOperations.LockList;
   try
   try
-    InternalAddOperationToHashTree(l,op,True);
+    Result := InternalAddOperationToHashTree(l,op,True);
   finally
   finally
     FHashTreeOperations.UnlockList;
     FHashTreeOperations.UnlockList;
   end;
   end;
@@ -1795,6 +1828,7 @@ begin
     FOnChanged := Nil;
     FOnChanged := Nil;
     try
     try
       ClearHastThree;
       ClearHastThree;
+      FMax0feeOperationsBySigner := Sender.Max0feeOperationsBySigner;
       lsender := Sender.FHashTreeOperations.LockList;
       lsender := Sender.FHashTreeOperations.LockList;
       try
       try
         for i := 0 to lsender.Count - 1 do begin
         for i := 0 to lsender.Count - 1 do begin
@@ -1825,6 +1859,7 @@ begin
   FTotalAmount := 0;
   FTotalAmount := 0;
   FTotalFee := 0;
   FTotalFee := 0;
   FHashTree := '';
   FHashTree := '';
+  FMax0feeOperationsBySigner := -1; // Unlimited by default
   FHashTreeOperations := TPCThreadList.Create('TOperationsHashTree_HashTreeOperations');
   FHashTreeOperations := TPCThreadList.Create('TOperationsHashTree_HashTreeOperations');
 end;
 end;
 
 
@@ -2021,7 +2056,7 @@ begin
   End;
   End;
 end;
 end;
 
 
-procedure TOperationsHashTree.InternalAddOperationToHashTree(list: TList; op: TPCOperation; CalcNewHashTree : Boolean);
+function TOperationsHashTree.InternalAddOperationToHashTree(list: TList; op: TPCOperation; CalcNewHashTree : Boolean) : Boolean;
 Var msCopy : TMemoryStream;
 Var msCopy : TMemoryStream;
   h : TRawBytes;
   h : TRawBytes;
   P : POperationHashTreeReg;
   P : POperationHashTreeReg;
@@ -2029,6 +2064,27 @@ Var msCopy : TMemoryStream;
   i,npos,iListSigners : Integer;
   i,npos,iListSigners : Integer;
   listSigners : TList;
   listSigners : TList;
 begin
 begin
+  Result := False;
+  // Protections:
+  // Protect 0-fee operations
+  if (op.OperationFee=0) And (FMax0feeOperationsBySigner>=0) then begin
+    if (FMax0feeOperationsBySigner=0) then Exit // Not allowed 0-fee operations!
+    else if (FMax0feeOperationsBySigner>0) then begin
+      listSigners := TList.Create;
+      try
+        op.SignerAccounts(listSigners);
+        for iListSigners:=0 to listSigners.Count-1 do begin
+          If FindOrderedByAccountData(list,PtrInt(listSigners[iListSigners]),i) then begin
+            PaccData := FListOrderedByAccountsData[i];
+            if (PaccData^.account_without_fee>=FMax0feeOperationsBySigner) then Exit; // Limit 0-fee reached
+          end;
+        end;
+      finally
+        listSigners.Free;
+      end;
+    end;
+  end;
+  Result := True; // Will add:
   msCopy := TMemoryStream.Create;
   msCopy := TMemoryStream.Create;
   try
   try
     New(P);
     New(P);
@@ -2274,6 +2330,16 @@ begin
   End;
   End;
 end;
 end;
 
 
+procedure TOperationsHashTree.SetMax0feeOperationsBySigner(const Value: Integer);
+var nv : Integer;
+begin
+  if Value<0 then nv:=-1
+  else nv := Value;
+  if nv=FMax0feeOperationsBySigner then Exit;
+  FMax0feeOperationsBySigner := nv;
+  ClearHastThree;
+end;
+
 { TStorage }
 { TStorage }
 
 
 procedure TStorage.CopyConfiguration(const CopyFrom: TStorage);
 procedure TStorage.CopyConfiguration(const CopyFrom: TStorage);