Browse Source

PascalCoinMiner 5.1 with Stratum support

Tested on OpenPool and Nanopool
PascalCoin 5 years ago
parent
commit
aa9d16a3c1
6 changed files with 324 additions and 130 deletions
  1. 0 0
      src/PascalCoinMiner.pp
  2. 42 15
      src/core/UPoolMinerThreads.pas
  3. 250 71
      src/core/UPoolMining.pas
  4. 3 1
      src/core/UTCPIP.pas
  5. 5 14
      src/pascalcoin_miner.lpi
  6. 24 29
      src/pascalcoin_miner.pp

+ 0 - 0
src/PascalCoinMiner.pp


+ 42 - 15
src/core/UPoolMinerThreads.pas

@@ -63,14 +63,14 @@ Type
     FTestingMode: Boolean;
     Procedure OnPoolMinerClientConnectionChanged(Sender : TObject);
     Procedure OnPoolMinerMustChangeValues(Sender : TObject);
-    Procedure OnMinerNewBlockFound(sender : TCustomMinerDeviceThread; const usedMinerValuesForWork : TMinerValuesForWork; Timestamp : Cardinal; NOnce : Cardinal);
+    Procedure OnMinerNewBlockFound(sender : TCustomMinerDeviceThread; const usedMinerValuesForWork : TMinerValuesForWork; Timestamp : Cardinal; NOnce : Cardinal; const AObtainedPoW : TRawBytes);
     Procedure NotifyPoolMinerConnectionChanged;
     procedure SetMinerAddName(AValue: String);
     procedure SetTestingPoWLeftBits(AValue: Byte);
   protected
     procedure BCExecute; override;
   public
-    Constructor Create(RemoteHost : String; RemotePort : Integer; InitialAccountKey : TAccountKey);
+    Constructor Create(RemoteHost : String; RemotePort : Integer; const AUserName, APassword : String);
     Destructor Destroy; override;
     Property PoolMinerClient : TPoolMinerClient read FPoolMinerClient;
     Property OnConnectionStateChanged : TNotifyEvent read FOnConnectionStateChanged write FOnConnectionStateChanged;
@@ -185,12 +185,15 @@ Var nID : Cardinal;
   i : Integer;
   ResponseMethod : String;
   l : TList<TCustomMinerDeviceThread>;
+  LParams : TPCJSONArray;
+  LPoolResult : TPCJSONObject;
 begin
   DebugStep:='Starting';
   Try
     while not Terminated do begin
       if FTestingMode then begin
       end else if not FPoolMinerClient.Connected then begin
+        FPoolMinerClient.Stratum_Authorized := False;
         DebugStep:='Not connected';
         If Not FPoolMinerClient.Connect then begin
         end else begin
@@ -198,9 +201,12 @@ begin
         end;
       end else begin
           DebugStep:='Starting process';
-          // Start Process
-          nId:=FPoolMinerClient.GetNewId;
-          FPoolMinerClient.SendJSONRPCMethod(CT_PoolMining_Method_MINER_NOTIFY,nil,nId);
+          if FPoolMinerClient.PoolType = ptPoolSubscription then begin
+          end else begin
+            // Start Process Solo mining (direct to PascalCoin node)
+            nId:=FPoolMinerClient.GetNewId;
+            FPoolMinerClient.SendJSONRPCMethod(CT_PoolMining_Method_MINER_NOTIFY,nil,nId);
+          end;
           json := TPCJSONObject.create;
           try
             DebugStep:='Starting repeat';
@@ -232,7 +238,7 @@ begin
   End;
 end;
 
-constructor TPoolMinerThread.Create(RemoteHost: String; RemotePort: Integer; InitialAccountKey : TAccountKey);
+constructor TPoolMinerThread.Create(RemoteHost: String; RemotePort: Integer; const AUserName, APassword : String);
 begin
   FGlobalMinerValuesForWork := CT_TMinerValuesForWork_NULL;
   FPoolMinerClient := TPoolMinerClient.Create(Nil);
@@ -241,6 +247,13 @@ begin
   FPoolMinerClient.OnMinerMustChangeValues := OnPoolMinerMustChangeValues;
   FPoolMinerClient.OnConnect := OnPoolMinerClientConnectionChanged;
   FPoolMinerClient.OnDisconnect := OnPoolMinerClientConnectionChanged;
+  if Length(AUserName)>0 then begin
+    FPoolMinerClient.PoolType := ptPoolSubscription;
+    FPoolMinerClient.UserName := AUserName;
+    FPoolMinerClient.Password := APassword;
+  end else begin
+    FPoolMinerClient.PoolType := ptSolomine;
+  end;
   FOnConnectionStateChanged := Nil;
   FDevicesList := TPCThreadList<TCustomMinerDeviceThread>.Create('TPoolMinerThread_DevicesList');
   FMinerThreads := 0;
@@ -351,12 +364,11 @@ begin
   else FTestingPoWLeftBits:=0;
 end;
 
-procedure TPoolMinerThread.OnMinerNewBlockFound(sender : TCustomMinerDeviceThread; const usedMinerValuesForWork : TMinerValuesForWork; Timestamp : Cardinal; NOnce : Cardinal);
+procedure TPoolMinerThread.OnMinerNewBlockFound(sender : TCustomMinerDeviceThread; const usedMinerValuesForWork : TMinerValuesForWork; Timestamp : Cardinal; NOnce : Cardinal; const AObtainedPoW : TRawBytes);
 begin
   FDevicesList.LockList;
   try
-    TLog.NewLog(ltinfo,ClassName,'FOUND VALID NONCE!!! Block:'+IntToStr(usedMinerValuesForWork.block)+' Timestamp:'+Inttostr(Timestamp)+ ' Nonce:'+Inttostr(NOnce)+' Payload:'+usedMinerValuesForWork.payload_start.ToString);
-    FPoolMinerClient.SubmitBlockFound(usedMinerValuesForWork,usedMinerValuesForWork.payload_start,Timestamp,NOnce);
+    FPoolMinerClient.SubmitBlockFound(usedMinerValuesForWork,usedMinerValuesForWork.FinalPayload,Timestamp,NOnce,AObtainedPoW);
   finally
     FDevicesList.UnlockList;
   end;
@@ -386,14 +398,19 @@ Var l : TList<TCustomMinerDeviceThread>;
   minervfw : TMinerValuesForWork;
   auxminervfw : TMinerValuesForWork;
   auxRaw : TRawBytes;
+  s : String;
 begin
   FGlobalMinerValuesForWork := FPoolMinerClient.MinerValuesForWork;
-  TLog.NewLog(ltupdate,ClassName,Format('New miner values. Block %d Target %s Payload %s',[FPoolMinerClient.MinerValuesForWork.block,
-    IntToHex(FPoolMinerClient.MinerValuesForWork.target,8), FPoolMinerClient.MinerValuesForWork.payload_start.ToPrintable]));
   l := FDevicesList.LockList;
   Try
     for i := 0 to l.Count - 1 do begin
       minervfw := FGlobalMinerValuesForWork;
+      if (FGlobalMinerValuesForWork.IsStratum) And (FGlobalMinerValuesForWork.extraNOnce2_Size>0) then begin
+        s := IntToStr(i+1);
+        while Length(s)<FGlobalMinerValuesForWork.extraNOnce2_Size do s := '0' + s;
+        minervfw.extraNOnce2_Used.FromString( s );
+      end else begin
+
       auxRaw.FromString(FMinerAddName);
       TBaseType.Concat(minervfw.payload_start,auxRaw,minervfw.payload_start);
       if (l.count>1) then begin
@@ -410,6 +427,8 @@ begin
           end;
         until (Ok);
       end;
+
+      end;
       If FTestingPoWLeftBits>0 then begin
         auxminervfw := minervfw;
         auxminervfw.target:= ((((auxminervfw.target AND $FF000000) SHR 24)-FTestingPoWLeftBits) SHL 24) + (minervfw.target AND $00FFFFFF);
@@ -420,6 +439,10 @@ begin
         TCustomMinerDeviceThread(l[i]).SetMinerValuesForWork(minervfw);
       end;
     end;
+    TLog.NewLog(ltupdate,ClassName,Format('Block %d Target %s Payload %s Devices %d PoW:%s',[FPoolMinerClient.MinerValuesForWork.block,
+      IntToHex(FPoolMinerClient.MinerValuesForWork.target,8), FPoolMinerClient.MinerValuesForWork.payload_start.ToPrintable,
+      l.Count,
+      FGlobalMinerValuesForWork.target_pow.ToHexaString ]));
   Finally
     FDevicesList.UnlockList;
   End;
@@ -507,7 +530,7 @@ begin
     LHash := TCrypto.DoSha256(TCrypto.DoSha256(digest));
   if (TBaseType.BinStrComp(LHash,usedMinerValuesForWork.target_pow)<=0) then begin
     inc(FGlobaDeviceStats.WinsCount);
-    FPoolMinerThread.OnMinerNewBlockFound(self,usedMinerValuesForWork,Timestamp,nOnce);
+    FPoolMinerThread.OnMinerNewBlockFound(self,usedMinerValuesForWork,Timestamp,nOnce,LHash);
     If Assigned(FOnFoundNOnce) then FOnFoundNOnce(Self,Timestamp,nOnce);
   end else begin
     inc(FGlobaDeviceStats.Invalids);
@@ -523,15 +546,18 @@ end;
 procedure TCustomMinerDeviceThread.CreateDigest(const AMinerValuesForWork: TMinerValuesForWork; Timestamp, nOnce: Cardinal; var ODigest: TRawBytes);
 begin
   // Validation
-  SetLength(ODigest,Length(AMinerValuesForWork.part1) + Length(AMinerValuesForWork.payload_start) + Length(AMinerValuesForWork.part3) + 8);
+  SetLength(ODigest,Length(AMinerValuesForWork.part1) + Length(AMinerValuesForWork.payload_start) + Length(AMinerValuesForWork.extraNOnce2_Used) + Length(AMinerValuesForWork.part3) + 8);
   move(AMinerValuesForWork.part1[0],
     ODigest[0],
     Length(AMinerValuesForWork.part1));
   move(AMinerValuesForWork.payload_start[0],
     ODigest[Length(AMinerValuesForWork.part1)],
     Length(AMinerValuesForWork.payload_start));
+  move(AMinerValuesForWork.extraNOnce2_Used[0],
+    ODigest[Length(AMinerValuesForWork.part1) + Length(AMinerValuesForWork.payload_start) ],
+    Length(AMinerValuesForWork.extraNOnce2_Used));
   move(AMinerValuesForWork.part3[0],
-    ODigest[ Length(AMinerValuesForWork.part1) + Length(AMinerValuesForWork.payload_start) ],
+    ODigest[ Length(AMinerValuesForWork.part1) + Length(AMinerValuesForWork.payload_start) + Length(AMinerValuesForWork.extraNOnce2_Used) ],
     Length(AMinerValuesForWork.part3));
   // Add timestamp and nonce
   move(Timestamp,ODigest[length(ODigest)-8],4);
@@ -582,7 +608,8 @@ var aux : Integer;
 begin
   FMinerValuesForWork := Value;
   UpdateMinerValuesForWorkLength(FMinerValuesForWork,aux);
-  TLog.NewLog(ltinfo,classname,Format('Updated MinerValuesForWork: Target:%s Payload:%s Target_PoW:%s',[IntToHex(FMinerValuesForWork.target,8),FMinerValuesForWork.payload_start.ToString,TCrypto.ToHexaString(FMinerValuesForWork.target_pow)]));
+  TLog.NewLog(ltdebug,classname,
+    Format('Updated MinerValuesForWork: Target:%s Payload:%s Target_PoW:%s',[IntToHex(FMinerValuesForWork.target,8),FMinerValuesForWork.payload_start.ToString,TCrypto.ToHexaString(FMinerValuesForWork.target_pow)]));
   If Assigned(FOnMinerValuesChanged) then FOnMinerValuesChanged(Self);
 end;
 

+ 250 - 71
src/core/UPoolMining.pas

@@ -39,8 +39,11 @@ Const
   CT_PoolMining_Method_MINER_NOTIFY = 'miner-notify'; // Server message to clients to update miners PoW data
   CT_PoolMining_Method_MINER_SUBMIT = 'miner-submit'; // Client message to server to notify a PoW found
 
-  CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE = 'mining-authorize';
-  CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE = 'mining-subscribe';
+  CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE = 'mining.authorize';
+  CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE = 'mining.subscribe';
+  CT_PoolMining_Method_STRATUM_MINING_SET_DIFFICULTY = 'mining.set_difficulty';
+  CT_PoolMining_Method_STRATUM_MINING_NOTIFY = 'mining.notify';
+  CT_PoolMining_Method_STRATUM_MINING_SUBMIT = 'mining.submit';
 
 Type
   TMinerValuesForWork = Record
@@ -52,10 +55,20 @@ Type
      target : Cardinal;
      timestamp : Cardinal;
      target_pow : TRawBytes;
-     // Stratum jobid
+     // Stratum information
      jobid : String;
+     extraNOnce2_Size : Integer;
+     extraNOnce2_Used : TRawBytes;
   End;
 
+  { TMinerValuesForWork_HELPER }
+
+  TMinerValuesForWork_HELPER = record helper for TMinerValuesForWork
+     function FinalPayload : TRawBytes;
+     function IsStratum : Boolean;
+  end;
+
+
   TProcessJSONObjectEvent = Procedure (json : TPCJSONObject; method : String) of object;
 
   { TJSONRPCTcpIpClient }
@@ -79,7 +92,7 @@ Type
     Function GetNewId : Cardinal;
   End;
 
-  TPoolType = (ptNone,ptIdentify);
+  TPoolType = (ptSolomine,ptPoolSubscription);
 
   { TPoolMinerClient }
 
@@ -92,6 +105,8 @@ Type
     FPoolType: TPoolType;
     FStratum_Target_PoW: TRawBytes;
     FUserName: String;
+    FStratumAuthorized : Boolean;
+    FStratum_ExtraNOnce2_Size: Integer;
     procedure SetMinerValuesForWork(const Value: TMinerValuesForWork);
   protected
     Procedure DoOnConnect; Override;
@@ -99,13 +114,15 @@ Type
     Constructor Create(AOwner : TComponent); override;
     Property OnMinerMustChangeValues : TNotifyEvent read FOnMinerMustChangeValues write FOnMinerMustChangeValues;
     Property MinerValuesForWork : TMinerValuesForWork read FMinerValuesForWork write SetMinerValuesForWork;
-    Procedure SubmitBlockFound(Const MinerValuesToGenerateBlock : TMinerValuesForWork; const Payload: TRawBytes; Timestamp, NOnce: Cardinal);
+    Procedure SubmitBlockFound(Const MinerValuesToGenerateBlock : TMinerValuesForWork; const Payload: TRawBytes; Timestamp, NOnce: Cardinal; const AObtainedPoW : TRawBytes);
     Procedure DoProcessJSONObject(json : TPCJSONObject; ResponseMethod : String);
     Property PoolType : TPoolType read FPoolType write FPoolType;
     Property UserName : String read FUserName write FUserName;
     Property Password : String read FPassword write FPassword;
     Property PoolFinalMinerName : TRawBytes read FPoolFinalMinerName;
     Property Stratum_Target_PoW : TRawBytes read FStratum_Target_PoW;
+    Property Stratum_Authorized : Boolean read FStratumAuthorized write FStratumAuthorized;
+    Property Stratum_ExtraNOnce2_Size : Integer read FStratum_ExtraNOnce2_Size write FStratum_ExtraNOnce2_Size;
   End;
 
   TPoolMiningServer = Class;
@@ -166,7 +183,7 @@ Type
 Function TBytesToString(Const bytes : TBytes):AnsiString;
 
 Const
-  CT_TMinerValuesForWork_NULL : TMinerValuesForWork = (block:0;version:0;part1:Nil;payload_start:Nil;part3:Nil;target:0;timestamp:0;target_pow:Nil;jobid:'');
+  CT_TMinerValuesForWork_NULL : TMinerValuesForWork = (block:0;version:0;part1:Nil;payload_start:Nil;part3:Nil;target:0;timestamp:0;target_pow:Nil;jobid:'';extraNOnce2_Size:0;extraNOnce2_Used:Nil);
 
 implementation
 
@@ -191,6 +208,27 @@ Begin
   end;
 End;
 
+{ TMinerValuesForWork_HELPER }
+
+function TMinerValuesForWork_HELPER.FinalPayload: TRawBytes;
+begin
+  if IsStratum then begin
+    // Check extraNOnce2 valid value
+    while ( Self.extraNOnce2_Size>Length(Self.extraNOnce2_Used) ) do begin
+      SetLength(Self.extraNOnce2_Used, Length(Self.extraNOnce2_Used) + 1);
+      Self.extraNOnce2_Used[ High(Self.extraNOnce2_Used) ] := 48; // 48 = Char '0'
+    end;
+  end;
+  SetLength(Result, Length(Self.payload_start) + Length(Self.extraNOnce2_Used) );
+  Move(Self.payload_start[0],Result[0],Length(Self.payload_start));
+  Move(Self.extraNOnce2_Used[0],Result[Length(Self.payload_start)],Length(Self.extraNOnce2_Used));
+end;
+
+function TMinerValuesForWork_HELPER.IsStratum: Boolean;
+begin
+  Result := Length(Self.jobid)>0;
+end;
+
 { TJSONRPCTcpIpClient }
 
 constructor TJSONRPCTcpIpClient.Create(AOwner: TComponent);
@@ -317,10 +355,11 @@ begin
           Try
             if jsonData is TPCJSONObject then begin
               jsonObject.Assign(jsonData);
-              If (Not jsonObject.IsNull('id')) And (jsonObject.IndexOfName('method')<0) then begin
+              If (Not jsonObject.IsNull('id')) then begin
                 // Is a Response!
                 FlushBufferPendingMessages(true,jsonObject.AsInteger('id',0));
               end;
+              TLog.NewLog(ltdebug,ClassName,'Received JSON: '+jsonObject.ToJSON(false));
               Result := true;
               exit;
             end else begin
@@ -413,7 +452,7 @@ begin
       P^.method:=method;
       FPendingResponseMessages.Add(P);
     end;
-    {$IFDEF HIGHLOG}TLog.NewLog(ltInfo,Classname,'Sending JSON: '+json.ToJSON(false));{$ENDIF}
+    TLog.NewLog(ltDebug,Classname,'Sending JSON: '+json.ToJSON(false));
     stream := TMemoryStream.Create;
     try
       json.SaveToStream(stream);
@@ -1057,11 +1096,13 @@ end;
 constructor TPoolMinerClient.Create(AOwner: TComponent);
 begin
   FMinerValuesForWork := CT_TMinerValuesForWork_NULL;
-  FPoolType:=ptNone;
+  FPoolType:=ptSolomine;
   FUserName:='';
   FPassword:='';
   FPoolFinalMinerName:=Nil;
   FStratum_Target_PoW:=Nil;
+  FStratumAuthorized := False;
+  FStratum_ExtraNOnce2_Size := 0;
   inherited;
 end;
 
@@ -1073,44 +1114,24 @@ Var params : TPCJSONArray;
   i : Integer;
 begin
   inherited DoOnConnect;
-  If FPoolType=ptIdentify then begin
+  If FPoolType=ptPoolSubscription then begin
     // Pool initialization
     params := TPCJSONArray.Create;
     resultObject := TPCJSONObject.Create;
     try
-      params.GetAsVariant(0).Value:=UserName;
-      params.GetAsVariant(1).Value:=Password;
-      If SendJSONRPCMethodAndWait(CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE,params,1000,resultObject,nil) then begin
-        TLog.NewLog(ltInfo,Classname,CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE+' response: '+resultObject.ToJSON(false));
-        // Now subscribe
-        params.Clear;
-        resultObject.Clear;
-        If SendJSONRPCMethodAndWait(CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE,params,1000,resultObject,nil) then begin
-          //
-          TLog.NewLog(ltInfo,Classname,CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE+' response: '+resultObject.ToJSON(false));
-          // Decode response
-          If (resultObject.IsNull('error')) then begin
-            s := resultObject.GetAsArray('result').GetAsArray(0).GetAsArray(0).GetAsVariant(0).AsString('');
-            if (s<>'mining.nonce') then Raise Exception.Create('Not a mining.nonce');
-            s := resultObject.GetAsArray('result').GetAsVariant(1).AsString('');
-            raws := TCrypto.HexaToRaw(s);
-            If (length(s)>0) And (length(raws)=0) then begin
-              TLog.NewLog(lterror,ClassName,'Invalid value to assign as a Miner name. Not hexadecimal '+s);
-              FPoolFinalMinerName:=Nil;
-            end else begin
-              FPoolFinalMinerName := raws;
-              for i:=Low(raws) to High(raws) do begin
-                if Not (raws[i] in [32..254]) then begin
-                  TLog.NewLog(ltError,ClassName,'Invalid proposed miner name. Value at pos '+inttostr(i)+' is not #32..#254: '+IntToStr(integer(raws[i])));
-                  FPoolFinalMinerName:=Nil;
-                  break;
-                end;
-              end;
-            end;
-            TLog.NewLog(ltInfo,Classname,'Final miner name: "'+FPoolFinalMinerName.ToPrintable+'" (Length '+IntToStr(length(FPoolFinalMinerName)));
-          end;
-        end else raise Exception.Create('Not response to "'+CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE+'" method for user "'+UserName+'"');
-      end else raise Exception.Create('Not response to "'+CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE+'" method for user "'+UserName+'"');
+      If SendJSONRPCMethodAndWait(CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE,params,8000,resultObject,DoProcessJSONObject) then begin
+        //
+        DoProcessJSONObject(resultObject,CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE);
+        //
+        Sleep(100);
+        //
+        params.GetAsVariant(0).Value:=UserName;
+        params.GetAsVariant(1).Value:=Password;
+        If SendJSONRPCMethodAndWait(CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE,params,8000,resultObject,DoProcessJSONObject) then begin
+          DoProcessJSONObject(resultObject,CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE);
+          FStratumAuthorized := True;
+        end else raise Exception.Create('Not response to "'+CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE+'" method for user "'+UserName+'"');
+      end else raise Exception.Create('Not response to "'+CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE+'" method for user "'+UserName+'"');
     finally
       resultObject.free;
       params.free;
@@ -1127,8 +1148,105 @@ Var method : String;
   params : TPCJSONData;
   mvfw : TMinerValuesForWork;
   prev_pow,proposed_pow : TRawBytes;
+
+  procedure StratumUpdatedDifficulty(ANewDifficultyValue : Double);
+  var LBigNumDiff : TBigNum;
+    LAuxPOW : TRawBytes;
+    LHexa : String;
+  begin
+    LBigNumDiff := TBigNum.Create('00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
+    try
+      if (ANewDifficultyValue<=0) then Exit;
+
+      if (ANewDifficultyValue<1) then begin
+        LBigNumDiff.Multiply( Round(1000.0/ANewDifficultyValue) ).Divide( 1000 );
+      end else begin
+        LBigNumDiff.Multiply( Round(ANewDifficultyValue*1000.0) ).Divide( 1000 );
+      end;
+      LHexa := LBigNumDiff.HexaValue;
+    finally
+      LBigNumDiff.Free;
+    end;
+    while (LHexa.Length<64) do LHexa.Insert(0,'0');
+    TCrypto.HexaToRaw( LHexa, LAuxPOW );
+    if TBaseType.Equals(LAuxPOW,FStratum_Target_PoW) then Exit; // Nothing to update
+
+    FStratum_Target_PoW := LAuxPOW;
+    TLog.NewLog(ltInfo,ClassName,Format('Set Difficulty to %.6f -> Target PoW:%s %s',[ANewDifficultyValue,
+      TPascalCoinProtocol.TargetToCompact(FStratum_Target_PoW,CT_PROTOCOL_4).ToHexString,
+      FStratum_Target_PoW.ToHexaString]));
+  end;
+
+  procedure ExtractStratumMinerValuesFromWork(const AJobID, ADigestPart_1, ADigestPart_2, AStratumTime : String);
+  var LMVFW : TMinerValuesForWork;
+    LStream : TStream;
+    LRaw : TRawBytes;
+  begin
+    LMVFW := CT_TMinerValuesForWork_NULL;
+    // Extracting Miner Values From Work from Digest parts:
+    if Not TCrypto.HexaToRaw( ADigestPart_1, LRaw) then begin
+      TLog.NewLog(ltError,ClassName,Format('Digest Part 1 obtained is not Hexadecimal: "%s" length %d',[ADigestPart_1,Length(ADigestPart_1)]));
+      Exit;
+    end;
+    if (Length(LRaw)<20) then begin
+      TLog.NewLog(ltError,ClassName,Format('Digest Part 1 is too short: "%s" length %d',[ADigestPart_1,Length(ADigestPart_1)]));
+      Exit;
+    end;
+    LMVFW.part1 := LRaw;
+    if Not TCrypto.HexaToRaw(ADigestPart_2,LMVFW.part3) then begin
+      TLog.NewLog(ltError,ClassName,Format('Digest Part 2 obtained is not Hexadecimal: "%s" length %d',[ADigestPart_2,Length(ADigestPart_2)]));
+      Exit;
+    end;
+    LStream := TMemoryStream.Create;
+    try
+      LStream.WriteBuffer( LRaw[0], Length(LRaw) );
+      LStream.Position := 0;
+      // Block number is stored as a Little Endian on first 4 bytes (8 hexa chars)
+      LStream.Read(LMVFW.block,4);
+
+      if LMVFW.block=0 then Exit;
+
+      // Latest 8 bytes are:
+      // 2 - protocol_version
+      // 2 - protocol_available
+      // 4 - target
+      LStream.Seek(-8,soFromEnd);
+      LStream.Read(LMVFW.version,2);
+      LStream.Seek(2,soFromCurrent);
+      LStream.Read(LMVFW.target,4);
+      LMVFW.jobid := AJobID;
+    finally
+      LStream.Free;
+    end;
+    // Capture timestamp
+    LMVFW.timestamp := StrToIntDef('$'+AStratumTime,0);
+    //
+    MinerValuesForWork := LMVFW;
+  end;
+
+  procedure ExtractStratumMinerValuesFromWorkFromParams;
+  var i : Integer;
+  begin
+    if Not Assigned(params_as_array) then Exit;
+    i := params_as_array.Count - 9;
+    if (i<0) or (i>1) then Exit;
+
+    if (i=1) then begin
+      // Extract difficulty from position 0
+      StratumUpdatedDifficulty( params_as_array.GetAsVariant(0).AsDouble(1) );
+    end;
+
+    // STRATUM
+    ExtractStratumMinerValuesFromWork(
+      params_as_array.GetAsVariant(0 + i).AsString(''),
+      params_as_array.GetAsVariant(2 + i).AsString(''),
+      params_as_array.GetAsVariant(3 + i).AsString(''),
+      params_as_array.GetAsVariant(7 + i).AsString('')
+    );
+  end;
+
+
 begin
-  TLog.NewLog(ltdebug,ClassName,'Received JSON: '+json.ToJSON(false));
   params := Nil;
   params_as_object := Nil;
   params_as_array := Nil;
@@ -1138,19 +1256,20 @@ begin
     if (i>=0) then begin
       params := json.Items[i];
     end;
-    TLog.NewLog(ltinfo,classname,'Received response method:'+ResponseMethod+' JSON:'+json.ToJSON(false));
+    {$IFDEF HIGHLOG}TLog.NewLog(ltinfo,classname,'Received response method:'+ResponseMethod+' JSON:'+json.ToJSON(false));{$ENDIF}
   end else begin
     method := json.AsString('method','');
-    i := json.IndexOfName('params');
-    if (i>=0) then begin
-      params := json.Items[i];
-    end;
+  end;
+  i := json.IndexOfName('params');
+  if (i>=0) then begin
+    params := json.Items[i];
   end;
   If Assigned(params) then begin
     if (params is TPCJSONNameValue) then begin
       if (TPCJSONNameValue(params).Value is TPCJSONObject) then params_as_object := TPCJSONObject(TPCJSONNameValue(params).Value)
       else if (TPCJSONNameValue(params).Value is TPCJSONArray) then params_as_array := TPCJSONArray(TPCJSONNameValue(params).Value);
-    end;
+    end else if (params is TPCJSONArray) then params_as_array := TPCJSONArray(params)
+    else if (params is TPCJSONObject) then params_as_object := TPCJSONObject(params)
   end;
   i := json.IndexOfName('id');
   if i<0 then begin
@@ -1172,7 +1291,7 @@ begin
       mvfw.timestamp := pobject.AsInteger('timestamp',0);
       mvfw.part1 := TCrypto.HexaToRaw(pobject.AsString('part1',''));
       mvfw.target_pow := TCrypto.HexaToRaw(pobject.AsString('target_pow',''));
-      If FPoolType=ptIdentify then begin
+      If FPoolType=ptPoolSubscription then begin
         mvfw.jobid:=pobject.AsString('jobid','');
       end;
       if (Not VarIsNull(id_value)) And (ResponseMethod='') then begin
@@ -1180,6 +1299,38 @@ begin
       end;
       MinerValuesForWork := mvfw;
     end else TLog.NewLog(ltError,ClassName,'method '+method+' without JSON object '+params.ToJSON(false));
+  end else if (method=CT_PoolMining_Method_STRATUM_MINING_AUTHORIZE) And
+    (PoolType=ptPoolSubscription) And (Assigned(params_as_array)) then begin
+    //
+    FStratumAuthorized := True;
+    ExtractStratumMinerValuesFromWorkFromParams;
+  end else if (method=CT_PoolMining_Method_STRATUM_MINING_SUBSCRIBE) And
+    (PoolType=ptPoolSubscription) And (Assigned(params_as_array)) then begin
+    // STRATUM
+    // {"id":2,"result":[[["mining.notify","00000000000000000000000000000000"]],"303030303030303030",8],"error":null}
+    // Explained at: https://slushpool.com/help/stratum-protocol/
+    // params[0] -> Subscription details
+    // params[1] -> Extranonce1
+    // params[2] -> Extranonce2_size
+    FPoolFinalMinerName := TCrypto.HexaToRaw( params_as_array.GetAsVariant(1).AsString('') );
+    FStratum_ExtraNOnce2_Size := params_as_array.GetAsVariant(2).AsInteger(0);
+  end else if (method=CT_PoolMining_Method_STRATUM_MINING_SET_DIFFICULTY) And
+    (PoolType=ptPoolSubscription) And (Assigned(params_as_array)) then begin
+    // STRATUM
+    // {"id":null,"method":"mining.set_difficulty","params":[0.0000152588]}
+    StratumUpdatedDifficulty( params_as_array.GetAsVariant(0).AsDouble(1) );
+
+  end else if (method=CT_PoolMining_Method_STRATUM_MINING_NOTIFY) And
+    (PoolType=ptPoolSubscription) And (Assigned(params_as_array)) then begin
+    ExtractStratumMinerValuesFromWorkFromParams;
+  end else if (method=CT_PoolMining_Method_STRATUM_MINING_SUBMIT) And
+    (PoolType=ptPoolSubscription) then begin
+    // Response to a mining submit event
+    if (json.GetAsVariant('error').AsBoolean(False)) then begin
+      TLog.NewLog(lterror,ClassName,Format('Invalid SHARE %s',[method,json.ToJSON(False)]));
+    end;
+  end else begin
+    TLog.NewLog(lterror,ClassName,Format('Unknown method received "%s": %s',[method,json.ToJSON(False)]));
   end;
 end;
 
@@ -1218,32 +1369,60 @@ begin
       end;
     end;
   end;
-  If (FPoolType=ptIdentify) And (Length(FPoolFinalMinerName)>0) then FMinerValuesForWork.payload_start:=FPoolFinalMinerName;
+  If (FPoolType=ptPoolSubscription) And (Length(FPoolFinalMinerName)>0) then begin
+    FMinerValuesForWork.payload_start:=FPoolFinalMinerName;
+    FMinerValuesForWork.extraNOnce2_Size:=FStratum_ExtraNOnce2_Size;
+    FMinerValuesForWork.extraNOnce2_Used:=Nil;
+  end;
   if Assigned(FOnMinerMustChangeValues) then FOnMinerMustChangeValues(Self);
 end;
 
-procedure TPoolMinerClient.SubmitBlockFound(Const MinerValuesToGenerateBlock : TMinerValuesForWork; const Payload: TRawBytes; Timestamp, NOnce: Cardinal);
-Var json, resultJSON : TPCJSONObject;
-  nOnceAsSignedInt : Int32;
+procedure TPoolMinerClient.SubmitBlockFound(Const MinerValuesToGenerateBlock : TMinerValuesForWork; const Payload: TRawBytes; Timestamp, NOnce: Cardinal; const AObtainedPoW : TRawBytes);
+Var json : TPCJSONObject;
+  LnOnceAsSignedInt : Int32;
+  LArrJSON : TPCJSONArray;
 begin
-  json := TPCJSONObject.Create;
-  Try
-    nOnceAsSignedInt := NOnce;
-    If FPoolType=ptIdentify then begin
-      json.GetAsVariant('jobid').Value := MinerValuesToGenerateBlock.jobid;
-    end;
-    json.GetAsVariant('payload').Value := TCrypto.ToHexaString(Payload);
-    json.GetAsVariant('timestamp').Value := Timestamp;
-    json.GetAsVariant('nonce').Value := nOnceAsSignedInt;
-    resultJSON := TPCJSONObject.Create;
-    try
+  LnOnceAsSignedInt := NOnce;
+  If FPoolType=ptPoolSubscription then begin
+    LArrJSON := TPCJSONArray.Create;
+    Try
+      LArrJSON.GetAsVariant(0).Value := UserName;
+      LArrJSON.GetAsVariant(1).Value := MinerValuesToGenerateBlock.jobid;
+      LArrJSON.GetAsVariant(2).Value := MinerValuesToGenerateBlock.extraNOnce2_Used.ToHexaString; //  Payload.ToHexaString;// + '1234567812345678'; // '1234567812345678'; //Payload.ToHexaString;
+      LArrJSON.GetAsVariant(3).Value := IntToHex(Timestamp,8);
+      LArrJSON.GetAsVariant(4).Value := IntToHex(LnOnceAsSignedInt,8);
+      SendJSONRPCMethod(CT_PoolMining_Method_STRATUM_MINING_SUBMIT,LArrJSON,GetNewId);
+      TLog.NewLog(ltInfo,ClassName,Format('SHARE for job %s block %d Time %s NOnce %s ExtraNOnce2 %s PoW %s',
+       [MinerValuesToGenerateBlock.jobid,
+        MinerValuesToGenerateBlock.block,
+        IntToHex(Timestamp,8),
+        IntToHex(LnOnceAsSignedInt,8),
+        MinerValuesToGenerateBlock.extraNOnce2_Used.ToHexaString,
+        AObtainedPoW.ToHexaString]));
+    Finally
+      LArrJSON.Free;
+    End;
+  end else begin
+    json := TPCJSONObject.Create;
+    Try
+      If FPoolType=ptPoolSubscription then begin
+        json.GetAsVariant('jobid').Value := MinerValuesToGenerateBlock.jobid;
+      end;
+      json.GetAsVariant('payload').Value := TCrypto.ToHexaString(Payload);
+      json.GetAsVariant('timestamp').Value := Timestamp;
+      json.GetAsVariant('nonce').Value := LnOnceAsSignedInt;
       SendJSONRPCMethod(CT_PoolMining_Method_MINER_SUBMIT,json,GetNewId);
+      TLog.NewLog(ltInfo,ClassName,Format('FOUND SOLUTION for block %d Time %s NOnce %s Payload %s PoW %s',
+       [
+        MinerValuesToGenerateBlock.block,
+        IntToHex(Timestamp,8),
+        IntToHex(LnOnceAsSignedInt,8),
+        Payload.ToHexaString,
+        AObtainedPoW.ToHexaString]));
     Finally
-      resultJSON.free;
-    end;
-  Finally
-    json.Free;
-  End;
+      json.Free;
+    End;
+  end;
 end;
 
 { TPoolMiningServerThread }

+ 3 - 1
src/core/UTCPIP.pas

@@ -264,7 +264,6 @@ begin
       if (FConnected) then begin
         FRemoteHost := FTcpBlockSocket.GetRemoteSinIP;
         FRemotePort := FTcpBlockSocket.GetRemoteSinPort;
-        DoOnConnect;
       end{$IFDEF HIGHLOG} else TLog.NewLog(ltdebug,Classname,'Cannot connect to a server at: '+ClientRemoteAddr+' Reason: '+FTcpBlockSocket.GetErrorDescEx){$ENDIF};
     Except
       On E:Exception do begin
@@ -278,6 +277,9 @@ begin
     FLock.Release;
   end;
   Result := FConnected;
+  if FConnected then begin
+    DoOnConnect;
+  end;
 end;
 
 constructor TNetTcpIpClient.Create(AOwner : TComponent);

+ 5 - 14
src/pascalcoin_miner.lpi

@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <CONFIG>
   <ProjectOptions>
-    <Version Value="12"/>
+    <Version Value="10"/>
     <PathDelim Value="\"/>
     <General>
       <Flags>
         <MainUnitHasCreateFormStatements Value="False"/>
         <MainUnitHasTitleStatement Value="False"/>
-        <CompatibilityMode Value="True"/>
       </Flags>
       <SessionStorage Value="InProjectDir"/>
-      <Title Value="PascalCoinMiner"/>
+      <MainUnit Value="0"/>      
       <UseAppBundle Value="False"/>
       <ResourceType Value="res"/>
     </General>
@@ -25,16 +24,9 @@
     </PublishOptions>
     <RunParams>
       <local>
+        <FormatVersion Value="1"/>
         <CommandLineParams Value="-c 1 -s -n TEST"/>
       </local>
-      <FormatVersion Value="2"/>
-      <Modes Count="1">
-        <Mode0 Name="default">
-          <local>
-            <CommandLineParams Value="-c 1 -s -n TEST"/>
-          </local>
-        </Mode0>
-      </Modes>
     </RunParams>
     <RequiredPackages Count="1">
       <Item1>
@@ -44,8 +36,7 @@
     <Units Count="1">
       <Unit0>
         <Filename Value="pascalcoin_miner.pp"/>
-        <IsPartOfProject Value="True"/>
-        <UnitName Value="PascalCoinMiner"/>
+        <IsPartOfProject Value="True"/>        
       </Unit0>
     </Units>
   </ProjectOptions>
@@ -62,7 +53,7 @@
     </SearchPaths>
     <CodeGeneration>
       <Optimizations>
-        <OptimizationLevel Value="4"/>
+        <OptimizationLevel Value="3"/>
       </Optimizations>
     </CodeGeneration>
     <Linking>

+ 24 - 29
src/pascalcoin_miner.pp

@@ -46,7 +46,6 @@ type
   protected
     FWindow32X1,FWindow32Y1,FWindow32X2,FWindow32Y2: DWord;
     FLock : TCriticalSection;
-    FPrivateKey : TECPrivateKey;
     FPoolMinerThread : TPoolMinerThread;
     FDeviceThreads : TList;
     FAppStartTime : TDateTime;
@@ -58,7 +57,7 @@ type
   end;
 
 Const
-  CT_MINER_VERSION = {$IFDEF PRODUCTION}'5.0'{$ELSE}{$IFDEF TESTNET}'5.0 TESTNET'{$ELSE}ERROR{$ENDIF}{$ENDIF};
+  CT_MINER_VERSION = {$IFDEF PRODUCTION}'5.1'{$ELSE}{$IFDEF TESTNET}'5.1 TESTNET'{$ELSE}ERROR{$ENDIF}{$ENDIF};
   CT_Line_DeviceStatus = 3;
   CT_Line_ConnectionStatus = 4;
   CT_Line_MinerValues = 7;
@@ -105,7 +104,7 @@ Const CT_state : Array[boolean] of String = ('Disconnected','Connected');
 var i : Integer;
   s : String;
 begin
-  If FPoolMinerThread.PoolMinerClient.PoolType=ptNone then s:='MINING'
+  If FPoolMinerThread.PoolMinerClient.PoolType=ptSolomine then s:='MINING'
   else s:='POOL MINING USER "'+FPoolMinerThread.PoolMinerClient.UserName+'"';
   If FPoolMinerThread.PoolMinerClient.Connected then begin
     WriteLine(CT_Line_ConnectionStatus,s + ' server: '+FPoolMinerThread.PoolMinerClient.ClientRemoteAddr);
@@ -203,6 +202,7 @@ var
   ErrorMsg: String;
   s : String;
   nsarr : TNodeServerAddressArray;
+  LLog : TLog;
 
   Function AddMiners : Boolean;
   var p,d,c,i : Integer;
@@ -345,17 +345,11 @@ var
 
   Procedure DoVisualprocess(minerName, UserName, Password : String);
   Var sc : tcrtcoord;
-    Flog : TLog;
     devt : TCustomMinerDeviceThread;
     i : Integer;
   Begin
-    FPoolMinerThread := TPoolMinerThread.Create(nsarr[0].ip,nsarr[0].port,FPrivateKey.PublicKey);
+    FPoolMinerThread := TPoolMinerThread.Create(nsarr[0].ip,nsarr[0].port,UserName,Password);
     try
-      If (UserName<>'') then begin
-        FPoolMinerThread.PoolMinerClient.PoolType:=ptIdentify;
-        FPoolMinerThread.PoolMinerClient.UserName:=UserName;
-        FPoolMinerThread.PoolMinerClient.Password:=Password;
-      end;
       If Not AddMiners then exit;
       if HasOption('t','testmode') then begin
         i := StrToIntDef(GetOptionValue('t','testmode'),-1);
@@ -393,12 +387,11 @@ var
         end else begin
           WriteLine(2,'Mining using '+IntToStr(FDeviceThreads.Count)+' devices');
         end;
-        Flog := TLog.Create(Nil);
+        LLog.OnInThreadNewLog:=OnInThreadNewLog;
         try
-          Flog.OnInThreadNewLog:=OnInThreadNewLog;
           DoWaitAndLog;
         finally
-          FLog.free;
+          LLog.OnInThreadNewLog:=Nil;
         end;
       finally
         cursoron;
@@ -411,11 +404,21 @@ var
 
 Var username,password : String;
 begin
+  LLog := TLog.Create(Self);
+  Try
+    If HasOption('l','logfile') then begin
+      s := Trim(GetOptionValue('l','logile'));
+      if s='' then s := 'PascalCoinMiner_'+FormatDateTime('yyyy-mm-dd_hh_nn_ss',Now)+'.log';
+      if HasOption('logall') then LLog.SaveTypes:=CT_TLogTypes_ALL
+      else LLog.SaveTypes:=CT_TLogTypes_DEFAULT;
+      LLog.FileName:=ExtractFileDir(ExeName)+PathDelim+s;
+    end;
+
   FLastLogs := TStringList.Create;
   FLock := TCriticalSection.Create;
   Try
     // quick check parameters
-    ErrorMsg:=CheckOptions('hp:d:s::c:n::t:u::x::', 'help platform device server cpu minername testmode user pwd');
+    ErrorMsg:=CheckOptions('hp:d:s::c:n::t:u::x::l::', 'help platform device server cpu minername testmode user pwd logfile logall');
     if ErrorMsg<>'' then begin
       //ShowException(Exception.Create(ErrorMsg));
       WriteLn(ErrorMsg);
@@ -486,37 +489,27 @@ begin
         WriteLn('Input Pool username (or empty for non pool connection):');
         Readln(username);
       end;
-      if (password='') And (username<>'') then begin
-        WriteLn('Input Pool password for user ',username,':');
-        Readln(password);
-      end;
     end;
 
-    FPrivateKey := TECPrivateKey.Create;
-    Try
-      FPrivateKey.GenerateRandomPrivateKey(CT_Default_EC_OpenSSL_NID);
-      DoVisualprocess(s,username,password);
-    finally
-      FreeAndNil(FPrivateKey);
-    end;
+    DoVisualprocess(s,username,password);
   finally
     FreeAndNil(FLock);
     FreeAndNil(FLastLogs);
     if not terminated then
       Terminate;
   end;
+
+  finally
+    FreeAndNil(LLog);
+  end;
 end;
 
 constructor TPascalMinerApp.Create(TheOwner: TComponent);
-Var FLog : TLog;
 begin
   inherited Create(TheOwner);
   FDeviceThreads := TList.Create;
   StopOnException:=True;
   FAppStartTime := Now;
-  FLog := TLog.Create(self);
-  FLog.SaveTypes:=CT_TLogTypes_DEFAULT;
-  FLog.FileName:=ExtractFileDir(ExeName)+PathDelim+'PascalCoinMiner.log';
 end;
 
 destructor TPascalMinerApp.Destroy;
@@ -537,6 +530,8 @@ begin
   writeln('    Y can be multiple devices. Example -d 0,2,3  Will use devices 0, 2 and 3');
   writeln('  -c N  (For CPU mining, where N is CPU''s to use. Activating this disable GPU mining)');
   writeln('  -n MYNAME  (Will add MYNAME value to miner name assigned by server)');
+  writeln('  -l LOG_FILENAME  (Will log to specified filename or will generate a new filename)');
+  writeln('    --logall log filename will include extra information (like full JSON commands)');
   writeln('  ** POOL IDENTIFICATION PROTOCOL **');
   writeln('  (Not needed for PascalCoin core, only some third party pools)');
   writeln('  -u USERNAME');