Files
StarPilot/openpilot/starpilot/flm/live_trial_contract.py
T

263 lines
9.8 KiB
Python

"""Explicit live tracking trial protocol, distinct from replay fit acceptance."""
from dataclasses import dataclass
from collections import OrderedDict
import math
from openpilot.starpilot.flm.canonical_surface_adapter import NAMES, canonical_names, coordinate_bounds, full_profile_sha256
VERSION = 'live-tracking-trial-v1'
MAX_STEP = 0.05
MAX_LEASE_NS = 500_000_000
SLEW_PER_SECOND = 0.005
# A one-percent source experiment ramps at the unchanged 0.5%/s slew.
# Native probes must measure this same amplitude before selection.
SOURCE_EXPERIMENT_STEP = 2 * SLEW_PER_SECOND
MAX_TRIAL_NS = 120_000_000_000
def _text(value, maximum=96):
return type(value) is str and 0 < len(value) <= maximum and value.isascii() and value.isprintable()
def _digest(value):
return type(value) is str and len(value) == 64 and not value.strip('0123456789abcdef')
@dataclass(frozen=True)
class LiveTrialBinding:
identity: str
cp_sha256: str
controller_revision: str
baseline_revision: str
source_epoch: str
reset_generation: str
calibration_generation: str
vehicle_model_generation: str
native_identity: str
physical_model_revision: str
persistent_source_epoch: str
native_required: bool = False
def validate(self):
if type(self) is not LiveTrialBinding or not all(_digest(value) for value in (self.identity, self.cp_sha256, self.baseline_revision, self.source_epoch)):
raise ValueError('live tracking identity binding')
if self.persistent_source_epoch != '' and not _digest(self.persistent_source_epoch):
raise ValueError('optional persistent source identity')
if type(self.native_required) is not bool or (not _digest(self.native_identity) if self.native_required else self.native_identity != ''):
raise ValueError('live ordinary/required native domain')
if not all(
_text(value, 192)
for value in (self.controller_revision, self.reset_generation, self.calibration_generation, self.vehicle_model_generation, self.physical_model_revision)
):
raise ValueError('live tracking source/model generation')
return self
@dataclass(frozen=True)
class LiveTrialOffer:
version: str
binding: LiveTrialBinding
trial_id: str
sequence: int
phase: str
group: str
names: tuple[str, ...]
baseline: tuple[tuple[str, float], ...]
target: tuple[tuple[str, float], ...]
baseline_sha256: str
target_sha256: str
started_ns: int
observed_ns: int
expires_ns: int
response_witness: object = None
target_sequence: int = 1
deadline_ns: int = 0
def validate(self):
return validate_offer(self)
def full_coordinates(rows, baseline_revision):
if (
type(rows) is not tuple
or len(rows) != len(NAMES)
or any(type(row) is not tuple or len(row) != 2 or type(row[0]) is not str or type(row[1]) not in (int, float) or not math.isfinite(row[1]) for row in rows)
or tuple(row[0] for row in rows) != NAMES
):
raise ValueError('exact immutable live full30 coordinates')
for name, value in rows:
low, high = coordinate_bounds(name)
if not low <= value <= high:
raise ValueError('original baseline25percent canonical bound')
return full_profile_sha256(baseline_revision, rows)
_OWNED_OFFER_CONTENT = OrderedDict()
def _content_refs(offer):
# Deep values were validated as exact immutable primitives before retention.
# No receiver permission, freshness or time verdict is retained here.
cls = type(offer)
if cls not in (LiveTrialOffer, LiveRestoreOffer) or cls.__mro__ != (cls, object) or LiveTrialBinding.__mro__ != (LiveTrialBinding, object):
return None
if '__getattr__' in vars(cls) or '__getattribute__' in vars(cls):
return None
own = object.__getattribute__(offer, '__dict__')
if type(own) is not dict or type(own.get('binding')) is not LiveTrialBinding:
return None
binding = object.__getattribute__(own['binding'], '__dict__')
if (
type(own) is not dict
or type(binding) is not dict
or set(own) != set(cls.__dataclass_fields__)
or set(binding) != set(LiveTrialBinding.__dataclass_fields__)
):
return None
if (
'__getattr__' in vars(LiveTrialOffer)
or '__getattribute__' in vars(LiveTrialOffer)
or '__getattr__' in vars(LiveTrialBinding)
or '__getattribute__' in vars(LiveTrialBinding)
):
return None
for checked_class in (cls, LiveTrialBinding):
for name in checked_class.__dataclass_fields__:
value = vars(checked_class).get(name)
if value is not None and hasattr(type(value), '__get__'):
return None
return (
own,
binding,
tuple(own[name] for name in cls.__dataclass_fields__),
tuple(binding[name] for name in LiveTrialBinding.__dataclass_fields__),
)
def _content_current(offer):
saved = _OWNED_OFFER_CONTENT.get(id(offer))
current = _content_refs(offer)
return bool(
saved is not None
and saved[0] is offer
and current is not None
and current[0] is saved[1][0]
and current[1] is saved[1][1]
and all(a is b for a, b in zip(current[2], saved[1][2], strict=True))
and all(a is b for a, b in zip(current[3], saved[1][3], strict=True))
)
def validate_offer(offer):
if _content_refs(offer) is None:
raise ValueError('owned plain live offer shape')
if _content_current(offer):
return offer
if type(offer) is not LiveTrialOffer or type(offer.version) is not str or offer.version != VERSION or type(offer.binding) is not LiveTrialBinding:
raise ValueError('explicit live tracking protocol')
offer.binding.validate()
from openpilot.starpilot.flm.live_tracking_response import validate_response_witness
witness = validate_response_witness(offer.response_witness, offer.binding, offer.observed_ns)
if (
not _digest(offer.trial_id)
or type(offer.sequence) is not int
or not 0 < offer.sequence < 2**64
or type(offer.phase) is not str
or offer.phase not in ('trial', 'confirmed', 'rollback')
or not _text(offer.group, 64)
):
raise ValueError('live trial identity/sequence/phase')
if type(offer.names) is not tuple or any(type(name) is not str for name in offer.names) or canonical_names(offer.names) != offer.names:
raise ValueError('one to four canonical live trial coordinates')
from openpilot.starpilot.flm.canonical_surface_adapter import GROUPS
if offer.group not in GROUPS or not set(offer.names).issubset(GROUPS[offer.group]):
raise ValueError('live canonical group')
if (
not _digest(offer.baseline_sha256)
or not _digest(offer.target_sha256)
or full_coordinates(offer.baseline, offer.binding.baseline_revision) != offer.baseline_sha256
or full_coordinates(offer.target, offer.binding.baseline_revision) != offer.target_sha256
):
raise ValueError('live full30 baseline/target hash')
if any(
type(v) is not int for v in (offer.started_ns, offer.observed_ns, offer.expires_ns)
) or not 0 < offer.started_ns <= offer.observed_ns < offer.expires_ns <= min(offer.observed_ns + MAX_LEASE_NS, witness.valid_until_ns):
raise ValueError('live trial immutable start/current lease')
before = dict(offer.baseline)
for name, value in offer.target:
if name not in offer.names and value != before[name] or abs(value - before[name]) > MAX_STEP + 1e-12:
raise ValueError('small selected live trial step')
if (
type(offer.target_sequence) is not int
or not 0 < offer.target_sequence < 2**64
or type(offer.deadline_ns) is not int
or not offer.started_ns < offer.deadline_ns <= offer.started_ns + MAX_TRIAL_NS
):
raise ValueError('immutable live target sequence/deadline')
if offer.phase == 'trial' and offer.observed_ns > offer.deadline_ns:
raise ValueError('live pending deadline exhausted')
if offer.phase == 'rollback' and offer.target != offer.baseline:
raise ValueError('rollback exact immutable baseline only')
refs = _content_refs(offer)
if refs is not None:
_OWNED_OFFER_CONTENT[id(offer)] = (offer, refs)
_OWNED_OFFER_CONTENT.move_to_end(id(offer))
while len(_OWNED_OFFER_CONTENT) > 8:
_OWNED_OFFER_CONTENT.popitem(last=False)
return offer
RESTORE_VERSION = 'live-tracking-restore-v1'
@dataclass(frozen=True)
class LiveRestoreOffer:
version: str
binding: LiveTrialBinding
history_scope: str
history_trial_id: str
target: tuple[tuple[str, float], ...]
target_sha256: str
sequence: int
observed_ns: int
expires_ns: int
response_witness: object
def validate(self):
if _content_refs(self) is None:
raise ValueError('owned plain restore offer shape')
if _content_current(self):
return self
if type(self) is not LiveRestoreOffer or type(self.version) is not str or self.version != RESTORE_VERSION or type(self.binding) is not LiveTrialBinding:
raise ValueError('separate confirmed-history restore protocol')
self.binding.validate()
if (
not _digest(self.history_scope)
or not _digest(self.target_sha256)
or not _digest(self.history_trial_id)
or full_coordinates(self.target, self.binding.baseline_revision) != self.target_sha256
):
raise ValueError('restore full30 historical target')
if (
type(self.sequence) is not int
or not 0 < self.sequence < 2**64
or type(self.observed_ns) is not int
or type(self.expires_ns) is not int
or not 0 < self.observed_ns < self.expires_ns <= self.observed_ns + MAX_LEASE_NS
):
raise ValueError('restore sequence/lease')
from openpilot.starpilot.flm.live_tracking_response import validate_response_witness
witness = validate_response_witness(self.response_witness, self.binding, self.observed_ns)
if self.expires_ns > witness.valid_until_ns:
raise ValueError('restore cannot extend current response witness')
refs = _content_refs(self)
if refs is not None:
_OWNED_OFFER_CONTENT[id(self)] = (self, refs)
_OWNED_OFFER_CONTENT.move_to_end(id(self))
while len(_OWNED_OFFER_CONTENT) > 8:
_OWNED_OFFER_CONTENT.popitem(last=False)
return self