94 lines
2.3 KiB
Go
94 lines
2.3 KiB
Go
package fleet
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// EarnBeforeBurnConfig gates sibling autospread until local mining proves viable.
|
|
type EarnBeforeBurnConfig struct {
|
|
MinHashrate float64 `json:"min_hashrate"`
|
|
MinDuration time.Duration `json:"min_duration"`
|
|
FirstTierOnly bool `json:"first_tier_only"`
|
|
}
|
|
|
|
// DefaultEarnConfig returns conservative earn-before-burn defaults.
|
|
func DefaultEarnConfig() EarnBeforeBurnConfig {
|
|
return EarnBeforeBurnConfig{
|
|
MinHashrate: 100.0,
|
|
MinDuration: 5 * time.Minute,
|
|
FirstTierOnly: true,
|
|
}
|
|
}
|
|
|
|
// EarnGate tracks local hashrate proof before sibling spread.
|
|
type EarnGate struct {
|
|
Store *Store
|
|
Config EarnBeforeBurnConfig
|
|
}
|
|
|
|
// SpreadDecision indicates whether sibling spread is allowed.
|
|
type SpreadDecision struct {
|
|
Allowed bool `json:"allowed"`
|
|
Reason string `json:"reason"`
|
|
LocalHashrate float64 `json:"local_hashrate"`
|
|
Siblings []string `json:"siblings,omitempty"`
|
|
}
|
|
|
|
// CanSpreadToSiblings checks hashrate gate before autospread to phenotype siblings.
|
|
func (g *EarnGate) CanSpreadToSiblings(ctx context.Context, hostID string) (*SpreadDecision, error) {
|
|
dec := &SpreadDecision{}
|
|
|
|
hr, err := g.Store.HostHashrate(ctx, hostID)
|
|
if err != nil {
|
|
dec.Reason = "host not found"
|
|
return dec, err
|
|
}
|
|
dec.LocalHashrate = hr
|
|
|
|
if hr < g.Config.MinHashrate {
|
|
dec.Reason = fmt.Sprintf("hashrate %.2f below threshold %.2f", hr, g.Config.MinHashrate)
|
|
return dec, nil
|
|
}
|
|
|
|
if g.Config.FirstTierOnly {
|
|
attempts, err := g.Store.ListLOTL(ctx, hostID, 50)
|
|
if err != nil {
|
|
return dec, err
|
|
}
|
|
hasSuccess := false
|
|
for _, a := range attempts {
|
|
if a.Phase == "deploy" && a.Status == "success" {
|
|
hasSuccess = true
|
|
break
|
|
}
|
|
}
|
|
if !hasSuccess {
|
|
dec.Reason = "first tier deploy has not succeeded yet"
|
|
return dec, nil
|
|
}
|
|
}
|
|
|
|
var phenotype string
|
|
err = g.Store.DB().QueryRowContext(ctx, `SELECT COALESCE(phenotype,'') FROM hosts WHERE id = ?`, hostID).Scan(&phenotype)
|
|
if err != nil {
|
|
dec.Reason = "phenotype unknown"
|
|
return dec, err
|
|
}
|
|
|
|
siblings, err := g.Store.SiblingHosts(ctx, hostID, phenotype)
|
|
if err != nil {
|
|
return dec, err
|
|
}
|
|
|
|
dec.Allowed = len(siblings) > 0
|
|
dec.Siblings = siblings
|
|
if !dec.Allowed {
|
|
dec.Reason = "no siblings in phenotype group"
|
|
} else {
|
|
dec.Reason = "earn-before-burn gate passed"
|
|
}
|
|
return dec, nil
|
|
}
|