分布式挖矿系统

一个分布式挖矿系统,包含Job Server和Device Server,支持实时挖矿任务管理和设备通信。

  • by Nileneb
  • Python
  • 本地部署
  • 更新于
在 GitHub 查看 Nileneb/Game-Mcp

使用说明

🚀 MCP矿池系统v2.0

分布式采矿系统mit 2台专用MCP服务器:

  1. 作业服务器 (端口8082) - 通过HTTP /mcp为n8n AI代理(任务管理)
  2. 设备服务器 (端口 8083) - 通过 SSE 和 REST 使用 Unity 设备

真正的采矿集成 没有模拟器,没有假数据。默认情况下启用了 Real Mining 。


快速启动

docker compose up -d --build

先决条件:

  • .env 包含您真正的Monero钱包(XMR_WALLET_ADDRESS)并且包含在repo中。
  • REAL_MINING_ENABLED=1 (已设定)。

重要:容器直接记录真实的 Stratum 事件;不再有模拟设备。


配置 (.env)

相关密钥:

  • FASTMCP_HOST / FASTMCP_PORT (作业服务器,HTTP/mcp)
  • DEVICE_SERVER_HOST / DEVICE_SERVER_PORT (团结苏格兰)
  • XMR_WALLET_ADDRESS (实际采矿需要)
  • POOL_HOST / POOL_PORT (设置 Nanopool Defaults)
  • REAL_MINING_ENABLED=1 (启用 Stratum Client)
  • ASSIGN_TTL, MAX_INFLIGHT_PER_DEVICE, DEFAULT_CHUNK_SIZE, DEFAULT_NUM_TASKS

作业服务器(n8n/HTTP)

  • 端点: http://<host>:8082/mcp
  • 协议:流式HTTP(基于HTTP/SSE的JSON-RPC 2.0)
  • 所需标题: Accept: application/json, text/event-stream, mcp-session-id: <unique>

n8n MCP客户端设置

  • 网址: http://<host>:8082/mcp
  • 传输/协议:HTTP(流式HTTP)
  • 工具:
    • create_mining_job(num_tasks, chunk_size)
    • get_pool_status()
    • get_leaderboard()
    • get_job_details(job_id)
    • list_devices()
    • (实际采矿) get_mining_job(device_id, device_type), submit_mining_result(task_id, nonce, result_hash)

示例调用(curl)

curl -X POST http://localhost:8082/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -H "mcp-session-id: demo-1" \
    -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}'

设备服务器(Unity/SSE)

  • 上海证券交易所 http://<host>:8083/sse?device_id=Unity1
  • 注册(REST): POST /device/register 返回钱包/池
  • 挖掘提交(REST): POST /mining/submit → echte股票/统计数据
  • 状态: GET /status?device_id=...

Unity 通过 SSE 接收任务并发布结果。不再启动假设备。


真实采矿

  • Stratum 客户端在下列情况下自动启动: REAL_MINING_ENABLED=1 和 XMR_WALLET_ADDRESS 已设置。
  • 游泳池设置: xmr-eu1.nanopool.org:10300,工人姓名通过 WORKER_NAME.
  • 日志显示实际的Stratum事件;只要有真正的工作,就不会创建虚拟工作。

操作/故障排除

  • 重新启动 : docker compose restart game-mining-mcp
  • 记录作业/设备: docker logs -f game-mining-mcp
  • Redis: docker logs -f redis
  • 如果无法访问池,请检查钱包/池主机/端口和防火墙。

目录结构(已清除)

  • mcp_job_server.py HTTP /mcp 为 n8n

  • mcp_device_server.py SSE/REST 用于 Unity

  • shared_state.py, redis_state.py --状态/队列

  • stratum_proxy.py — 然而 Stratum 客户端

  • start_mining.sh — 启动工作和设备服务器

  • docker-compose.yml, Dockerfile, .env

  • data/ — 空, 用于到期数据

    头球📊 运行时状态”)] [SerializeField]私有ConnectionStatus状态=ConnectionStatus。断开的; [SerializeField]私有int活动任务=0; [SerializeField]私有int任务已完成=0; [SerializeField]私有int共享提交=0; [SerializeField]私有int sharesAccepted=0; [SerializeField]私有浮点数currentHashrate=0f; [SerializeField]私有字符串lastError=“”;

    头球🐛 调试“)] public bool enableDebugLogs=true; public bool showTaskDetails=false;

    //私人 私有UnityWebRequest请求; 私有SSEDownloadHandler sseHandler; 专用队列 taskQueue=新队列(); 私人列表 activeTaskCoroutines=新列表(); private bool isReconnecting=false;

    //采矿发动机 私人randomxminer; 私人SHA256Miner SHA256Miner;

    //绩效跟踪 私有浮点数lastHashrateUpdate; 私有长合计HashesComputed;

      public void AddTotalHashesComputed(long count)
      {
          totalHashesComputed += count;
      }
    

    公共枚举连接状态 { 断开的, 连接, 有联系的, 重新连接, 错误 }

    公共枚举设备类型 { 汽车, 移动电话, 桌面, 浏览器 }

    公共枚举挖掘算法 { RandomX,//门罗币(CPU友好型) SHA256, // 比特币(用于测试) 以太坊经典(GPU) }

    // ================================================================ //生命周期 // ================================================================

    private void Awake() { if(字符串.IsNullOrEmpty(设备ID)) { deviceId=生成设备ID(); }

      // Device Type Detection
      if (deviceType == DeviceType.Auto)
      {
          deviceType = DetectDeviceType();
      }
    
      // Mining Engine initialisieren
      InitializeMiningEngine();
    

    }

    private void开始() { if(自动连接) { 启动子程序(ConnectToMCPServer()); } }

    私有void更新() { // 计算哈希率 如果(Time.Time-lastHashrateUpdate>5f) { currentHashrate=计算的总哈希数/5f; totalHashesComputed=0; lastHashrateUpdate=Time.Time; } }

    private void OnDestroy() { 断开SSE(); 清洁采矿机(); }

    私有字符串GenerateDeviceId() { string platform=应用程序平台。ToString(); string unique=SystemInfo.deviceUniqueIdentifier。子环(0,8); return$“Unity_{platform}_{unique}“; }

    私有设备类型检测设备类型() { if(Application.platform==运行时平台.WIGLPlayer) 返回设备类型。浏览器; 否则if(Application.isMobilePlatform) 返回设备类型。移动; 否则 返回设备类型。桌面; }

    // ================================================================ //采矿发动机设置 // ================================================================

    private void初始化挖掘引擎() { 开关(算法) { 案例挖掘算法。RandomX: randomXMiner=新的randomXMiner(this); 日志(“🔧 RandomX Miner已初始化”); 断裂;

          case MiningAlgorithm.SHA256:
              sha256Miner = new SHA256Miner();
              Log("🔧 SHA256 Miner initialized");
              break;
    
          case MiningAlgorithm.Ethash:
              LogError("Ethash not yet implemented");
              break;
      }
    

    }

    私有空间清理采矿引擎() { RandomXMiner?.dispose(); sha256Miner?.Dispose(); }

    // ================================================================ //MCP服务器连接-SSE流 // ================================================================

    公共IE分子连接到MCPServer() { if(状态==连接状态.正在连接||状态==已连接状态.已连接) { 日志(“已连接/正在连接”); 产量突破; }

      status = ConnectionStatus.Connecting;
      lastError = "";
    
      Log($"🔌 Connecting to MCP Server: {mcpServerUrl}...");
    
      // 1. Health Check
      yield return HealthCheck();
    
      if (status == ConnectionStatus.Error)
      {
          yield return ReconnectCoroutine();
          yield break;
      }
    
      // 2. Device Registration
      yield return RegisterDevice();
    
      // 3. SSE Stream öffnen (kompatibel mit Device Server Port 8083)
      // Nutzt /sse?device_id= Format (auch /sse/device?device_id= wird unterstützt)
      string sseUrl = $"{mcpServerUrl}/sse?device_id={deviceId}";
    
      sseRequest = UnityWebRequest.Get(sseUrl);
      sseHandler = new SSEDownloadHandler(this);
      sseRequest.downloadHandler = sseHandler;
      sseRequest.SetRequestHeader("Accept", "text/event-stream");
      sseRequest.SetRequestHeader("Cache-Control", "no-cache");
      sseRequest.SetRequestHeader("X-Device-Type", deviceType.ToString());
      sseRequest.SetRequestHeader("X-Algorithm", algorithm.ToString());
    
      sseRequest.SendWebRequest();
    
      // Warte auf Hello Event
      float timeout = 10f;
      while (!sseHandler.isConnected && timeout > 0)
      {
          yield return new WaitForSeconds(0.1f);
          timeout -= 0.1f;
      }
    
      if (sseHandler.isConnected)
      {
          status = ConnectionStatus.Connected;
          Log($"✅ Connected as {deviceId} ({deviceType})");
    
          // Task Processing starten
          StartCoroutine(ProcessTaskQueue());
      }
      else
      {
          status = ConnectionStatus.Error;
          lastError = "SSE connection timeout";
          LogError("Connection timeout");
          yield return ReconnectCoroutine();
      }
    

    }

    私有IEnumerator健康检查() { 使用(var req=UnityWebRequest.Get(mcpServerUrl+“/health”)) { 请求超时=5; 收益回报要求。SendWebRequest();

          if (req.result == UnityWebRequest.Result.Success)
          {
              Log("✅ MCP Server health check OK");
          }
          else
          {
              status = ConnectionStatus.Error;
              lastError = "MCP Server unreachable: " + req.error;
              LogError("Health check failed: " + req.error);
          }
      }
    

    }

    私有IE分子注册设备() { var设备信息=新设备注册 { device_id=设备id, device_type=设备类型。ToString(), 算法=算法。ToString(), max_countrent_tasks=maxConcurrentTasks, platform=应用程序平台。ToString(), system_info=新系统信息2 { processor_type=系统信息处理器类型, processor_count=系统信息处理器计数, system_memory_size=系统信息系统内存大小, graphics_device_name=系统信息图形设备名称 } };

      string json = JsonUtility.ToJson(deviceInfo);
    
      using (var req = new UnityWebRequest(mcpServerUrl + "/device/register", "POST"))
      {
          req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
          req.downloadHandler = new DownloadHandlerBuffer();
          req.SetRequestHeader("Content-Type", "application/json");
          req.timeout = 10;
    
          yield return req.SendWebRequest();
    
          if (req.result == UnityWebRequest.Result.Success)
          {
              Log("✅ Device registered with MCP Server");
    
              try
              {
                  var response = JsonUtility.FromJson<DeviceRegistrationResponse>(req.downloadHandler.text);
                  
                  // Server-verwaltete Wallet verwenden (zentral verwaltet)
                  currentWallet = response.wallet_address;
                  currentPoolAddress = response.pool_address;
    
                  if (string.IsNullOrEmpty(currentWallet))
                  {
                      LogError("⚠️ Keine Wallet vom Server erhalten!");
                  }
                  else
                  {
                      Log($"💰 Wallet assigned: {currentWallet}");
                  }
    
                  if (!string.IsNullOrEmpty(currentPoolAddress))
                  {
                      Log($"🏊 Pool: {currentPoolAddress}");
                  }
              }
              catch (Exception e)
              {
                  LogError($"Registration response parse error: {e.Message}");
              }
          }
          else
          {
              LogError($"Device registration failed: {req.error}");
          }
      }
    

    }

    private void DisconnectSSE() { if(sseRequest!=空) { sseRequest。中止(); sseRequest。Dispose(); sseRequest=空; }

      status = ConnectionStatus.Disconnected;
      Log("🔌 Disconnected from MCP Server");
    

    }

    私有IEnumerator重新连接协程() { 如果(正在重新连接)屈服断裂;

      isReconnecting = true;
      status = ConnectionStatus.Reconnecting;
    
      Log($"🔄 Reconnecting in {reconnectDelay}s...");
    
      DisconnectSSE();
    
      yield return new WaitForSeconds(reconnectDelay);
    
      isReconnecting = false;
      yield return ConnectToMCPServer();
    

    }

    // ================================================================ //SSE事件处理程序 // ================================================================

    公共无效OnSSEConnected() { 日志(“📡 SSE流已打开”); }

    public void OnSSEMessage(字符串事件类型,字符串数据) { 尝试 { if(事件类型==“你好”) { 日志($“👋 MCP服务器:{data}“); } 否则if(事件类型==“mining_job”) { //真实采矿作业vom设备服务器(端口8083) OnMiningJobReceived(数据); } else if(事件类型==“赋值”) { // 支持基于分配的模拟 收到分配(数据); } 否则if(事件类型==“pool_update”) { OnPoolUpdate(数据); } 否则,如果(事件类型==“钱包更新”) { OnWalletUpdate(数据); } 否则 { if(showTaskDetails) 日志($)($)📨 SSE:{eventtype}={data}“); } } catch(例外情况e) { LogError($“SSE消息错误:{e.message}”); } }

    public void OnSSEError(字符串错误) { LogError($“SSE错误:{Error}”); status=连接状态。错误; lastError=错误; StartCoroutine(ReconnectCoroutine); }

    private void OnMiningJobReceived(字符串json) { 尝试 { RealMiningTask任务=JsonUtility。FromJson(json);

          if (task == null || string.IsNullOrEmpty(task.job_id))
          {
              LogError("Invalid mining job data");
              return;
          }
    
          taskQueue.Enqueue(task);
    
          Log($"📥 Mining Job queued: {task.job_id}");
    
          if (showTaskDetails)
          {
              Log($"   Pool: {task.pool_job.pool_address}");
              Log($"   Difficulty: {task.pool_job.difficulty}");
              Log($"   Algorithm: {task.pool_job.algorithm}");
          }
      }
      catch (Exception e)
      {
          LogError($"Mining job parse error: {e.Message}");
      }
    

    }

    private void OnAssignmentReceived(字符串json) { // 传统支持:设备服务器也可以发送“分配”事件 // 如果只有 assignment_id + job_id 尝试 { Log()📨 接收分配(使用mining_job格式以获得最佳兼容性)”; } catch(例外情况e) { LogError($“分配解析错误:{e.Message}”); } }

    private void OnPoolUpdate(字符串json) { 尝试 { var更新=JsonUtility。FromJson(json); 日志($“🏊 池更新:难度={Update.current_difficity},哈希率={Update.Pool_Hashrate}“); } catch(例外情况e) { LogError($“池更新解析错误:{e.Message}”); } }

    private void OnWalletUpdate(字符串json) { 尝试 { var更新=JsonUtility。FromJson(json); currentWallet=update.wallet_address; 日志($“💰 钱包已更新:{currentWallet}“); } catch(例外情况e) { LogError($“钱包更新解析错误:{e.Message}”); } }

    // ================================================================ //基于队列的任务处理 // ================================================================

    私有IE分子进程任务队列() { while(状态==连接状态。已连接) { //清理已完成的任务 activeTaskCoroutines。全部移除(c=>c==null); activeTasks=activeTask-Coroutines。计数;

          // Start new tasks if available
          while (taskQueue.Count > 0 && activeTasks < maxConcurrentTasks)
          {
              RealMiningTask task = taskQueue.Dequeue();
              var coroutine = StartCoroutine(MineRealTask(task));
              activeTaskCoroutines.Add(coroutine);
              activeTasks++;
          }
    
          yield return new WaitForSeconds(0.1f);
      }
    

    }

    // ================================================================ //真实挖掘算法调度 // ================================================================

    私有IE分子MineRealTask(RealMiningTask任务) { Log($"⛏️ 挖掘已开始:{task.job_id}({task.pool_job.algority})“);

      float startTime = Time.time;
    
      // Algorithm-spezifisches Mining
      MiningResult result = null;
    
      switch (task.pool_job.algorithm.ToLower())
      {
          case "randomx":
              yield return randomXMiner.Mine(task, r => result = r);
              break;
    
          case "sha256":
          case "sha256d":
              yield return sha256Miner.Mine(task, r => result = r);
              break;
    
          default:
              LogError($"Unsupported algorithm: {task.pool_job.algorithm}");
              yield break;
      }
    
      float duration = Time.time - startTime;
    
      if (result != null && showTaskDetails)
      {
          Log($"⛏️ Mining completed in {duration:F2}s");
          Log($"   Hashes computed: {result.hashes_computed}");
          Log($"   Hashrate: {result.hashes_computed / duration:F0} H/s");
      }
    
      // Result submitten
      if (result != null)
      {
          yield return SubmitMiningResult(task, result);
      }
    
      tasksCompleted++;
      activeTasks--;
    

    }

    // ================================================================ //结果提交到MCP服务器 // ================================================================

    私有IEnumerator提交挖掘结果(RealMiningTask任务,挖掘结果) { // Mining Submit Payload - 与 8083 端口上的设备服务器兼容 // 文档中的结构: UNITY_CLIENT_COMPAT.md var payload=新的MiningResultPayload { job_id=task.job_id, device_id=设备id, nonce=结果.nonce, hash=result.hash, hashes_computed=result.hashes_computed, 持续时间秒=结果持续时间秒, 算法=task.pool_job算法 };

      string json = JsonUtility.ToJson(payload);
    
      // POST /mining/submit an Device Server (Port 8083)
      using (var req = new UnityWebRequest(mcpServerUrl + "/mining/submit", "POST"))
      {
          req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(json));
          req.downloadHandler = new DownloadHandlerBuffer();
          req.SetRequestHeader("Content-Type", "application/json");
          req.timeout = 10;
    
          yield return req.SendWebRequest();
    
          if (req.result == UnityWebRequest.Result.Success)
          {
              try
              {
                  var response = JsonUtility.FromJson<MiningResultResponse>(req.downloadHandler.text);
    
                  sharesSubmitted++;
    
                  if (response.share_accepted)
                  {
                      sharesAccepted++;
                      Log($"✅ Share ACCEPTED! ({sharesAccepted}/{sharesSubmitted})");
    
                      if (response.block_found)
                      {
                          Log($"🎉🎉🎉 BLOCK FOUND! 🎉🎉🎉");
                          Log($"💰 Reward: {response.reward} XMR");
                      }
                  }
                  else
                  {
                      Log($"❌ Share rejected: {response.reason}");
                  }
              }
              catch (Exception e)
              {
                  LogError($"Submit response parse error: {e.Message}");
              }
          }
          else
          {
              LogError($"Submit failed: {req.error}");
          }
      }
    

    }

    // ================================================================ //公共API // ================================================================

    public void连接() { if(状态!=连接状态。已连接) { 启动子程序(ConnectToMCPServer()); } }

    public void断开连接() { StopAllCoroutines(); 断开SSE(); }

    public void Reconnect() { 断开连接(); 启动子程序(ConnectToMCPServer()); }

    公共字典<string,object>GetStats() { 返回新字典<string,object> { {“status”,state.ToString()}, {“device_id”,deviceId}, {“device_type”,deviceTypeToString()}, {“算法”,algorithm.ToString()}, {“tasks_completed”,tasksCollected}, {“shares_submitted”,shares_submited}, {“shares_accepted”,sharesAccepted}, {“acceptance_rate”,提交的共享>0?(已接受的共享*100f/已提交的共享):0f}, {“current_hashrate”,currentHashrate}, {“active_tasks”,activeTasks}, {“queued_tasks”,taskQueue.Count}, {“钱包”,currentWallet}, {“池”,当前池地址} }; }

    // ================================================================ //调试 // ================================================================

    public void日志(字符串msg) { if(启用DebugLogs) 调试。日志($“[MCP矿业]{msg}”); }

    public void日志错误(字符串msg) { 调试。日志错误($“[MCP挖掘]{msg}”); }

    [上下文菜单(“🔌 连接“)] void MenuConnect()=>Connect();

    [上下文菜单(“🔌 断开连接”)] void菜单断开连接()=>断开();

    [上下文菜单(“🔄 重新连接“)] void菜单重新连接()=>重新连接(;

    [上下文菜单(“📊 显示统计数据”)] void菜单显示统计() { var stats = GetStats(); 调试。日志(“===MCP采矿统计===”); foreach(统计中的var kv) { 调试。日志($“{kv.Key}:{kv.Value}”); } } }

// ================================================================ //矿用发动机 // ================================================================

公共类RandomXMiner { 私人UnityMCPClient客户端;

#如果UNITY_WEBGL&&!UNITY_EDITOR [DllImport(“__内部”)] 私有静态外部void InitRandomX();

[DllImport("__Internal")]
private static extern string ComputeRandomX(string input);

#endif

public RandomXMiner(UnityMCPClient client)
{
    this.client = client;

#如果UNITY_WEBGL&&!UNITY_EDITOR Initrandomxtools); #endif }

public IEnumerator Mine(RealMiningTask task, System.Action<MiningResult> callback)
{
    float startTime = Time.time;
    long hashCount = 0;

    string blob = task.pool_job.blob;
    long target = task.pool_job.target;

    // Nonce Range aus Task
    uint nonceStart = task.nonce_start;
    uint nonceEnd = task.nonce_end;

    for (uint nonce = nonceStart; nonce < nonceEnd; nonce++)
    {
        // Nonce in Blob einfügen (Bytes 39-42 bei Monero)
        string blobWithNonce = InsertNonce(blob, nonce);

        // RandomX Hash berechnen
        string hash = ComputeRandomXHash(blobWithNonce);
        hashCount++;

        // Target check
        if (CheckTarget(hash, target))
        {
            // SHARE GEFUNDEN!
            var result = new MiningResult
            {
                nonce = (int)nonce,
                hash = hash,
                hashes_computed = hashCount,
                duration_seconds = Time.time - startTime
            };

            callback?.Invoke(result);
            yield break;
        }

        // Frame Budget
        if (hashCount % client.hashesPerFrame == 0)
        {
            client.AddTotalHashesComputed(client.hashesPerFrame);
            yield return null;
        }
    }

    // Kein Share gefunden, trotzdem Statistik zurückgeben
    callback?.Invoke(new MiningResult
    {
        nonce = 0,
        hash = "",
        hashes_computed = hashCount,
        duration_seconds = Time.time - startTime
    });
}

private string ComputeRandomXHash(string blob)
{

#如果UNITY_WEBGL&&!UNITY_EDITOR 返回ComputeRandomX(blob); #其他 // Native C# RandomX (如果可用) 或 Fallback // TODO: 包含 RandomX 库 返回“00000000000000000000000000000000000000000000000000000000000”; #endif }

private string InsertNonce(string blob, uint nonce)
{
    // Monero: Nonce an Position 39 (Byte 78-85 in Hex)
    byte[] bytes = HexToBytes(blob);
    byte[] nonceBytes = BitConverter.GetBytes(nonce);

    Array.Copy(nonceBytes, 0, bytes, 39, 4);

    return BytesToHex(bytes);
}

private bool CheckTarget(string hash, long target)
{
    // Hash < Target prüfen
    // TODO: Proper BigInteger comparison
    return hash.StartsWith("0000");
}

private byte[] HexToBytes(string hex)
{
    byte[] bytes = new byte[hex.Length / 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
    }
    return bytes;
}

private string BytesToHex(byte[] bytes)
{
    StringBuilder sb = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
    {
        sb.Append(b.ToString("x2"));
    }
    return sb.ToString();
}

public void Dispose()
{
    // Cleanup
}

}

公共类SHA256Miner { 私人系统。安全。密码学。SHA256 SHA256;

public SHA256Miner()
{
    sha256 = System.Security.Cryptography.SHA256.Create();
}

public IEnumerator Mine(RealMiningTask task, System.Action<MiningResult> callback)
{
    float startTime = Time.time;
    long hashCount = 0;

    string blockHeader = task.pool_job.blob;
    int difficulty = task.pool_job.difficulty;
    string targetPrefix = new string('0', difficulty);

    for (int nonce = (int)task.nonce_start; nonce < (int)task.nonce_end; nonce++)
    {
        string input = $"{blockHeader}:{nonce}";
        string hash = ComputeSHA256(input);
        hashCount++;

        if (hash.StartsWith(targetPrefix))
        {
            var result = new MiningResult
            {
                nonce = nonce,
                hash = hash,
                hashes_computed = hashCount,
                duration_seconds = Time.time - startTime
            };

            callback?.Invoke(result);
            yield break;
        }

        if (hashCount % 10000 == 0)
        {
            yield return null;
        }
    }

    callback?.Invoke(new MiningResult
    {
        nonce = 0,
        hash = "",
        hashes_computed = hashCount,
        duration_seconds = Time.time - startTime
    });
}

private string ComputeSHA256(string input)
{
    byte[] inputBytes = Encoding.UTF8.GetBytes(input);
    byte[] hashBytes = sha256.ComputeHash(inputBytes);

    StringBuilder sb = new StringBuilder(64);
    foreach (byte b in hashBytes)
    {
        sb.Append(b.ToString("x2"));
    }

    return sb.ToString();
}

public void Dispose()
{
    sha256?.Dispose();
}

}

// ================================================================ //数据模型-真实挖掘 // ================================================================

[可序列化] 公共类RealMiningTask { 公共字符串job_id; 公共PoolJob pool_job; public uint nonce-start; public uint noncend; 公共字符串wallet_address; }

[可序列化] 公共类PoolJob { 公共字符串pool_address; 公共字符串blob; 公共长期目标; 公共信息困难; 公共字符串算法; 公共点高度; }

[可序列化] 公共类MiningResult { public int nonce; 公共字符串哈希; 公共长哈希计算; 公众持股持续时间_秒; }

[可序列化] 公共类设备注册 { 公共字符串device_id; 公共字符串device_type; 公共字符串算法; 公共int max_countrent_tasks; 公共字符串平台; 公共系统信息2系统信息; }

[可序列化] 公共类SystemInfo2 { 公共字符串处理器_ type; 公共int处理器计数; public int system_memory_size; 公共字符串graphics_device_name; }

[可序列化] 公共类设备注册响应 { 公共布尔成功; 公共字符串wallet_address; 公共字符串pool_address; 公共字符串消息; }

[可序列化] 公共类MiningResultPayload { 公共字符串job_id; 公共字符串device_id; public int nonce; 公共字符串哈希; 公共长哈希计算; 公众持股持续时间_秒; 公共字符串算法; }

[可序列化] 公共类MiningResultResponse { 公共bool共享接受; 公共bool block_found; 公众浮动奖励; 公共字符串原因; 公众持股流动平衡; }

[可序列化] 公共类PoolUpdateEvent { public int当前困难; 公共字符串pool_hashrate; 公共点连通矿工; }

[可序列化] 公共类WalletUpdateEvent { 公共字符串wallet_address; 公众持股余额; }

// ================================================================ // SSE DOWNLOAD HANDLER (未更改,可用) // ================================================================

公共类SSEDownloadHandler:DownloadHandlerScript { 私人UnityMCPClient客户端; 私有StringBuilder缓冲区=new StringBuilder(); public bool isConnected=false;

public SSEDownloadHandler(UnityMCPClient client) : base()
{
    this.client = client;
}

protected override bool ReceiveData(byte[] data, int dataLength)
{
    if (data == null || dataLength == 0)
        return true;

    string text = Encoding.UTF8.GetString(data, 0, dataLength);
    buffer.Append(text);

    string bufferStr = buffer.ToString();
    string[] parts = bufferStr.Split(new[] { "\n\n" }, StringSplitOptions.None);

    for (int i = 0; i < parts.Length - 1; i++)
    {
        ParseSSEEvent(parts[i]);
    }

    buffer.Clear();
    buffer.Append(parts[parts.Length - 1]);

    return true;
}

private void ParseSSEEvent(string eventData)
{
    if (string.IsNullOrEmpty(eventData))
        return;

    string[] lines = eventData.Split('\n');
    string eventType = "";
    string data = "";

    foreach (string line in lines)
    {
        if (line.StartsWith("event: "))
        {
            eventType = line.Substring(7).Trim();
        }
        else if (line.StartsWith("data: "))
        {
            data = line.Substring(6).Trim();
        }
    }

    if (!string.IsNullOrEmpty(data))
    {
        if (!isConnected && data.Contains("hello"))
        {
            isConnected = true;
            client.OnSSEConnected();
        }

        try
        {
            // Neue Parsing-Logik für Device Server (Port 8083)
            // Der Server sendet: data: {json-object mit "type" Feld}
            if (data.StartsWith("{"))
            {
                // Versuche, den "type" aus dem JSON zu extrahieren
                int typeStart = data.IndexOf("\"type\":\"") + 8;
                int typeEnd = data.IndexOf("\"", typeStart);

                if (typeStart > 7 && typeEnd > typeStart)
                {
                    string type = data.Substring(typeStart, typeEnd - typeStart);
                    // Nutze extrahierten Type als eventType
                    client.OnSSEMessage(type, data);
                }
                else
                {
                    // Fallback: Nutze expliziten event Type falls vorhanden
                    if (!string.IsNullOrEmpty(eventType))
                    {
                        client.OnSSEMessage(eventType, data);
                    }
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogWarning($"SSE parse error: {e.Message}");
        }
    }
}

protected override void CompleteContent()
{
    client.OnSSEError("Stream closed");
}

}


### REST API (Alternative zu SSE)

Falls SSE nicht funktioniert, kann Unity auch pollen:

```bash
# Status abrufen
curl "http://192.168.178.12:8083/status?device_id=Unity1"

🔄 工作流程

1.n8n人工智能代理销售任务

AI Agent: "Erstelle einen Mining Job mit 20 Tasks"
  ↓
Tool: create_mining_job(num_tasks=20)
  ↓
Server: Block #5 wird generiert, 20 Tasks erstellt
  ↓
Tasks landen in Queue

2.Unity设备完成任务(通过SSE!)

Unity1 verbindet zu SSE
  ↓
Bekommt sofort Task 1 gepusht
  ↓
Beginnt Mining (nonce 0-1000000)
  ↓
Während dem Mining bekommt Unity1 Task 2 gepusht
  (wenn max_inflight=2)

3.结果提交

Unity1 findet Hash "0000abc..."
  ↓
POST /result {nonce: 123456, hash: "0000..."}
  ↓
Server prüft Hash
  ↓
WINNER! 50 Coins für Unity1
  ↓
Block #5 zur Blockchain hinzugefügt
  ↓
Job als "completed" markiert
  ↓
Alle anderen Tasks cancelled

4.超时并重试

Unity2 bekommt Task 5
  ↓
120 Sekunden vergehen
  ↓
Unity2 antwortet nicht
  ↓
Task 5 wird automatisch neu vergeben
  ↓
Unity3 bekommt Task 5

🎯 这种架构的优势

✅ 推动而不是投票

  • Unity不等待,立即获取任务
  • 无 /next Polling 更多
  • 更有效率!

✅ 清洁分离

  • 作业管理≠任务分配
  • 不同的安全可能
  • 独立可扩展

✅ 自动重试

  • 超时会自动处理
  • 没有丢失的任务
  • 就像示例代码一样。

✅ 内存队列

  • asyncio.Queue 对于设备
  • 快速简单
  • 完美用于原型设计

📊 监控

作业服务器运行状况

curl http://192.168.178.12:8082/health

设备服务器运行状况

curl http://192.168.178.12:8083/health

设备状态

curl "http://192.168.178.12:8083/status?device_id=Unity1"

🔐 可选:HMAC签名

对于生产环境,您可以启用 HMAC:

  1. .env 创建 :
HMAC_SECRET=your_secret_key_here
  1. 然后,Unity 必须发送签名:
string sig = ComputeHMAC(assignmentId, secret);

🐛 调试

日志

两个服务器都会详细记录:

📨 MCP: tools/call
🔧 Tool: create_mining_job({'num_tasks': 10})
📋 Job job_abc123: Block #5, 10 tasks, diff=4
🎮 Device connected: Unity1
📤 Unity1: Assignment sent
🎉 POTENTIAL WINNER: Unity1 found hash!
🏆 WINNER CONFIRMED: Unity1 - Block #5 - Reward: 50.0 coins

测试没有Unity

# SSE verbinden (Terminal)
curl -N "http://192.168.178.12:8083/sse?device_id=test1"

# In anderem Terminal: Result senden
curl -X POST http://192.168.178.12:8083/result \
  -H "Content-Type: application/json" \
  -d '{
    "assignment_id": "asg_xxx",
    "job_id": "job_xxx",
    "device_id": "test1",
    "nonce": 0,
    "hash": "",
    "conf": 1.0
  }'

🚀 后续步骤

  1. 启动服务器:

    ./start_mining.sh
    
  2. n8n MCP工具:

    • SSE端点: http://192.168.178.12:8082/sse
    • 测试: create_mining_job
  3. Unity SSE:

    • 复制C#代码
    • 连接到 : http://192.168.178.12:8083/sse?device_id=Unity1
  4. AI代理提示:

    Erstelle einen Mining Job mit 10 Tasks.
    Wie ist der Pool Status?
    Zeige mir das Leaderboard.
    

📝 数据概述

mining-pool/
├── shared_state.py          # Gemeinsamer State (Blockchain, Jobs, Queue)
├── mcp_job_server.py        # Job Management für n8n (Port 8082)
├── mcp_device_server.py     # Task Distribution für Unity (Port 8083)
├── requirements.txt         # Python Dependencies
├── start_mining.sh          # Startup Script
└── mining.env               # Config (optional)

💡 提示

  • 调整难度: 在 shared_state.py → base_difficulty
  • 更多设备: 使用多个Unity客户端 device_id
  • 生产: Redis 而不是内存中的多服务器设置
  • 监控: 添加 Prometheus Metrics

祝矿业成功! (2)⛏️