diff --git a/x/bearers/types/types.go b/x/bearers/types/types.go new file mode 100644 index 0000000..8a607bf --- /dev/null +++ b/x/bearers/types/types.go @@ -0,0 +1,63 @@ +package types + +import "encoding/json" + +const ( + ModuleName = "bearers" + StoreKey = ModuleName + RouterKey = ModuleName + QuerierRoute = ModuleName +) + +// BearerType defines the transport bearers (§14) +type BearerType string + +const ( + BearerInternet BearerType = "Internet" // Global, carrier-dependent + BearerOYLR BearerType = "OY-LR" // LoRa, 2-10km, surveillance-resistant + BearerOYBLE BearerType = "OY-BLE" // Bluetooth, 10-100m + BearerOYWiFiDirect BearerType = "OY-WiFi-Direct" // 50-200m + BearerOYSAT BearerType = "OY-SAT" // Satellite, global + BearerOYQR BearerType = "OY-QR" // Paper, 0 range +) + +// BearerInfo describes a bearer's properties (§14) +type BearerInfo struct { + Type BearerType `json:"type" yaml:"type"` + RangeMeters int32 `json:"range_meters" yaml:"range_meters"` + CarrierDependent bool `json:"carrier_dependent" yaml:"carrier_dependent"` + SurveillanceResistant bool `json:"surveillance_resistant" yaml:"surveillance_resistant"` +} + +// AllBearers returns all bearer types with their properties (§14) +func AllBearers() []BearerInfo { + return []BearerInfo{ + {BearerInternet, 0, true, false}, + {BearerOYLR, 10000, false, true}, + {BearerOYBLE, 100, false, true}, + {BearerOYWiFiDirect, 200, false, true}, + {BearerOYSAT, 0, false, true}, + {BearerOYQR, 0, false, true}, + } +} + +// UnifiedBearerLayer implements first-to-deliver-wins (§14) +// RFC 5050 Bundle Protocol principles, delay-tolerant networking +type UnifiedBearerLayer struct { + ActiveBearers []BearerType `json:"active_bearers" yaml:"active_bearers"` + FirstToDeliver bool `json:"first_to_deliver" yaml:"first_to_deliver"` +} + +type Params struct{} + +func DefaultParams() Params { return Params{} } + +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +func DefaultGenesisState() *GenesisState { + return &GenesisState{Params: DefaultParams()} +} + +func ValidateGenesis(bz json.RawMessage) error { return nil } diff --git a/x/bearers/types/types_test.go b/x/bearers/types/types_test.go new file mode 100644 index 0000000..ab72bc3 --- /dev/null +++ b/x/bearers/types/types_test.go @@ -0,0 +1,58 @@ +package types_test + +import ( + "testing" + + btypes "github.com/oy/openyield/x/bearers/types" + ptypes "github.com/oy/openyield/x/processing/types" +) + +func TestBearerCount(t *testing.T) { + bearers := btypes.AllBearers() + if len(bearers) != 6 { + t.Errorf("Expected 6 bearers (§14), got %d", len(bearers)) + } +} + +func TestSurveillanceResistantBearers(t *testing.T) { + bearers := btypes.AllBearers() + for _, b := range bearers { + if b.Type == btypes.BearerInternet && b.SurveillanceResistant { + t.Error("Internet bearer should NOT be surveillance-resistant (§14)") + } + if b.Type != btypes.BearerInternet && !b.SurveillanceResistant { + t.Errorf("Bearer %s should be surveillance-resistant (§14)", b.Type) + } + } +} + +func TestProcessingModeFCFS(t *testing.T) { + if ptypes.ModeFCFS != "FCFS" { + t.Error("Processing mode should be FCFS (§15 LOCKED)") + } +} + +func TestLightClientSize(t *testing.T) { + if ptypes.LightClientSizeMB != 30 { + t.Errorf("LightClientSize = %d, expected 30 MB (§15)", ptypes.LightClientSizeMB) + } +} + +func TestProcessorSelectionByProximity(t *testing.T) { + processors := []ptypes.Processor{ + {ProcessorID: "far", Latitude: 40.0, Longitude: 40.0}, + {ProcessorID: "close", Latitude: 10.0, Longitude: 10.0}, + {ProcessorID: "mid", Latitude: 20.0, Longitude: 20.0}, + } + selected := ptypes.SelectProcessorByProximity(10.1, 10.1, processors) + if selected == nil || selected.ProcessorID != "close" { + t.Error("Should select closest processor (§15: geographic proximity wins)") + } +} + +func TestEmptyProcessorSelection(t *testing.T) { + selected := ptypes.SelectProcessorByProximity(10.0, 10.0, []ptypes.Processor{}) + if selected != nil { + t.Error("Empty processor list should return nil") + } +} diff --git a/x/processing/types/types.go b/x/processing/types/types.go new file mode 100644 index 0000000..491cf48 --- /dev/null +++ b/x/processing/types/types.go @@ -0,0 +1,85 @@ +package types + +import "encoding/json" + +const ( + ModuleName = "processing" + StoreKey = ModuleName + RouterKey = ModuleName + QuerierRoute = ModuleName + + // FCFS processing — NOT fee-auctioned (§15 LOCKED) + // Geographic proximity wins (closest processor to recipient) + // Anyone with a phone can process via light client (~30 MB app) + + LightClientSizeMB = 30 // ~30 MB app + BatteryPerDayActivePct = 3 // ~1-3pct battery/day active +) + +// ProcessingMode is FCFS (§15) — never fee-auctioned +type ProcessingMode string + +const ( + ModeFCFS ProcessingMode = "FCFS" // First-come-first-served, not fee-auctioned +) + +// Processor is a light client processing Pass-Acts (§15) +type Processor struct { + ProcessorID string `json:"processor_id" yaml:"processor_id"` + ReachID string `json:"reach_id" yaml:"reach_id"` + Latitude float64 `json:"latitude" yaml:"latitude"` + Longitude float64 `json:"longitude" yaml:"longitude"` + ActiveSince int64 `json:"active_since" yaml:"active_since"` + BlocksProcessed uint64 `json:"blocks_processed" yaml:"blocks_processed"` +} + +// ProcessingJob is a Pass-Act awaiting processing +type ProcessingJob struct { + JobID string `json:"job_id" yaml:"job_id"` + SenderLat float64 `json:"sender_lat" yaml:"sender_lat"` + SenderLng float64 `json:"sender_lng" yaml:"sender_lng"` + RecipientLat float64 `json:"recipient_lat" yaml:"recipient_lat"` + RecipientLng float64 `json:"recipient_lng" yaml:"recipient_lng"` + AmountGrain int64 `json:"amount_grain" yaml:"amount_grain"` + Timestamp int64 `json:"timestamp" yaml:"timestamp"` +} + +// SelectProcessorByProximity — geographic proximity wins (§15) +// In FCFS mode, the closest processor to the recipient processes the job +func SelectProcessorByProximity(recipientLat, recipientLng float64, processors []Processor) *Processor { + if len(processors) == 0 { + return nil + } + closest := &processors[0] + minDist := haversine(recipientLat, recipientLng, closest.Latitude, closest.Longitude) + for i := 1; i < len(processors); i++ { + dist := haversine(recipientLat, recipientLng, processors[i].Latitude, processors[i].Longitude) + if dist < minDist { + minDist = dist + closest = &processors[i] + } + } + return closest +} + +// haversine computes distance between two coordinates (simplified) +func haversine(lat1, lng1, lat2, lng2 float64) float64 { + dlat := lat2 - lat1 + dlng := lng2 - lng1 + return dlat*dlat + dlng*dlng // squared distance (sufficient for comparison) +} + +type Params struct{} + +func DefaultParams() Params { return Params{} } + +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + Processors []Processor `json:"processors" yaml:"processors"` +} + +func DefaultGenesisState() *GenesisState { + return &GenesisState{Params: DefaultParams(), Processors: []Processor{}} +} + +func ValidateGenesis(bz json.RawMessage) error { return nil }