[agent/codex:gpt-5.3-codex-spark] Read docs/RFC.md fully. Find ONE feature described in the sp... #11

Merged
Virgil merged 1 commit from main into dev 2026-04-03 20:01:26 +00:00
3 changed files with 408 additions and 0 deletions

244
mainchain.go Normal file
View file

@ -0,0 +1,244 @@
package dns
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
type MainchainClientOptions struct {
URL string
Username string
Password string
HTTPClient *http.Client
}
type MainchainAliasClient struct {
baseURL string
username string
password string
httpClient *http.Client
}
type MainchainRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []any `json:"params"`
ID int `json:"id"`
}
type MainchainRPCResponse struct {
Result json.RawMessage `json:"result"`
Error *HSDRPCError `json:"error"`
}
func NewMainchainAliasClient(options MainchainClientOptions) *MainchainAliasClient {
client := options.HTTPClient
if client == nil {
client = &http.Client{}
}
baseURL := strings.TrimSpace(options.URL)
if baseURL == "" {
baseURL = "http://127.0.0.1:14037"
}
return &MainchainAliasClient{
baseURL: baseURL,
username: options.Username,
password: options.Password,
httpClient: client,
}
}
// GetAllAliasDetails returns alias names mapped to HNS DNS names.
//
// client := dns.NewMainchainAliasClient(dns.MainchainClientOptions{
// URL: "http://127.0.0.1:14037",
// Username: "user",
// Password: "pass",
// })
// aliases, err := client.GetAllAliasDetails(context.Background())
func (client *MainchainAliasClient) GetAllAliasDetails(ctx context.Context) ([]string, error) {
request := MainchainRPCRequest{
JSONRPC: defaultHSDJSONRPCVersion,
Method: "get_all_alias_details",
Params: []any{},
ID: 1,
}
raw, err := client.call(ctx, request)
if err != nil {
return nil, err
}
aliases, err := parseMainchainAliases(raw)
if err != nil {
return nil, err
}
return aliases, nil
}
func (client *MainchainAliasClient) call(ctx context.Context, request MainchainRPCRequest) (json.RawMessage, error) {
body, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, client.baseURL, io.NopCloser(io.Reader(strings.NewReader(string(body)))))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Content-Type", "application/json")
if client.username != "" || client.password != "" {
httpRequest.Header.Set("Authorization", "Basic "+basicAuthToken(client.username, client.password))
}
response, err := client.httpClient.Do(httpRequest)
if err != nil {
return nil, err
}
defer func() { _ = response.Body.Close() }()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
return nil, fmt.Errorf("mainchain rpc request failed with status %d: %s", response.StatusCode, strings.TrimSpace(string(responseBody)))
}
var decoded MainchainRPCResponse
if err := json.Unmarshal(responseBody, &decoded); err != nil {
return nil, err
}
if decoded.Error != nil {
return nil, decoded.Error
}
return decoded.Result, nil
}
func parseMainchainAliases(raw json.RawMessage) ([]string, error) {
var result []string
if err := json.Unmarshal(raw, &result); err == nil {
return normalizeAliasList(result), nil
}
var wrappedResult map[string]json.RawMessage
if err := json.Unmarshal(raw, &wrappedResult); err == nil {
if aliasesRaw, ok := wrappedResult["aliases"]; ok {
return parseMainchainAliases(aliasesRaw)
}
if resultRaw, ok := wrappedResult["result"]; ok {
return parseMainchainAliases(resultRaw)
}
}
var rawRecords []json.RawMessage
if err := json.Unmarshal(raw, &rawRecords); err != nil {
return nil, errors.New("unable to parse get_all_alias_details result")
}
parsed := make([]string, 0, len(rawRecords))
for _, rawRecord := range rawRecords {
next, err := parseMainchainAliasRecord(rawRecord)
if err != nil {
return nil, err
}
if next != "" {
parsed = append(parsed, next)
}
}
return normalizeAliasList(parsed), nil
}
func parseMainchainAliasRecord(raw json.RawMessage) (string, error) {
var name string
if err := json.Unmarshal(raw, &name); err == nil {
return normalizeName(name), nil
}
var record map[string]json.RawMessage
if err := json.Unmarshal(raw, &record); err != nil {
return "", err
}
var candidate string
if value, ok := record["hns"]; ok {
if hns, ok := decodeString(value); ok {
candidate = normalizeName(hns)
}
}
if candidate == "" && record["comment"] != nil {
if comment, ok := decodeString(record["comment"]); ok {
candidate = extractAliasFromComment(comment)
}
}
if candidate == "" && record["alias"] != nil {
if alias, ok := decodeString(record["alias"]); ok {
candidate = normalizeName(alias)
}
}
if candidate == "" && record["name"] != nil {
if alias, ok := decodeString(record["name"]); ok {
candidate = normalizeName(alias)
}
}
return candidate, nil
}
func decodeString(raw json.RawMessage) (string, bool) {
var value string
if len(raw) == 0 {
return "", false
}
if err := json.Unmarshal(raw, &value); err != nil {
return "", false
}
value = strings.TrimSpace(value)
return value, value != ""
}
func extractAliasFromComment(comment string) string {
for _, token := range strings.Fields(comment) {
if strings.HasPrefix(token, "hns=") {
return normalizeName(strings.TrimSuffix(strings.TrimPrefix(token, "hns="), ";"))
}
}
if marker := strings.Index(comment, "hns="); marker >= 0 {
alias := comment[marker+4:]
if trim := strings.IndexAny(alias, " ;,"); trim >= 0 {
alias = alias[:trim]
}
alias = strings.TrimSpace(alias)
alias = strings.TrimSuffix(alias, ";")
return normalizeName(alias)
}
return ""
}
func normalizeAliasList(raw []string) []string {
seen := map[string]bool{}
normalized := make([]string, 0, len(raw))
for _, name := range raw {
next := normalizeName(name)
if next == "" {
continue
}
if seen[next] {
continue
}
seen[next] = true
normalized = append(normalized, next)
}
return normalized
}

142
mainchain_test.go Normal file
View file

@ -0,0 +1,142 @@
package dns
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
)
func TestMainchainClientGetsAliasDetailsAndParsesHNSComments(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
var payload struct {
Method string `json:"method"`
}
if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
t.Fatalf("unexpected request payload: %v", err)
}
if payload.Method != "get_all_alias_details" {
t.Fatalf("expected method get_all_alias_details, got %s", payload.Method)
}
responseWriter.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(responseWriter).Encode(map[string]any{
"result": []any{
map[string]any{
"name": "gateway",
"comment": "gateway alias hns=gateway.charon.lthn",
},
map[string]any{
"hns": "node.charon.lthn",
"name": "node",
},
},
})
}))
defer server.Close()
client := NewMainchainAliasClient(MainchainClientOptions{
URL: server.URL,
})
aliases, err := client.GetAllAliasDetails(context.Background())
if err != nil {
t.Fatalf("unexpected get_all_alias_details error: %v", err)
}
if len(aliases) != 2 {
t.Fatalf("expected 2 aliases, got %d", len(aliases))
}
if aliases[0] != "gateway.charon.lthn" || aliases[1] != "node.charon.lthn" {
t.Fatalf("unexpected aliases: %#v", aliases)
}
}
func TestServiceDiscoverFromMainchainAliasesUsesMainchainThenHSD(t *testing.T) {
var chainCalls 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(&chainCalls, 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()
service := NewService(ServiceOptions{})
chainClient := NewMainchainAliasClient(MainchainClientOptions{
URL: server.URL,
})
hsdClient := NewHSDClient(HSDClientOptions{
URL: server.URL,
})
if err := service.DiscoverFromMainchainAliases(context.Background(), chainClient, hsdClient); err != nil {
t.Fatalf("expected discover from mainchain aliases: %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 chainCalls != 1 || hsdTreeRootCalls != 1 || hsdAliasCalls != 2 {
t.Fatalf("expected chain=1 tree-root=1 name-resource=2, got %d %d %d", chainCalls, hsdTreeRootCalls, hsdAliasCalls)
}
}

View file

@ -93,6 +93,28 @@ func (service *Service) DiscoverFromChainAliases(ctx context.Context, client *HS
return service.discoverFromChainAliasesUsingTreeRoot(ctx, aliases, client)
}
// DiscoverFromMainchainAliases updates records from main-chain aliases resolved through HSD.
//
// service.DiscoverFromMainchainAliases(context.Background(), dns.NewMainchainAliasClient(dns.MainchainClientOptions{
// URL: "http://127.0.0.1:14037",
// }), dns.NewHSDClient(dns.HSDClientOptions{
// URL: "http://127.0.0.1:14037",
// }))
func (service *Service) DiscoverFromMainchainAliases(ctx context.Context, chainClient *MainchainAliasClient, hsdClient *HSDClient) error {
if chainClient == nil {
return fmt.Errorf("mainchain alias client is required")
}
aliases, err := chainClient.GetAllAliasDetails(ctx)
if err != nil {
return err
}
if len(aliases) == 0 {
return nil
}
return service.discoverFromChainAliasesUsingTreeRoot(ctx, aliases, hsdClient)
}
func (service *Service) discoverFromChainAliasesUsingTreeRoot(ctx context.Context, aliases []string, client *HSDClient) error {
if len(aliases) == 0 {
return nil