Skip to content

Stars and roles

Every participant in the network is a Star: a record in StarRegistry with an owner, a signing key, a role and the epoch it registered in.

struct StarInfo {
address owner; // cold wallet, receives rewards
address signer; // hot key on the server
Role role; // None | Node | CP | Banned
uint64 registerEpoch;
}

The owner is a cold wallet. The money goes there, and it is the only address that can change anything about the Star. Ownership cannot be transferred: the owner is set at registration and stays for good.

The signer is a hot key on the server. The program signs its working messages with it: intervals served, reports, calls to other participants.

  • A node sends no transactions with its signer.
  • A Control Plane does: publishing time served and relaying gas-free operations are paid from the signer’s address. That address needs ETH.

Taking over the server gets you the signer, not the money: claimNode and claimCp read the payee from the registry, not from whoever made the call.

One address can sign for only one Star. register and updateSigner refuse a signer that already belongs to another.

StarRegistry.updateSigner(starId, newSigner)

Only the owner can call it, from the cold wallet. In the same transaction the old key stops being associated with the Star, and anything signed with it afterwards belongs to nobody. A signer already bound to another Star is refused.

In Stargate this is the Rotate signer button on the Star’s card — the same call, signed by the same wallet.

A full recovery goes like this:

  1. Shut the compromised server down.
  2. Bring up a clean one and take its new signer address from the startup banner.
  3. Call updateSigner from the owner wallet.

A node’s signer holds no ETH. A Control Plane’s holds only a little — see funding the signer — so there is nothing worth moving off the old address.

RoleValueWhat it means
None0No Star: never registered, or deregistered
Node1Carries traffic, earns from the node pool
CP2Issues sessions and publishes time served, earns from the CP pool
Banned3Blocked: earns nothing and cannot publish

The node pool and the CP pool are separate money, filled by different shares of each Orbit payment. A Control Plane takes no cut of what nodes earn.

StarRegistry.register(signer)

Anyone can call it. No stake, no queue, no approval. It returns a starId, and the role is Node straight away. One owner can hold any number of Stars.

To leave:

StarRegistry.deregister(starId)

Owner only, and only while the role is Node. A Control Plane is demoted first.

For now, during the test stage, the founder grants the CP role by hand, by calling forceSetRole, which only the registry’s owner can do. If you want to run a Control Plane, get in touch. The contracts have no other way to get the role yet.

Later the role will need nobody’s decision. A Star puts up a stake, and if it meets the requirements the contract promotes it to Control Plane by itself. The requirements — the size of the stake and the conditions for promotion — will in the end be set by the DAO, not the founder.

Why not this way from the start: whoever grants the CP role decides who clients get their sessions through, so granting it is opened up last, once reports of work and penalties for dishonest ones have been proven.

Everything else already needs nobody’s permission. The endpoint list is public, clients poll it themselves and choose by response time, and joining the federation does not need the other members’ consent.

forceSetRole(starId, Banned, withSlashing) blocks a Star. The second argument adds a permanent mark:

slashedCp[signer] = true

The mark is set only when the Star’s previous role was CP. Banning a node with the flag does nothing extra. Once set, the mark is never cleared, and both EpochAttestations.push and RewardDistributor.claimCp refuse a marked signer.

The mark sits on the address, not on the Star. Rotating to a new signer gives the Star a clean key, and the old address stays marked for good.

CPEndpointRegistry.setEndpoint(starId, host, port) is called by the Star’s owner, and only while the role is CP. Port 0 means “the protocol’s default” and is stored as such.

If the Star loses the role, the row stays, but that does no harm: every read checks the role again, so getActiveEndpoints() stops returning a demoted Star at once. Anyone can free the storage with cleanupStale(starIds).

CPEndpointRegistry.setRealityKey(starId, key) publishes the public key of the Control Plane’s front, 32 bytes. Same rule: the owner, while the role is CP. getRealityKey(starId) returns the key, or zero for a Star that is not a Control Plane, and clearEndpoint removes the key together with the endpoint.