The pinaivu-protocol crate (coordinator/src/pinaivu-protocol/) defines
every type that goes over the wire — both gossipsub broadcasts and
request-response messages — plus signing helpers. Both the coordinator
and the node depend on it; the node’s git dependency points at
Pinaivu-AI/Coordinator with package = "pinaivu-protocol" so providers
don’t need to clone the coordinator code itself.
Types
| Type | Where it travels | Signed by |
|---|
InferenceRequest | gossipsub /pinaivu/inference/any/1.0.0 | none (client identity carried in body) |
InferenceBid | gossipsub /pinaivu/bids/1.0.0 | none in v1 (libp2p mesh authenticates the sender) |
DispatchToken | HTTP body (coordinator → client → node) | coordinator’s enclave key |
ProofOfInference | nested inside CompletionAck | each contributing node’s key |
CompletionAck | libp2p request-response /pinaivu/completion/1.0.0 | primary node’s key (covers the proof set) |
RecruitRequest / RecruitResponse | libp2p request-response /pinaivu/recruit/1.0.0 | primary’s signed request; helper returns a signed proof |
RoutingReceipt | stored in Postgres, served via GET /v1/proofs/{id} | coordinator’s enclave key |
All signatures are Ed25519. Most types use serde-JSON canonical bytes as
the signed message (canonical_bytes() on each type).
RoutingReceipt is different — it’s signed as a BCS-encoded
IntentMessage, matching the shape pinaivu::receipts::ReceiptPayload
expects on-chain so enclave::verify_signature accepts it natively:
sign( bcs(IntentMessage {
intent: 1, // INTENT_ROUTING_RECEIPT
timestamp_ms: u64,
payload: ReceiptPayload {
request_id: vector<u8>, // 16-byte UUID
aggregated_output_hash: vector<u8>, // 32-byte SHA-256
payouts: vector<Payout>, // {sui_address: address, amount: u64}
},
})
)
v1 limitation: the receipt signature only covers the settlement
subset — (request_id, aggregated_output_hash, payouts). The receipt
struct also carries descriptive metadata (client_id, primary_peer_id,
helper_peer_ids, proof_ids, bid_set_hash) that the off-chain
explorer renders, but those fields are not cryptographically committed
in v1. A future tighter signature can add a full_receipt_hash covered
by the same payload.
Topic names
gossipsub:
/pinaivu/inference/any/1.0.0 broadcast inference requests
/pinaivu/bids/1.0.0 broadcast bids
/pinaivu/announce/1.0.0 (planned) periodic NodeCapabilities
/pinaivu/reputation/1.0.0 (planned) reputation roots
request-response:
/pinaivu/completion/1.0.0 node → coordinator: CompletionAck
/pinaivu/recruit/1.0.0 node → node: RecruitRequest
kademlia:
/pinaivu/kad/1.0.0 DHT (isolated from the public libp2p DHT)
libp2p behaviour composition
pinaivu_protocol::mesh::PinaivuBehaviour is a single
#[derive(NetworkBehaviour)] struct used by both the coordinator and
every node, so peer-id derivation, gossipsub mesh formation, and
request-response routing are identical on both sides:
PinaivuBehaviour {
gossipsub: pub/sub for auction topics over the decentralized GPU mesh
kademlia: peer routing
identify: protocol negotiation + observed addrs
ping: liveness
completion: request_response::cbor for CompletionAck
recruit: request_response::cbor for RecruitRequest
}
Multi-node recruitment (/pinaivu/recruit/1.0.0)
The wire-level primitives for a primary node to recruit a helper node for
part of a job are shipped — RecruitRequest/RecruitResponse round-trip
correctly, and CompletionAck.proofs already accepts more than one
ProofOfInference. The coordinator extracts proofs[0] as primary and
proofs[1..] as helpers when it builds the RoutingReceipt, with no
coordinator-side changes needed. The actual orchestration — deciding
how to split a job, which helper to pick, and running the map/reduce — is
intentionally out of scope for the node binary itself and is handled by a
separate orchestration engine that drives the SendRecruit command.