Browse Source

Merge branch 'master' of https://github.com/PascalCoin/PascalCoin

PascalCoin 7 years ago
parent
commit
5c0e29efbe

+ 67 - 15
src/core/URandomHash.pas

@@ -106,7 +106,7 @@ type
   TRandomHash = class sealed(TObject)
     const
       N = 5;               // Number of hashing rounds required to compute a nonce (total rounds = 2^N - 1)
-      M = (10 * 1024) * 5; // 10KB The memory expansion unit (in bytes)
+      M = (10 * 1024) * 5; // The memory expansion unit (in bytes)
 
     {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
       FMurmurHash3_x86_32 : IHash;
@@ -122,7 +122,7 @@ type
       function MemTransform8(const AChunk: TBytes): TBytes; inline;
       function Expand(const AInput: TBytes; AExpansionFactor: Int32) : TBytes;
       function Compress(const AInputs: TArray<TBytes>): TBytes; inline;
-      function ChangeNonce(const ABlockHeader: TBytes; ANonce: Int32): TBytes; inline;
+      function ChangeNonce(const ABlockHeader: TBytes; ANonce: UInt32): TBytes; inline;
       function Checksum(const AInput: TBytes): UInt32; overload; inline;
       function Checksum(const AInput: TArray<TBytes>): UInt32; overload; inline;
       function Hash(const ABlockHeader: TBytes; ARound: Int32) : TArray<TBytes>; overload;
@@ -139,10 +139,15 @@ type
     const
       N = 5;               // Number of hashing rounds required to compute a nonce (total rounds = 2^N - 1)
       M = (10 * 1024) * 5; // 10KB The memory expansion unit (in bytes)
-
+    private
+      function GetCachedHeader : TBytes;
     {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
       FMurmurHash3_x86_32 : IHash;
       FHashAlg : array[0..17] of IHash;  // declared here to avoid race-condition during mining
+      FCachedHeader : TBytes;
+      FCachedNonce : UInt32;
+      FCachedOutput : TArray<TBytes>;
+
       procedure MemTransform1(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
       procedure MemTransform2(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
       procedure MemTransform3(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
@@ -153,12 +158,15 @@ type
       procedure MemTransform8(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
       function Expand(const AInput: TBytes; AExpansionFactor: Int32) : TBytes;
       function Compress(const AInputs: TArray<TBytes>): TBytes; inline;
-      function ChangeNonce(const ABlockHeader: TBytes; ANonce: Int32): TBytes; inline;
+      function GetNonce(const ABlockHeader: TBytes) : UInt32;
+      function ChangeNonce(const ABlockHeader: TBytes; ANonce: UInt32): TBytes; inline;
       function Checksum(const AInput: TBytes; AOffset, ALength: Integer): UInt32; overload; inline;
       function Checksum(const AInput: TBytes): UInt32; overload; inline;
       function Checksum(const AInput: TArray<TBytes>): UInt32; overload; inline;
       function Hash(const ABlockHeader: TBytes; ARound: Int32) : TArray<TBytes>; overload;
     public
+      property CachedHeader : TBytes read GetCachedHeader;
+      property CachedNonce : UInt32 read FCachedNonce;
       constructor Create;
       destructor Destroy; override;
       function Hash(const ABlockHeader: TBytes): TBytes; overload; inline;
@@ -212,6 +220,7 @@ resourcestring
   SInvalidRound = 'Round must be between 0 and N inclusive';
   SOverlappingArgs = 'Overlapping read/write regions';
   SBufferTooSmall = 'Buffer too small to apply memory transform';
+  SBlockHeaderTooSmallForNonce = 'Buffer too small to contain nonce';
 
 implementation
 
@@ -284,7 +293,7 @@ var
   LRoundOutputs: TList<TBytes>;
   LSeed: UInt32;
   LGen: TMersenne32;
-  LRoundInput, LOtherNonceHeader, LOutput, LBytes: TBytes;
+  LRoundInput, LNeighbourNonceHeader, LOutput, LBytes: TBytes;
   LParentOutputs, LNeighborOutputs, LToArray: TArray<TBytes>;
   LHashFunc: IHash;
   i: Int32;
@@ -304,8 +313,8 @@ begin
     LSeed := Checksum(LParentOutputs);
     LGen.Initialize(LSeed);
     LRoundOutputs.AddRange( LParentOutputs );
-    LOtherNonceHeader := ChangeNonce(ABlockHeader, LGen.NextUInt32);
-    LNeighborOutputs := Hash(LOtherNonceHeader, ARound - 1);
+    LNeighbourNonceHeader := ChangeNonce(ABlockHeader, LGen.NextUInt32);
+    LNeighborOutputs := Hash(LNeighbourNonceHeader, ARound - 1);
     LRoundOutputs.AddRange(LNeighborOutputs);
     LRoundInput := Compress( LRoundOutputs.ToArray );
   end;
@@ -318,7 +327,7 @@ begin
   Result := LRoundOutputs.ToArray;
 end;
 
-function TRandomHash.ChangeNonce(const ABlockHeader: TBytes;  ANonce: Int32): TBytes;
+function TRandomHash.ChangeNonce(const ABlockHeader: TBytes;  ANonce: UInt32): TBytes;
 var
   LHeaderLength : Integer;
 begin
@@ -590,10 +599,10 @@ var
   LRoundOutputs: TList<TBytes>;
   LSeed: UInt32;
   LGen: TMersenne32;
-  LRoundInput, LOtherNonceHeader, LOutput, LBytes: TBytes;
+  LRoundInput, LNeighbourNonceHeader, LOutput, LBytes: TBytes;
   LParentOutputs, LNeighborOutputs, LToArray: TArray<TBytes>;
   LHashFunc: IHash;
-  i: Int32;
+  i, LNeighbourNonce, LSeedToCache: UInt32;
   LDisposables : TDisposables;
 begin
   if (ARound < 1) or (ARound > N) then
@@ -601,17 +610,36 @@ begin
 
   LRoundOutputs := LDisposables.AddObject( TList<TBytes>.Create() ) as TList<TBytes>;
   LGen := LDisposables.AddObject( TMersenne32.Create(0) ) as TMersenne32;
-  if ARound = 1 then  begin
+  if ARound = 1 then begin
     LSeed := Checksum(ABlockHeader);
     LGen.Initialize(LSeed);
     LRoundInput := ABlockHeader;
   end else begin
-    LParentOutputs := Hash(ABlockHeader, ARound - 1);
+
+    if (ARound = N) and (Length(ABlockHeader) >= 4) AND (FCachedNonce = GetNonce(ABlockHeader)) and BytesEqual(ABlockHeader, FCachedHeader) then begin
+      // Parent (round N - 1) has already been calculated so re-use values. This saves 50% of calculations!
+      LParentOutputs := FCachedOutput;
+    end else begin
+      // Need to calculate parent output
+      LParentOutputs := Hash(ABlockHeader, ARound - 1);
+    end;
     LSeed := Checksum(LParentOutputs);
+
     LGen.Initialize(LSeed);
     LRoundOutputs.AddRange( LParentOutputs );
-    LOtherNonceHeader := ChangeNonce(ABlockHeader, LGen.NextUInt32);
-    LNeighborOutputs := Hash(LOtherNonceHeader, ARound - 1);
+
+    // Determine the neighbouring nonce
+    LNeighbourNonce := LGen.NextUInt32;
+    LNeighbourNonceHeader := ChangeNonce(ABlockHeader, LNeighbourNonce);
+    LNeighborOutputs := Hash(LNeighbourNonceHeader, ARound - 1);
+
+    // Cache neighbour nonce n-1 calculation if on final round (neighbour will be next nonce)
+    if (ARound = N) then begin
+      FCachedNonce := LNeighbourNonce;
+      FCachedHeader := LNeighbourNonceHeader;
+      FCachedOutput := LNeighborOutputs;
+    end;
+
     LRoundOutputs.AddRange(LNeighborOutputs);
     LRoundInput := Compress( LRoundOutputs.ToArray );
   end;
@@ -624,7 +652,21 @@ begin
   Result := LRoundOutputs.ToArray;
 end;
 
-function TRandomHashFast.ChangeNonce(const ABlockHeader: TBytes;  ANonce: Int32): TBytes;
+function TRandomHashFast.GetNonce(const ABlockHeader: TBytes) : UInt32;
+var LLen : Integer;
+begin
+ LLen := Length(ABlockHeader);
+ if LLen < 4 then
+   raise EArgumentOutOfRangeException.CreateRes(@SBlockHeaderTooSmallForNonce);
+
+ // Last 4 bytes are nonce (LE)
+ Result := ABlockHeader[LLen - 4] OR
+           (ABlockHeader[LLen - 3] SHL 8) OR
+           (ABlockHeader[LLen - 2] SHL 16) OR
+           (ABlockHeader[LLen - 1] SHL 24);
+end;
+
+function TRandomHashFast.ChangeNonce(const ABlockHeader: TBytes; ANonce: UInt32): TBytes;
 var
   LHeaderLength : Integer;
 begin
@@ -887,6 +929,16 @@ begin
   Result := LOutput;
 end;
 
+function TRandomHashFast.GetCachedHeader : TBytes;
+begin
+  if Assigned(FCachedHeader) then begin
+    SetLength(Result, Length(FCachedHeader));
+    Move(FCachedHeader[0], Result[0], Length(FCachedHeader));
+  end else begin
+    SetLength(Result, 0);
+  end;
+end;
+
 { TMersenne32 }
 
 constructor TMersenne32.Create(ASeed: UInt32);

+ 12 - 0
src/libraries/sphere10/UCommon.pas

@@ -47,6 +47,7 @@ function Bytes2Hex(const ABytes: TBytes; AUsePrefix : boolean = false) : AnsiStr
 { Binary-safe StrComp replacement. StrComp will return 0 for when str1 and str2 both start with NUL character. }
 function BinStrComp(const Str1, Str2 : String): Integer;
 function BytesCompare(const ABytes1, ABytes2: TBytes): integer;
+function BytesEqual(const ABytes1, ABytes2 : TBytes) : boolean; inline;
 
 { Ternary operator equivalent of predicate ? (true-val) : (false-value) }
 function IIF(const ACondition: Boolean; const ATrueResult, AFalseResult: Cardinal): Cardinal; overload;
@@ -499,6 +500,17 @@ begin
    end;
 end;
 
+function BytesEqual(const ABytes1, ABytes2 : TBytes) : boolean;
+var ABytes1Len, ABytes2Len : Integer;
+begin
+  ABytes1Len := Length(ABytes1);
+  ABytes2Len := Length(ABytes2);
+  if (ABytes1Len <> ABytes2Len) OR (ABytes1Len = 0) then
+    Result := False
+  else
+    Result := CompareMem(@ABytes1[0], @ABytes2[0], ABytes1Len);
+end;
+
 {%endregion}
 
 {$IFDEF FPC}

+ 4 - 8
src/tests/PascalCoinUnitTests.lpi

@@ -105,19 +105,12 @@
         <OptimizationLevel Value="4"/>
       </Optimizations>
     </CodeGeneration>
-    <Linking>
-      <Options>
-        <Win32>
-          <GraphicApplication Value="True"/>
-        </Win32>
-      </Options>
-    </Linking>
     <Other>
       <CustomOptions Value="-dUNITTESTS"/>
     </Other>
   </CompilerOptions>
   <Debugging>
-    <Exceptions Count="4">
+    <Exceptions Count="5">
       <Item1>
         <Name Value="EAbort"/>
       </Item1>
@@ -130,6 +123,9 @@
       <Item4>
         <Name Value="EAssertionFailedError"/>
       </Item4>
+      <Item5>
+        <Name Value="EArgumentOutOfRangeException"/>
+      </Item5>
     </Exceptions>
   </Debugging>
 </CONFIG>

+ 66 - 5
src/tests/URandomHashTests.pas

@@ -69,6 +69,8 @@ type
   published
     procedure TestRandomHash_Standard;
     procedure TestRandomHash;
+    procedure TestRandomHash_CachedHeaderConsistency;
+    procedure TestRandomHash_NonceOptimization;
     procedure TestExpand;
     procedure TestCompress;
     procedure TestChecksum_1;
@@ -96,8 +98,9 @@ type
 
   TRandomHashStressTest = class(TPascalCoinUnitTest)
   published
-    procedure Standard_1000_Hashes;
-    procedure Optimised_1000_Hashes;
+    procedure Reference1000;
+    procedure Optimized1000;
+    procedure OptimizedWithCPUOptimization1000;
   end;
 
 implementation
@@ -1092,6 +1095,48 @@ begin
   end;
 end;
 
+procedure TRandomHashFastTest.TestRandomHash_CachedHeaderConsistency;
+var
+  LInput, LOutput : TBytes;
+  LCase : TTestItem<Integer, String>;
+  LOptimized : TRandomHashFast;
+  LNonce : UInt32;
+  LDisposables : TDisposables;
+begin
+  LOptimized := LDisposables.AddObject( TRandomHashFast.Create ) as TRandomHashFast;
+  for LCase in DATA_RANDOMHASH do begin
+    LInput := TArrayTool<byte>.Copy(ParseBytes(DATA_BYTES), 0, LCase.Input);
+    LOutput := LOptimized.Hash(LInput);
+    LNonce := LOptimized.GetNonce(LOptimized.CachedHeader);
+    AssertEquals(LNonce, LOptimized.CachedNonce);
+  end;
+end;
+
+procedure TRandomHashFastTest.TestRandomHash_NonceOptimization;
+var
+  LInput, LOutput : TBytes;
+  LCase : TTestItem<Integer, String>;
+  LReference : TRandomHash;
+  LOptimized : TRandomHashFast;
+  LNonce : UInt32;
+  LDisposables : TDisposables;
+begin
+  LReference := LDisposables.AddObject( TRandomHash.Create ) as TRandomHash;
+  LOptimized := LDisposables.AddObject( TRandomHashFast.Create ) as TRandomHashFast;
+  for LCase in DATA_RANDOMHASH do begin
+    LInput := TArrayTool<byte>.Copy(ParseBytes(DATA_BYTES), 0, LCase.Input);
+
+    // Do 1 round of optimized hashing to cache a nonce
+    LOutput := LOptimized.Hash(LInput);
+
+    // Test consistency of cached nonce hash with reference impl
+    LInput := LOptimized.CachedHeader;
+
+    // Test reference hash of cached header same as optimized
+    AssertEquals(LReference.Hash(LInput), LOptimized.Hash(LInput));
+  end;
+end;
+
 procedure TRandomHashFastTest.TestExpand;
 var
   LCase : TTestItem<UInt32, UInt32, UInt32>;
@@ -1392,8 +1437,7 @@ end;
 
 { TRandomHashStressTest }
 
-
-procedure TRandomHashStressTest.Standard_1000_Hashes;
+procedure TRandomHashStressTest.Reference1000;
 const
   NUM_ITER = 1000;
 var
@@ -1409,7 +1453,7 @@ begin
   // no exceptions should occur
 end;
 
-procedure TRandomHashStressTest.Optimised_1000_Hashes;
+procedure TRandomHashStressTest.Optimized1000;
 const
   NUM_ITER = 1000;
 var
@@ -1425,6 +1469,23 @@ begin
   // no exceptions should occur
 end;
 
+procedure TRandomHashStressTest.OptimizedWithCPUOptimization1000;
+const
+  NUM_ITER = 1000;
+var
+  i : Integer;
+  LBuff : TBytes;
+  LHasher : TRandomHashFast;
+  LDisposables : TDisposables;
+begin
+  LBuff := ParseBytes(DATA_BYTES);
+  LHasher := LDisposables.AddObject( TRandomHashFast.Create ) as TRandomHashFast;
+  LHasher.Hash(LBuff);
+  for i := 1 to Pred(NUM_ITER) do
+    LBuff := LHasher.Hash(LHasher.CachedHeader);
+  // no exceptions should occur
+end;
+
 initialization
 
 {$IFDEF FPC}