diff --git a/api/client.go b/api/client.go index f1a9e030fbcebaa229dd2e33ba246f491e3141c4..ac47598a9869e0be6a74f0937979de4cd909c362 100644 --- a/api/client.go +++ b/api/client.go @@ -30,6 +30,7 @@ import ( "gitlab.com/xx_network/crypto/signature/rsa" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/ndf" + "sync" "time" ) @@ -65,6 +66,10 @@ type Client struct { services *serviceProcessiesList clientErrorChannel chan interfaces.ClientError + + //lock to ensure only once instance of stop/start network follower is + //going at a time + followerLock sync.Mutex } // NewClient creates client storage, generates keys, connects, and registers @@ -214,7 +219,7 @@ func Login(storageDir string, password []byte, parameters params.Network) (*Clie } //get the NDF to pass into permissioning and the network manager - def := c.storage.GetBaseNDF() + def := c.storage.GetNDF() //initialize permissioning if def.Registration.Address != "" { @@ -281,7 +286,7 @@ func LoginWithNewBaseNDF_UNSAFE(storageDir string, password []byte, } //store the updated base NDF - c.storage.SetBaseNDF(def) + c.storage.SetNDF(def) //initialize permissioning if def.Registration.Address != "" { @@ -381,10 +386,17 @@ func (c *Client) initPermissioning(def *ndf.NetworkDefinition) error { // - Auth Callback (/auth/callback.go) // Handles both auth confirm and requests func (c *Client) StartNetworkFollower(timeout time.Duration) (<-chan interfaces.ClientError, error) { + c.followerLock.Lock() + defer c.followerLock.Unlock() u := c.GetUser() jww.INFO.Printf("StartNetworkFollower() \n\tTransmisstionID: %s "+ "\n\tReceptionID: %s", u.TransmissionID, u.ReceptionID) + if status := c.status.get(); status != Stopped{ + return nil, errors.Errorf("Cannot Stop the Network Follower when it is not running, status: %s", status) + } + + c.clientErrorChannel = make(chan interfaces.ClientError, 1000) cer := func(source, message, trace string) { @@ -441,6 +453,13 @@ func (c *Client) StartNetworkFollower(timeout time.Duration) (<-chan interfaces. // if the network follower is running and this fails, the client object will // most likely be in an unrecoverable state and need to be trashed. func (c *Client) StopNetworkFollower() error { + c.followerLock.Lock() + defer c.followerLock.Unlock() + + if status := c.status.get(); status != Running{ + return errors.Errorf("Cannot Stop the Network Follower when it is not running, status: %s", status) + } + err := c.status.toStopping() if err != nil { return errors.WithMessage(err, "Failed to Stop the Network Follower") @@ -616,7 +635,7 @@ func checkVersionAndSetupStorage(def *ndf.NetworkDefinition, storageDir string, } // Save NDF to be used in the future - storageSess.SetBaseNDF(def) + storageSess.SetNDF(def) if !isPrecanned { //store the registration code for later use diff --git a/bindings/params.go b/bindings/params.go index 35afbb8901cfd5e8309f8be7a30a14c907496c23..d4896a2e9e89c49c177689c85ff0a2cf410f9277 100644 --- a/bindings/params.go +++ b/bindings/params.go @@ -13,22 +13,22 @@ import ( "gitlab.com/elixxir/client/interfaces/params" ) -func (c *Client) GetCMIXParams() (string, error) { +func GetCMIXParams() (string, error) { p, err := params.GetDefaultCMIX().Marshal() return string(p), err } -func (c *Client) GetE2EParams() (string, error) { +func GetE2EParams() (string, error) { p, err := params.GetDefaultE2E().Marshal() return string(p), err } -func (c *Client) GetNetworkParams() (string, error) { +func GetNetworkParams() (string, error) { p, err := params.GetDefaultNetwork().Marshal() return string(p), err } -func (c *Client) GetUnsafeParams() (string, error) { +func GetUnsafeParams() (string, error) { p, err := params.GetDefaultUnsafe().Marshal() return string(p), err } diff --git a/go.mod b/go.mod index a534cac91c9efd0f75f3fcfd2d9c5f40587c06d3..751d38d99f09935e88760efb7eb2ba47be7d2755 100644 --- a/go.mod +++ b/go.mod @@ -17,13 +17,13 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 - gitlab.com/elixxir/comms v0.0.4-0.20210607222512-0f2e89b475b4 - gitlab.com/elixxir/crypto v0.0.7-0.20210607221512-0a9bff216f7c + gitlab.com/elixxir/comms v0.0.4-0.20210614160131-de90e88a68cc + gitlab.com/elixxir/crypto v0.0.7-0.20210614155844-c1e9c23a6ba7 gitlab.com/elixxir/ekv v0.1.5 - gitlab.com/elixxir/primitives v0.0.3-0.20210607210820-afd1b028b558 - gitlab.com/xx_network/comms v0.0.4-0.20210603164237-d0c36076d7f0 - gitlab.com/xx_network/crypto v0.0.5-0.20210603164136-743cb9b0a967 - gitlab.com/xx_network/primitives v0.0.4-0.20210607221158-361a2cbc5529 + gitlab.com/elixxir/primitives v0.0.3-0.20210614155726-ebcf2d47a527 + gitlab.com/xx_network/comms v0.0.4-0.20210614155654-191473de2702 + gitlab.com/xx_network/crypto v0.0.5-0.20210614155554-8c333814205b + gitlab.com/xx_network/primitives v0.0.4-0.20210608160426-670aab2d82cf golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/net v0.0.0-20210525063256-abc453219eb5 google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 // indirect diff --git a/go.sum b/go.sum index 66a72049798930218404a8ba6a848ee93c29d687..7f1f0b4b1edfbdfcf92ef60042ae87ba814e74ce 100644 --- a/go.sum +++ b/go.sum @@ -247,33 +247,32 @@ github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0= github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 h1:Gi6rj4mAlK0BJIk1HIzBVMjWNjIUfstrsXC2VqLYPcA= gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/comms v0.0.4-0.20210607222512-0f2e89b475b4 h1:9h/Dkvu8t4/Q2LY81qoxjbO4GJtVanjWV3B9UMuZgDA= -gitlab.com/elixxir/comms v0.0.4-0.20210607222512-0f2e89b475b4/go.mod h1:Ox1NgdvFRy4/AfWAIrKZR9W6O+PAYeNOZviGbhqH+eo= +gitlab.com/elixxir/comms v0.0.4-0.20210614160131-de90e88a68cc h1:MB+Ixmz/0eWt2akBYajjH2h53GF0NjWn4Oy4Q7x4QVg= +gitlab.com/elixxir/comms v0.0.4-0.20210614160131-de90e88a68cc/go.mod h1:JeCKUXRS9xP3YYGPl4+OMFdvtt7ySJIxEsL9AzgeCu0= gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c= gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= -gitlab.com/elixxir/crypto v0.0.7-0.20210607221512-0a9bff216f7c h1:G4IE4xEnoapQoZsMK1XRAB5QdOr2z+IyxZ6JOIXXuQ8= -gitlab.com/elixxir/crypto v0.0.7-0.20210607221512-0a9bff216f7c/go.mod h1:HAW5sLSUHKgylen7C4YoVNr0kp5g21/eKJw6sCuBRFs= +gitlab.com/elixxir/crypto v0.0.7-0.20210614155844-c1e9c23a6ba7 h1:UBq4/xMUWkYmEzUN2F7nLw5qQeiNKoLaoX3vZ/flz1c= +gitlab.com/elixxir/crypto v0.0.7-0.20210614155844-c1e9c23a6ba7/go.mod h1:FP848WCzyf81/Csz1lJpi3NXgIdpzJ4hoJam53xwCuo= gitlab.com/elixxir/ekv v0.1.5 h1:R8M1PA5zRU1HVnTyrtwybdABh7gUJSCvt1JZwUSeTzk= gitlab.com/elixxir/ekv v0.1.5/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg= gitlab.com/elixxir/primitives v0.0.0-20200804170709-a1896d262cd9/go.mod h1:p0VelQda72OzoUckr1O+vPW0AiFe0nyKQ6gYcmFSuF8= gitlab.com/elixxir/primitives v0.0.0-20200804182913-788f47bded40/go.mod h1:tzdFFvb1ESmuTCOl1z6+yf6oAICDxH2NPUemVgoNLxc= gitlab.com/elixxir/primitives v0.0.1/go.mod h1:kNp47yPqja2lHSiS4DddTvFpB/4D9dB2YKnw5c+LJCE= -gitlab.com/elixxir/primitives v0.0.3-0.20210607210820-afd1b028b558 h1:J8FllvIDv5RkON+Bg61NxNr78cYLOLifRrg/ugm5mW8= -gitlab.com/elixxir/primitives v0.0.3-0.20210607210820-afd1b028b558/go.mod h1:1EFCSsERjE5RagCjys/70sqkAZC5PimEkWHcljh4bwQ= +gitlab.com/elixxir/primitives v0.0.3-0.20210614155726-ebcf2d47a527 h1:kBNAGFy5Ylz7F0K3DmyzuHLf1npBg7a3t4qKvfqPL3Y= +gitlab.com/elixxir/primitives v0.0.3-0.20210614155726-ebcf2d47a527/go.mod h1:nSmBXcw4hkBLFdhu+araAPvf9szCDQF1fpRZ9/BgBec= gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw= -gitlab.com/xx_network/comms v0.0.4-0.20210603164237-d0c36076d7f0 h1:+/U+6Ra5pqDhIHCqMniESovsakooCFTv/omlpfvffU8= -gitlab.com/xx_network/comms v0.0.4-0.20210603164237-d0c36076d7f0/go.mod h1:cpogFfWweZFzldGnRmgI9ilW2IFyeINDXpa1sAP950U= +gitlab.com/xx_network/comms v0.0.4-0.20210614155654-191473de2702 h1:ydi8FaAjFGfxMcvmIGlvnng491K2uEl3ymALC2Hh8Vw= +gitlab.com/xx_network/comms v0.0.4-0.20210614155654-191473de2702/go.mod h1:ehwxZxcAQHkJjP5BNkwPNK8/o6avUn0j0iDDiu+nMFc= gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE= gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk= -gitlab.com/xx_network/crypto v0.0.5-0.20210603164136-743cb9b0a967 h1:gGDytDkXdwBDV92Xk8bcfHIzyoUfzIdFRNZnmTtx+Bc= -gitlab.com/xx_network/crypto v0.0.5-0.20210603164136-743cb9b0a967/go.mod h1:qOOPEZXbsgM9n1wh9UDd9zd2IzuklQE+b7H2CMXaHx0= +gitlab.com/xx_network/crypto v0.0.5-0.20210614155554-8c333814205b h1:X2Hhg9/IYowxMdI6TTnWj6WW3pnO2vMB/7f4mnu6Muw= +gitlab.com/xx_network/crypto v0.0.5-0.20210614155554-8c333814205b/go.mod h1:wiaQXyI9C9UGxxgLd+2lDmKyovO+PjFxaesCBgG0YDA= gitlab.com/xx_network/primitives v0.0.0-20200803231956-9b192c57ea7c/go.mod h1:wtdCMr7DPePz9qwctNoAUzZtbOSHSedcK++3Df3psjA= gitlab.com/xx_network/primitives v0.0.0-20200804183002-f99f7a7284da/go.mod h1:OK9xevzWCaPO7b1wiluVJGk7R5ZsuC7pHY5hteZFQug= gitlab.com/xx_network/primitives v0.0.2/go.mod h1:cs0QlFpdMDI6lAo61lDRH2JZz+3aVkHy+QogOB6F/qc= -gitlab.com/xx_network/primitives v0.0.4-0.20210603164056-0abf3f914f25/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= -gitlab.com/xx_network/primitives v0.0.4-0.20210607221158-361a2cbc5529 h1:zC1z2Pcxy+fQc3ZzRxz2lOorj1LqBms3xohIVmThPb0= -gitlab.com/xx_network/primitives v0.0.4-0.20210607221158-361a2cbc5529/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= +gitlab.com/xx_network/primitives v0.0.4-0.20210608160426-670aab2d82cf h1:4snRmLHx/mgbdMtuXT2ITxyCElgvlOd7bJA1QSx0aI4= +gitlab.com/xx_network/primitives v0.0.4-0.20210608160426-670aab2d82cf/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE= gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5 h1:FY+4Rh1Q2rgLyv10aKJjhWApuKRCR/054XhreudfAvw= gitlab.com/xx_network/ring v0.0.3-0.20210527191221-ce3f170aabd5/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/network/ephemeral/tracker.go b/network/ephemeral/tracker.go index 83f855375c23ad6fa0d95d175802de5d12f5659c..5480125492017b62b6eaf8e14f7bbd6ab384f1f7 100644 --- a/network/ephemeral/tracker.go +++ b/network/ephemeral/tracker.go @@ -122,6 +122,7 @@ func track(session *storage.Session, addrSpace *AddressSpace, ourId *id.ID, stop case addressSize = <-addressSizeUpdate: receptionStore.SetToExpire(addressSize) case <-stop.Quit(): + addrSpace.UnregisterNotification(addressSpaceSizeChanTag) stop.ToStopped() return } diff --git a/network/follow.go b/network/follow.go index aa505deecbb5cda93f1ebed0124ef070cf234705..3d5d8734b83c5892aae387e55e9e4ab6dba23e38 100644 --- a/network/follow.go +++ b/network/follow.go @@ -159,6 +159,7 @@ func (m *manager) follow(report interfaces.ClientErrorReport, rng csprng.Source, // update gateway connections m.GetSender().UpdateNdf(m.GetInstance().GetPartialNdf().Get()) + m.Session.SetNDF(m.GetInstance().GetPartialNdf().Get()) } // Update the address space size diff --git a/network/gateway/hostPool.go b/network/gateway/hostPool.go index ba8198da839c4e9d2693d866be165f6afaf08d47..1447608bfcc825f5e1269b2941d617718461662d 100644 --- a/network/gateway/hostPool.go +++ b/network/gateway/hostPool.go @@ -13,7 +13,6 @@ package gateway import ( "encoding/binary" - "fmt" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/storage" @@ -81,7 +80,7 @@ func DefaultPoolParams() PoolParams { p.HostParams.EnableCoolOff = true p.HostParams.NumSendsBeforeCoolOff = 1 p.HostParams.CoolOffTimeout = 5 * time.Minute - p.HostParams.SendTimeout = 3500 * time.Millisecond + p.HostParams.SendTimeout = 2000 * time.Millisecond return p } @@ -115,8 +114,24 @@ func newHostPool(poolParams PoolParams, rng *fastRNG.StreamGenerator, ndf *ndf.N return nil, err } + // Get the last used list of hosts and use it to seed the host pool list + hostList, err := storage.HostList().Get() + numHostsAdded := 0 + if err == nil { + for _, hid := range hostList { + err := result.replaceHostNoStore(hid, uint32(numHostsAdded)) + if err != nil { + jww.WARN.Printf("Unable to add stored host %s: %s", hid, err.Error()) + } else { + numHostsAdded++ + } + } + } else { + jww.WARN.Printf("Building new HostPool because no HostList stored: %+v", err) + } + // Build the initial HostPool and return - for i := 0; i < len(result.hostList); i++ { + for i := numHostsAdded; i < len(result.hostList); i++ { err := result.forceReplace(uint32(i)) if err != nil { return nil, err @@ -279,8 +294,29 @@ func (h *HostPool) forceReplace(oldPoolIndex uint32) error { } } -// Replace the given slot in the HostPool with a new Gateway with the specified ID +// replaceHost replaces the given slot in the HostPool with a new Gateway with +// the specified ID. The resulting host list is saved to storage. func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error { + err := h.replaceHostNoStore(newId, oldPoolIndex) + if err != nil { + return err + } + + // Convert list of of non-nil and non-zero hosts to ID list + idList := make([]*id.ID, 0, len(h.hostList)) + for _, host := range h.hostList { + if host.GetId() != nil && !host.GetId().Cmp(&id.ID{}) { + idList = append(idList, host.GetId()) + } + } + + // Save the list to storage + return h.storage.HostList().Store(idList) +} + +// replaceHostNoStore replaces the given slot in the HostPool with a new Gateway +// with the specified ID. +func (h *HostPool) replaceHostNoStore(newId *id.ID, oldPoolIndex uint32) error { // Obtain that GwId's Host object newHost, ok := h.manager.GetHost(newId) if !ok { @@ -291,7 +327,8 @@ func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error { // Keep track of oldHost for cleanup oldHost := h.hostList[oldPoolIndex] - // Use the poolIdx to overwrite the random Host in the corresponding index in the hostList + // Use the poolIdx to overwrite the random Host in the corresponding index + // in the hostList h.hostList[oldPoolIndex] = newHost // Use the GwId to keep track of the new random Host's index in the hostList h.hostMap[*newId] = oldPoolIndex @@ -301,7 +338,9 @@ func (h *HostPool) replaceHost(newId *id.ID, oldPoolIndex uint32) error { delete(h.hostMap, *oldHost.GetId()) go oldHost.Disconnect() } - jww.DEBUG.Printf("Replaced Host at %d with new Host %s", oldPoolIndex, newId.String()) + jww.DEBUG.Printf("Replaced Host at %d with new Host %s", oldPoolIndex, + newId.String()) + return nil } @@ -388,7 +427,7 @@ func (h *HostPool) removeGateway(gwId *id.ID) { func (h *HostPool) addGateway(gwId *id.ID, ndfIndex int) { gw := h.ndf.Gateways[ndfIndex] - //check if the host exists + // Check if the host exists host, ok := h.manager.GetHost(gwId) if !ok { @@ -443,7 +482,7 @@ func readUint32(rng io.Reader) uint32 { var rndBytes [4]byte i, err := rng.Read(rndBytes[:]) if i != 4 || err != nil { - panic(fmt.Sprintf("cannot read from rng: %+v", err)) + jww.FATAL.Panicf("cannot read from rng: %+v", err) } return binary.BigEndian.Uint32(rndBytes[:]) } diff --git a/network/gateway/hostpool_test.go b/network/gateway/hostpool_test.go index 49dcacf6b8231647b8c0cbc0ab6e6f84b0c7d126..f88bdf49a5c02e757781f3c452b9eef2759862bb 100644 --- a/network/gateway/hostpool_test.go +++ b/network/gateway/hostpool_test.go @@ -54,6 +54,49 @@ func TestNewHostPool(t *testing.T) { } } +// Tests that the hosts are loaded from storage, if they exist. +func TestNewHostPool_HostListStore(t *testing.T) { + manager := newMockManager() + rng := fastRNG.NewStreamGenerator(1, 1, csprng.NewSystemRNG) + testNdf := getTestNdf(t) + testStorage := storage.InitTestingSession(t) + addGwChan := make(chan network.NodeGateway) + params := DefaultPoolParams() + params.MaxPoolSize = uint32(len(testNdf.Gateways)) + + addedIDs := []*id.ID{ + id.NewIdFromString("testID0", id.Gateway, t), + id.NewIdFromString("testID1", id.Gateway, t), + id.NewIdFromString("testID2", id.Gateway, t), + id.NewIdFromString("testID3", id.Gateway, t), + } + err := testStorage.HostList().Store(addedIDs) + if err != nil { + t.Fatalf("Failed to store host list: %+v", err) + } + + for i, hid := range addedIDs { + testNdf.Gateways[i].ID = hid.Marshal() + } + + // Call the constructor + hp, err := newHostPool(params, rng, testNdf, manager, testStorage, addGwChan) + if err != nil { + t.Fatalf("Failed to create mock host pool: %v", err) + } + + // Check that the host list was saved to storage + hostList, err := hp.storage.HostList().Get() + if err != nil { + t.Errorf("Failed to get host list: %+v", err) + } + + if !reflect.DeepEqual(addedIDs, hostList) { + t.Errorf("Failed to save expected host list to storage."+ + "\nexpected: %+v\nreceived: %+v", addedIDs, hostList) + } +} + // Unit test func TestHostPool_ManageHostPool(t *testing.T) { manager := newMockManager() @@ -115,7 +158,7 @@ func TestHostPool_ManageHostPool(t *testing.T) { for _, ndfGw := range testNdf.Gateways { gwId, err := id.Unmarshal(ndfGw.ID) if err != nil { - t.Errorf("Failed to marshal gateway id for %v", ndfGw) + t.Fatalf("Failed to marshal gateway id for %v", ndfGw) } if _, ok := testPool.hostMap[*gwId]; ok { t.Errorf("Expected gateway %v to be removed from pool", gwId) @@ -135,6 +178,7 @@ func TestHostPool_ReplaceHost(t *testing.T) { hostList: make([]*connect.Host, newIndex+1), hostMap: make(map[id.ID]uint32), ndf: testNdf, + storage: storage.InitTestingSession(t), } /* "Replace" a host with no entry */ @@ -228,6 +272,18 @@ func TestHostPool_ReplaceHost(t *testing.T) { "\n\tReceived: %d", newIndex, retrievedIndex) } + // Check that the host list was saved to storage + hostList, err := hostPool.storage.HostList().Get() + if err != nil { + t.Errorf("Failed to get host list: %+v", err) + } + + expectedList := []*id.ID{gwIdTwo} + + if !reflect.DeepEqual(expectedList, hostList) { + t.Errorf("Failed to save expected host list to storage."+ + "\nexpected: %+v\nreceived: %+v", expectedList, hostList) + } } // Error path, could not get host @@ -754,7 +810,7 @@ func TestHostPool_UpdateConns_RemoveGateways(t *testing.T) { for _, ndfGw := range testNdf.Gateways { gwId, err := id.Unmarshal(ndfGw.ID) if err != nil { - t.Errorf("Failed to marshal gateway id for %v", ndfGw) + t.Fatalf("Failed to marshal gateway id for %v", ndfGw) } if _, ok := testPool.hostMap[*gwId]; ok { t.Errorf("Expected gateway %v to be removed from pool", gwId) diff --git a/storage/hostList/hostList.go b/storage/hostList/hostList.go new file mode 100644 index 0000000000000000000000000000000000000000..5242e7b8483ace3820111aa6c22455c7ba5a0257 --- /dev/null +++ b/storage/hostList/hostList.go @@ -0,0 +1,116 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package hostList + +import ( + "bytes" + "github.com/pkg/errors" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/primitives/id" + "gitlab.com/xx_network/primitives/netTime" +) + +// Storage values. +const ( + hostListPrefix = "hostLists" + hostListKey = "hostListIDs" + hostListVersion = 0 +) + +// Error messages. +const ( + getStorageErr = "failed to get host list from storage: %+v" + unmarshallIdErr = "unmarshal host list error: %+v" + unmarshallLenErr = "malformed data: length of data %d incorrect" +) + +type Store struct { + kv *versioned.KV +} + +// NewStore creates a new Store with a prefixed KV. +func NewStore(kv *versioned.KV) *Store { + return &Store{ + kv: kv.Prefix(hostListPrefix), + } +} + +// Store saves the list of host IDs to storage. +func (s *Store) Store(list []*id.ID) error { + obj := &versioned.Object{ + Version: hostListVersion, + Data: marshalHostList(list), + Timestamp: netTime.Now(), + } + + return s.kv.Set(hostListKey, hostListVersion, obj) +} + +// Get returns the host list from storage. +func (s *Store) Get() ([]*id.ID, error) { + obj, err := s.kv.Get(hostListKey, hostListVersion) + if err != nil { + return nil, errors.Errorf(getStorageErr, err) + } + + return unmarshalHostList(obj.Data) +} + +// marshalHostList marshals the list of IDs into a byte slice. +func marshalHostList(list []*id.ID) []byte { + buff := bytes.NewBuffer(nil) + buff.Grow(len(list) * id.ArrIDLen) + + for _, hid := range list { + if hid != nil { + buff.Write(hid.Marshal()) + } else { + buff.Write((&id.ID{}).Marshal()) + } + } + + return buff.Bytes() +} + +// unmarshalHostList unmarshal the host list data into an ID list. An error is +// returned if an ID cannot be unmarshalled or if the data is not of the correct +// length. +func unmarshalHostList(data []byte) ([]*id.ID, error) { + // Return an error if the data is not of the required length + if len(data)%id.ArrIDLen != 0 { + return nil, errors.Errorf(unmarshallLenErr, len(data)) + } + + buff := bytes.NewBuffer(data) + list := make([]*id.ID, 0, len(data)/id.ArrIDLen) + + // Read each ID from data, unmarshal, and add to list + length := id.ArrIDLen + for n := buff.Next(length); len(n) == length; n = buff.Next(length) { + hid, err := id.Unmarshal(n) + if err != nil { + return nil, errors.Errorf(unmarshallIdErr, err) + } + + // If the ID is all zeroes, then treat it as a nil ID. + if *hid == (id.ID{}) { + hid = nil + } + + list = append(list, hid) + } + + return list, nil +} diff --git a/storage/hostList/hostList_test.go b/storage/hostList/hostList_test.go new file mode 100644 index 0000000000000000000000000000000000000000..32780fae0a09db487520596e3748611a1bc5636c --- /dev/null +++ b/storage/hostList/hostList_test.go @@ -0,0 +1,114 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +//////////////////////////////////////////////////////////////////////////////// + +package hostList + +import ( + "fmt" + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/ekv" + "gitlab.com/xx_network/primitives/id" + "reflect" + "strings" + "testing" +) + +// Unit test of NewStore. +func TestNewStore(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + expected := &Store{kv: kv.Prefix(hostListPrefix)} + + s := NewStore(kv) + + if !reflect.DeepEqual(expected, s) { + t.Errorf("NewStore did not return the expected object."+ + "\nexpected: %+v\nreceived: %+v", expected, s) + } +} + +// Tests that a host list saved by Store.Store matches the host list returned +// by Store.Get. +func TestStore_Store_Get(t *testing.T) { + s := NewStore(versioned.NewKV(make(ekv.Memstore))) + list := []*id.ID{ + id.NewIdFromString("histID_1", id.Node, t), + nil, + id.NewIdFromString("histID_2", id.Node, t), + id.NewIdFromString("histID_3", id.Node, t), + } + + err := s.Store(list) + if err != nil { + t.Errorf("Store returned an error: %+v", err) + } + + newList, err := s.Get() + if err != nil { + t.Errorf("Get returned an error: %+v", err) + } + + if !reflect.DeepEqual(list, newList) { + t.Errorf("Failed to save and load host list."+ + "\nexpected: %+v\nreceived: %+v", list, newList) + } +} + +// Error path: tests that Store.Get returns an error if not host list is +// saved in storage. +func TestStore_Get_StorageError(t *testing.T) { + s := NewStore(versioned.NewKV(make(ekv.Memstore))) + expectedErr := strings.SplitN(getStorageErr, "%", 2)[0] + + _, err := s.Get() + if err == nil || !strings.Contains(err.Error(), expectedErr) { + t.Errorf("Get failed to return the expected error."+ + "\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} + +// Tests that a list of IDs that is marshalled using marshalHostList and +// unmarshalled using unmarshalHostList matches the original. +func Test_marshalHostList_unmarshalHostList(t *testing.T) { + list := []*id.ID{ + id.NewIdFromString("histID_1", id.Node, t), + nil, + id.NewIdFromString("histID_2", id.Node, t), + id.NewIdFromString("histID_3", id.Node, t), + } + + data := marshalHostList(list) + + newList, err := unmarshalHostList(data) + if err != nil { + t.Errorf("unmarshalHostList produced an error: %+v", err) + } + + if !reflect.DeepEqual(list, newList) { + t.Errorf("Failed to marshal and unmarshal ID list."+ + "\nexpected: %+v\nreceived: %+v", list, newList) + } +} + +// Error path: tests that unmarshalHostList returns an error if the data is not +// of the correct length. +func Test_unmarshalHostList_InvalidDataErr(t *testing.T) { + data := []byte("Invalid Data") + expectedErr := fmt.Sprintf(unmarshallLenErr, len(data)) + + _, err := unmarshalHostList(data) + if err == nil || err.Error() != expectedErr { + t.Errorf("unmarshalHostList failed to return the expected error."+ + "\nexpected: %s\nreceived: %+v", expectedErr, err) + } +} diff --git a/storage/ndf.go b/storage/ndf.go index 14cb4147b89a4fa53de52e505627c12dbd3abc15..1b081fd0f68c32b140492cc5a61b4f8052eb2134 100644 --- a/storage/ndf.go +++ b/storage/ndf.go @@ -13,25 +13,25 @@ import ( "gitlab.com/xx_network/primitives/ndf" ) -const baseNdfKey = "baseNdf" +const ndfKey = "ndf" -func (s *Session) SetBaseNDF(def *ndf.NetworkDefinition) { - err := utility.SaveNDF(s.kv, baseNdfKey, def) +func (s *Session) SetNDF(def *ndf.NetworkDefinition) { + err := utility.SaveNDF(s.kv, ndfKey, def) if err != nil { - jww.FATAL.Printf("Failed to dave the base NDF: %s", err) + jww.FATAL.Printf("Failed to dave the NDF: %+v", err) } - s.baseNdf = def + s.ndf = def } -func (s *Session) GetBaseNDF() *ndf.NetworkDefinition { - if s.baseNdf != nil { - return s.baseNdf +func (s *Session) GetNDF() *ndf.NetworkDefinition { + if s.ndf != nil { + return s.ndf } - def, err := utility.LoadNDF(s.kv, baseNdfKey) + def, err := utility.LoadNDF(s.kv, ndfKey) if err != nil { - jww.FATAL.Printf("Could not load the base NDF: %s", err) + jww.FATAL.Printf("Could not load the NDF: %+v", err) } - s.baseNdf = def + s.ndf = def return def } diff --git a/storage/session.go b/storage/session.go index 019e187d3176b9c4ea1b24f26eaf31697e13e458..e42cad445594e30d7b85eafa688d90b80d4d62ed 100644 --- a/storage/session.go +++ b/storage/session.go @@ -10,6 +10,7 @@ package storage import ( + "gitlab.com/elixxir/client/storage/hostList" "gitlab.com/elixxir/client/storage/rounds" "sync" "testing" @@ -50,7 +51,7 @@ type Session struct { //memoized data regStatus RegistrationStatus - baseNdf *ndf.NetworkDefinition + ndf *ndf.NetworkDefinition //sub-stores e2e *e2e.Store @@ -65,6 +66,7 @@ type Session struct { reception *reception.Store clientVersion *clientVersion.Store uncheckedRounds *rounds.UncheckedRoundStore + hostList *hostList.Store } // Initialize a new Session object @@ -148,6 +150,9 @@ func New(baseDir, password string, u userInterface.User, currentVersion version. if err != nil { return nil, errors.WithMessage(err, "Failed to create unchecked round store") } + + s.hostList = hostList.NewStore(s.kv) + return s, nil } @@ -224,6 +229,8 @@ func Load(baseDir, password string, currentVersion version.Version, return nil, errors.WithMessage(err, "Failed to load unchecked round store") } + s.hostList = hostList.NewStore(s.kv) + return s, nil } @@ -300,6 +307,12 @@ func (s *Session) UncheckedRounds() *rounds.UncheckedRoundStore { return s.uncheckedRounds } +func (s *Session) HostList() *hostList.Store { + s.mux.RLock() + defer s.mux.RUnlock() + return s.hostList +} + // Get an object from the session func (s *Session) Get(key string) (*versioned.Object, error) { return s.kv.Get(key, currentSessionVersion) @@ -401,5 +414,7 @@ func InitTestingSession(i interface{}) *Session { jww.FATAL.Panicf("Failed to create uncheckRound store: %v", err) } + s.hostList = hostList.NewStore(s.kv) + return s } diff --git a/ud/manager.go b/ud/manager.go index 842d8d353a6d05ab523db2f887c2036d169f976c..f3b7afc643c27af41d2d1591de2ff89755c6104d 100644 --- a/ud/manager.go +++ b/ud/manager.go @@ -89,6 +89,8 @@ func NewManager(client *api.Client, single *single.Manager) (*Manager, error) { // Create the user discovery host object hp := connect.GetDefaultHostParams() + hp.MaxRetries = 3 + hp.SendTimeout = 3 * time.Second m.host, err = m.comms.AddHost(&id.UDB, def.UDB.Address, []byte(def.UDB.Cert), hp) if err != nil { return nil, errors.WithMessage(err, "User Discovery host object could "+