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 <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-03 23:46:56 +00:00
parent 958a799c45
commit 6e6b2b63c2
2 changed files with 47 additions and 1 deletions

View file

@ -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:

View file

@ -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