mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
Drop 31 demo/benchmark/experiment one-off cmds whose coverage lives in
package tests (bench-all, dag-network, dclient, dexe2e, e2e-fix-zmq,
fix-benchmark, hybrid-auto, latency-benchmark, memory-*, mlx-memory-analysis,
multi-node, multi-node-test, nats-*, perf-test, stress-test, test-client,
zmq-*, dvenue-cancel, dvenue-custody, venueprobe, empty api-server/server/trader).
Keep production binaries: dvenue, dchain, dex-server, dex-api-server, gateway,
price-feed, monitor, persistent-server, demo (each referenced by a
Dockerfile/Makefile/script/k8s manifest or a documented entrypoint).
Consolidate cmd/dvenue + cmd/dvenue-cpu into one tag-free cmd/dvenue: the two
mains were byte-identical except a build tag and a log label; the GPU-vs-CPU
matcher is already selected inside pkg/lx by its own cgo-tagged files, so the
main needs no tag. CGO_ENABLED alone picks the matcher; one binary builds in
both modes. engine_{cgo,nocgo}.go carry only the engine display label. dvenue
now builds under the default CGO_ENABLED=0 gate (was tag-excluded before).
Dockerfile.dvenue: drop the dvenue-custody build+copy (cmd removed).
Makefile build-tools: point at real tools (price-feed, monitor) instead of the
removed cmd/trader and never-existent cmd/lx-metrics.
pkg/price: fix the three CChainSource tests. They were gated on requireLuxd
(a luxd TCP dial) but CChainSource never dials luxd — fetch() is pure simulate().
The gate skipped only when luxd was down, masking the real defect on hosts where
luxd runs. Rewrite the tests to drive the source's true contract deterministically
via src.poll() (no ticker race) and assert against the symbols the source actually
polls (cchainTokens), plus not-found for untracked symbols. Remove the dead
requireLuxd helper and its net import.
1327 lines
32 KiB
Go
1327 lines
32 KiB
Go
package price
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// =============================================================================
|
|
// OrderbookSource Tests
|
|
// =============================================================================
|
|
|
|
// mockOrderbookProvider implements OrderbookProvider for testing.
|
|
type mockOrderbookProvider struct {
|
|
bids map[string]float64
|
|
asks map[string]float64
|
|
symbols []string
|
|
}
|
|
|
|
func (m *mockOrderbookProvider) GetBestBid(symbol string) float64 {
|
|
return m.bids[symbol]
|
|
}
|
|
|
|
func (m *mockOrderbookProvider) GetBestAsk(symbol string) float64 {
|
|
return m.asks[symbol]
|
|
}
|
|
|
|
func (m *mockOrderbookProvider) GetSymbols() []string {
|
|
return m.symbols
|
|
}
|
|
|
|
func TestOrderbookSourceBasic(t *testing.T) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{"LUX-USDC": 10.0, "ETH-USDC": 3000.0},
|
|
asks: map[string]float64{"LUX-USDC": 10.01, "ETH-USDC": 3001.0},
|
|
symbols: []string{"LUX-USDC", "ETH-USDC"},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
if err := src.Start(); err != nil {
|
|
t.Fatalf("Start failed: %v", err)
|
|
}
|
|
defer src.Stop()
|
|
|
|
// Wait for data
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Check health
|
|
if !src.Healthy() {
|
|
t.Error("Source should be healthy")
|
|
}
|
|
|
|
// Check name
|
|
if src.Name() != "orderbook" {
|
|
t.Errorf("Name() = %q, want orderbook", src.Name())
|
|
}
|
|
|
|
// Check weight
|
|
if src.Weight() != 1.0 {
|
|
t.Errorf("Weight() = %f, want 1.0", src.Weight())
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourcePrice(t *testing.T) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{"LUX-USDC": 10.0},
|
|
asks: map[string]float64{"LUX-USDC": 10.02},
|
|
symbols: []string{"LUX-USDC"},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
src.Start()
|
|
defer src.Stop()
|
|
|
|
// Wait for data
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Get price
|
|
p, err := src.Price("LUX-USDC")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
// Mid price = (10.0 + 10.02) / 2 = 10.01
|
|
expected := 10.01
|
|
if p.Price < 10.0 || p.Price > 10.02 {
|
|
t.Errorf("Price = %f, expected ~%f", p.Price, expected)
|
|
}
|
|
|
|
// Check bid/ask
|
|
if p.Bid != 10.0 {
|
|
t.Errorf("Bid = %f, want 10.0", p.Bid)
|
|
}
|
|
if p.Ask != 10.02 {
|
|
t.Errorf("Ask = %f, want 10.02", p.Ask)
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourceNotFound(t *testing.T) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{},
|
|
asks: map[string]float64{},
|
|
symbols: []string{},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
src.Start()
|
|
defer src.Stop()
|
|
|
|
// Try to get non-existent price
|
|
_, err := src.Price("NONEXISTENT")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourcePrices(t *testing.T) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{"LUX-USDC": 10.0, "ETH-USDC": 3000.0},
|
|
asks: map[string]float64{"LUX-USDC": 10.02, "ETH-USDC": 3002.0},
|
|
symbols: []string{"LUX-USDC", "ETH-USDC"},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
src.Start()
|
|
defer src.Stop()
|
|
|
|
// Wait for data
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Get multiple prices
|
|
prices, err := src.Prices([]string{"LUX-USDC", "ETH-USDC", "NONEXISTENT"})
|
|
if err != nil {
|
|
t.Fatalf("Prices failed: %v", err)
|
|
}
|
|
|
|
// Should have 2 valid prices
|
|
if len(prices) != 2 {
|
|
t.Errorf("Expected 2 prices, got %d", len(prices))
|
|
}
|
|
|
|
if _, ok := prices["LUX-USDC"]; !ok {
|
|
t.Error("Missing LUX-USDC")
|
|
}
|
|
if _, ok := prices["ETH-USDC"]; !ok {
|
|
t.Error("Missing ETH-USDC")
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourceSubscribe(t *testing.T) {
|
|
provider := &mockOrderbookProvider{}
|
|
src := NewOrderbookSource(provider)
|
|
|
|
// Subscribe/Unsubscribe are no-ops
|
|
if err := src.Subscribe("LUX-USDC"); err != nil {
|
|
t.Errorf("Subscribe failed: %v", err)
|
|
}
|
|
if err := src.Unsubscribe("LUX-USDC"); err != nil {
|
|
t.Errorf("Unsubscribe failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourceStartIdempotent(t *testing.T) {
|
|
provider := &mockOrderbookProvider{symbols: []string{}}
|
|
src := NewOrderbookSource(provider)
|
|
|
|
// Start twice should be idempotent
|
|
src.Start()
|
|
src.Start() // Should not panic or error
|
|
|
|
src.Stop()
|
|
}
|
|
|
|
func TestOrderbookSourceBidOnlyPrice(t *testing.T) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{"BID-ONLY": 50.0},
|
|
asks: map[string]float64{},
|
|
symbols: []string{"BID-ONLY"},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
src.Start()
|
|
defer src.Stop()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
p, err := src.Price("BID-ONLY")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if p.Price != 50.0 {
|
|
t.Errorf("Price = %f, want 50.0 (bid only)", p.Price)
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourceAskOnlyPrice(t *testing.T) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{},
|
|
asks: map[string]float64{"ASK-ONLY": 60.0},
|
|
symbols: []string{"ASK-ONLY"},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
src.Start()
|
|
defer src.Stop()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
p, err := src.Price("ASK-ONLY")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if p.Price != 60.0 {
|
|
t.Errorf("Price = %f, want 60.0 (ask only)", p.Price)
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourceStalePrice(t *testing.T) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{"STALE-TEST": 100.0},
|
|
asks: map[string]float64{"STALE-TEST": 100.1},
|
|
symbols: []string{"STALE-TEST"},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
src.Start()
|
|
time.Sleep(50 * time.Millisecond)
|
|
src.Stop() // Stop updates
|
|
|
|
// Manually set lastUpdate to be old
|
|
src.mu.Lock()
|
|
src.lastUpdate["STALE-TEST"] = time.Now().Add(-5 * time.Second)
|
|
src.mu.Unlock()
|
|
|
|
p, err := src.Price("STALE-TEST")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if !p.Stale {
|
|
t.Error("Expected price to be marked stale")
|
|
}
|
|
}
|
|
|
|
func TestOrderbookSourceNilBooks(t *testing.T) {
|
|
// Test update with nil books (should not panic)
|
|
src := NewOrderbookSource(nil)
|
|
src.update() // Should handle nil gracefully
|
|
}
|
|
|
|
// =============================================================================
|
|
// ZooChainSource Tests
|
|
// =============================================================================
|
|
|
|
func TestZooChainSourceBasic(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
if err := src.Start(); err != nil {
|
|
t.Fatalf("Start failed: %v", err)
|
|
}
|
|
defer src.Close()
|
|
|
|
// Wait for data
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Check health
|
|
if !src.Healthy() {
|
|
t.Error("Source should be healthy")
|
|
}
|
|
|
|
// Check name
|
|
if src.Name() != "zoo-chain" {
|
|
t.Errorf("Name() = %q, want zoo-chain", src.Name())
|
|
}
|
|
|
|
// Check weight
|
|
if src.Weight() != 1.7 {
|
|
t.Errorf("Weight() = %f, want 1.7", src.Weight())
|
|
}
|
|
}
|
|
|
|
func TestZooChainSourcePrice(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
p, err := src.Price("ZOO-USDC")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if p.Price <= 0 {
|
|
t.Error("Price should be > 0")
|
|
}
|
|
if p.Source != "zoo-chain" {
|
|
t.Errorf("Source = %q, want zoo-chain", p.Source)
|
|
}
|
|
}
|
|
|
|
func TestZooChainSourcePrices(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
prices, err := src.Prices([]string{"ZOO-USDC", "ZOO-USDT", "NONEXISTENT"})
|
|
if err != nil {
|
|
t.Fatalf("Prices failed: %v", err)
|
|
}
|
|
|
|
if len(prices) < 2 {
|
|
t.Errorf("Expected at least 2 prices, got %d", len(prices))
|
|
}
|
|
}
|
|
|
|
func TestZooChainSourceNotFound(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
// Don't start - no data
|
|
|
|
_, err := src.Price("NONEXISTENT")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestZooChainSourceStalePrice(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
time.Sleep(200 * time.Millisecond)
|
|
src.Close()
|
|
|
|
// Manually set old timestamp
|
|
src.mu.Lock()
|
|
src.last["ZOO-USDC"] = time.Now().Add(-10 * time.Second)
|
|
src.mu.Unlock()
|
|
|
|
p, err := src.Price("ZOO-USDC")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if !p.Stale {
|
|
t.Error("Expected price to be stale")
|
|
}
|
|
}
|
|
|
|
func TestZooChainSourceGetReserves(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
// No reserves by default
|
|
_, err := src.GetReserves("ZOO-USDC")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestZooChainSourcePairs(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
pairs := src.Pairs()
|
|
if len(pairs) == 0 {
|
|
t.Error("Expected pairs")
|
|
}
|
|
|
|
// Check specific pair exists
|
|
if _, ok := pairs["ZOO-USDC"]; !ok {
|
|
t.Error("Missing ZOO-USDC pair")
|
|
}
|
|
}
|
|
|
|
func TestZooChainSourceStartIdempotent(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
src.Start()
|
|
src.Start() // Should not panic
|
|
src.Close()
|
|
}
|
|
|
|
func TestZooChainSourceSubscribe(t *testing.T) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
// Subscribe/Unsubscribe are no-ops
|
|
if err := src.Subscribe("ZOO-USDC"); err != nil {
|
|
t.Errorf("Subscribe failed: %v", err)
|
|
}
|
|
if err := src.Unsubscribe("ZOO-USDC"); err != nil {
|
|
t.Errorf("Unsubscribe failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// ChainlinkSource Extended Tests
|
|
// =============================================================================
|
|
|
|
func TestChainlinkSourceStart(t *testing.T) {
|
|
src := NewChainlinkSource()
|
|
if err := src.Start(); err != nil {
|
|
t.Fatalf("Start failed: %v", err)
|
|
}
|
|
defer src.Close()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
if !src.Healthy() {
|
|
t.Error("Source should be healthy")
|
|
}
|
|
}
|
|
|
|
func TestChainlinkSourceStartIdempotent(t *testing.T) {
|
|
src := NewChainlinkSource()
|
|
|
|
src.Start()
|
|
src.Start() // Should not panic
|
|
src.Close()
|
|
}
|
|
|
|
func TestChainlinkSourcePrice(t *testing.T) {
|
|
src := NewChainlinkSource()
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
p, err := src.Price("BTC-USD")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if p.Price <= 0 {
|
|
t.Error("Price should be > 0")
|
|
}
|
|
if p.Source != "chainlink" {
|
|
t.Errorf("Source = %q, want chainlink", p.Source)
|
|
}
|
|
if p.Confidence != 0.99 {
|
|
t.Errorf("Confidence = %f, want 0.99", p.Confidence)
|
|
}
|
|
}
|
|
|
|
func TestChainlinkSourcePrices(t *testing.T) {
|
|
src := NewChainlinkSource()
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
prices, err := src.Prices([]string{"BTC-USD", "ETH-USD", "NONEXISTENT"})
|
|
if err != nil {
|
|
t.Fatalf("Prices failed: %v", err)
|
|
}
|
|
|
|
if len(prices) < 2 {
|
|
t.Errorf("Expected at least 2 prices, got %d", len(prices))
|
|
}
|
|
}
|
|
|
|
func TestChainlinkSourceNotFound(t *testing.T) {
|
|
src := NewChainlinkSource()
|
|
// Don't start
|
|
|
|
_, err := src.Price("NONEXISTENT")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestChainlinkSourceStalePrice(t *testing.T) {
|
|
src := NewChainlinkSource()
|
|
src.Start()
|
|
time.Sleep(100 * time.Millisecond)
|
|
src.Close()
|
|
|
|
// Manually set old timestamp
|
|
src.mu.Lock()
|
|
src.last["BTC-USD"] = time.Now().Add(-120 * time.Second)
|
|
src.mu.Unlock()
|
|
|
|
p, err := src.Price("BTC-USD")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if !p.Stale {
|
|
t.Error("Expected price to be stale")
|
|
}
|
|
}
|
|
|
|
func TestChainlinkSourceSubscribe(t *testing.T) {
|
|
src := NewChainlinkSource()
|
|
|
|
if err := src.Subscribe("BTC-USD"); err != nil {
|
|
t.Errorf("Subscribe failed: %v", err)
|
|
}
|
|
if err := src.Unsubscribe("BTC-USD"); err != nil {
|
|
t.Errorf("Unsubscribe failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// AChainSource Extended Tests
|
|
// =============================================================================
|
|
|
|
func TestAChainSourceValidators(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
defer src.Close()
|
|
|
|
validators := src.Validators()
|
|
if len(validators) == 0 {
|
|
t.Error("Expected validators")
|
|
}
|
|
|
|
// Check validator fields
|
|
for name, v := range validators {
|
|
if v.Address == "" {
|
|
t.Errorf("Validator %s missing address", name)
|
|
}
|
|
if v.Stake == 0 {
|
|
t.Errorf("Validator %s has zero stake", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAChainSourceVerify(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// Get attestation for reference price
|
|
att, err := src.Attestation("LUX-USD")
|
|
if err != nil {
|
|
t.Fatalf("Attestation failed: %v", err)
|
|
}
|
|
|
|
// Verify exact price
|
|
if !src.Verify("LUX-USD", att.Price, 0.0) {
|
|
t.Error("Should verify exact price")
|
|
}
|
|
|
|
// Verify within tolerance
|
|
if !src.Verify("LUX-USD", att.Price*1.001, 0.01) {
|
|
t.Error("Should verify within 1% tolerance")
|
|
}
|
|
|
|
// Reject outside tolerance
|
|
if src.Verify("LUX-USD", att.Price*1.2, 0.01) {
|
|
t.Error("Should reject price 20% off")
|
|
}
|
|
}
|
|
|
|
func TestAChainSourceVerifyNotFinalized(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
// Don't start - no finalized attestations
|
|
|
|
// Should return false for non-finalized
|
|
if src.Verify("LUX-USD", 10.0, 0.1) {
|
|
t.Error("Should return false when no attestation")
|
|
}
|
|
}
|
|
|
|
func TestAChainSourceSetQuorum(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
defer src.Close()
|
|
|
|
// Default quorum
|
|
src.SetQuorum(5)
|
|
|
|
// Should not panic
|
|
}
|
|
|
|
func TestAChainSourcePrices(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
prices, err := src.Prices([]string{"LUX-USD", "ETH-USD", "NONEXISTENT"})
|
|
if err != nil {
|
|
t.Fatalf("Prices failed: %v", err)
|
|
}
|
|
|
|
if len(prices) < 2 {
|
|
t.Errorf("Expected at least 2 prices, got %d", len(prices))
|
|
}
|
|
}
|
|
|
|
func TestAChainSourceStalePrice(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
time.Sleep(300 * time.Millisecond)
|
|
src.Close()
|
|
|
|
// Manually set old timestamp
|
|
src.mu.Lock()
|
|
src.last["LUX-USD"] = time.Now().Add(-10 * time.Second)
|
|
src.mu.Unlock()
|
|
|
|
p, err := src.Price("LUX-USD")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if !p.Stale {
|
|
t.Error("Expected price to be stale")
|
|
}
|
|
}
|
|
|
|
func TestAChainSourceStartIdempotent(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
src.Start()
|
|
src.Start() // Should not panic
|
|
src.Close()
|
|
}
|
|
|
|
func TestAChainSourceSubscribe(t *testing.T) {
|
|
src := NewAChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
defer src.Close()
|
|
|
|
if err := src.Subscribe("LUX-USD"); err != nil {
|
|
t.Errorf("Subscribe failed: %v", err)
|
|
}
|
|
if err := src.Unsubscribe("LUX-USD"); err != nil {
|
|
t.Errorf("Unsubscribe failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// CChainSource Extended Tests
|
|
// =============================================================================
|
|
|
|
func TestCChainSourceGetReserves(t *testing.T) {
|
|
src := NewCChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
// No reserves populated by default simulation
|
|
_, err := src.GetReserves("LUX-USDC")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCChainSourceStartIdempotent(t *testing.T) {
|
|
src := NewCChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
src.Start()
|
|
src.Start() // Should not panic
|
|
src.Close()
|
|
}
|
|
|
|
func TestCChainSourcePrices(t *testing.T) {
|
|
src := NewCChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.poll()
|
|
|
|
// Collect two pairs the source is configured to track plus one it is not.
|
|
var tracked []string
|
|
for sym := range src.tokens {
|
|
tracked = append(tracked, sym)
|
|
if len(tracked) == 2 {
|
|
break
|
|
}
|
|
}
|
|
if len(tracked) < 2 {
|
|
t.Fatalf("source tracks only %d pairs; need >= 2", len(tracked))
|
|
}
|
|
query := append([]string{}, tracked...)
|
|
query = append(query, "NONEXISTENT")
|
|
|
|
prices, err := src.Prices(query)
|
|
if err != nil {
|
|
t.Fatalf("Prices failed: %v", err)
|
|
}
|
|
|
|
// Prices returns entries for tracked symbols and omits the untracked one.
|
|
if len(prices) != len(tracked) {
|
|
t.Errorf("got %d prices, want %d (tracked pairs only)", len(prices), len(tracked))
|
|
}
|
|
if _, ok := prices["NONEXISTENT"]; ok {
|
|
t.Error("Prices returned an entry for an untracked symbol")
|
|
}
|
|
}
|
|
|
|
func TestCChainSourceStalePrice(t *testing.T) {
|
|
src := NewCChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.poll()
|
|
|
|
sym := firstPolledSymbol(t, src)
|
|
|
|
// Backdate the last-update so the staleness window (5s) is exceeded.
|
|
src.mu.Lock()
|
|
src.last[sym] = time.Now().Add(-10 * time.Second)
|
|
src.mu.Unlock()
|
|
|
|
p, err := src.Price(sym)
|
|
if err != nil {
|
|
t.Fatalf("Price(%q): %v", sym, err)
|
|
}
|
|
if !p.Stale {
|
|
t.Error("Expected price to be stale")
|
|
}
|
|
}
|
|
|
|
func TestCChainSourceSubscribe(t *testing.T) {
|
|
src := NewCChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
defer src.Close()
|
|
|
|
if err := src.Subscribe("LUX-USDC"); err != nil {
|
|
t.Errorf("Subscribe failed: %v", err)
|
|
}
|
|
if err := src.Unsubscribe("LUX-USDC"); err != nil {
|
|
t.Errorf("Unsubscribe failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// XChainSource Extended Tests
|
|
// =============================================================================
|
|
|
|
func TestXChainSourceOrderbook(t *testing.T) {
|
|
src := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
book, err := src.Orderbook("LUX-USDC")
|
|
if err != nil {
|
|
t.Fatalf("Orderbook failed: %v", err)
|
|
}
|
|
|
|
if len(book.Bids) == 0 {
|
|
t.Error("Expected bids")
|
|
}
|
|
if len(book.Asks) == 0 {
|
|
t.Error("Expected asks")
|
|
}
|
|
if book.Spread <= 0 {
|
|
t.Error("Expected spread > 0")
|
|
}
|
|
}
|
|
|
|
func TestXChainSourceOrderbookNotFound(t *testing.T) {
|
|
src := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
// Don't start
|
|
|
|
_, err := src.Orderbook("NONEXISTENT")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestXChainSourceMarkets(t *testing.T) {
|
|
src := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
markets := src.Markets()
|
|
if len(markets) == 0 {
|
|
t.Error("Expected markets")
|
|
}
|
|
|
|
// Check market fields
|
|
if m, ok := markets["LUX-USDC"]; ok {
|
|
if m.BaseAsset != "LUX" {
|
|
t.Errorf("BaseAsset = %q, want LUX", m.BaseAsset)
|
|
}
|
|
if m.QuoteAsset != "USDC" {
|
|
t.Errorf("QuoteAsset = %q, want USDC", m.QuoteAsset)
|
|
}
|
|
} else {
|
|
t.Error("Missing LUX-USDC market")
|
|
}
|
|
}
|
|
|
|
func TestXChainSourcePrices(t *testing.T) {
|
|
src := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
defer src.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
prices, err := src.Prices([]string{"LUX-USDC", "ETH-LUX", "NONEXISTENT"})
|
|
if err != nil {
|
|
t.Fatalf("Prices failed: %v", err)
|
|
}
|
|
|
|
if len(prices) < 2 {
|
|
t.Errorf("Expected at least 2 prices, got %d", len(prices))
|
|
}
|
|
}
|
|
|
|
func TestXChainSourceStalePrice(t *testing.T) {
|
|
src := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
time.Sleep(200 * time.Millisecond)
|
|
src.Close()
|
|
|
|
// Manually set old timestamp
|
|
src.mu.Lock()
|
|
src.last["LUX-USDC"] = time.Now().Add(-10 * time.Second)
|
|
src.mu.Unlock()
|
|
|
|
p, err := src.Price("LUX-USDC")
|
|
if err != nil {
|
|
t.Fatalf("Price failed: %v", err)
|
|
}
|
|
|
|
if !p.Stale {
|
|
t.Error("Expected price to be stale")
|
|
}
|
|
}
|
|
|
|
func TestXChainSourceStartIdempotent(t *testing.T) {
|
|
src := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
src.Start()
|
|
src.Start() // Should not panic
|
|
src.Close()
|
|
}
|
|
|
|
func TestXChainSourceSubscribe(t *testing.T) {
|
|
src := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
defer src.Close()
|
|
|
|
if err := src.Subscribe("LUX-USDC"); err != nil {
|
|
t.Errorf("Subscribe failed: %v", err)
|
|
}
|
|
if err := src.Unsubscribe("LUX-USDC"); err != nil {
|
|
t.Errorf("Unsubscribe failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// QChainVerifier Extended Tests
|
|
// =============================================================================
|
|
|
|
func TestQChainVerifierVerifyInclusion(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
v.Start()
|
|
defer v.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Verify an order inclusion
|
|
inc, err := v.VerifyInclusion("order-123", "x-chain", "tx-abc123")
|
|
if err != nil {
|
|
t.Fatalf("VerifyInclusion failed: %v", err)
|
|
}
|
|
|
|
if inc.OrderID != "order-123" {
|
|
t.Errorf("OrderID = %q, want order-123", inc.OrderID)
|
|
}
|
|
if inc.Chain != "x-chain" {
|
|
t.Errorf("Chain = %q, want x-chain", inc.Chain)
|
|
}
|
|
if inc.TxHash != "tx-abc123" {
|
|
t.Errorf("TxHash = %q, want tx-abc123", inc.TxHash)
|
|
}
|
|
if !inc.Verified {
|
|
t.Error("Expected Verified=true")
|
|
}
|
|
}
|
|
|
|
func TestQChainVerifierVerifyInclusionCached(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
v.Start()
|
|
defer v.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// First call
|
|
inc1, _ := v.VerifyInclusion("order-456", "c-chain", "tx-def456")
|
|
|
|
// Second call should return cached
|
|
inc2, _ := v.VerifyInclusion("order-456", "c-chain", "tx-def456")
|
|
|
|
if inc1 != inc2 {
|
|
t.Error("Expected cached inclusion to be returned")
|
|
}
|
|
}
|
|
|
|
func TestQChainVerifierVerifyInclusionNoFinality(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
// Don't start - no finality data
|
|
|
|
_, err := v.VerifyInclusion("order-789", "unknown-chain", "tx-xyz")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestQChainVerifierVerifyOrderPrice(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
v.Start()
|
|
defer v.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Should verify (finality is recent)
|
|
if !v.VerifyOrderPrice("LUX-USD", 10.0, "x-chain") {
|
|
t.Error("Expected price to be verified")
|
|
}
|
|
}
|
|
|
|
func TestQChainVerifierVerifyOrderPriceNoFinality(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
// Don't start
|
|
|
|
if v.VerifyOrderPrice("LUX-USD", 10.0, "x-chain") {
|
|
t.Error("Expected false when no finality")
|
|
}
|
|
}
|
|
|
|
func TestQChainVerifierCrossChainVerifyMissing(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
v.Start()
|
|
defer v.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Include a non-existent chain
|
|
chains := []string{"x-chain", "nonexistent-chain"}
|
|
allFinalized, results := v.CrossChainVerify(chains)
|
|
|
|
if allFinalized {
|
|
t.Error("Should not be all finalized with missing chain")
|
|
}
|
|
if len(results) != 1 {
|
|
t.Errorf("Expected 1 result, got %d", len(results))
|
|
}
|
|
}
|
|
|
|
func TestQChainVerifierFinalityNotFound(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
// Don't start
|
|
|
|
_, err := v.Finality("x-chain")
|
|
if err != ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestQChainVerifierStartIdempotent(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
v.Start()
|
|
v.Start() // Should not panic
|
|
v.Close()
|
|
}
|
|
|
|
func TestQChainVerifierCloseIdempotent(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
|
|
// Close without start
|
|
v.Close() // Should not panic
|
|
|
|
v.Start()
|
|
v.Close()
|
|
// Note: Can't close twice due to channel already closed panic
|
|
}
|
|
|
|
func TestQChainVerifierFinalityLatencyEmpty(t *testing.T) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
// Don't start - no finality data
|
|
|
|
latency := v.FinalityLatency()
|
|
if latency != 0 {
|
|
t.Errorf("Expected 0 latency with no data, got %v", latency)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// PythSource Basic Tests (without actual connection)
|
|
// =============================================================================
|
|
|
|
func TestPythSourceBasic(t *testing.T) {
|
|
src := NewPythSource("ws://localhost:4200/ws", "https://hermes.pyth.network")
|
|
|
|
// Check name
|
|
if src.Name() != "pyth" {
|
|
t.Errorf("Name() = %q, want pyth", src.Name())
|
|
}
|
|
|
|
// Check weight
|
|
if src.Weight() != 1.2 {
|
|
t.Errorf("Weight() = %f, want 1.2", src.Weight())
|
|
}
|
|
}
|
|
|
|
func TestPythSourceHealthyInitial(t *testing.T) {
|
|
src := NewPythSource("ws://localhost:4200/ws", "https://hermes.pyth.network")
|
|
defer src.Close()
|
|
|
|
// Should not be healthy without connection
|
|
if src.Healthy() {
|
|
t.Error("Should not be healthy without connection")
|
|
}
|
|
}
|
|
|
|
func TestPythSourcePrices(t *testing.T) {
|
|
src := NewPythSource("ws://localhost:4200/ws", "https://hermes.pyth.network")
|
|
defer src.Close()
|
|
|
|
// Without connection, Prices should still work (return empty)
|
|
prices, err := src.Prices([]string{"BTC-USD", "ETH-USD"})
|
|
if err != nil {
|
|
t.Fatalf("Prices failed: %v", err)
|
|
}
|
|
|
|
// Should be empty without data
|
|
if len(prices) != 0 {
|
|
t.Errorf("Expected 0 prices without data, got %d", len(prices))
|
|
}
|
|
}
|
|
|
|
func TestPythSourceSubscribe(t *testing.T) {
|
|
src := NewPythSource("ws://localhost:4200/ws", "https://hermes.pyth.network")
|
|
defer src.Close()
|
|
|
|
// Subscribe should track internally
|
|
if err := src.Subscribe("BTC-USD"); err != nil {
|
|
t.Errorf("Subscribe failed: %v", err)
|
|
}
|
|
|
|
// Unsubscribe
|
|
if err := src.Unsubscribe("BTC-USD"); err != nil {
|
|
t.Errorf("Unsubscribe failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Oracle Extended Tests
|
|
// =============================================================================
|
|
|
|
func TestOracleWithMultipleSources(t *testing.T) {
|
|
oracle := NewOracle()
|
|
|
|
// Add multiple sources
|
|
xchain := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
xchain.Start()
|
|
|
|
cchain := NewCChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
cchain.Start()
|
|
|
|
chainlink := NewChainlinkSource()
|
|
chainlink.Start()
|
|
|
|
oracle.AddSource("xchain", xchain)
|
|
oracle.AddSource("cchain", cchain)
|
|
oracle.AddSource("chainlink", chainlink)
|
|
|
|
// Watch symbol that exists in multiple sources
|
|
oracle.Watch("LUX-USDC")
|
|
|
|
oracle.Start()
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// Should have aggregated price
|
|
price := oracle.Price("LUX-USDC")
|
|
// Price might be 0 if sources don't have this exact symbol mapped
|
|
// At minimum, the oracle should run without crashing
|
|
|
|
oracle.Stop()
|
|
xchain.Close()
|
|
cchain.Close()
|
|
chainlink.Close()
|
|
|
|
_ = price // Suppress unused warning
|
|
}
|
|
|
|
func TestOracleCircuitBreakerTrip(t *testing.T) {
|
|
oracle := NewOracle()
|
|
|
|
// Add circuit breaker
|
|
oracle.mu.Lock()
|
|
oracle.breakers["TEST-USD"] = &CircuitBreaker{
|
|
Symbol: "TEST-USD",
|
|
MaxChange: 5.0,
|
|
Reset: time.Second,
|
|
LastPrice: 100.0, // Pre-set last price
|
|
}
|
|
oracle.mu.Unlock()
|
|
|
|
oracle.Start()
|
|
defer oracle.Stop()
|
|
|
|
// Breaker is set - should affect update behavior
|
|
// (Full integration would require injecting prices that trip breaker)
|
|
}
|
|
|
|
func TestOracleMinSourcesFiltering(t *testing.T) {
|
|
oracle := NewOracle()
|
|
oracle.minSources = 2 // Require 2 sources
|
|
|
|
// Add only 1 source
|
|
xchain := NewXChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
xchain.Start()
|
|
oracle.AddSource("xchain", xchain)
|
|
|
|
oracle.Watch("LUX-USDC")
|
|
oracle.Start()
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// With only 1 source, shouldn't aggregate (needs 2)
|
|
// Price should be 0
|
|
price := oracle.Price("LUX-USDC")
|
|
|
|
oracle.Stop()
|
|
xchain.Close()
|
|
|
|
_ = price // May or may not be 0 depending on implementation
|
|
}
|
|
|
|
func TestOracleUpdateChannel(t *testing.T) {
|
|
oracle := NewOracle()
|
|
|
|
updates := oracle.Updates()
|
|
if updates == nil {
|
|
t.Fatal("Updates channel should not be nil")
|
|
}
|
|
|
|
// Channel is read-only, verify it exists and is the correct type
|
|
// We can't write to it (which is correct behavior for subscribers)
|
|
_ = updates // Use the channel to verify it's valid
|
|
}
|
|
|
|
// =============================================================================
|
|
// Confidence Calculation Tests
|
|
// =============================================================================
|
|
|
|
func TestWeightedMedianConfidenceZero(t *testing.T) {
|
|
wm := &WeightedMedian{
|
|
MinSources: 1,
|
|
MaxDeviation: 0.10,
|
|
}
|
|
|
|
// Empty prices
|
|
conf := wm.confidence([]*Data{})
|
|
if conf != 0 {
|
|
t.Errorf("Expected 0 confidence for empty, got %f", conf)
|
|
}
|
|
}
|
|
|
|
func TestWeightedMedianConfidenceSingleSource(t *testing.T) {
|
|
wm := &WeightedMedian{
|
|
MinSources: 1,
|
|
MaxDeviation: 0.10,
|
|
}
|
|
|
|
prices := []*Data{
|
|
{Symbol: "TEST", Price: 100.0},
|
|
}
|
|
|
|
conf := wm.confidence(prices)
|
|
// Single source: sourceScore = 1/(1*2) = 0.5
|
|
// devScore = 1 (no deviation with single price)
|
|
// confidence = 0.5*0.6 + 1.0*0.4 = 0.7
|
|
if conf < 0.5 || conf > 1.0 {
|
|
t.Errorf("Expected confidence between 0.5 and 1.0, got %f", conf)
|
|
}
|
|
}
|
|
|
|
func TestWeightedMedianConfidenceHighAgreement(t *testing.T) {
|
|
wm := &WeightedMedian{
|
|
MinSources: 2,
|
|
MaxDeviation: 0.10,
|
|
}
|
|
|
|
prices := []*Data{
|
|
{Symbol: "TEST", Price: 100.0},
|
|
{Symbol: "TEST", Price: 100.001},
|
|
{Symbol: "TEST", Price: 99.999},
|
|
{Symbol: "TEST", Price: 100.0},
|
|
}
|
|
|
|
conf := wm.confidence(prices)
|
|
// 4 sources: sourceScore = 4/(2*2) = 1.0 (capped)
|
|
// Very low deviation -> devScore near 1.0
|
|
// Should be high confidence
|
|
if conf < 0.8 {
|
|
t.Errorf("Expected high confidence for high agreement, got %f", conf)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Symbol Normalization Edge Cases
|
|
// =============================================================================
|
|
|
|
func TestNormalizeWithSpaces(t *testing.T) {
|
|
result := Normalize(" LUX/USD ")
|
|
if result != "LUX-USD" {
|
|
t.Errorf("Normalize with spaces = %q, want LUX-USD", result)
|
|
}
|
|
}
|
|
|
|
func TestDetectPairWithBTC(t *testing.T) {
|
|
result := detectPair("ETHBTC")
|
|
if result != "ETH-BTC" {
|
|
t.Errorf("detectPair(ETHBTC) = %q, want ETH-BTC", result)
|
|
}
|
|
}
|
|
|
|
func TestDetectPairWithETH(t *testing.T) {
|
|
result := detectPair("LUXETH")
|
|
if result != "LUX-ETH" {
|
|
t.Errorf("detectPair(LUXETH) = %q, want LUX-ETH", result)
|
|
}
|
|
}
|
|
|
|
func TestDetectPairWithLUX(t *testing.T) {
|
|
result := detectPair("ETHLUX")
|
|
if result != "ETH-LUX" {
|
|
t.Errorf("detectPair(ETHLUX) = %q, want ETH-LUX", result)
|
|
}
|
|
}
|
|
|
|
func TestReverseWithMultipleMatches(t *testing.T) {
|
|
m := NewSymbolMap()
|
|
|
|
// LUX-USD has multiple aliases
|
|
variants := m.Reverse("LUX-USD")
|
|
|
|
// Should include LUX-USD itself plus aliases
|
|
found := false
|
|
for _, v := range variants {
|
|
if v == "LUX-USD" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Reverse should include the normalized symbol itself")
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Error Variable Tests
|
|
// =============================================================================
|
|
|
|
func TestErrorVariables(t *testing.T) {
|
|
// Verify error messages
|
|
if ErrNotFound.Error() != "price not found" {
|
|
t.Errorf("ErrNotFound = %q", ErrNotFound.Error())
|
|
}
|
|
if ErrStale.Error() != "price is stale" {
|
|
t.Errorf("ErrStale = %q", ErrStale.Error())
|
|
}
|
|
if ErrInsufficientSources.Error() != "insufficient price sources" {
|
|
t.Errorf("ErrInsufficientSources = %q", ErrInsufficientSources.Error())
|
|
}
|
|
if ErrCircuitBreaker.Error() != "circuit breaker tripped" {
|
|
t.Errorf("ErrCircuitBreaker = %q", ErrCircuitBreaker.Error())
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Additional Helper Tests
|
|
// =============================================================================
|
|
|
|
func TestQuoteFunction(t *testing.T) {
|
|
tests := []struct {
|
|
symbol string
|
|
expected string
|
|
}{
|
|
{"LUX-USD", "USD"},
|
|
{"BTC-USDC", "USDC"},
|
|
{"ETH/USDT", "USDT"},
|
|
{"SINGLE", ""},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
got := Quote(tc.symbol)
|
|
if got != tc.expected {
|
|
t.Errorf("Quote(%q) = %q, want %q", tc.symbol, got, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Benchmarks for New Functions
|
|
// =============================================================================
|
|
|
|
func BenchmarkOrderbookSourcePrice(b *testing.B) {
|
|
provider := &mockOrderbookProvider{
|
|
bids: map[string]float64{"LUX-USDC": 10.0},
|
|
asks: map[string]float64{"LUX-USDC": 10.02},
|
|
symbols: []string{"LUX-USDC"},
|
|
}
|
|
|
|
src := NewOrderbookSource(provider)
|
|
src.Start()
|
|
defer src.Stop()
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = src.Price("LUX-USDC")
|
|
}
|
|
}
|
|
|
|
func BenchmarkZooChainSourcePrice(b *testing.B) {
|
|
src := NewZooChainSource("http://localhost:9650", "ws://localhost:9650")
|
|
src.Start()
|
|
defer src.Close()
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = src.Price("ZOO-USDC")
|
|
}
|
|
}
|
|
|
|
func BenchmarkQChainVerifierFinality(b *testing.B) {
|
|
v := NewQChainVerifier("http://localhost:9650", "ws://localhost:9650")
|
|
v.Start()
|
|
defer v.Close()
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = v.Finality("x-chain")
|
|
}
|
|
}
|