From a59590d633bb6245a2b235ab46c1a5947a44fde5 Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 20:06:07 +0000 Subject: [PATCH] feat(dns): fallback to chain client when alias discoverer fails Co-Authored-By: Virgil --- mainchain_test.go | 94 +++++++++++++++++++++++++++++++++++++++++++++++ service.go | 9 +++-- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/mainchain_test.go b/mainchain_test.go index d8a8e73..606dbd3 100644 --- a/mainchain_test.go +++ b/mainchain_test.go @@ -289,3 +289,97 @@ func TestServiceDiscoverFromMainchainAliasesFallsBackToFallbackChainAliasDiscove t.Fatalf("expected fallback discovery to still trigger RPC calls, got treeRoot=%d nameResource=%d", atomic.LoadInt32(&treeRootCalls), atomic.LoadInt32(&nameResourceCalls)) } } + +func TestServiceDiscoverFromMainchainAliasesFallsBackToChainClientWhenPrimaryDiscovererFails(t *testing.T) { + var chainAliasCalls int32 + var hsdTreeRootCalls int32 + var hsdAliasCalls int32 + + server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) { + var payload struct { + Method string `json:"method"` + Params []any `json:"params"` + } + if err := json.NewDecoder(request.Body).Decode(&payload); err != nil { + t.Fatalf("unexpected request payload: %v", err) + } + + switch payload.Method { + case "get_all_alias_details": + atomic.AddInt32(&chainAliasCalls, 1) + responseWriter.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(responseWriter).Encode(map[string]any{ + "result": []any{ + map[string]any{ + "hns": "gateway.charon.lthn", + }, + map[string]any{ + "hns": "node.charon.lthn", + }, + }, + }) + case "getblockchaininfo": + atomic.AddInt32(&hsdTreeRootCalls, 1) + responseWriter.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(responseWriter).Encode(map[string]any{ + "result": map[string]any{ + "tree_root": "chain-root-1", + }, + }) + case "getnameresource": + atomic.AddInt32(&hsdAliasCalls, 1) + responseWriter.Header().Set("Content-Type", "application/json") + switch payload.Params[0] { + case "gateway.charon.lthn": + _ = json.NewEncoder(responseWriter).Encode(map[string]any{ + "result": map[string]any{ + "a": []string{"10.10.10.10"}, + }, + }) + case "node.charon.lthn": + _ = json.NewEncoder(responseWriter).Encode(map[string]any{ + "result": map[string]any{ + "aaaa": []string{"2600:1f1c:7f0:4f01::2"}, + }, + }) + default: + t.Fatalf("unexpected alias lookup: %#v", payload.Params) + } + default: + t.Fatalf("unexpected method: %s", payload.Method) + } + })) + defer server.Close() + + chainClient := NewMainchainAliasClient(MainchainClientOptions{ + URL: server.URL, + }) + hsdClient := NewHSDClient(HSDClientOptions{ + URL: server.URL, + }) + service := NewService(ServiceOptions{ + ChainAliasDiscoverer: func(_ context.Context) ([]string, error) { + return nil, errors.New("blockchain service unavailable") + }, + }) + + if err := service.DiscoverFromMainchainAliases(context.Background(), chainClient, hsdClient); err != nil { + t.Fatalf("expected chain alias discover fallback to chain client: %v", err) + } + + gateway, ok := service.Resolve("gateway.charon.lthn") + if !ok || len(gateway.A) != 1 || gateway.A[0] != "10.10.10.10" { + t.Fatalf("expected gateway A record, got %#v (ok=%t)", gateway, ok) + } + node, ok := service.Resolve("node.charon.lthn") + if !ok || len(node.AAAA) != 1 || node.AAAA[0] != "2600:1f1c:7f0:4f01::2" { + t.Fatalf("expected node AAAA record, got %#v (ok=%t)", node, ok) + } + + if atomic.LoadInt32(&chainAliasCalls) != 1 { + t.Fatalf("expected chain alias to be queried once, got %d", atomic.LoadInt32(&chainAliasCalls)) + } + if atomic.LoadInt32(&hsdTreeRootCalls) != 1 || atomic.LoadInt32(&hsdAliasCalls) != 2 { + t.Fatalf("expected one tree-root and two name-resource RPC calls, got treeRoot=%d nameResource=%d", atomic.LoadInt32(&hsdTreeRootCalls), atomic.LoadInt32(&hsdAliasCalls)) + } +} diff --git a/service.go b/service.go index f544427..50ea1e3 100644 --- a/service.go +++ b/service.go @@ -111,15 +111,18 @@ func (service *Service) DiscoverFromMainchainAliases(ctx context.Context, chainC if service.chainAliasDiscoverer != nil { return service.chainAliasDiscoverer(ctx) } - if chainClient == nil { - return nil, nil + if chainClient != nil { + return chainClient.GetAllAliasDetails(ctx) } - return chainClient.GetAllAliasDetails(ctx) + return nil, nil }, func(ctx context.Context) ([]string, error) { if service.fallbackChainAliasDiscoverer != nil { return service.fallbackChainAliasDiscoverer(ctx) } + if chainClient != nil && service.chainAliasDiscoverer != nil { + return chainClient.GetAllAliasDetails(ctx) + } return nil, nil }, ) -- 2.45.3