Ctrl K
ComunidadGuías…

internal/tondns/resolver.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package tondns

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/xssnick/tonutils-go/liteclient"
	"github.com/xssnick/tonutils-go/ton"
	"github.com/xssnick/tonutils-go/ton/dns"
)

const defaultConfigURL = "https://ton-blockchain.github.io/global.config.json"

// Resolver answers which wallet address a .ton name points at, so an operator
// can enter the name a collector actually uses instead of its raw form. It
// keeps one lite server connection for its lifetime, the same way the custom
// fragment verifier does, and connects lazily on the first lookup.
type Resolver struct {
	configURL string

	mu     sync.Mutex
	pool   *liteclient.ConnectionPool
	api    ton.APIClientWrapped
	client *dns.Client
}

// New returns a resolver reading the mainnet config from configURL, falling
// back to the public config when none is configured.
func New(configURL string) *Resolver {
	configURL = strings.TrimSpace(configURL)
	if configURL == "" {
		configURL = defaultConfigURL
	}
	return &Resolver{configURL: configURL}
}

// Resolve returns the raw mainnet address published by name's wallet record.
func (r *Resolver) Resolve(ctx context.Context, name string) (string, error) {
	if r == nil {
		return "", errors.New("ton dns resolver is not configured")
	}
	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()
	client, err := r.dnsClient(ctx)
	if err != nil {
		return "", err
	}
	domain, err := client.Resolve(ctx, name)
	if err != nil {
		if errors.Is(err, dns.ErrNoSuchRecord) {
			// WHY: an unregistered .ton name is the common operator mistake, so
			// the message names the domain and says what to send instead.
			return "", fmt.Errorf("%s is not registered on ton dns", name)
		}
		return "", fmt.Errorf("resolve %s: %w", name, err)
	}
	wallet := domain.GetWalletRecord()
	if wallet == nil || wallet.IsAddrNone() || wallet.Workchain() != 0 || wallet.IsTestnetOnly() {
		return "", fmt.Errorf("%s has no mainnet wallet record", name)
	}
	return wallet.StringRaw(), nil
}

// Close releases the lite server connections; a later Resolve reconnects.
func (r *Resolver) Close() {
	if r == nil {
		return
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.pool != nil {
		r.pool.Stop()
	}
	r.pool, r.api, r.client = nil, nil, nil
}

func (r *Resolver) dnsClient(ctx context.Context) (*dns.Client, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.client != nil {
		return r.client, nil
	}
	pool := liteclient.NewConnectionPool()
	connectCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
	defer cancel()
	if err := pool.AddConnectionsFromConfigUrl(connectCtx, r.configURL); err != nil {
		pool.Stop()
		return nil, fmt.Errorf("connect ton lite servers: %w", err)
	}
	api := ton.NewAPIClient(pool, ton.ProofCheckPolicyFast).WithRetryTimeout(2, 4*time.Second)
	root, err := dns.GetRootContractAddr(ctx, api)
	if err != nil {
		pool.Stop()
		return nil, fmt.Errorf("read ton dns root: %w", err)
	}
	r.pool, r.api, r.client = pool, api, dns.NewDNSClient(api, root)
	return r.client, nil
}