mirror of
https://github.com/luxfi/indexer.git
synced 2026-09-24 00:25:37 +00:00
Lux Partners Limited is not the entity behind this work. Every place the old name stood — copyright lines, site footers, brand config, legal and compliance text — now names the company that actually holds it. lux.fund is the exception and is not renamed to match: that site belongs to Lux Assets Management LLC, a different company, and it says so. Two kinds of line were left for a person rather than rewritten. A dated news item recording that Lux Partners Limited was established in the Isle of Man is a historical fact, and renaming it would falsify the record. And a sentence asserting that the company is domiciled in the Isle of Man is a claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle of Man company, so swapping the name there would assert something untrue. Co-authored-by: Hanzo Dev <[email protected]>
318 lines
7.6 KiB
Go
318 lines
7.6 KiB
Go
// Copyright (c) 2025 Lux Industries Inc
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package threshold
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestParseVertexInvalidJSON tests error on 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")
|
|
}
|
|
}
|
|
|
|
// TestParseVertexAllMetadataFields tests all MPC metadata fields
|
|
func TestParseVertexAllMetadataFields(t *testing.T) {
|
|
adapter := New("")
|
|
|
|
vertexJSON := `{
|
|
"id": "tv-full",
|
|
"type": "signing_session",
|
|
"parentIds": ["tv-p1"],
|
|
"height": 500,
|
|
"epoch": 25,
|
|
"timestamp": 1705323000,
|
|
"status": "accepted",
|
|
"sessionId": "sess-abc",
|
|
"keyGenId": "kg-xyz",
|
|
"messageId": "msg-123",
|
|
"data": {"threshold": 3, "messageHash": "0xabc"}
|
|
}`
|
|
|
|
v, err := adapter.ParseVertex(json.RawMessage(vertexJSON))
|
|
if err != nil {
|
|
t.Fatalf("ParseVertex failed: %v", err)
|
|
}
|
|
|
|
if v.Metadata["sessionId"] != "sess-abc" {
|
|
t.Errorf("sessionId: got %v, want sess-abc", v.Metadata["sessionId"])
|
|
}
|
|
if v.Metadata["keyGenId"] != "kg-xyz" {
|
|
t.Errorf("keyGenId: got %v, want kg-xyz", v.Metadata["keyGenId"])
|
|
}
|
|
if v.Metadata["messageId"] != "msg-123" {
|
|
t.Errorf("messageId: got %v, want msg-123", v.Metadata["messageId"])
|
|
}
|
|
}
|
|
|
|
// TestParseVertexEmptyMetadata tests vertex with no MPC-specific fields
|
|
func TestParseVertexEmptyMetadata(t *testing.T) {
|
|
adapter := New("")
|
|
|
|
vertexJSON := `{
|
|
"id": "tv-empty",
|
|
"type": "generic",
|
|
"parentIds": [],
|
|
"height": 1,
|
|
"epoch": 1,
|
|
"timestamp": 1705323000,
|
|
"status": "pending",
|
|
"data": {}
|
|
}`
|
|
|
|
v, err := adapter.ParseVertex(json.RawMessage(vertexJSON))
|
|
if err != nil {
|
|
t.Fatalf("ParseVertex failed: %v", err)
|
|
}
|
|
|
|
if _, ok := v.Metadata["sessionId"]; ok {
|
|
t.Error("sessionId should not be in metadata when empty")
|
|
}
|
|
}
|
|
|
|
// TestInferVertexTypeEdgeCases tests edge cases for type inference
|
|
func TestInferVertexTypeEdgeCases(t *testing.T) {
|
|
adapter := New("")
|
|
|
|
tests := []struct {
|
|
name string
|
|
data string
|
|
wantType string
|
|
}{
|
|
{
|
|
name: "empty object",
|
|
data: `{}`,
|
|
wantType: "unknown",
|
|
},
|
|
{
|
|
name: "threshold without messageHash or publicKey",
|
|
data: `{"threshold": 3}`,
|
|
wantType: "unknown",
|
|
},
|
|
{
|
|
name: "null data",
|
|
data: `null`,
|
|
wantType: "unknown",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := adapter.inferVertexType(json.RawMessage(tt.data))
|
|
if result != tt.wantType {
|
|
t.Errorf("got %s, want %s", result, tt.wantType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestComputeThresholdEdgeCases tests threshold computation edge cases
|
|
func TestComputeThresholdEdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
total uint32
|
|
want uint32
|
|
}{
|
|
{0, 1}, // Zero shares
|
|
{1, 1}, // Single share
|
|
{2, 2}, // Minimum multi-party
|
|
{3, 3}, // 3 * 2/3 + 1 = 3
|
|
{21, 15}, // 21 * 2/3 + 1 = 15
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := ComputeThreshold(tt.total)
|
|
if got != tt.want {
|
|
t.Errorf("ComputeThreshold(%d) = %d, want %d", tt.total, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestGetSigningSessionRPCError tests RPC error from GetSigningSession
|
|
func TestGetSigningSessionRPCError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"error": map[string]interface{}{"code": -32000, "message": "session not found"},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
_, err := adapter.GetSigningSession(context.Background(), "nonexistent")
|
|
if err == nil {
|
|
t.Error("expected error for RPC error response")
|
|
}
|
|
}
|
|
|
|
// TestGetKeyGenerationRPCError tests RPC error from GetKeyGeneration
|
|
func TestGetKeyGenerationRPCError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"error": map[string]interface{}{"code": -32000, "message": "keygen not found"},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
_, err := adapter.GetKeyGeneration(context.Background(), "nonexistent")
|
|
if err == nil {
|
|
t.Error("expected error for RPC error response")
|
|
}
|
|
}
|
|
|
|
// TestGetActiveKeySharesRPCError tests RPC error from GetActiveKeyShares
|
|
func TestGetActiveKeySharesRPCError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"error": map[string]interface{}{"code": -32000, "message": "key not found"},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
adapter := New(server.URL)
|
|
_, err := adapter.GetActiveKeyShares(context.Background(), "0xnonexistent")
|
|
if err == nil {
|
|
t.Error("expected error for RPC error response")
|
|
}
|
|
}
|
|
|
|
// TestSigningSessionNilCompletedAt tests session with nil CompletedAt
|
|
func TestSigningSessionNilCompletedAt(t *testing.T) {
|
|
session := SigningSession{
|
|
ID: "sess-nil",
|
|
Threshold: 2,
|
|
TotalShares: 3,
|
|
MessageHash: "0xhash",
|
|
Status: SessionPending,
|
|
CreatedAt: time.Now(),
|
|
CompletedAt: nil,
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
}
|
|
|
|
data, err := json.Marshal(session)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
|
|
var decoded SigningSession
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.CompletedAt != nil {
|
|
t.Error("CompletedAt should be nil")
|
|
}
|
|
if decoded.Status != SessionPending {
|
|
t.Errorf("Status: got %s, want pending", decoded.Status)
|
|
}
|
|
}
|
|
|
|
// TestKeyShareNilOptionalTimes tests KeyShare with nil optional times
|
|
func TestKeyShareNilOptionalTimes(t *testing.T) {
|
|
share := KeyShare{
|
|
ID: "share-nil",
|
|
PublicKey: "0xpub",
|
|
ShareIndex: 1,
|
|
NodeID: "node1",
|
|
KeyGenID: "kg1",
|
|
Status: SharePending,
|
|
CreatedAt: time.Now(),
|
|
RotatedAt: nil,
|
|
ExpiresAt: nil,
|
|
}
|
|
|
|
data, err := json.Marshal(share)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
|
|
var decoded KeyShare
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.RotatedAt != nil {
|
|
t.Error("RotatedAt should be nil")
|
|
}
|
|
if decoded.ExpiresAt != nil {
|
|
t.Error("ExpiresAt should be nil")
|
|
}
|
|
}
|
|
|
|
// TestTeleportMessageNilOptionalTimes tests message with nil optional times
|
|
func TestTeleportMessageNilOptionalTimes(t *testing.T) {
|
|
msg := TeleportMessage{
|
|
ID: "msg-nil",
|
|
SourceChain: "C",
|
|
DestChain: "X",
|
|
Sender: "0xsender",
|
|
Receiver: "X-lux1rcpt",
|
|
Payload: json.RawMessage(`{}`),
|
|
Nonce: 1,
|
|
Status: "pending",
|
|
CreatedAt: time.Now(),
|
|
SignedAt: nil,
|
|
DeliveredAt: nil,
|
|
}
|
|
|
|
data, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
|
|
var decoded TeleportMessage
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.SignedAt != nil {
|
|
t.Error("SignedAt should be nil")
|
|
}
|
|
if decoded.DeliveredAt != nil {
|
|
t.Error("DeliveredAt should be nil")
|
|
}
|
|
}
|
|
|
|
// TestVertexDataNilFields tests VertexData with all nil fields
|
|
func TestVertexDataNilFields(t *testing.T) {
|
|
vd := VertexData{}
|
|
|
|
data, err := json.Marshal(vd)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
|
|
var decoded VertexData
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if decoded.Session != nil {
|
|
t.Error("Session should be nil")
|
|
}
|
|
if decoded.KeyShare != nil {
|
|
t.Error("KeyShare should be nil")
|
|
}
|
|
if decoded.KeyGen != nil {
|
|
t.Error("KeyGen should be nil")
|
|
}
|
|
if decoded.Message != nil {
|
|
t.Error("Message should be nil")
|
|
}
|
|
}
|