github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/herocache/dns_cache.go (about)

     1  package herocache
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  
     7  	"github.com/rs/zerolog"
     8  
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/module"
    11  	"github.com/onflow/flow-go/module/mempool"
    12  	herocache "github.com/onflow/flow-go/module/mempool/herocache/backdata"
    13  	"github.com/onflow/flow-go/module/mempool/herocache/backdata/heropool"
    14  	"github.com/onflow/flow-go/module/mempool/stdmap"
    15  )
    16  
    17  type DNSCache struct {
    18  	ipCache  *stdmap.Backend
    19  	txtCache *stdmap.Backend
    20  }
    21  
    22  func NewDNSCache(sizeLimit uint32, logger zerolog.Logger, ipCollector module.HeroCacheMetrics, txtCollector module.HeroCacheMetrics,
    23  ) *DNSCache {
    24  	return &DNSCache{
    25  		txtCache: stdmap.NewBackend(
    26  			stdmap.WithBackData(
    27  				herocache.NewCache(
    28  					sizeLimit,
    29  					herocache.DefaultOversizeFactor,
    30  					heropool.LRUEjection,
    31  					logger.With().Str("mempool", "dns-txt-cache").Logger(),
    32  					txtCollector))),
    33  		ipCache: stdmap.NewBackend(
    34  			stdmap.WithBackData(
    35  				herocache.NewCache(
    36  					sizeLimit,
    37  					herocache.DefaultOversizeFactor,
    38  					heropool.LRUEjection,
    39  					logger.With().Str("mempool", "dns-ip-cache").Logger(),
    40  					ipCollector))),
    41  	}
    42  }
    43  
    44  // PutIpDomain adds the given ip domain into the cache.
    45  func (d *DNSCache) PutIpDomain(domain string, addresses []net.IPAddr, timestamp int64) bool {
    46  	i := ipEntity{
    47  		IpRecord: mempool.IpRecord{
    48  			Domain:    domain,
    49  			Addresses: addresses,
    50  			Timestamp: timestamp,
    51  			Locked:    false,
    52  		},
    53  		id: domainToIdentifier(domain),
    54  	}
    55  
    56  	return d.ipCache.Add(i)
    57  }
    58  
    59  // PutTxtRecord adds the given txt record into the cache.
    60  func (d *DNSCache) PutTxtRecord(domain string, record []string, timestamp int64) bool {
    61  	t := txtEntity{
    62  		TxtRecord: mempool.TxtRecord{
    63  			Txt:       domain,
    64  			Records:   record,
    65  			Timestamp: timestamp,
    66  			Locked:    false,
    67  		},
    68  		id: domainToIdentifier(domain),
    69  	}
    70  
    71  	return d.txtCache.Add(t)
    72  }
    73  
    74  // GetDomainIp returns the ip domain if exists in the cache.
    75  // The boolean return value determines if domain exists in the cache.
    76  func (d *DNSCache) GetDomainIp(domain string) (*mempool.IpRecord, bool) {
    77  	entity, ok := d.ipCache.ByID(domainToIdentifier(domain))
    78  	if !ok {
    79  		return nil, false
    80  	}
    81  
    82  	i, ok := entity.(ipEntity)
    83  	if !ok {
    84  		return nil, false
    85  	}
    86  	ipRecord := i.IpRecord
    87  
    88  	return &ipRecord, true
    89  }
    90  
    91  // GetTxtRecord returns the txt record if exists in the cache.
    92  // The boolean return value determines if record exists in the cache.
    93  func (d *DNSCache) GetTxtRecord(domain string) (*mempool.TxtRecord, bool) {
    94  	entity, ok := d.txtCache.ByID(domainToIdentifier(domain))
    95  	if !ok {
    96  		return nil, false
    97  	}
    98  
    99  	t, ok := entity.(txtEntity)
   100  	if !ok {
   101  		return nil, false
   102  	}
   103  	txtRecord := t.TxtRecord
   104  
   105  	return &txtRecord, true
   106  }
   107  
   108  // RemoveIp removes an ip domain from cache.
   109  func (d *DNSCache) RemoveIp(domain string) bool {
   110  	return d.ipCache.Remove(domainToIdentifier(domain))
   111  }
   112  
   113  // RemoveTxt removes a txt record from cache.
   114  func (d *DNSCache) RemoveTxt(domain string) bool {
   115  	return d.txtCache.Remove(domainToIdentifier(domain))
   116  }
   117  
   118  // LockIPDomain locks an ip address dns record if exists in the cache.
   119  // The boolean return value determines whether attempt on locking was successful.
   120  //
   121  // A locking attempt is successful when the domain record exists in the cache and has not
   122  // been locked before.
   123  // Once a domain record gets locked the only way to unlock it is through updating that record.
   124  //
   125  // The locking process is defined to record that a resolving attempt is ongoing for an expired domain.
   126  // So the locking happens to avoid any other parallel resolving
   127  func (d *DNSCache) LockIPDomain(domain string) (bool, error) {
   128  	locked := false
   129  	err := d.ipCache.Run(func(backdata mempool.BackData) error {
   130  		id := domainToIdentifier(domain)
   131  		entity, ok := backdata.ByID(id)
   132  		if !ok {
   133  			return fmt.Errorf("ip record does not exist in cache for locking: %s", domain)
   134  		}
   135  
   136  		record, ok := entity.(ipEntity)
   137  		if !ok {
   138  			return fmt.Errorf("unexpected type retrieved, expected: %T, obtained: %T", ipEntity{}, entity)
   139  		}
   140  
   141  		if record.Locked {
   142  			return nil // record has already been locked
   143  		}
   144  
   145  		record.Locked = true
   146  
   147  		if _, removed := backdata.Remove(id); !removed {
   148  			return fmt.Errorf("ip record could not be removed from backdata")
   149  		}
   150  
   151  		if added := backdata.Add(id, record); !added {
   152  			return fmt.Errorf("updated ip record could not be added to back data")
   153  		}
   154  
   155  		locked = record.Locked
   156  		return nil
   157  	})
   158  
   159  	return locked, err
   160  }
   161  
   162  // UpdateIPDomain updates the dns record for the given ip domain with the new address and timestamp values.
   163  func (d *DNSCache) UpdateIPDomain(domain string, addresses []net.IPAddr, timestamp int64) error {
   164  	return d.ipCache.Run(func(backdata mempool.BackData) error {
   165  		id := domainToIdentifier(domain)
   166  
   167  		// removes old entry if exists.
   168  		backdata.Remove(id)
   169  
   170  		ipRecord := ipEntity{
   171  			IpRecord: mempool.IpRecord{
   172  				Domain:    domain,
   173  				Addresses: addresses,
   174  				Timestamp: timestamp,
   175  				Locked:    false, // by default an ip record is unlocked.
   176  			},
   177  			id: id,
   178  		}
   179  
   180  		if added := backdata.Add(id, ipRecord); !added {
   181  			return fmt.Errorf("updated ip record could not be added to backdata")
   182  		}
   183  
   184  		return nil
   185  	})
   186  }
   187  
   188  // UpdateTxtRecord updates the dns record for the given txt domain with the new address and timestamp values.
   189  func (d *DNSCache) UpdateTxtRecord(txt string, records []string, timestamp int64) error {
   190  	return d.txtCache.Run(func(backdata mempool.BackData) error {
   191  		id := domainToIdentifier(txt)
   192  
   193  		// removes old entry if exists.
   194  		backdata.Remove(id)
   195  
   196  		txtRecord := txtEntity{
   197  			TxtRecord: mempool.TxtRecord{
   198  				Txt:       txt,
   199  				Records:   records,
   200  				Timestamp: timestamp,
   201  				Locked:    false, // by default a txt record is unlocked.
   202  			},
   203  			id: id,
   204  		}
   205  
   206  		if added := backdata.Add(id, txtRecord); !added {
   207  			return fmt.Errorf("updated txt record could not be added to backdata")
   208  		}
   209  
   210  		return nil
   211  	})
   212  }
   213  
   214  // LockTxtRecord locks a txt address dns record if exists in the cache.
   215  // The boolean return value determines whether attempt on locking was successful.
   216  //
   217  // A locking attempt is successful when the domain record exists in the cache and has not
   218  // been locked before.
   219  // Once a domain record gets locked the only way to unlock it is through updating that record.
   220  //
   221  // The locking process is defined to record that a resolving attempt is ongoing for an expired domain.
   222  // So the locking happens to avoid any other parallel resolving.
   223  func (d *DNSCache) LockTxtRecord(txt string) (bool, error) {
   224  	locked := false
   225  	err := d.txtCache.Run(func(backdata mempool.BackData) error {
   226  		id := domainToIdentifier(txt)
   227  		entity, ok := backdata.ByID(id)
   228  		if !ok {
   229  			return fmt.Errorf("txt record does not exist in cache for locking: %s", txt)
   230  		}
   231  
   232  		record, ok := entity.(txtEntity)
   233  		if !ok {
   234  			return fmt.Errorf("unexpected type retrieved, expected: %T, obtained: %T", txtEntity{}, entity)
   235  		}
   236  
   237  		if record.Locked {
   238  			return nil // record has already been locked
   239  		}
   240  
   241  		record.Locked = true
   242  
   243  		if _, removed := backdata.Remove(id); !removed {
   244  			return fmt.Errorf("txt record could not be removed from backdata")
   245  		}
   246  
   247  		if added := backdata.Add(id, record); !added {
   248  			return fmt.Errorf("updated txt record could not be added to back data")
   249  		}
   250  
   251  		locked = record.Locked
   252  		return nil
   253  	})
   254  
   255  	return locked, err
   256  }
   257  
   258  // Size returns total domains maintained into this cache.
   259  // The first returned value determines number of ip domains.
   260  // The second returned value determines number of txt records.
   261  func (d DNSCache) Size() (uint, uint) {
   262  	return d.ipCache.Size(), d.txtCache.Size()
   263  }
   264  
   265  // ipEntity is a dns cache entry for ip records.
   266  type ipEntity struct {
   267  	mempool.IpRecord
   268  	// caching identifier to avoid cpu overhead
   269  	// per query.
   270  	id flow.Identifier
   271  }
   272  
   273  func (i ipEntity) ID() flow.Identifier {
   274  	return i.id
   275  }
   276  
   277  func (i ipEntity) Checksum() flow.Identifier {
   278  	return domainToIdentifier(i.IpRecord.Domain)
   279  }
   280  
   281  // txtEntity is a dns cache entry for txt records.
   282  type txtEntity struct {
   283  	mempool.TxtRecord
   284  	// caching identifier to avoid cpu overhead
   285  	// per query.
   286  	id flow.Identifier
   287  }
   288  
   289  func (t txtEntity) ID() flow.Identifier {
   290  	return t.id
   291  }
   292  
   293  func (t txtEntity) Checksum() flow.Identifier {
   294  	return domainToIdentifier(t.TxtRecord.Txt)
   295  }
   296  
   297  func domainToIdentifier(domain string) flow.Identifier {
   298  	return flow.MakeID(domain)
   299  }