mirror of
https://github.com/luxfi/indexer.git
synced 2026-09-24 00:25:37 +00:00
844 lines
21 KiB
Go
844 lines
21 KiB
Go
// Copyright (c) 2025 Lux Industries Inc
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package quantum
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/indexer/dag"
|
|
)
|
|
|
|
// TestNew tests the adapter constructor
|
|
func TestNew(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
endpoint string
|
|
wantDefault bool
|
|
}{
|
|
{
|
|
name: "custom endpoint",
|
|
endpoint: "http://custom:9999/rpc",
|
|
wantDefault: false,
|
|
},
|
|
{
|
|
name: "empty uses default",
|
|
endpoint: "",
|
|
wantDefault: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
adapter := New(tt.endpoint)
|
|
if adapter == nil {
|
|
t.Fatal("New returned nil")
|
|
}
|
|
if adapter.httpClient == nil {
|
|
t.Error("httpClient is nil")
|
|
}
|
|
if tt.wantDefault && adapter.rpcEndpoint != DefaultRPCEndpoint {
|
|
t.Errorf("expected default endpoint %s, got %s", DefaultRPCEndpoint, adapter.rpcEndpoint)
|
|
}
|
|
if !tt.wantDefault && adapter.rpcEndpoint != tt.endpoint {
|
|
t.Errorf("expected endpoint %s, got %s", tt.endpoint, adapter.rpcEndpoint)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConstants tests Q-Chain specific constants
|
|
func TestConstants(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value interface{}
|
|
want interface{}
|
|
}{
|
|
{"DefaultRPCEndpoint", DefaultRPCEndpoint, "http://localhost:9650/v1/chain/Q/rpc"},
|
|
{"DefaultHTTPPort", DefaultHTTPPort, 4300},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.value != tt.want {
|
|
t.Errorf("got %v, want %v", tt.value, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestProofTypes tests proof type constants
|
|
func TestProofTypes(t *testing.T) {
|
|
proofTypes := []ProofType{ProofDilithium, ProofKyber, ProofFalcon, ProofSphincsh}
|
|
expected := []string{"dilithium", "kyber", "falcon", "sphincs+"}
|
|
|
|
for i, pt := range proofTypes {
|
|
if string(pt) != expected[i] {
|
|
t.Errorf("proof type %d: got %s, want %s", i, pt, expected[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFinalityProofJSON tests FinalityProof JSON serialization
|
|
func TestFinalityProofJSON(t *testing.T) {
|
|
proof := FinalityProof{
|
|
ID: "proof-123",
|
|
VertexID: "vertex-456",
|
|
ProofType: ProofDilithium,
|
|
LatticeParams: Lattice{
|
|
Dimension: 256,
|
|
Modulus: 8380417,
|
|
ErrorBound: 2,
|
|
SecurityLvl: 3,
|
|
Algorithm: "dilithium3",
|
|
},
|
|
Signature: []byte{0x01, 0x02, 0x03},
|
|
PublicKey: []byte{0x04, 0x05, 0x06},
|
|
Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
Verified: true,
|
|
}
|
|
|
|
data, err := json.Marshal(proof)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
}
|
|
|
|
var decoded FinalityProof
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.ID != proof.ID {
|
|
t.Errorf("ID: got %s, want %s", decoded.ID, proof.ID)
|
|
}
|
|
if decoded.ProofType != proof.ProofType {
|
|
t.Errorf("ProofType: got %s, want %s", decoded.ProofType, proof.ProofType)
|
|
}
|
|
if decoded.LatticeParams.Dimension != 256 {
|
|
t.Errorf("Dimension: got %d, want 256", decoded.LatticeParams.Dimension)
|
|
}
|
|
if decoded.LatticeParams.SecurityLvl != 3 {
|
|
t.Errorf("SecurityLvl: got %d, want 3", decoded.LatticeParams.SecurityLvl)
|
|
}
|
|
if !decoded.Verified {
|
|
t.Error("Verified should be true")
|
|
}
|
|
}
|
|
|
|
// TestLatticeJSON tests Lattice JSON serialization
|
|
func TestLatticeJSON(t *testing.T) {
|
|
lattice := Lattice{
|
|
Dimension: 1024,
|
|
Modulus: 12289,
|
|
ErrorBound: 1,
|
|
SecurityLvl: 5,
|
|
Algorithm: "falcon1024",
|
|
}
|
|
|
|
data, err := json.Marshal(lattice)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
}
|
|
|
|
var decoded Lattice
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.Dimension != lattice.Dimension {
|
|
t.Errorf("Dimension: got %d, want %d", decoded.Dimension, lattice.Dimension)
|
|
}
|
|
if decoded.Modulus != lattice.Modulus {
|
|
t.Errorf("Modulus: got %d, want %d", decoded.Modulus, lattice.Modulus)
|
|
}
|
|
if decoded.Algorithm != lattice.Algorithm {
|
|
t.Errorf("Algorithm: got %s, want %s", decoded.Algorithm, lattice.Algorithm)
|
|
}
|
|
}
|
|
|
|
// TestQuantumStampJSON tests QuantumStamp JSON serialization
|
|
func TestQuantumStampJSON(t *testing.T) {
|
|
stamp := QuantumStamp{
|
|
ID: "stamp-123",
|
|
VertexID: "vertex-456",
|
|
ChainID: "C",
|
|
BlockHeight: 1000000,
|
|
BlockHash: []byte{0xab, 0xcd, 0xef},
|
|
Entropy: []byte{0x11, 0x22, 0x33},
|
|
KeyID: "key-789",
|
|
Signature: []byte{0xaa, 0xbb, 0xcc},
|
|
Timestamp: time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC),
|
|
Certified: true,
|
|
}
|
|
|
|
data, err := json.Marshal(stamp)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
}
|
|
|
|
var decoded QuantumStamp
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.ID != stamp.ID {
|
|
t.Errorf("ID: got %s, want %s", decoded.ID, stamp.ID)
|
|
}
|
|
if decoded.ChainID != stamp.ChainID {
|
|
t.Errorf("ChainID: got %s, want %s", decoded.ChainID, stamp.ChainID)
|
|
}
|
|
if decoded.BlockHeight != stamp.BlockHeight {
|
|
t.Errorf("BlockHeight: got %d, want %d", decoded.BlockHeight, stamp.BlockHeight)
|
|
}
|
|
if !decoded.Certified {
|
|
t.Error("Certified should be true")
|
|
}
|
|
}
|
|
|
|
// TestCoronaKeyJSON tests CoronaKey JSON serialization
|
|
func TestCoronaKeyJSON(t *testing.T) {
|
|
now := time.Now().UTC().Truncate(time.Second)
|
|
key := CoronaKey{
|
|
ID: "key-123",
|
|
PublicKey: []byte{0x01, 0x02, 0x03, 0x04},
|
|
KeyType: ProofDilithium,
|
|
Algorithm: "dilithium3",
|
|
SecurityLvl: 3,
|
|
Owner: "lux1abc123",
|
|
ValidFrom: now,
|
|
ValidUntil: now.Add(365 * 24 * time.Hour),
|
|
Revoked: false,
|
|
CreatedAt: now,
|
|
}
|
|
|
|
data, err := json.Marshal(key)
|
|
if err != nil {
|
|
t.Fatalf("failed to marshal: %v", err)
|
|
}
|
|
|
|
var decoded CoronaKey
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.ID != key.ID {
|
|
t.Errorf("ID: got %s, want %s", decoded.ID, key.ID)
|
|
}
|
|
if decoded.KeyType != key.KeyType {
|
|
t.Errorf("KeyType: got %s, want %s", decoded.KeyType, key.KeyType)
|
|
}
|
|
if decoded.SecurityLvl != key.SecurityLvl {
|
|
t.Errorf("SecurityLvl: got %d, want %d", decoded.SecurityLvl, key.SecurityLvl)
|
|
}
|
|
if decoded.Owner != key.Owner {
|
|
t.Errorf("Owner: got %s, want %s", decoded.Owner, key.Owner)
|
|
}
|
|
if decoded.Revoked {
|
|
t.Error("Revoked should be false")
|
|
}
|
|
}
|
|
|
|
// TestInterfaceCompliance verifies Adapter implements dag.Adapter
|
|
func TestInterfaceCompliance(t *testing.T) {
|
|
var _ dag.Adapter = (*Adapter)(nil)
|
|
}
|
|
|
|
// TestParseVertex tests vertex parsing with finality proof
|
|
func TestParseVertex(t *testing.T) {
|
|
adapter := New("")
|
|
|
|
vertexJSON := `{
|
|
"id": "qv-123abc",
|
|
"parentIds": ["qv-parent1", "qv-parent2"],
|
|
"height": 5000,
|
|
"epoch": 100,
|
|
"timestamp": "2025-01-15T10:30:00Z",
|
|
"status": "pending",
|
|
"txIds": ["tx1", "tx2"],
|
|
"finalityProof": {
|
|
"id": "proof-xyz",
|
|
"vertexId": "qv-123abc",
|
|
"proofType": "dilithium",
|
|
"latticeParams": {
|
|
"dimension": 256,
|
|
"modulus": 8380417,
|
|
"errorBound": 2,
|
|
"securityLvl": 3,
|
|
"algorithm": "dilithium3"
|
|
},
|
|
"signature": "AQID",
|
|
"publicKey": "BAUG",
|
|
"timestamp": "2025-01-15T10:30:00Z",
|
|
"verified": true
|
|
},
|
|
"proofCount": 3,
|
|
"finalized": true
|
|
}`
|
|
|
|
v, err := adapter.ParseVertex(json.RawMessage(vertexJSON))
|
|
if err != nil {
|
|
t.Fatalf("ParseVertex failed: %v", err)
|
|
}
|
|
|
|
if v.ID != "qv-123abc" {
|
|
t.Errorf("ID: got %s, want qv-123abc", v.ID)
|
|
}
|
|
if v.Type != "quantum" {
|
|
t.Errorf("Type: got %s, want quantum", v.Type)
|
|
}
|
|
if v.Height != 5000 {
|
|
t.Errorf("Height: got %d, want 5000", v.Height)
|
|
}
|
|
if v.Epoch != 100 {
|
|
t.Errorf("Epoch: got %d, want 100", v.Epoch)
|
|
}
|
|
if len(v.ParentIDs) != 2 {
|
|
t.Errorf("ParentIDs count: got %d, want 2", len(v.ParentIDs))
|
|
}
|
|
// Status should be accepted because finalized=true
|
|
if v.Status != dag.StatusAccepted {
|
|
t.Errorf("Status: got %s, want %s", v.Status, dag.StatusAccepted)
|
|
}
|
|
|
|
// Check metadata
|
|
if v.Metadata["proof_count"] != 3 {
|
|
t.Errorf("proof_count: got %v, want 3", v.Metadata["proof_count"])
|
|
}
|
|
if v.Metadata["finalized"] != true {
|
|
t.Errorf("finalized: got %v, want true", v.Metadata["finalized"])
|
|
}
|
|
if v.Metadata["proof_type"] != ProofDilithium {
|
|
t.Errorf("proof_type: got %v, want dilithium", v.Metadata["proof_type"])
|
|
}
|
|
if v.Metadata["security_level"] != 3 {
|
|
t.Errorf("security_level: got %v, want 3", v.Metadata["security_level"])
|
|
}
|
|
}
|
|
|
|
// TestParseVertexWithoutProof tests parsing vertex without finality proof
|
|
func TestParseVertexWithoutProof(t *testing.T) {
|
|
adapter := New("")
|
|
|
|
vertexJSON := `{
|
|
"id": "qv-noProof",
|
|
"parentIds": [],
|
|
"height": 1,
|
|
"epoch": 1,
|
|
"timestamp": "2025-01-01T00:00:00Z",
|
|
"status": "pending",
|
|
"proofCount": 0,
|
|
"finalized": false
|
|
}`
|
|
|
|
v, err := adapter.ParseVertex(json.RawMessage(vertexJSON))
|
|
if err != nil {
|
|
t.Fatalf("ParseVertex failed: %v", err)
|
|
}
|
|
|
|
if v.Status != dag.StatusPending {
|
|
t.Errorf("Status: got %s, want %s", v.Status, dag.StatusPending)
|
|
}
|
|
if v.Metadata["finalized"] != false {
|
|
t.Errorf("finalized: got %v, want false", v.Metadata["finalized"])
|
|
}
|
|
}
|
|
|
|
// TestParseVertexInvalidJSON tests error handling for invalid JSON
|
|
func TestParseVertexInvalidJSON(t *testing.T) {
|
|
adapter := New("")
|
|
|
|
_, err := adapter.ParseVertex(json.RawMessage("invalid json"))
|
|
if err == nil {
|
|
t.Error("expected error for invalid JSON")
|
|
}
|
|
}
|
|
|
|
// TestGetRecentVertices tests fetching recent vertices via RPC
|
|
func TestGetRecentVertices(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var req rpcRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if req.Method != "qvm.getRecentVertices" {
|
|
t.Errorf("unexpected method: %s", req.Method)
|
|
}
|
|
|
|
response := rpcResponse{
|
|
JSONRPC: "2.0",
|
|
ID: 1,
|
|
Result: json.RawMessage(`{
|
|
"vertices": [
|
|
{"id": "qv-1", "height": 100, "finalized": true},
|
|
{"id": "qv-2", "height": 101, "finalized": false}
|
|
]
|
|
}`),
|
|
}
|
|
json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
ctx := context.Background()
|
|
|
|
vertices, err := adapter.GetRecentVertices(ctx, 10)
|
|
if err != nil {
|
|
t.Fatalf("GetRecentVertices failed: %v", err)
|
|
}
|
|
|
|
if len(vertices) != 2 {
|
|
t.Errorf("expected 2 vertices, got %d", len(vertices))
|
|
}
|
|
}
|
|
|
|
// TestGetVertexByID tests fetching a specific vertex
|
|
func TestGetVertexByID(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
response := rpcResponse{
|
|
JSONRPC: "2.0",
|
|
ID: 1,
|
|
Result: json.RawMessage(`{
|
|
"vertex": {"id": "qv-target", "height": 500, "finalized": true}
|
|
}`),
|
|
}
|
|
json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
ctx := context.Background()
|
|
|
|
vertex, err := adapter.GetVertexByID(ctx, "qv-target")
|
|
if err != nil {
|
|
t.Fatalf("GetVertexByID failed: %v", err)
|
|
}
|
|
|
|
if vertex == nil {
|
|
t.Fatal("expected non-nil vertex")
|
|
}
|
|
}
|
|
|
|
// TestVerifyProof tests proof verification via RPC
|
|
func TestVerifyProof(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
valid bool
|
|
wantError bool
|
|
}{
|
|
{"valid proof", true, false},
|
|
{"invalid proof", false, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
response := rpcResponse{
|
|
JSONRPC: "2.0",
|
|
ID: 1,
|
|
Result: json.RawMessage(`{
|
|
"valid": ` + boolToString(tt.valid) + `,
|
|
"message": "test"
|
|
}`),
|
|
}
|
|
json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
ctx := context.Background()
|
|
|
|
proof := &FinalityProof{
|
|
ID: "test-proof",
|
|
VertexID: "test-vertex",
|
|
ProofType: ProofDilithium,
|
|
Signature: []byte{0x01, 0x02},
|
|
PublicKey: []byte{0x03, 0x04},
|
|
}
|
|
|
|
valid, err := adapter.VerifyProof(ctx, proof)
|
|
if tt.wantError && err == nil {
|
|
t.Error("expected error")
|
|
}
|
|
if !tt.wantError && err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if valid != tt.valid {
|
|
t.Errorf("valid: got %v, want %v", valid, tt.valid)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRPCError tests handling of RPC errors
|
|
func TestRPCError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
response := rpcResponse{
|
|
JSONRPC: "2.0",
|
|
ID: 1,
|
|
Error: &rpcError{
|
|
Code: -32600,
|
|
Message: "Invalid request",
|
|
},
|
|
}
|
|
json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
ctx := context.Background()
|
|
|
|
_, err := adapter.GetRecentVertices(ctx, 10)
|
|
if err == nil {
|
|
t.Error("expected error for RPC error response")
|
|
}
|
|
}
|
|
|
|
// TestNetworkError tests handling of network errors
|
|
func TestNetworkError(t *testing.T) {
|
|
adapter := New("http://invalid-host:99999/rpc")
|
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
defer cancel()
|
|
|
|
_, err := adapter.GetRecentVertices(ctx, 10)
|
|
if err == nil {
|
|
t.Error("expected error for network failure")
|
|
}
|
|
}
|
|
|
|
// TestContextCancellation tests context cancellation
|
|
func TestContextCancellation(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(5 * time.Second)
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // Cancel immediately
|
|
|
|
_, err := adapter.GetRecentVertices(ctx, 10)
|
|
if err == nil {
|
|
t.Error("expected error for cancelled context")
|
|
}
|
|
}
|
|
|
|
// TestNewConfig tests default configuration creation
|
|
func TestNewConfig(t *testing.T) {
|
|
config := NewConfig()
|
|
|
|
if config.ChainType != dag.ChainQ {
|
|
t.Errorf("ChainType: got %s, want %s", config.ChainType, dag.ChainQ)
|
|
}
|
|
if config.ChainName != "Q-Chain (Quantum)" {
|
|
t.Errorf("ChainName: got %s, want Q-Chain (Quantum)", config.ChainName)
|
|
}
|
|
if config.RPCEndpoint != DefaultRPCEndpoint {
|
|
t.Errorf("RPCEndpoint: got %s, want %s", config.RPCEndpoint, DefaultRPCEndpoint)
|
|
}
|
|
if config.HTTPPort != DefaultHTTPPort {
|
|
t.Errorf("HTTPPort: got %d, want %d", config.HTTPPort, DefaultHTTPPort)
|
|
}
|
|
if config.RPCMethod != "qvm" {
|
|
t.Errorf("RPCMethod: got %s, want qvm", config.RPCMethod)
|
|
}
|
|
}
|
|
|
|
// TestLatticeSecurityLevels tests NIST security levels
|
|
func TestLatticeSecurityLevels(t *testing.T) {
|
|
// Security levels should be 1-5 for NIST compliance
|
|
levels := []int{1, 2, 3, 4, 5}
|
|
|
|
for _, lvl := range levels {
|
|
lattice := Lattice{
|
|
Dimension: 256,
|
|
Modulus: 8380417,
|
|
ErrorBound: lvl,
|
|
SecurityLvl: lvl,
|
|
Algorithm: "dilithium",
|
|
}
|
|
|
|
if lattice.SecurityLvl < 1 || lattice.SecurityLvl > 5 {
|
|
t.Errorf("invalid security level: %d", lattice.SecurityLvl)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestQuantumVertexEmbedding tests QuantumVertex embedding dag.Vertex
|
|
func TestQuantumVertexEmbedding(t *testing.T) {
|
|
qv := QuantumVertex{
|
|
Vertex: dag.Vertex{
|
|
ID: "qv-embed",
|
|
Type: "quantum",
|
|
ParentIDs: []string{"parent1"},
|
|
Height: 100,
|
|
Epoch: 10,
|
|
Status: dag.StatusAccepted,
|
|
},
|
|
FinalityProof: &FinalityProof{
|
|
ID: "proof-1",
|
|
ProofType: ProofDilithium,
|
|
Verified: true,
|
|
},
|
|
ProofCount: 1,
|
|
Finalized: true,
|
|
}
|
|
|
|
// Test embedded fields are accessible
|
|
if qv.ID != "qv-embed" {
|
|
t.Errorf("ID: got %s, want qv-embed", qv.ID)
|
|
}
|
|
if qv.Height != 100 {
|
|
t.Errorf("Height: got %d, want 100", qv.Height)
|
|
}
|
|
if !qv.Finalized {
|
|
t.Error("Finalized should be true")
|
|
}
|
|
if qv.FinalityProof.ProofType != ProofDilithium {
|
|
t.Errorf("ProofType: got %s, want dilithium", qv.FinalityProof.ProofType)
|
|
}
|
|
}
|
|
|
|
// TestCoronaKeyValidity tests key validity period
|
|
func TestCoronaKeyValidity(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
key := CoronaKey{
|
|
ID: "key-valid",
|
|
ValidFrom: now.Add(-24 * time.Hour),
|
|
ValidUntil: now.Add(365 * 24 * time.Hour),
|
|
Revoked: false,
|
|
}
|
|
|
|
isValid := !key.Revoked && now.After(key.ValidFrom) && now.Before(key.ValidUntil)
|
|
if !isValid {
|
|
t.Error("key should be valid")
|
|
}
|
|
|
|
// Test expired key
|
|
expiredKey := CoronaKey{
|
|
ID: "key-expired",
|
|
ValidFrom: now.Add(-48 * time.Hour),
|
|
ValidUntil: now.Add(-24 * time.Hour),
|
|
Revoked: false,
|
|
}
|
|
|
|
isExpired := now.After(expiredKey.ValidUntil)
|
|
if !isExpired {
|
|
t.Error("key should be expired")
|
|
}
|
|
|
|
// Test revoked key
|
|
revokedKey := CoronaKey{
|
|
ID: "key-revoked",
|
|
ValidFrom: now.Add(-24 * time.Hour),
|
|
ValidUntil: now.Add(365 * 24 * time.Hour),
|
|
Revoked: true,
|
|
}
|
|
|
|
if !revokedKey.Revoked {
|
|
t.Error("key should be revoked")
|
|
}
|
|
}
|
|
|
|
// TestQuasarCertJSON tests QuasarCert JSON serialization
|
|
func TestQuasarCertJSON(t *testing.T) {
|
|
cert := QuasarCert{
|
|
ID: "cert-001",
|
|
VertexID: "qv-123",
|
|
BLS: make([]byte, 48), // 48-byte BLS aggregate
|
|
PQProof: make([]byte, 200), // ~200-byte STARK proof
|
|
Epoch: 42,
|
|
Finality: time.Date(2026, 4, 10, 12, 0, 0, 0, time.UTC),
|
|
Validators: 67,
|
|
}
|
|
// Fill with recognizable patterns
|
|
cert.BLS[0] = 0xBE
|
|
cert.BLS[47] = 0xEF
|
|
cert.PQProof[0] = 0xDE
|
|
cert.PQProof[199] = 0xAD
|
|
|
|
data, err := json.Marshal(cert)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
|
|
var decoded QuasarCert
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.ID != "cert-001" {
|
|
t.Errorf("ID: got %s, want cert-001", decoded.ID)
|
|
}
|
|
if decoded.VertexID != "qv-123" {
|
|
t.Errorf("VertexID: got %s, want qv-123", decoded.VertexID)
|
|
}
|
|
if len(decoded.BLS) != 48 {
|
|
t.Errorf("BLS size: got %d, want 48", len(decoded.BLS))
|
|
}
|
|
if len(decoded.PQProof) != 200 {
|
|
t.Errorf("PQProof size: got %d, want 200", len(decoded.PQProof))
|
|
}
|
|
if decoded.Epoch != 42 {
|
|
t.Errorf("Epoch: got %d, want 42", decoded.Epoch)
|
|
}
|
|
if decoded.Validators != 67 {
|
|
t.Errorf("Validators: got %d, want 67", decoded.Validators)
|
|
}
|
|
if decoded.BLS[0] != 0xBE || decoded.BLS[47] != 0xEF {
|
|
t.Error("BLS data corrupted")
|
|
}
|
|
if decoded.PQProof[0] != 0xDE || decoded.PQProof[199] != 0xAD {
|
|
t.Error("PQProof data corrupted")
|
|
}
|
|
}
|
|
|
|
// TestParseVertexWithQuasarCert tests vertex parsing with QuasarCert
|
|
func TestParseVertexWithQuasarCert(t *testing.T) {
|
|
adapter := New("")
|
|
|
|
vertexJSON := `{
|
|
"id": "qv-pqz-001",
|
|
"parentIds": ["qv-parent1"],
|
|
"height": 9000,
|
|
"epoch": 42,
|
|
"timestamp": "2026-04-10T12:00:00Z",
|
|
"status": "pending",
|
|
"quasarCert": {
|
|
"id": "cert-pqz-001",
|
|
"vertexId": "qv-pqz-001",
|
|
"bls": "vg==",
|
|
"zkProof": "3g==",
|
|
"epoch": 42,
|
|
"finality": "2026-04-10T12:00:00Z",
|
|
"validators": 21
|
|
},
|
|
"proofCount": 1,
|
|
"finalized": true
|
|
}`
|
|
|
|
v, err := adapter.ParseVertex(json.RawMessage(vertexJSON))
|
|
if err != nil {
|
|
t.Fatalf("ParseVertex: %v", err)
|
|
}
|
|
|
|
if v.ID != "qv-pqz-001" {
|
|
t.Errorf("ID: got %s, want qv-pqz-001", v.ID)
|
|
}
|
|
if v.Status != dag.StatusAccepted {
|
|
t.Errorf("Status: got %s, want accepted", v.Status)
|
|
}
|
|
if v.Metadata["quasar_cert"] != true {
|
|
t.Error("quasar_cert metadata missing")
|
|
}
|
|
if v.Metadata["quasar_epoch"] != uint64(42) {
|
|
t.Errorf("quasar_epoch: got %v, want 42", v.Metadata["quasar_epoch"])
|
|
}
|
|
if v.Metadata["quasar_validators"] != 21 {
|
|
t.Errorf("quasar_validators: got %v, want 21", v.Metadata["quasar_validators"])
|
|
}
|
|
if v.QuasarCert == nil {
|
|
t.Fatal("QuasarCert should be non-nil on vertex")
|
|
}
|
|
}
|
|
|
|
// TestQuantumVertexWithQuasarCert tests QuantumVertex embedding with cert
|
|
func TestQuantumVertexWithQuasarCert(t *testing.T) {
|
|
qv := QuantumVertex{
|
|
Vertex: dag.Vertex{
|
|
ID: "qv-triple",
|
|
Height: 500,
|
|
Status: dag.StatusAccepted,
|
|
},
|
|
QuasarCert: &QuasarCert{
|
|
ID: "cert-triple",
|
|
VertexID: "qv-triple",
|
|
BLS: make([]byte, 48),
|
|
PQProof: make([]byte, 200),
|
|
Epoch: 10,
|
|
Validators: 21,
|
|
},
|
|
Finalized: true,
|
|
}
|
|
|
|
if qv.QuasarCert == nil {
|
|
t.Fatal("QuasarCert should be set")
|
|
}
|
|
if qv.QuasarCert.Validators != 21 {
|
|
t.Errorf("Validators: got %d, want 21", qv.QuasarCert.Validators)
|
|
}
|
|
if len(qv.QuasarCert.BLS) != 48 {
|
|
t.Errorf("BLS size: got %d, want 48", len(qv.QuasarCert.BLS))
|
|
}
|
|
if len(qv.QuasarCert.PQProof) != 200 {
|
|
t.Errorf("PQProof size: got %d, want 200", len(qv.QuasarCert.PQProof))
|
|
}
|
|
}
|
|
|
|
// boolToString converts bool to JSON string
|
|
func boolToString(b bool) string {
|
|
if b {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
}
|
|
|
|
// BenchmarkParseVertex benchmarks vertex parsing
|
|
func BenchmarkParseVertex(b *testing.B) {
|
|
adapter := New("")
|
|
vertexJSON := json.RawMessage(`{
|
|
"id": "qv-bench",
|
|
"parentIds": ["p1", "p2", "p3"],
|
|
"height": 10000,
|
|
"epoch": 500,
|
|
"timestamp": "2025-01-15T10:30:00Z",
|
|
"status": "accepted",
|
|
"finalityProof": {
|
|
"id": "proof-bench",
|
|
"proofType": "dilithium",
|
|
"latticeParams": {"dimension": 256, "modulus": 8380417, "securityLvl": 3},
|
|
"verified": true
|
|
},
|
|
"proofCount": 5,
|
|
"finalized": true
|
|
}`)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = adapter.ParseVertex(vertexJSON)
|
|
}
|
|
}
|
|
|
|
// BenchmarkFinalityProofSerialization benchmarks proof serialization
|
|
func BenchmarkFinalityProofSerialization(b *testing.B) {
|
|
proof := FinalityProof{
|
|
ID: "proof-bench",
|
|
VertexID: "vertex-bench",
|
|
ProofType: ProofDilithium,
|
|
LatticeParams: Lattice{
|
|
Dimension: 256,
|
|
Modulus: 8380417,
|
|
ErrorBound: 2,
|
|
SecurityLvl: 3,
|
|
Algorithm: "dilithium3",
|
|
},
|
|
Signature: make([]byte, 2420), // Approximate Dilithium3 signature size
|
|
PublicKey: make([]byte, 1952), // Approximate Dilithium3 public key size
|
|
Timestamp: time.Now(),
|
|
Verified: true,
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
data, _ := json.Marshal(proof)
|
|
var decoded FinalityProof
|
|
_ = json.Unmarshal(data, &decoded)
|
|
}
|
|
}
|