HIP-150: value multiplier tickets and the multiplier on burned sessions - #483
Open
michaeldjeffrey wants to merge 3 commits into
Open
HIP-150: value multiplier tickets and the multiplier on burned sessions#483michaeldjeffrey wants to merge 3 commits into
michaeldjeffrey wants to merge 3 commits into
Conversation
HIP-150 Decision 1 grants a hotspot a multiplier on the data credits
derived from its rewardable bytes, raising both what a payer burns for its
data and what its deployer earns, in the same proportion.
Adds to poc_mobile:
value_multiplier_ticket_req_v1 the grant, signed by an
authorized issuing key
value_multiplier_ticket_resp_v1
value_multiplier_ticket_ingest_report_v1 what ingest writes to s3
verified_value_multiplier_ticket_report_v1 the verifier's outcome,
recording rejections as well as
grants so both are auditable
verified_value_multiplier_ticket_status
rpc submit_value_multiplier_ticket
The multiplier is a helium.Decimal rather than a scaled integer. A message
field is nullable, so an absent multiplier is absent rather than a
reserved value of a scalar — there is no sentinel to misread.
The accepted range is deliberately not stated here. It is policy the
oracles enforce and can change without a schema change, so the wire format
does not encode it; the verifier emits a verified report for a rejected
multiplier, keeping the rejection publicly auditable rather than dropping
it at ingest.
valid_data_transfer_session gains the same type. num_dcs stays the
post-multiplier figure — what was actually burned, and what the reward
path already reads — so this field is what makes the pre-multiplier value
recoverable. Absent means no multiplier was in force or the record
predates HIP-150; readers resolve absent to 1, which needs no sentinel and
keeps old records decoding correctly.
network_key_role gains value_multiplier. Kept separate from
mobile_carrier so that holding a carrier key does not confer the ability
to grant a hotspot a reward multiplier.
michaeldjeffrey
force-pushed
the
mj/hip-150
branch
from
August 25, 2026 17:30
9aaf3f9 to
0e05e39
Compare
bbalser
approved these changes
Aug 25, 2026
Rename: value_multiplier -> data_transfer_multiplier throughout. "Value multiplier" does not say what is multiplied; the multiplier applies to the data credits derived from a hotspot's rewardable bytes, and the new name says so. Covers the ticket messages, the verified report and its status enum, the rpc, and the network_key_role variant. Reserve zero in the status enum as `unspecified` rather than `valid`. Proto3 cannot distinguish an absent field from one explicitly set to zero, so with `valid = 0` a producer that forgets to set the status, a bug that clears it, or a partially-constructed message all decode as a grant. The sibling verified-report enums in this file put `valid` at zero and fail open the same way; that is not followed here, and the comment says why so it is not "fixed" back later. Matches the newer enums in this file (carrier_id_v2_unspecified, ban_reason_unknown). Add `invalid_timestamp` for a ticket dated in the future, or old enough that honouring it would mean acting on a replayed or long-delayed grant. A signature stays valid forever, so without a freshness window a captured ticket is replayable indefinitely.
michaeldjeffrey
force-pushed
the
mj/hip-150
branch
from
August 25, 2026 17:30
0e05e39 to
79c4771
Compare
The verified report is written only by our own packet verifier and read as an audit artifact; it never arrives from an untrusted party. Reserving zero was defending against a bug in a single producer we control, at the cost of an impossible state every consumer has to match on and a divergence from the twelve sibling verified-report enums in this file. The attacker-facing message is data_transfer_multiplier_ticket_req_v1, and it has no status field — what protects it is the signature, the allow-list and the freshness window, none of which this variant contributed to. `valid` returns to zero, matching every other verified-report enum here. invalid_timestamp is unchanged and renumbers with the rest; nothing consumes these values yet, so the renumber is free.
michaeldjeffrey
added a commit
to helium/oracles
that referenced
this pull request
Aug 26, 2026
* HIP-150: accept data transfer multiplier tickets in ingest Adds submit_data_transfer_multiplier_ticket. A ticket grants one hotspot a multiplier on the data credits derived from its rewardable bytes. Ingest verifies the signer and the timestamp, then persists the ticket verbatim. It does not parse the multiplier — the packet verifier decides whether one is acceptable and records that verdict in a verified report, so a rejection stays as auditable as a grant. Notes: - Ticket signers get their own allow-list and is_ticket_signer() check, rather than a NetworkKeyRole variant: that enum belongs to the decommissioned mobile-config. A carrier key must not be able to grant a multiplier. - The list may be empty and defaults to empty, since the release ships before any ticket can be issued. Empty rejects every ticket and warns at startup. - Tickets older than data_transfer_multiplier_ticket_max_age (default 10 minutes) or dated in the future are refused. Signatures never expire, so without this a captured ticket is replayable forever. - valid_data_transfer_session gained a multiplier field with the proto bump; set to None here, so behaviour is unchanged. Cargo.toml patches helium-proto to proto's mj/hip-150 (helium/proto#483). REVERT BEFORE MERGING mj/hip-150 TO main. * HIP-150: tolerate client clock drift on multiplier tickets A client does not share a clock with ingest, so a ticket signed at what the client believes is "now" can arrive stamped slightly ahead of us. Those were refused as post-dated, which is a confusing failure for an honest client with a drifting clock. Tickets up to MAX_CLOCK_DRIFT (1 minute) in the future are now treated as current. Beyond that they are still refused: post-dating must not buy an attacker a longer replay window than an honest client gets, and a ticket inside the allowance still ages out of the freshness window at the same rate, it just starts a minute earlier. The allowance is a shared constant in file-store-oracles rather than a setting in each service. The packet verifier checks freshness too, and measures a ticket's age against the timestamp ingest stamped on it — so if ingest tolerated drift the verifier did not, every ticket ingest accepted from a fast client would be refused downstream. One value, not two that can be configured apart.
michaeldjeffrey
added a commit
to helium/oracles
that referenced
this pull request
Aug 28, 2026
* HIP-150: accept data transfer multiplier tickets in ingest Adds submit_data_transfer_multiplier_ticket. A ticket grants one hotspot a multiplier on the data credits derived from its rewardable bytes. Ingest verifies the signer and the timestamp, then persists the ticket verbatim. It does not parse the multiplier — the packet verifier decides whether one is acceptable and records that verdict in a verified report, so a rejection stays as auditable as a grant. Notes: - Ticket signers get their own allow-list and is_ticket_signer() check, rather than a NetworkKeyRole variant: that enum belongs to the decommissioned mobile-config. A carrier key must not be able to grant a multiplier. - The list may be empty and defaults to empty, since the release ships before any ticket can be issued. Empty rejects every ticket and warns at startup. - Tickets older than data_transfer_multiplier_ticket_max_age (default 10 minutes) or dated in the future are refused. Signatures never expire, so without this a captured ticket is replayable forever. - valid_data_transfer_session gained a multiplier field with the proto bump; set to None here, so behaviour is unchanged. Cargo.toml patches helium-proto to proto's mj/hip-150 (helium/proto#483). REVERT BEFORE MERGING mj/hip-150 TO main. * HIP-150: tolerate client clock drift on multiplier tickets A client does not share a clock with ingest, so a ticket signed at what the client believes is "now" can arrive stamped slightly ahead of us. Those were refused as post-dated, which is a confusing failure for an honest client with a drifting clock. Tickets up to MAX_CLOCK_DRIFT (1 minute) in the future are now treated as current. Beyond that they are still refused: post-dating must not buy an attacker a longer replay window than an honest client gets, and a ticket inside the allowance still ages out of the freshness window at the same rate, it just starts a minute earlier. The allowance is a shared constant in file-store-oracles rather than a setting in each service. The packet verifier checks freshness too, and measures a ticket's age against the timestamp ingest stamped on it — so if ingest tolerated drift the verifier did not, every ticket ingest accepted from a fast client would be refused downstream. One value, not two that can be configured apart.
macpie
approved these changes
Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HIP-150 Decision 1 grants a hotspot a multiplier on the data credits derived from its rewardable bytes, raising both what a payer burns for its data and what its deployer earns, in the same proportion.
Adds to
poc_mobile:value_multiplier_ticket_req_v1:: the grant, signed by an authorized issuing keyvalue_multiplier_ticket_resp_v1value_multiplier_ticket_ingest_report_v1:: ingest writes to s3verified_value_multiplier_ticket_report_v1:: the verifier's outcome, recording rejections as well as grants so both are auditableverified_value_multiplier_ticket_statusrpc submit_value_multiplier_ticketThe multiplier is a helium.Decimal rather than a scaled integer. A message field is nullable, so an absent multiplier is absent rather than a reserved value of a scalar — there is no sentinel to misread.
The accepted range is deliberately not stated here. It is policy the oracles enforce and can change without a schema change, so the wire format does not encode it; the verifier emits a verified report for a rejected multiplier, keeping the rejection publicly auditable rather than dropping it at ingest.
valid_data_transfer_session gains the same type. num_dcs stays the post-multiplier figure — what was actually burned, and what the reward path already reads — so this field is what makes the pre-multiplier value recoverable. Absent means no multiplier was in force or the record predates HIP-150; readers resolve absent to 1, which needs no sentinel and keeps old records decoding correctly.
network_key_role gains value_multiplier. Kept separate from mobile_carrier so that holding a carrier key does not confer the ability to grant a hotspot a reward multiplier.