github.com/ethersphere/bee/v2@v2.2.0/pkg/resolver/multiresolver/multiresolver.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package multiresolver
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"path"
    11  	"strings"
    12  
    13  	"github.com/ethersphere/bee/v2/pkg/log"
    14  	"github.com/ethersphere/bee/v2/pkg/resolver"
    15  	"github.com/ethersphere/bee/v2/pkg/resolver/cidv1"
    16  	"github.com/ethersphere/bee/v2/pkg/resolver/client/ens"
    17  	"github.com/hashicorp/go-multierror"
    18  )
    19  
    20  // loggerName is the tree path name of the logger for this package.
    21  const loggerName = "multiresolver"
    22  
    23  // Ensure MultiResolver implements Resolver interface.
    24  var _ resolver.Interface = (*MultiResolver)(nil)
    25  
    26  var (
    27  	// ErrTLDTooLong denotes when a TLD in a name exceeds maximum length.
    28  	ErrTLDTooLong = fmt.Errorf("TLD exceeds maximum length of %d characters", maxTLDLength)
    29  	// ErrInvalidTLD denotes passing an invalid TLD to the MultiResolver.
    30  	ErrInvalidTLD = errors.New("invalid TLD")
    31  	// ErrResolverChainEmpty denotes trying to pop an empty resolver chain.
    32  	ErrResolverChainEmpty = errors.New("resolver chain empty")
    33  	// ErrResolverChainFailed denotes that an entire name resolution chain
    34  	// for a given TLD failed.
    35  	ErrResolverChainFailed = errors.New("resolver chain failed")
    36  	// ErrCloseFailed denotes that closing the multiresolver failed.
    37  	ErrCloseFailed = errors.New("close failed")
    38  )
    39  
    40  type resolverMap map[string][]resolver.Interface
    41  
    42  // MultiResolver performs name resolutions based on the TLD label in the name.
    43  type MultiResolver struct {
    44  	resolvers resolverMap
    45  	logger    log.Logger
    46  	cfgs      []ConnectionConfig
    47  	// ForceDefault will force all names to be resolved by the default
    48  	// resolution chain, regadless of their TLD.
    49  	ForceDefault bool
    50  }
    51  
    52  // Option is a function that applies an option to a MultiResolver.
    53  type Option func(*MultiResolver)
    54  
    55  // NewMultiResolver will return a new MultiResolver instance.
    56  func NewMultiResolver(opts ...Option) *MultiResolver {
    57  	mr := &MultiResolver{
    58  		resolvers: make(resolverMap),
    59  	}
    60  
    61  	// Apply all options.
    62  	for _, o := range opts {
    63  		o(mr)
    64  	}
    65  
    66  	// Discard log output by default.
    67  	if mr.logger == nil {
    68  		mr.logger = log.Noop
    69  	}
    70  	log := mr.logger
    71  
    72  	if len(mr.cfgs) == 0 {
    73  		log.Info("name resolver: no name resolution service provided")
    74  		return mr
    75  	}
    76  
    77  	// Attempt to connect to each resolver using the connection string.
    78  	for _, c := range mr.cfgs {
    79  
    80  		// NOTE: if we want to create a specific client based on the TLD
    81  		// we can do it here.
    82  		mr.connectENSClient(c.TLD, c.Address, c.Endpoint)
    83  	}
    84  
    85  	return mr
    86  }
    87  
    88  // WithConnectionConfigs will set the initial connection configuration.
    89  func WithConnectionConfigs(cfgs []ConnectionConfig) Option {
    90  	return func(mr *MultiResolver) {
    91  		mr.cfgs = cfgs
    92  	}
    93  }
    94  
    95  // WithLogger will set the logger used by the MultiResolver.
    96  func WithLogger(logger log.Logger) Option {
    97  	return func(mr *MultiResolver) {
    98  		mr.logger = logger.WithName(loggerName).Register()
    99  	}
   100  }
   101  
   102  // WithForceDefault will force resolution using the default resolver chain.
   103  func WithForceDefault() Option {
   104  	return func(mr *MultiResolver) {
   105  		mr.ForceDefault = true
   106  	}
   107  }
   108  
   109  func WithDefaultCIDResolver() Option {
   110  	return func(mr *MultiResolver) {
   111  		mr.PushResolver("", cidv1.Resolver{})
   112  	}
   113  }
   114  
   115  // PushResolver will push a new Resolver to the name resolution chain for the
   116  // given TLD. An empty TLD will push to the default resolver chain.
   117  func (mr *MultiResolver) PushResolver(tld string, r resolver.Interface) {
   118  	mr.resolvers[tld] = append(mr.resolvers[tld], r)
   119  }
   120  
   121  // PopResolver will pop the last resolver from the name resolution chain for the
   122  // given TLD. An empty TLD will pop from the default resolver chain.
   123  func (mr *MultiResolver) PopResolver(tld string) error {
   124  	l := len(mr.resolvers[tld])
   125  	if l == 0 {
   126  		return fmt.Errorf("tld %s: %w", tld, ErrResolverChainEmpty)
   127  	}
   128  	mr.resolvers[tld] = mr.resolvers[tld][:l-1]
   129  	return nil
   130  }
   131  
   132  // ChainCount returns the number of resolvers in a resolver chain for the given
   133  // tld.
   134  // TLD names should be prepended with a dot (eg ".tld"). An empty TLD will
   135  // return the number of resolvers in the default resolver chain.
   136  func (mr *MultiResolver) ChainCount(tld string) int {
   137  	return len(mr.resolvers[tld])
   138  }
   139  
   140  // GetChain will return the resolution chain for a given TLD.
   141  // TLD names should be prepended with a dot (eg ".tld"). An empty TLD will
   142  // return all resolvers in the default resolver chain.
   143  func (mr *MultiResolver) GetChain(tld string) []resolver.Interface {
   144  	return mr.resolvers[tld]
   145  }
   146  
   147  // Resolve will attempt to resolve a name to an address.
   148  // The resolution chain is selected based on the TLD of the name. If the name
   149  // does not end in a TLD, the default resolution chain is selected.
   150  // The resolution will be performed iteratively on the resolution chain,
   151  // returning the result of the first Resolver that succeeds. If all resolvers
   152  // in the chain return an error, the function will return an ErrResolveFailed.
   153  func (mr *MultiResolver) Resolve(name string) (addr resolver.Address, err error) {
   154  	tld := ""
   155  	if !mr.ForceDefault {
   156  		tld = getTLD(name)
   157  	}
   158  	chain := mr.resolvers[tld]
   159  
   160  	// If no resolver chain is found, switch to the default chain.
   161  	if len(chain) == 0 {
   162  		chain = mr.resolvers[""]
   163  	}
   164  
   165  	var errs *multierror.Error
   166  	for _, res := range chain {
   167  		addr, err = res.Resolve(name)
   168  		if err == nil {
   169  			return addr, nil
   170  		}
   171  		errs = multierror.Append(err)
   172  	}
   173  
   174  	return addr, errs.ErrorOrNil()
   175  }
   176  
   177  // Close all will call Close on all resolvers in all resolver chains.
   178  func (mr *MultiResolver) Close() error {
   179  	var errs *multierror.Error
   180  
   181  	for _, chain := range mr.resolvers {
   182  		for _, r := range chain {
   183  			if err := r.Close(); err != nil {
   184  				errs = multierror.Append(err)
   185  			}
   186  		}
   187  	}
   188  
   189  	return errs.ErrorOrNil()
   190  }
   191  
   192  func getTLD(name string) string {
   193  	return path.Ext(strings.ToLower(name))
   194  }
   195  
   196  func (mr *MultiResolver) connectENSClient(tld, address, endpoint string) {
   197  	log := mr.logger
   198  
   199  	if address == "" {
   200  		log.Debug("connecting to endpoint", "tld", tld, "endpoint", endpoint)
   201  	} else {
   202  		log.Debug("connecting to endpoint with contract address", "tld", tld, "endpoint", endpoint, "contract_address", address)
   203  	}
   204  
   205  	ensCl, err := ens.NewClient(endpoint, ens.WithContractAddress(address))
   206  	if err != nil {
   207  		log.Error(err, "resolver on endpoint failed", "tld", tld, "endpoint", endpoint)
   208  	} else {
   209  		log.Info("connected", "tld", tld, "endpoint", endpoint)
   210  		mr.PushResolver(tld, ensCl)
   211  	}
   212  }