github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/herocache/dns_cache_test.go (about) 1 package herocache_test 2 3 import ( 4 "net" 5 "sync" 6 "testing" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/onflow/flow-go/module/mempool/herocache" 11 "github.com/onflow/flow-go/module/metrics" 12 "github.com/onflow/flow-go/utils/unittest" 13 "github.com/onflow/flow-go/utils/unittest/network" 14 ) 15 16 // TestDNSCache_HappyPath checks the correctness storing and retrieving from dns cache. 17 func TestDNSCache_HappyPath(t *testing.T) { 18 total := 700 // total entries to store (i.e., 700 ip domains and 700 txt records) 19 sizeLimit := uint32(500) // cache size limit (i.e., 500 ip domains and 500 txt records) 20 21 ipFixtures := network.IpLookupFixture(total) 22 txtFixtures := network.TxtLookupFixture(total) 23 24 cache := herocache.NewDNSCache(sizeLimit, unittest.Logger(), metrics.NewNoopCollector(), metrics.NewNoopCollector()) 25 26 // cache must be initially empty 27 ips, txts := cache.Size() 28 require.Equal(t, uint(0), ips) 29 require.Equal(t, uint(0), txts) 30 31 // adding 700 txt records and 700 ip domains to cache 32 testAddToCache(t, cache, ipFixtures, txtFixtures) 33 34 // cache must be full up to its limit 35 ips, txts = cache.Size() 36 require.Equal(t, uint(sizeLimit), ips) 37 require.Equal(t, uint(sizeLimit), txts) 38 39 // only 500 txt records and 500 ip domains must be retrievable 40 testRetrievalMatchCount(t, cache, ipFixtures, txtFixtures, int(sizeLimit)) 41 } 42 43 // TestDNSCache_Update checks the correctness of updating dns records. 44 func TestDNSCache_Update(t *testing.T) { 45 ipFixture := []net.IPAddr{network.NetIPAddrFixture()} 46 ipDomain := "ip-domain" 47 48 txtFixture := []string{network.TxtIPFixture()} 49 txtDomain := "txt-domain" 50 51 cache := herocache.NewDNSCache(10, 52 unittest.Logger(), 53 metrics.NewNoopCollector(), 54 metrics.NewNoopCollector()) 55 56 // adding records to dns cache 57 require.True(t, cache.PutIpDomain(ipDomain, ipFixture, int64(0))) 58 require.True(t, cache.PutTxtRecord(txtDomain, txtFixture, int64(0))) 59 60 // locking both txt and ip records before updating 61 // to later check an update unlocks them. 62 locked, err := cache.LockIPDomain(ipDomain) 63 require.NoError(t, err) 64 require.True(t, locked) 65 locked, err = cache.LockTxtRecord(txtDomain) 66 require.NoError(t, err) 67 require.True(t, locked) 68 69 // updating ip record 70 updatedIpFixture := []net.IPAddr{network.NetIPAddrFixture()} 71 updatedIpTimestamp := int64(10) 72 require.NoError(t, cache.UpdateIPDomain(ipDomain, updatedIpFixture, updatedIpTimestamp)) 73 74 // updating txt record 75 updatedTxtFixture := []string{network.TxtIPFixture()} 76 updatedTxtTimestamp := int64(12) 77 require.NoError(t, cache.UpdateTxtRecord(txtDomain, updatedTxtFixture, updatedTxtTimestamp)) 78 79 // sanity checking of the updated records 80 // updated ip record 81 ipRecord, ok := cache.GetDomainIp(ipDomain) 82 require.True(t, ok) 83 require.Equal(t, ipRecord.Addresses, updatedIpFixture) 84 require.Equal(t, ipRecord.Domain, ipDomain) 85 require.False(t, ipRecord.Locked) // an update must unlock it. 86 require.Equal(t, ipRecord.Timestamp, updatedIpTimestamp) 87 88 // updated txt record 89 txtRecord, ok := cache.GetTxtRecord(txtDomain) 90 require.True(t, ok) 91 require.Equal(t, txtRecord.Records, updatedTxtFixture) 92 require.Equal(t, txtRecord.Txt, txtDomain) 93 require.False(t, txtRecord.Locked) // an update must unlock it. 94 require.Equal(t, txtRecord.Timestamp, updatedTxtTimestamp) 95 } 96 97 // TestDNSCache_Lock evaluates that locking a txt (or ip) record can be done successfully once, and 98 // attempts to lock and already locked record fail. 99 // It also evaluates that a locked record can be retrieved successfully. 100 func TestDNSCache_Lock(t *testing.T) { 101 ipFixture := []net.IPAddr{network.NetIPAddrFixture()} 102 ipDomain := "ip-domain" 103 104 txtFixture := []string{network.TxtIPFixture()} 105 txtDomain := "txt-domain" 106 107 cache := herocache.NewDNSCache( 108 10, 109 unittest.Logger(), 110 metrics.NewNoopCollector(), 111 metrics.NewNoopCollector()) 112 113 // adding records to dns cache 114 require.True(t, cache.PutIpDomain(ipDomain, ipFixture, int64(0))) 115 require.True(t, cache.PutTxtRecord(txtDomain, txtFixture, int64(0))) 116 117 // locks ip record 118 locked, err := cache.LockIPDomain(ipDomain) 119 require.NoError(t, err) 120 require.True(t, locked) // first locking attempt must go through 121 locked, err = cache.LockIPDomain(ipDomain) 122 require.NoError(t, err) 123 require.False(t, locked) // other locking attempts must fail 124 125 // locks txt record 126 locked, err = cache.LockTxtRecord(txtDomain) 127 require.NoError(t, err) 128 require.True(t, locked) // first locking attempt must go through 129 locked, err = cache.LockTxtRecord(txtDomain) 130 require.NoError(t, err) 131 require.False(t, locked) // other locking attempts must fail 132 133 // locked ip record must be retrievable 134 ipRecord, ok := cache.GetDomainIp(ipDomain) 135 require.True(t, ok) 136 require.Equal(t, ipRecord.Addresses, ipFixture) 137 require.Equal(t, ipRecord.Domain, ipDomain) 138 require.True(t, ipRecord.Locked) 139 require.Equal(t, ipRecord.Timestamp, int64(0)) 140 141 // locked txt record must be retrievable 142 txtRecord, ok := cache.GetTxtRecord(txtDomain) 143 require.True(t, ok) 144 require.Equal(t, txtRecord.Records, txtFixture) 145 require.Equal(t, txtRecord.Txt, txtDomain) 146 require.True(t, txtRecord.Locked) 147 require.Equal(t, txtRecord.Timestamp, int64(0)) 148 } 149 150 // TestDNSCache_LRU checks the correctness of cache against LRU ejection. 151 func TestDNSCache_LRU(t *testing.T) { 152 total := 700 // total entries to store (i.e., 700 ip and 700 txt domains) 153 sizeLimit := uint32(500) // cache size limit (i.e., 500 ip and 500 txt domains) 154 155 ipFixtures := network.IpLookupListFixture(total) 156 txtFixtures := network.TxtLookupListFixture(total) 157 158 cache := herocache.NewDNSCache(sizeLimit, unittest.Logger(), metrics.NewNoopCollector(), metrics.NewNoopCollector()) 159 160 // cache must be initially empty 161 ips, txts := cache.Size() 162 require.Equal(t, uint(0), ips) 163 require.Equal(t, uint(0), txts) 164 165 // adding 700 txt and 700 ip domains to cache 166 for _, fixture := range ipFixtures { 167 require.True(t, cache.PutIpDomain(fixture.Domain, fixture.Result, fixture.TimeStamp)) 168 } 169 170 for _, fixture := range txtFixtures { 171 require.True(t, cache.PutTxtRecord(fixture.Txt, fixture.Records, fixture.TimeStamp)) 172 } 173 174 // cache must be full up to its limit 175 ips, txts = cache.Size() 176 require.Equal(t, uint(sizeLimit), ips) 177 require.Equal(t, uint(sizeLimit), txts) 178 179 // only last 500 ip domains and txt records must be retained in the DNS cache 180 for i := 0; i < total; i++ { 181 if i < total-int(sizeLimit) { 182 // old dns entries must be ejected 183 // ip 184 ipRecord, ok := cache.GetDomainIp(ipFixtures[i].Domain) 185 require.False(t, ok) 186 require.Nil(t, ipRecord) 187 // txt records 188 txt, ok := cache.GetTxtRecord(txtFixtures[i].Txt) 189 require.False(t, ok) 190 require.Nil(t, txt) 191 192 continue 193 } 194 195 // new dns entries must be persisted 196 // ip 197 ipRecord, ok := cache.GetDomainIp(ipFixtures[i].Domain) 198 require.True(t, ok) 199 require.Equal(t, ipFixtures[i].Result, ipRecord.Addresses) 200 require.Equal(t, ipFixtures[i].TimeStamp, ipRecord.Timestamp) 201 // txt records 202 txtRecord, ok := cache.GetTxtRecord(txtFixtures[i].Txt) 203 require.True(t, ok) 204 require.Equal(t, txtFixtures[i].Records, txtRecord.Records) 205 require.Equal(t, txtFixtures[i].TimeStamp, txtRecord.Timestamp) 206 } 207 } 208 209 // testAddToCache is a test helper that adds ip and txt records to the cache. 210 func testAddToCache(t *testing.T, 211 cache *herocache.DNSCache, 212 ipTestCases map[string]*network.IpLookupTestCase, 213 txtTestCases map[string]*network.TxtLookupTestCase) { 214 215 wg := sync.WaitGroup{} 216 wg.Add(len(ipTestCases) + len(txtTestCases)) 217 218 for _, fixture := range ipTestCases { 219 require.True(t, cache.PutIpDomain(fixture.Domain, fixture.Result, fixture.TimeStamp)) 220 } 221 222 for _, fixture := range txtTestCases { 223 require.True(t, cache.PutTxtRecord(fixture.Txt, fixture.Records, fixture.TimeStamp)) 224 } 225 } 226 227 // TestDNSCache_Remove checks the correctness of cache against removal. 228 func TestDNSCache_Remove(t *testing.T) { 229 total := 30 // total entries to store (i.e., 700 ip domains and 700 txt records) 230 sizeLimit := uint32(500) // cache size limit (i.e., 500 ip domains and 500 txt records) 231 232 ipFixtures := network.IpLookupListFixture(total) 233 txtFixtures := network.TxtLookupListFixture(total) 234 235 cache := herocache.NewDNSCache(sizeLimit, unittest.Logger(), metrics.NewNoopCollector(), metrics.NewNoopCollector()) 236 237 // cache must be initially empty 238 ips, txts := cache.Size() 239 require.Equal(t, uint(0), ips) 240 require.Equal(t, uint(0), txts) 241 242 // adding 700 txt records and 700 ip domains to cache 243 for _, fixture := range ipFixtures { 244 require.True(t, cache.PutIpDomain(fixture.Domain, fixture.Result, fixture.TimeStamp)) 245 } 246 247 for _, fixture := range txtFixtures { 248 require.True(t, cache.PutTxtRecord(fixture.Txt, fixture.Records, fixture.TimeStamp)) 249 } 250 251 // cache must be full up to its limit 252 ips, txts = cache.Size() 253 require.Equal(t, uint(total), ips) 254 require.Equal(t, uint(total), txts) 255 256 // removes a single ip domains and txt records 257 require.True(t, cache.RemoveIp(ipFixtures[0].Domain)) 258 require.True(t, cache.RemoveTxt(txtFixtures[0].Txt)) 259 // repeated attempts on removing already removed entries must return false. 260 require.False(t, cache.RemoveIp(ipFixtures[0].Domain)) 261 require.False(t, cache.RemoveTxt(txtFixtures[0].Txt)) 262 263 // size must be updated post removal 264 ips, txts = cache.Size() 265 require.Equal(t, uint(total-1), ips) 266 require.Equal(t, uint(total-1), txts) 267 268 // only last 500 ip domains and txt records must be retained in the DNS cache 269 for i := 0; i < total; i++ { 270 if i == 0 { 271 // removed entries must no longer exist. 272 // ip 273 ipRecord, ok := cache.GetDomainIp(ipFixtures[i].Domain) 274 require.False(t, ok) 275 require.Nil(t, ipRecord) 276 // txt records 277 txtRecord, ok := cache.GetTxtRecord(txtFixtures[i].Txt) 278 require.False(t, ok) 279 require.Nil(t, txtRecord) 280 281 continue 282 } 283 284 // other entries must be existing. 285 // ip 286 ipRecord, ok := cache.GetDomainIp(ipFixtures[i].Domain) 287 require.True(t, ok) 288 require.Equal(t, ipFixtures[i].Result, ipRecord.Addresses) 289 require.Equal(t, ipFixtures[i].TimeStamp, ipRecord.Timestamp) 290 // txt records 291 txtRecord, ok := cache.GetTxtRecord(txtFixtures[i].Txt) 292 require.True(t, ok) 293 require.Equal(t, txtFixtures[i].Records, txtRecord.Records) 294 require.Equal(t, txtFixtures[i].TimeStamp, txtRecord.Timestamp) 295 } 296 } 297 298 // testMatchCount is a test helper that checks specified number of txt and ip domains are retrievable from the cache. 299 // The `count` parameter specifies number of expected matches from txt and ip domains, separately. 300 func testRetrievalMatchCount(t *testing.T, 301 cache *herocache.DNSCache, 302 ipTestCases map[string]*network.IpLookupTestCase, 303 txtTestCases map[string]*network.TxtLookupTestCase, 304 count int) { 305 306 // checking ip domains 307 actualCount := 0 308 for _, tc := range ipTestCases { 309 ipRecord, ok := cache.GetDomainIp(tc.Domain) 310 if !ok { 311 continue 312 } 313 require.True(t, ok) 314 315 require.Equal(t, tc.TimeStamp, ipRecord.Timestamp) 316 require.Equal(t, tc.Result, ipRecord.Addresses) 317 actualCount++ 318 } 319 require.Equal(t, count, actualCount) 320 321 // checking txt records 322 actualCount = 0 323 for _, tc := range txtTestCases { 324 txtRecord, ok := cache.GetTxtRecord(tc.Txt) 325 if !ok { 326 continue 327 } 328 require.True(t, ok) 329 330 require.Equal(t, tc.TimeStamp, txtRecord.Timestamp) 331 require.Equal(t, tc.Records, txtRecord.Records) 332 actualCount++ 333 } 334 require.Equal(t, count, actualCount) 335 336 }