mirror of
https://github.com/luxfi/session.git
synced 2026-10-30 15:17:14 +00:00
227 lines
5.7 KiB
Go
227 lines
5.7 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package protocol
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/luxfi/session/core"
|
|
)
|
|
|
|
// RequestKind indicates the type of oracle request.
|
|
type RequestKind uint8
|
|
|
|
const (
|
|
// RequestKindWrite - external write request
|
|
RequestKindWrite RequestKind = iota
|
|
// RequestKindRead - external read request
|
|
RequestKindRead
|
|
)
|
|
|
|
func (k RequestKind) String() string {
|
|
switch k {
|
|
case RequestKindWrite:
|
|
return "write"
|
|
case RequestKindRead:
|
|
return "read"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// RequestStatus indicates the status of an oracle request.
|
|
type RequestStatus uint8
|
|
|
|
const (
|
|
RequestStatusPending RequestStatus = iota // Waiting for records
|
|
RequestStatusCommitted // Records committed
|
|
RequestStatusAttested // Attestation received
|
|
)
|
|
|
|
func (s RequestStatus) String() string {
|
|
switch s {
|
|
case RequestStatusPending:
|
|
return "pending"
|
|
case RequestStatusCommitted:
|
|
return "committed"
|
|
case RequestStatusAttested:
|
|
return "attested"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// OracleRequest represents a request for external I/O.
|
|
type OracleRequest struct {
|
|
// ID is the deterministic request identifier
|
|
// H("LUX:OracleRequest:v1" || service_id || session_id || step || retry || tx_id)
|
|
ID core.ID `json:"id"`
|
|
|
|
// ServiceID identifies the service
|
|
ServiceID core.ID `json:"serviceId"`
|
|
|
|
// SessionID identifies the session
|
|
SessionID core.ID `json:"sessionId"`
|
|
|
|
// StepIndex is the step number
|
|
StepIndex uint32 `json:"stepIndex"`
|
|
|
|
// RetryIndex for retry attempts
|
|
RetryIndex uint32 `json:"retryIndex"`
|
|
|
|
// TxID that triggered this request
|
|
TxID core.ID `json:"txId"`
|
|
|
|
// Kind of request (read or write)
|
|
Kind RequestKind `json:"kind"`
|
|
|
|
// Status of the request
|
|
Status RequestStatus `json:"status"`
|
|
|
|
// InputHash is the hash of request inputs
|
|
InputHash core.ID `json:"inputHash"`
|
|
|
|
// Records collected for this request
|
|
Records []*OracleRecord `json:"records,omitempty"`
|
|
|
|
// CommitRoot is the Merkle root of committed records
|
|
CommitRoot core.ID `json:"commitRoot,omitempty"`
|
|
|
|
// AttestationID is the attestation over the commit
|
|
AttestationID core.ID `json:"attestationId,omitempty"`
|
|
|
|
// CreatedAt is when the request was created
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
|
|
// CommittedAt is when records were committed
|
|
CommittedAt time.Time `json:"committedAt,omitempty"`
|
|
}
|
|
|
|
// OracleRecord represents a single oracle observation.
|
|
type OracleRecord struct {
|
|
// RecordID is the unique record identifier
|
|
RecordID core.ID `json:"recordId"`
|
|
|
|
// RequestID this record belongs to
|
|
RequestID core.ID `json:"requestId"`
|
|
|
|
// SubmitterID is the node that submitted this record
|
|
SubmitterID core.ID `json:"submitterId"`
|
|
|
|
// Data is the record payload
|
|
Data []byte `json:"data"`
|
|
|
|
// DataHash is the hash of the data
|
|
DataHash core.ID `json:"dataHash"`
|
|
|
|
// Signature from the submitter
|
|
Signature []byte `json:"signature"`
|
|
|
|
// CreatedAt is when the record was created
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// OracleCommit represents a committed set of oracle records.
|
|
type OracleCommit struct {
|
|
// RequestID this commit is for
|
|
RequestID core.ID `json:"requestId"`
|
|
|
|
// MerkleRoot of all records
|
|
MerkleRoot core.ID `json:"merkleRoot"`
|
|
|
|
// RecordCount is the number of records
|
|
RecordCount uint32 `json:"recordCount"`
|
|
|
|
// Epoch when committed
|
|
Epoch uint64 `json:"epoch"`
|
|
|
|
// CommittedAt is when the commit was created
|
|
CommittedAt time.Time `json:"committedAt"`
|
|
}
|
|
|
|
// NewOracleRequest creates a new oracle request.
|
|
func NewOracleRequest(serviceID, sessionID, txID, inputHash core.ID, step, retry uint32, kind RequestKind) *OracleRequest {
|
|
return &OracleRequest{
|
|
ID: core.ComputeRequestID(serviceID, sessionID, txID, step, retry),
|
|
ServiceID: serviceID,
|
|
SessionID: sessionID,
|
|
StepIndex: step,
|
|
RetryIndex: retry,
|
|
TxID: txID,
|
|
Kind: kind,
|
|
Status: RequestStatusPending,
|
|
InputHash: inputHash,
|
|
Records: []*OracleRecord{},
|
|
CreatedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// NewOracleRecord creates a new oracle record.
|
|
func NewOracleRecord(requestID, submitterID core.ID, data []byte, signature []byte) *OracleRecord {
|
|
dataHash := core.Hash(data)
|
|
return &OracleRecord{
|
|
RecordID: core.HashMulti(requestID[:], submitterID[:], dataHash[:]),
|
|
RequestID: requestID,
|
|
SubmitterID: submitterID,
|
|
Data: data,
|
|
DataHash: dataHash,
|
|
Signature: signature,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// ComputeMerkleRoot computes the Merkle root of a set of records.
|
|
// Uses a simple binary Merkle tree construction.
|
|
func ComputeMerkleRoot(records []*OracleRecord) core.ID {
|
|
if len(records) == 0 {
|
|
return core.ID{}
|
|
}
|
|
|
|
// Get leaf hashes
|
|
leaves := make([]core.ID, len(records))
|
|
for i, r := range records {
|
|
leaves[i] = r.DataHash
|
|
}
|
|
|
|
// Build tree bottom-up
|
|
for len(leaves) > 1 {
|
|
var nextLevel []core.ID
|
|
for i := 0; i < len(leaves); i += 2 {
|
|
if i+1 < len(leaves) {
|
|
nextLevel = append(nextLevel, core.HashMulti(leaves[i][:], leaves[i+1][:]))
|
|
} else {
|
|
nextLevel = append(nextLevel, leaves[i])
|
|
}
|
|
}
|
|
leaves = nextLevel
|
|
}
|
|
|
|
return leaves[0]
|
|
}
|
|
|
|
// MerkleProof represents a Merkle inclusion proof.
|
|
type MerkleProof struct {
|
|
// LeafIndex is the index of the leaf
|
|
LeafIndex uint32 `json:"leafIndex"`
|
|
|
|
// Siblings are the sibling hashes along the path
|
|
Siblings []core.ID `json:"siblings"`
|
|
|
|
// IsLeft indicates if the sibling is on the left at each level
|
|
IsLeft []bool `json:"isLeft"`
|
|
}
|
|
|
|
// VerifyMerkleProof verifies a Merkle inclusion proof.
|
|
func VerifyMerkleProof(root, leaf core.ID, proof *MerkleProof) bool {
|
|
current := leaf
|
|
for i, sibling := range proof.Siblings {
|
|
if proof.IsLeft[i] {
|
|
current = core.HashMulti(sibling[:], current[:])
|
|
} else {
|
|
current = core.HashMulti(current[:], sibling[:])
|
|
}
|
|
}
|
|
return current == root
|
|
}
|