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 }