Skip to content

Commit

Permalink
Testing exercising merging of primary/secondary relay addresses - cov…
Browse files Browse the repository at this point in the history
…ers cases with minimal and various definition of 'partial' overlaps.
  • Loading branch information
gmalouf committed Mar 24, 2023
1 parent 02500ac commit 1bcbd48
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 38 deletions.
23 changes: 14 additions & 9 deletions network/wsNetwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -1740,7 +1740,7 @@ func (wn *WebsocketNetwork) refreshRelayArchivePhonebookAddresses() {
if len(dnsBootstrapArray) >= 2 {
primaryRelayAddrs, primaryArchiveAddrs := wn.getDNSAddrs(dnsBootstrapArray[0])
secondaryRelayAddrs, secondaryArchiveAddrs := wn.getDNSAddrs(dnsBootstrapArray[1])
dedupedRelayAddresses := wn.mergePrimarySecondaryRelayAddressLists(wn.NetworkID, primaryRelayAddrs, secondaryRelayAddrs)
dedupedRelayAddresses := wn.mergePrimarySecondaryRelayAddressSlices(wn.NetworkID, primaryRelayAddrs, secondaryRelayAddrs)

wn.updatePhonebookAddresses(dedupedRelayAddresses, append(primaryArchiveAddrs, secondaryArchiveAddrs...))
//TODO: Sanity check these work in unit tests
Expand Down Expand Up @@ -1980,11 +1980,11 @@ func (wn *WebsocketNetwork) prioWeightRefresh() {
}

func primaryNetwork(network protocol.NetworkID) string {
return fmt.Sprintf("algorand-%s.network", network)
return fmt.Sprintf(`algorand-%s.network`, network)
}

func secondaryNetwork(network protocol.NetworkID) string {
return fmt.Sprintf("algorand-%s.net", network)
return fmt.Sprintf(`algorand-%s.net`, network)
}

var networkIDToPrimarySRVBaseURIRegex = map[protocol.NetworkID]*regexp.Regexp{
Expand All @@ -1997,27 +1997,32 @@ var networkIDToSecondarySRVBaseURIRegex = map[protocol.NetworkID]*regexp.Regexp{
config.Testnet: regexp.MustCompile(secondaryNetwork(config.Testnet)),
}

func (wn *WebsocketNetwork) mergePrimarySecondaryRelayAddressLists(network protocol.NetworkID,
// This logic assumes that the primary and secondary relay addresses suffixes
// correspond to the primary/secondary network conventions. If this proves to be false, i.e. one network's
// suffix is a substring of another network's suffix, then duplicates can end up in the merged slice.
func (wn *WebsocketNetwork) mergePrimarySecondaryRelayAddressSlices(network protocol.NetworkID,
primaryRelayAddresses []string, secondaryRelayAddresses []string) (dedupedRelayAddresses []string) {
// Initialize w/ worst case capacity
//dedupedRelayAddresses = make([]string, 0, len(primaryRelayAddresses)+len(secondaryRelayAddresses))
//copy(dedupedRelayAddresses, primaryRelayAddresses)

var relayAddressPrefixToValue = make(map[string]string, 2*len(primaryRelayAddresses))
var prgx = networkIDToPrimarySRVBaseURIRegex[network]

for _, pra := range primaryRelayAddresses {
var normalizedPra = strings.ToLower(pra)
relayAddressPrefixToValue[prgx.ReplaceAllString(normalizedPra, "")] = normalizedPra
var pfxKey = prgx.ReplaceAllString(normalizedPra, "")

if _, exists := relayAddressPrefixToValue[pfxKey]; !exists {
relayAddressPrefixToValue[pfxKey] = normalizedPra
}
}

var srgx = networkIDToSecondarySRVBaseURIRegex[network]

for _, sra := range secondaryRelayAddresses {
var normalizedSra = strings.ToLower(sra)
var pfxKey = srgx.ReplaceAllString(normalizedSra, "")
// Add to our address map if prefix does not exist, otherwise take no action (already covered by primary)
if relayAddressPrefixToValue[pfxKey] == "" {

if _, exists := relayAddressPrefixToValue[pfxKey]; !exists {
relayAddressPrefixToValue[pfxKey] = normalizedSra
}
}
Expand Down
100 changes: 93 additions & 7 deletions network/wsNetwork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func init() {

func makeTestWebsocketNodeWithConfig(t testing.TB, conf config.Local, opts ...testWebsocketOption) *WebsocketNetwork {
log := logging.TestingLog(t)
log.SetLevel(logging.Level(conf.BaseLoggerDebugLevel))
log.SetLevel(logging.Warn)
wn := &WebsocketNetwork{
log: log,
config: conf,
Expand Down Expand Up @@ -3785,8 +3785,8 @@ func TestUpdatePhonebookAddresses(t *testing.T) {
netA.updatePhonebookAddresses(relayDomains, archiveDomains)

// Check that entries are in fact in phonebook less any duplicates
dedupedRelayDomains := removeDuplicateStr(relayDomains)
dedupedArchiveDomains := removeDuplicateStr(archiveDomains)
dedupedRelayDomains := removeDuplicateStr(relayDomains, false)
dedupedArchiveDomains := removeDuplicateStr(archiveDomains, false)

relayPeers = netA.GetPeers(PeersPhonebookRelays)
assert.Equal(t, len(dedupedRelayDomains), len(relayPeers))
Expand Down Expand Up @@ -3825,7 +3825,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) {
netA.updatePhonebookAddresses(relayDomains, nil)

// Check that entries are in fact in phonebook less any duplicates
dedupedRelayDomains = removeDuplicateStr(relayDomains)
dedupedRelayDomains = removeDuplicateStr(relayDomains, false)

relayPeers = netA.GetPeers(PeersPhonebookRelays)
assert.Equal(t, len(dedupedRelayDomains), len(relayPeers))
Expand All @@ -3851,14 +3851,100 @@ func TestUpdatePhonebookAddresses(t *testing.T) {
})
}

func removeDuplicateStr(strSlice []string) []string {
func removeDuplicateStr(strSlice []string, lowerCase bool) []string {
allKeys := make(map[string]bool)
dedupStrSlice := []string{}
var dedupStrSlice = make([]string, 0)
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
if lowerCase {
item = strings.ToLower(item)
}
if _, exists := allKeys[item]; !exists {
allKeys[item] = true
dedupStrSlice = append(dedupStrSlice, item)
}
}
return dedupStrSlice
}

func replaceAllIn(strSlice []string, strToReplace string, newStr string) []string {
var subbedStrSlice = make([]string, 0)
for _, item := range strSlice {
item = strings.ReplaceAll(item, strToReplace, newStr)
subbedStrSlice = append(subbedStrSlice, item)
}

return subbedStrSlice
}

func supportedNetworkGen() *rapid.Generator {
return rapid.OneOf(rapid.StringMatching(string(config.Testnet)), rapid.StringMatching(string(config.Mainnet)))
// rapid.StringMatching(string(config.Devnet)), rapid.StringMatching(string(config.Betanet)),
// rapid.StringMatching(string(config.Alphanet)), rapid.StringMatching(string(config.Devtestnet))
}

func TestMergePrimarySecondaryRelayAddressListsMinOverlap(t *testing.T) {
partitiontest.PartitionTest(t)
var netA *WebsocketNetwork

rapid.Check(t, func(t1 *rapid.T) {
netA = makeTestWebsocketNode(t)

network := supportedNetworkGen().Draw(t1, "network").(string)
domainGen := rapidgen.Domain()

// Generate between 0 and N examples - if no dups, should end up in phonebook
domainsGen := rapid.SliceOfN(domainGen, 0, 200)

primaryRelayAddresses := domainsGen.Draw(t1, "primaryRelayAddresses").([]string)
secondaryRelayAddresses := domainsGen.Draw(t1, "secondaryRelayAddresses").([]string)

// Intentionally add duplicates to secondary relay addresses
mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(protocol.NetworkID(network),
primaryRelayAddresses, secondaryRelayAddresses)

expectedRelayAddresses := removeDuplicateStr(append(primaryRelayAddresses, secondaryRelayAddresses...), true)

sort.Strings(expectedRelayAddresses)
sort.Strings(mergedRelayAddresses)

assert.Equal(t, expectedRelayAddresses, mergedRelayAddresses)
})
}

func TestMergePrimarySecondaryRelayAddressListsPartialOverlap(t *testing.T) {
partitiontest.PartitionTest(t)
var netA *WebsocketNetwork

rapid.Check(t, func(t1 *rapid.T) {
netA = makeTestWebsocketNode(t)

// NOTE: Current implementation only merges for supported networks
network := protocol.NetworkID(supportedNetworkGen().Draw(t1, "network").(string))
// Generate hosts for the primary network domain
primaryNetworkDomainGen := rapidgen.DomainWithSuffix(primaryNetwork(network), nil)
primaryDomainsGen := rapid.SliceOfN(primaryNetworkDomainGen, 0, 200)

primaryRelayAddresses := primaryDomainsGen.Draw(t1, "primaryRelayAddresses").([]string)
// Generate these addresses from primary ones, find/replace domain suffix appropriately
secondaryRelayAddresses := replaceAllIn(primaryRelayAddresses, primaryNetwork(network), secondaryNetwork(network))
// Add some generated addresses to secondary list - to simplify verification further down
// (substituting suffixes, etc), we dont want the generated addresses to duplicate any of
// the replaced secondary ones
secondaryNetworkDomainGen := rapidgen.DomainWithSuffix(secondaryNetwork(network), secondaryRelayAddresses)
secondaryDomainsGen := rapid.SliceOfN(secondaryNetworkDomainGen, 0, 200)
generatedSecondaryRelayAddresses := secondaryDomainsGen.Draw(t1, "secondaryRelayAddresses").([]string)
secondaryRelayAddresses = append(secondaryRelayAddresses, generatedSecondaryRelayAddresses...)

mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(network,
primaryRelayAddresses, secondaryRelayAddresses)

// We expect the primary addresses to take precedence over a "matching" secondary address, randomly generated
// secondary addresses should be present in the merged slice
expectedRelayAddresses := removeDuplicateStr(append(primaryRelayAddresses, generatedSecondaryRelayAddresses...), true)

sort.Strings(expectedRelayAddresses)
sort.Strings(mergedRelayAddresses)

assert.Equal(t, expectedRelayAddresses, mergedRelayAddresses)
})
}
81 changes: 59 additions & 22 deletions test/rapidgen/rapidgenerators.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,86 @@
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package rapidgen

// See https://github.com/flyingmutant/rapid/pull/18

import (
"fmt"
"pgregory.net/rapid"
)

const (
domainMaxLength = 255
domainMaxElementLength = 63
"strings"
)

// Domain generates an RFC 1035 compliant domain name.
func Domain() *rapid.Generator {
return DomainOf(255, 63)
return DomainOf(255, 63, "", nil)
}

func DomainWithSuffix(suffix string, dontMatch []string) *rapid.Generator {
return DomainOf(255, 63, suffix, dontMatch)
}

// DomainOf generates an RFC 1035 compliant domain name,
// with a maximum overall length of maxLength
// and a maximum number of elements of maxElements.
func DomainOf(maxLength, maxElementLength int) *rapid.Generator {
// a maximum number of elements of maxElements
// and the specified domain suffix (assumes compliant).
func DomainOf(maxLength, maxElementLength int, domainSuffix string, dontMatch []string) *rapid.Generator {
assertf(4 <= maxLength, "maximum length (%v) should not be less than 4, to generate a two character domain and a one character subdomain", maxLength)
assertf(maxLength <= 255, "maximum length (%v) should not be greater than 255 to comply with RFC 1035", maxLength)
assertf(1 <= maxElementLength, "maximum element length (%v) should not be less than 1 to comply with RFC 1035", maxElementLength)
assertf(maxElementLength <= 63, "maximum element length (%v) should not be greater than 63 to comply with RFC 1035", maxElementLength)

return rapid.Custom(func(t *rapid.T) string {
domain := fmt.Sprint(tldGenerator.
Filter(func(s string) bool { return len(s)+2 <= domainMaxLength }).
Draw(t, "domain"))
genDomain := func() *rapid.Generator {
return rapid.Custom(func(t *rapid.T) string {
var domain string
if domainSuffix != "" {
domain = domainSuffix
} else {
domain = fmt.Sprint(tldGenerator.
Filter(func(s string) bool { return len(s)+2 <= maxLength }).
Draw(t, "domain"))
}

expr := fmt.Sprintf(`[a-zA-Z]([a-zA-Z0-9\-]{0,%d}[a-zA-Z0-9])?`, domainMaxElementLength-2)
expr := fmt.Sprintf(`[a-zA-Z]([a-zA-Z0-9\-]{0,%d}[a-zA-Z0-9])?`, maxElementLength-2)

el := rapid.IntRange(1, 126).Example().(int)
for i := 0; i < el; i++ {
subDomain := fmt.Sprint(rapid.StringMatching(expr).Draw(t, "subdomain"))
if len(domain)+len(subDomain) >= domainMaxLength {
break
el := rapid.IntRange(1, 126).Example().(int)
for i := 0; i < el; i++ {
subDomain := fmt.Sprint(rapid.StringMatching(expr).Draw(t, "subdomain"))
if len(domain)+len(subDomain) >= maxLength {
break
}
domain = subDomain + "." + domain
}
domain = subDomain + "." + domain
}

return domain
})
return domain
})
}

if len(dontMatch) > 0 {
return genDomain().Filter(func(domain string) bool {
for _, v := range dontMatch {
if strings.ToLower(v) == strings.ToLower(domain) {
return false
}
}
return true
})
} else {
return genDomain()
}
}

var tldGenerator = rapid.SampledFrom(tlds) //nolint:golint,typecheck
Expand Down
16 changes: 16 additions & 0 deletions test/rapidgen/tld.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

//go:build !codeanalysis
// +build !codeanalysis

Expand Down

0 comments on commit 1bcbd48

Please sign in to comment.