From 6e6b2b63c2a4b82eece7e683d0df77844ec59571 Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 23:46:56 +0000 Subject: [PATCH] feat(dns): treat nil chain alias action result as empty alias list Treating a nil blockchain.chain.aliases response as an explicit empty alias set allows dns.discover to clear stale cache and avoid unnecessary fallback. Co-Authored-By: Virgil --- service.go | 2 +- service_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/service.go b/service.go index 915b062..4f1f3b3 100644 --- a/service.go +++ b/service.go @@ -411,7 +411,7 @@ func (service *Service) discoverAliasesFromActionCaller(ctx context.Context, act func parseActionAliasList(value any) ([]string, error) { switch aliases := value.(type) { case nil: - return nil, fmt.Errorf("blockchain.chain.aliases action returned no value") + return []string{}, nil case string: return normalizeAliasList([]string{aliases}), nil case []string: diff --git a/service_test.go b/service_test.go index d25a334..42a74f1 100644 --- a/service_test.go +++ b/service_test.go @@ -899,6 +899,52 @@ func TestServiceDiscoverAliasesUsesConfiguredActionCaller(t *testing.T) { } } +func TestServiceDiscoverAliasesTreatsNilActionResponseAsNoAliases(t *testing.T) { + var actionCalled bool + var fallbackCallCount int32 + + server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) { + atomic.AddInt32(&fallbackCallCount, 1) + t.Fatalf("expected nil action result to avoid fallback RPC calls, got %s", request.Method) + _, _ = responseWriter.Write([]byte("{}")) + })) + defer server.Close() + + service := NewService(ServiceOptions{ + Records: map[string]NameRecords{ + "gateway.charon.lthn": { + A: []string{"10.10.10.10"}, + }, + }, + ChainAliasActionCaller: actionCallerFunc(func(ctx context.Context, name string, values map[string]any) (any, bool, error) { + actionCalled = true + if name != "blockchain.chain.aliases" { + t.Fatalf("unexpected action name: %s", name) + } + return nil, true, nil + }), + HSDClient: NewHSDClient(HSDClientOptions{URL: server.URL}), + MainchainURL: server.URL, + }) + + if err := service.DiscoverAliases(context.Background()); err != nil { + t.Fatalf("expected DiscoverAliases to treat nil action result as empty aliases: %v", err) + } + if !actionCalled { + t.Fatal("expected action caller to be invoked") + } + if atomic.LoadInt32(&fallbackCallCount) != 0 { + t.Fatalf("expected no fallback RPC calls, got %d", atomic.LoadInt32(&fallbackCallCount)) + } + if _, ok := service.Resolve("gateway.charon.lthn"); ok { + t.Fatal("expected existing records to be cleared after nil alias response") + } + health := service.Health() + if health.NamesCached != 0 { + t.Fatalf("expected empty cache after nil alias response, got %d", health.NamesCached) + } +} + func TestServiceDiscoverAliasesUsesConfiguredChainAliasAction(t *testing.T) { var treeRootCalls int32 var nameResourceCalls int32