diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b29e09218b488027b32ab2402fcb3c600fd633bb..6646077df5fe882f78f3467cc6448a32307fc649 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,7 +9,7 @@ cache:
 variables:
   REPO_DIR: gitlab.com/elixxir
   REPO_NAME: registration
-  DOCKER_IMAGE: elixxirlabs/cuda-go:latest
+  DOCKER_IMAGE: elixxirlabs/cuda-go:go1.13-cuda11.1-mc
   MIN_CODE_COVERAGE: "0.0"
 
 before_script:
@@ -77,11 +77,19 @@ build:
     - tags
   script:
     - mkdir -p release
+    - rm -rf upload || true
     - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' ./...
     - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/registration.linux64 main.go
+    - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -tags stateless -trimpath -ldflags '-w -s' -o release/registration.stateless.linux64 main.go
     - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/registration.win64 main.go
+    - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -tags stateless -trimpath -ldflags '-w -s' -o release/registration.stateless.win64 main.go
     - GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/registration.win32 main.go
+    - GOOS=windows GOARCH=386 CGO_ENABLED=0 go build -tags stateless -trimpath -ldflags '-w -s' -o release/registration.stateless.win32 main.go
     - GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o release/registration.darwin64 main.go
+    - GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -tags stateless -trimpath -ldflags '-w -s' -o release/registration.stateless.darwin64 main.go
+    - mkdir -p upload
+    - mv release/registration.stateless.* upload/
+    - /upload-artifacts.sh upload/
   artifacts:
     paths:
      - release/
diff --git a/Makefile b/Makefile
index 86491db6d4e21f04eabcc8c459b22012d683b24e..37f286586e0ae2aeeb980466bafe7dd933d0c27c 100644
--- a/Makefile
+++ b/Makefile
@@ -20,12 +20,14 @@ build:
 
 update_release:
 	GOFLAGS="" go get -u gitlab.com/elixxir/primitives@release
+	GOFLAGS="" go get -u gitlab.com/xx_network/crypto@release
 	GOFLAGS="" go get -u gitlab.com/elixxir/crypto@release
 	GOFLAGS="" go get -u gitlab.com/xx_network/comms@release
 	GOFLAGS="" go get -u gitlab.com/elixxir/comms@release
 
 update_master:
 	GOFLAGS="" go get -u gitlab.com/elixxir/primitives@master
+	GOFLAGS="" go get -u gitlab.com/xx_network/crypto@master
 	GOFLAGS="" go get -u gitlab.com/elixxir/crypto@master
 	GOFLAGS="" go get -u gitlab.com/xx_network/comms@master
 	GOFLAGS="" go get -u gitlab.com/elixxir/comms@master
diff --git a/README.md b/README.md
index dfa1a133efeab6f25f5b635c0eea57cab589cb3a..ee04257af4df339150df7c489796a959eb81731a 100644
--- a/README.md
+++ b/README.md
@@ -10,29 +10,33 @@ cMix
 # Permissioning Server Configuration
 # ==================================
 
-# Log message level
+# Log message level (0 = info, 1 = debug, >1 = trace)
 logLevel: 1
+
 # Path to log file
 logPath: "registration.log"
+
 # Path to the node topology permissioning info
 ndfOutputPath: "ndf.json"
-# Minimum number of nodes to begin running rounds. this differs from the number of members 
-# in a team because some scheduling algorithms may require multiple teams worth of nodes at minimum
+
+# Minimum number of nodes to begin running rounds. This differs from the number
+# of members in a team because some scheduling algorithms may require multiple
+# teams worth of nodes at minimum.
 minimumNodes: 3
 
-# Path to the file containing the round ID
-roundIdPath: "roundId.txt"
+# "Location of the user discovery contact file.
+udContactPath: "udContact.bin"
 
-# Path to the file containing the update ID
-updateIdPath: "updateId.txt"
+# Path to UDB cert file
+udbCertPath: "udb.crt"
 
-# UDB ID
-udbID: 1
+# Address for UDB
+udbAddress: "1.2.3.4:11420"
 
 # Public address, used in NDF it gives to client
-publicAdress: "0.0.0.0:11420"
+publicAddress: "0.0.0.0:11420"
 
-# The listening port of this  server
+# The listening port of this server
 port: 11420
 
 # The minimum version required of gateways to connect
@@ -52,7 +56,7 @@ dbAddress: ""
 
 # Path to JSON file with list of Node registration codes (in order of network 
 # placement)
-RegCodesFilePath: "regCodes.json"
+regCodesFilePath: "regCodes.json"
 
 # List of client codes to be added to the database (for testing)
 clientRegCodes:
@@ -64,8 +68,7 @@ clientRegCodes:
 # Client version (will allow all versions with major version 0)
 clientVersion: "0.0.0"
 
-# The duration between polling the disabled Node list for updates. Optional.
-# Defaults to 1m.
+# The duration between polling the disabled Node list for updates (Default 1m)
 disabledNodesPollDuration: 1m
 
 # Path to the text file with a list of IDs of disabled Nodes. If no path is,
@@ -81,8 +84,7 @@ certPath: ""
 # Time interval (in seconds) between committing Node statistics to storage
 nodeMetricInterval: 180
 
-# Time interval (in minutes) in which the database is 
-# checked for banned nodes
+# Time interval (in minutes) in which the database is checked for banned nodes
 BanTrackerInterval: "3"
 
 # E2E/CMIX Primes
@@ -94,20 +96,27 @@ groups:
     prime: "${e2e_prime}"
     generator: "${e2e_generator}"
 
-# Selection of scheduling algorithm to use. Options are:
-#   simple - Schedules multiple teams to maximize performance, does not randomly re-arrange teams, if only a single
-#            only scheduling a single team, will use numerical ordering data for AlphaNet
-#   secure - Schedules new teams randomly, has appropriate buffers to ensure
-# unpredictability, designed for BetaNet
-schedulingAlgorithm: "single"
-
 # Path to file with config for scheduling algorithm within the user directory 
 schedulingConfigPath: "Scheduling_Simple_NonRandom.json"
 
-# Time that the registration server waits before timing out while killing the round scheduling thread
+# Time that the registration server waits before timing out while killing the
+# round scheduling thread
 schedulingKillTimeout: 10s
 # Time the registration waits for rounds to close out and stop (optional)
 closeTimeout: 60s
+
+# Address of the notification server
+nsAddress: ""
+# Path to certificate for the notification server
+nsCertPath: ""
+
+# Maximum number of connections per period
+userRegCapacity: 1000
+# How often the number of connections is reset
+userRegLeakPeriod: "24h"
+
+# The size of the address space used for ephemeral IDs
+addressSpace: 10
 ```
 
 ### SchedulingConfig template:
diff --git a/cmd/bannedNodeTracker_test.go b/cmd/bannedNodeTracker_test.go
index dc278f5e9c38c6ccb3f2cb444c959393b1ec753e..9ca3e37a2700738d99979bc52b2f6251f05960e1 100644
--- a/cmd/bannedNodeTracker_test.go
+++ b/cmd/bannedNodeTracker_test.go
@@ -8,11 +8,11 @@ package cmd
 import (
 	"crypto/rand"
 	"fmt"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
 	"sync"
 	"testing"
 )
@@ -28,7 +28,7 @@ func TestBannedNodeTracker(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	impl := &RegistrationImpl{
 		State:   testState,
 		NDFLock: sync.Mutex{},
@@ -64,12 +64,10 @@ func TestBannedNodeTracker(t *testing.T) {
 	}
 
 	// Clean out banned nodes
-	fmt.Println("1")
 	err = BannedNodeTracker(impl)
 	if err != nil {
 		t.Errorf("Error with node tracker: %v", err)
 	}
-	fmt.Println("2")
 
 	updatedDef := testState.GetFullNdf().Get()
 	if len(updatedDef.Nodes) != 1 {
diff --git a/cmd/impl.go b/cmd/impl.go
index 94c060bd15d1354565f1a56b5e287ec79e3c3333..942063ece60a2da42c9ccd88c8bb25961ccce054 100644
--- a/cmd/impl.go
+++ b/cmd/impl.go
@@ -14,43 +14,40 @@ import (
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/comms/registration"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/crypto/tls"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/elixxir/primitives/utils"
-	"gitlab.com/elixxir/primitives/version"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/crypto/tls"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"gitlab.com/xx_network/primitives/rateLimiting"
+	"gitlab.com/xx_network/primitives/utils"
 	"sync"
 	"time"
 )
 
-//generally large buffer, should be roughly as many nodes as are expected
-const nodeCompletionChanLen = 1000
-
 // The main registration instance object
 type RegistrationImpl struct {
-	Comms                   *registration.Comms
-	params                  *Params
-	State                   *storage.NetworkState
-	Stopped                 *uint32
-	permissioningCert       *x509.Certificate
-	ndfOutputPath           string
-	NdfReady                *uint32
-	certFromFile            string
-	registrationsRemaining  *uint64
-	maxRegistrationAttempts uint64
-	disableGatewayPing      bool
+	Comms                *registration.Comms
+	params               *Params
+	State                *storage.NetworkState
+	Stopped              *uint32
+	permissioningCert    *x509.Certificate
+	ndfOutputPath        string
+	NdfReady             *uint32
+	certFromFile         string
+	registrationLimiting *rateLimiting.Bucket
+	disableGatewayPing   bool
 
 	//registration status trackers
 	numRegistered int
 	//FIXME: it is possible that polling lock and registration lock
 	// do the same job and could conflict. reconsiderations of this logic
 	// may be fruitful
-	registrationLock sync.Mutex
-	beginScheduling  chan struct{}
+	registrationLock  sync.Mutex
+	beginScheduling   chan struct{}
+	registrationTimes map[id.ID]int64
 
 	NDFLock sync.Mutex
 }
@@ -58,49 +55,10 @@ type RegistrationImpl struct {
 //function used to schedule nodes
 type SchedulingAlgorithm func(params []byte, state *storage.NetworkState) error
 
-// Params object for reading in configuration data
-type Params struct {
-	Address                   string
-	CertPath                  string
-	KeyPath                   string
-	NdfOutputPath             string
-	NsCertPath                string
-	NsAddress                 string
-	cmix                      ndf.Group
-	e2e                       ndf.Group
-	publicAddress             string
-	maxRegistrationAttempts   uint64
-	registrationCountDuration time.Duration
-	schedulingKillTimeout     time.Duration
-	closeTimeout              time.Duration
-	minimumNodes              uint32
-	udbId                     []byte
-	minGatewayVersion         version.Version
-	minServerVersion          version.Version
-	roundIdPath               string
-	updateIdPath              string
-	disableGatewayPing        bool
-}
-
-// toGroup takes a group represented by a map of string to string,
-// then uses the prime and generator to create an ndf group object.
-func toGroup(grp map[string]string) (*ndf.Group, error) {
-	jww.DEBUG.Printf("Group is: %v", grp)
-	pStr, pOk := grp["prime"]
-	gStr, gOk := grp["generator"]
-
-	if !gOk || !pOk {
-		return nil, errors.Errorf("Invalid Group Config "+
-			"(prime: %v, generator: %v", pOk, gOk)
-	}
-	return &ndf.Group{Prime: pStr, Generator: gStr}, nil
-}
-
 // Configure and start the Permissioning Server
-func StartRegistration(params Params, done chan bool) (*RegistrationImpl, error) {
+func StartRegistration(params Params) (*RegistrationImpl, error) {
 
 	// Initialize variables
-	regRemaining := uint64(0)
 	ndfReady := uint32(0)
 	roundCreationStopped := uint32(0)
 
@@ -117,33 +75,27 @@ func StartRegistration(params Params, done chan bool) (*RegistrationImpl, error)
 			"PermissioningKey is %+v", err, pk)
 	}
 
-	//initilize the state tracking object
-	state, err := storage.NewState(pk, params.roundIdPath, params.updateIdPath)
+	// Initialize the state tracking object
+	state, err := storage.NewState(pk, params.addressSpace)
 	if err != nil {
 		return nil, err
 	}
 
 	// Build default parameters
 	regImpl := &RegistrationImpl{
-		State:                   state,
-		params:                  &params,
-		maxRegistrationAttempts: params.maxRegistrationAttempts,
-		registrationsRemaining:  &regRemaining,
-		ndfOutputPath:           params.NdfOutputPath,
-		NdfReady:                &ndfReady,
-		Stopped:                 &roundCreationStopped,
-
+		State:              state,
+		params:             &params,
+		ndfOutputPath:      params.NdfOutputPath,
+		NdfReady:           &ndfReady,
+		Stopped:            &roundCreationStopped,
 		numRegistered:      0,
 		beginScheduling:    make(chan struct{}, 1),
 		disableGatewayPing: params.disableGatewayPing,
+		registrationTimes:  make(map[id.ID]int64),
 	}
 
-	// Create timer and channel to be used by routine that clears the number of
-	// registrations every time the ticker activates
-	go func() {
-		ticker := time.NewTicker(params.registrationCountDuration)
-		regImpl.registrationCapacityRestRunner(ticker, done)
-	}()
+	//regImpl.registrationLimiting = rateLimiting.Create(params.userRegCapacity, params.userRegLeakRate)
+	regImpl.registrationLimiting = rateLimiting.CreateBucket(params.userRegCapacity, params.userRegCapacity, params.userRegLeakPeriod, func(u uint32, i int64) {})
 
 	if !noTLS {
 		// Read in TLS keys from files
@@ -162,6 +114,12 @@ func StartRegistration(params Params, done chan bool) (*RegistrationImpl, error)
 
 	}
 
+	// Load the UDB cert from file
+	udbCert, err := utils.ReadFile(params.udbCertPath)
+	if err != nil {
+		return nil, errors.Errorf("failed to read UDB cert: %+v", err)
+	}
+
 	// Construct the NDF
 	networkDef := &ndf.NetworkDefinition{
 		Registration: ndf.Registration{
@@ -170,13 +128,20 @@ func StartRegistration(params Params, done chan bool) (*RegistrationImpl, error)
 		},
 
 		Timestamp: time.Now(),
-		UDB:       ndf.UDB{ID: RegParams.udbId},
-		E2E:       RegParams.e2e,
-		CMIX:      RegParams.cmix,
+		UDB: ndf.UDB{
+			ID:       RegParams.udbId,
+			Cert:     string(udbCert),
+			Address:  RegParams.udbAddress,
+			DhPubKey: RegParams.udbDhPubKey,
+		},
+		E2E:  RegParams.e2e,
+		CMIX: RegParams.cmix,
 		// fixme: consider removing. this allows clients to remain agnostic of teaming order
 		//  by forcing team order == ndf order for simple non-random
-		Nodes:    make([]ndf.Node, 0),
-		Gateways: make([]ndf.Gateway, 0),
+		Nodes:            make([]ndf.Node, 0),
+		Gateways:         make([]ndf.Gateway, 0),
+		AddressSpaceSize: params.addressSpace,
+		ClientVersion:    RegParams.minClientVersion.String(),
 	}
 
 	// Assemble notification server information if configured
@@ -235,8 +200,8 @@ func BannedNodeTracker(impl *RegistrationImpl) error {
 
 		var newNodes []ndf.Node
 		// Loop through NDF nodes to remove any that are banned
-		for i, node := range def.Nodes {
-			ndfNodeID, err := id.Unmarshal(node.ID)
+		for i, n := range def.Nodes {
+			ndfNodeID, err := id.Unmarshal(n.ID)
 			if err != nil {
 				return errors.WithMessage(err, "Failed to unmarshal node id from NDF")
 			}
@@ -284,23 +249,12 @@ func BannedNodeTracker(impl *RegistrationImpl) error {
 // NewImplementation returns a registration server Handler
 func NewImplementation(instance *RegistrationImpl) *registration.Implementation {
 	impl := registration.NewImplementation()
-	impl.Functions.RegisterUser = func(
-		registrationCode, pubKey string) (signature []byte, err error) {
-
-		response, err := instance.RegisterUser(registrationCode, pubKey)
+	impl.Functions.RegisterUser = func(regCode string, pubKey, receptionPubKey string) ([]byte, []byte, error) {
+		transmissionSig, receptionSig, err := instance.RegisterUser(regCode, pubKey, receptionPubKey)
 		if err != nil {
 			jww.ERROR.Printf("RegisterUser error: %+v", err)
 		}
-		return response, err
-	}
-
-	impl.Functions.GetCurrentClientVersion = func() (version string, err error) {
-		response, err := instance.GetCurrentClientVersion()
-		if err != nil {
-			jww.ERROR.Printf("GetCurrentClientVersion error: %+v", err)
-		}
-
-		return response, err
+		return transmissionSig, receptionSig, err
 	}
 	impl.Functions.RegisterNode = func(salt []byte, serverAddr, serverTlsCert, gatewayAddr,
 		gatewayTlsCert, registrationCode string) error {
@@ -313,9 +267,9 @@ func NewImplementation(instance *RegistrationImpl) *registration.Implementation
 
 		return err
 	}
-	impl.Functions.PollNdf = func(theirNdfHash []byte, auth *connect.Auth) ([]byte, error) {
+	impl.Functions.PollNdf = func(theirNdfHash []byte) ([]byte, error) {
 
-		response, err := instance.PollNdf(theirNdfHash, auth)
+		response, err := instance.PollNdf(theirNdfHash)
 		if err != nil && err.Error() != ndf.NO_NDF {
 			jww.ERROR.Printf("PollNdf error: %+v", err)
 		}
@@ -323,9 +277,9 @@ func NewImplementation(instance *RegistrationImpl) *registration.Implementation
 		return response, err
 	}
 
-	impl.Functions.Poll = func(msg *pb.PermissioningPoll, auth *connect.Auth, serverAddress string) (*pb.PermissionPollResponse, error) {
+	impl.Functions.Poll = func(msg *pb.PermissioningPoll, auth *connect.Auth) (*pb.PermissionPollResponse, error) {
 		//ensure a bad poll can not take down the permisisoning server
-		response, err := instance.Poll(msg, auth, serverAddress)
+		response, err := instance.Poll(msg, auth)
 
 		return response, err
 	}
diff --git a/cmd/params.go b/cmd/params.go
new file mode 100644
index 0000000000000000000000000000000000000000..0c845bf730e106ae419da9da724162975dbf38c2
--- /dev/null
+++ b/cmd/params.go
@@ -0,0 +1,60 @@
+///////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                          //
+//                                                                           //
+// Use of this source code is governed by a license that can be found in the //
+// LICENSE file                                                              //
+///////////////////////////////////////////////////////////////////////////////
+
+// Contains Params-related functionality
+
+package cmd
+
+import (
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/primitives/version"
+	"gitlab.com/xx_network/primitives/ndf"
+	"time"
+)
+
+// Params object for reading in configuration data
+type Params struct {
+	Address               string
+	CertPath              string
+	KeyPath               string
+	NdfOutputPath         string
+	NsCertPath            string
+	NsAddress             string
+	cmix                  ndf.Group
+	e2e                   ndf.Group
+	publicAddress         string
+	schedulingKillTimeout time.Duration
+	closeTimeout          time.Duration
+	minimumNodes          uint32
+	udbId                 []byte
+	udbDhPubKey           []byte
+	udbCertPath           string
+	udbAddress            string
+	minGatewayVersion     version.Version
+	minServerVersion      version.Version
+	minClientVersion      version.Version
+	addressSpace          uint32
+	disableGatewayPing    bool
+	// User registration can take userRegCapacity registrations in userRegLeakPeriod period of time
+	userRegCapacity   uint32
+	userRegLeakPeriod time.Duration
+}
+
+// toGroup takes a group represented by a map of string to string,
+// then uses the prime and generator to create an ndf group object.
+func toGroup(grp map[string]string) (*ndf.Group, error) {
+	jww.DEBUG.Printf("Group is: %v", grp)
+	pStr, pOk := grp["prime"]
+	gStr, gOk := grp["generator"]
+
+	if !gOk || !pOk {
+		return nil, errors.Errorf("Invalid Group Config "+
+			"(prime: %v, generator: %v", pOk, gOk)
+	}
+	return &ndf.Group{Prime: pStr, Generator: gStr}, nil
+}
diff --git a/cmd/permissioning.go b/cmd/permissioning.go
index bb95004134783167c6bfd4eeeff78d0025ef870c..de0b26fb0d8d149f96fc8d6bf233e5a46a1e9297 100644
--- a/cmd/permissioning.go
+++ b/cmd/permissioning.go
@@ -14,16 +14,15 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/crypto/tls"
-	"gitlab.com/elixxir/crypto/xx"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/xx_network/comms/connect"
-	"strconv"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/crypto/tls"
+	"gitlab.com/xx_network/crypto/xx"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"gitlab.com/xx_network/primitives/utils"
 	"sync/atomic"
 )
 
@@ -60,10 +59,20 @@ func (m *RegistrationImpl) CheckNodeRegistration(msg *mixmessages.RegisteredNode
 
 }
 
+// An atomic counter for which regcode we're using next, when disableRegCodes is enabled
+var curNodeReg = uint32(0)
+var curNodeRegPtr = &curNodeReg
+
 // Handle registration attempt by a Node
 func (m *RegistrationImpl) RegisterNode(salt []byte, serverAddr, serverTlsCert, gatewayAddr,
 	gatewayTlsCert, registrationCode string) error {
 
+	// If disableRegCodes is set, we atomically increase curNodeReg and use the previous code in the sequence
+	if disableRegCodes {
+		regNum := atomic.AddUint32(curNodeRegPtr, 1)
+		registrationCode = regCodeInfos[regNum-1].RegCode
+	}
+
 	// Check that the node hasn't already been registered
 	nodeInfo, err := storage.PermissioningDb.GetNode(registrationCode)
 	if err != nil {
@@ -91,7 +100,7 @@ func (m *RegistrationImpl) RegisterNode(salt []byte, serverAddr, serverTlsCert,
 		// Ensure that generated ID matches stored ID
 		// Ensure that salt is not already stored
 		if !bytes.Equal(nodeInfo.Id, nodeId.Marshal()) {
-			return errors.Errorf("Submitted salt %+v does not match stored salt: %+v", salt, nodeInfo.Salt)
+			return errors.Errorf("Generated ID %+v does not match stored ID: %+v", nodeId.Marshal(), nodeInfo.Id)
 
 		} else if len(nodeInfo.Salt) != 0 {
 			return errors.Errorf(
@@ -205,8 +214,7 @@ func (m *RegistrationImpl) completeNodeRegistration(regCode string) error {
 	// Add the new node to the topology
 	m.NDFLock.Lock()
 	networkDef := m.State.GetFullNdf().Get()
-	gateway, n, order, err := assembleNdf(regCode)
-
+	gateway, n, regTime, err := assembleNdf(regCode)
 	if err != nil {
 		m.NDFLock.Unlock()
 		err := errors.Errorf("unable to assemble topology: %+v", err)
@@ -214,18 +222,17 @@ func (m *RegistrationImpl) completeNodeRegistration(regCode string) error {
 		return errors.Errorf("Could not complete registration: %+v", err)
 	}
 
-	if order != -1 {
-		// fixme: consider removing. this allows clients to remain agnostic of teaming order
-		//  by forcing team order == ndf order for simple non-random
-		if order >= len(networkDef.Nodes) {
-			appendNdf(networkDef, order)
-		}
+	nodeID, err := id.Unmarshal(n.ID)
+	if err != nil {
+		m.NDFLock.Unlock()
+		return errors.WithMessage(err, "Error parsing node ID")
+	}
 
-		networkDef.Gateways[order] = gateway
-		networkDef.Nodes[order] = n
-	} else {
-		networkDef.Gateways = append(networkDef.Gateways, gateway)
-		networkDef.Nodes = append(networkDef.Nodes, n)
+	m.registrationTimes[*nodeID] = regTime
+	err = m.insertNdf(networkDef, gateway, n, regTime)
+	if err != nil {
+		m.NDFLock.Unlock()
+		return errors.WithMessage(err, "Failed to insert nodes in definition")
 	}
 
 	// update the internal state with the newly-updated ndf
@@ -273,8 +280,33 @@ func appendNdf(definition *ndf.NetworkDefinition, order int) {
 
 }
 
+// Insert a node into the NDF, preserving ordering
+func (m *RegistrationImpl) insertNdf(definition *ndf.NetworkDefinition, g ndf.Gateway,
+	n ndf.Node, regTime int64) error {
+	var i int
+	for i = 0; i < len(definition.Nodes); i++ {
+		nid, err := id.Unmarshal(definition.Nodes[i].ID)
+		if err != nil {
+			return errors.Errorf("Could not unmarshal ID from definition: %+v", err)
+		}
+		cmpTime := m.registrationTimes[*nid]
+		if regTime < cmpTime {
+			break
+		}
+	}
+
+	if i == len(definition.Nodes) {
+		definition.Nodes = append(definition.Nodes, n)
+		definition.Gateways = append(definition.Gateways, g)
+	} else {
+		definition.Nodes = append(definition.Nodes[0:i], append([]ndf.Node{n}, definition.Nodes[i:]...)...)
+		definition.Gateways = append(definition.Gateways[0:i], append([]ndf.Gateway{g}, definition.Gateways[i:]...)...)
+	}
+	return nil
+}
+
 // Assemble information for the given registration code
-func assembleNdf(code string) (ndf.Gateway, ndf.Node, int, error) {
+func assembleNdf(code string) (ndf.Gateway, ndf.Node, int64, error) {
 
 	// Get node information for each registration code
 	nodeInfo, err := storage.PermissioningDb.GetNode(code)
@@ -305,12 +337,7 @@ func assembleNdf(code string) (ndf.Gateway, ndf.Node, int, error) {
 		TlsCertificate: nodeInfo.GatewayCertificate,
 	}
 
-	order, err := strconv.Atoi(nodeInfo.Sequence)
-	if err != nil {
-		return gateway, n, -1, nil
-	}
-
-	return gateway, n, order, nil
+	return gateway, n, nodeInfo.DateRegistered.UnixNano(), nil
 }
 
 // outputNodeTopologyToJSON encodes the NodeTopology structure to JSON and
diff --git a/cmd/permissioning_test.go b/cmd/permissioning_test.go
index eda3286f79611d71d5ad475812f503a0d399e2c9..a118f8b0993ec91d64bddafdce89bc92e87cb8b7 100644
--- a/cmd/permissioning_test.go
+++ b/cmd/permissioning_test.go
@@ -1,11 +1,11 @@
 package cmd
 
 import (
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/testkeys"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
 	"testing"
 	"time"
 )
@@ -34,38 +34,47 @@ func TestLoadAllRegisteredNodes(t *testing.T) {
 
 	// Create a new ID and store a new active node into the database
 	activeNodeId := id.NewIdFromUInt(0, id.Node, t)
-	err = storage.PermissioningDb.RegisterNode(activeNodeId, []byte("test"), "AAAA", "0.0.0.0", string(crt),
+	err = storage.PermissioningDb.RegisterNode(activeNodeId, []byte("test1"), "AAAA", "0.0.0.0", string(crt),
 		"0.0.0.0", string(crt))
 	if err != nil {
 		t.Error(err)
 	}
+	time.Sleep(1)
 
 	// Create a new ID and store a new *banned* node into the database
 	bannedNodeId := id.NewIdFromUInt(1, id.Node, t)
-	err = storage.PermissioningDb.RegisterNode(bannedNodeId, []byte("test"), "BBBB", "0.0.0.0", string(crt),
+	err = storage.PermissioningDb.RegisterNode(bannedNodeId, []byte("test2"), "BBBB", "0.0.0.0", string(crt),
 		"0.0.0.0", string(crt))
 	if err != nil {
 		t.Error(err)
 	}
-	permissioningMap := storage.PermissioningDb.NodeRegistration.(*storage.MapImpl)
+	time.Sleep(1)
+
+	// Create a new ID and store a new *banned* node into the database
+	altNodeID := id.NewIdFromString("alt", id.Node, t)
+	err = storage.PermissioningDb.RegisterNode(altNodeID, []byte("test3"), "CCCC", "0.0.0.0", string(crt),
+		"0.0.0.0", string(crt))
+	if err != nil {
+		t.Error(err)
+	}
+
+	permissioningMap := storage.PermissioningDb.GetMapImpl(t)
 	err = permissioningMap.BannedNode(bannedNodeId, t)
 	if err != nil {
 		t.Error(err)
 	}
 	//endregion
-
 	//region Test code
 	// Create params for test registration server
 	testParams := Params{
-		CertPath:                  testkeys.GetCACertPath(),
-		KeyPath:                   testkeys.GetCAKeyPath(),
-		NdfOutputPath:             testkeys.GetNDFPath(),
-		maxRegistrationAttempts:   5,
-		registrationCountDuration: time.Hour,
+		CertPath:      testkeys.GetCACertPath(),
+		KeyPath:       testkeys.GetCAKeyPath(),
+		NdfOutputPath: testkeys.GetNDFPath(),
+		udbCertPath:   testkeys.GetUdbCertPath(),
 	}
-	bc := make(chan bool, 1)
+
 	// Start registration server
-	impl, err := StartRegistration(testParams, bc)
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Error(err)
 	}
@@ -95,34 +104,51 @@ func TestLoadAllRegisteredNodes(t *testing.T) {
 	if !hmBannedNode.GetId().Cmp(bannedNodeId) {
 		t.Error("Unexpected node ID for node 0:\r\tGot: %i\r\tExpected: %i", hmBannedNode.GetId(), bannedNodeId)
 	}
-	//endregion
 
 	//region Node map checking
 	// Check that the nodes were added to the node map
+	expected_nodes := 3
 	nodeMapNodes := impl.State.GetNodeMap().GetNodeStates()
-	if len(nodeMapNodes) != 2 {
-		t.Errorf("Unexpected number of nodes found in node map:\r\tGot: %d\r"+
-			"\tExpected: %d", len(nodeMapNodes), 2)
+	if len(nodeMapNodes) != expected_nodes {
+		t.Errorf("Unexpected number of nodes found in node map:\n\tGot: %d\n"+
+			"\tExpected: %d", len(nodeMapNodes), expected_nodes)
 	}
-
-	if !nodeMapNodes[0].GetID().Cmp(activeNodeId) {
-		t.Errorf("Unexpected node ID for node 0:\r\tGot: %d\r\tExpected: %d",
+	def := impl.State.GetFullNdf().Get()
+	id0, err := id.Unmarshal(def.Nodes[0].ID)
+	if err != nil {
+		t.Error("Failed to unmarshal ID")
+	}
+	if !id0.Cmp(activeNodeId) {
+		t.Errorf("Unexpected node ID for node 0:\n\tGot: %d\n\tExpected: %d",
 			nodeMapNodes[0].GetID(), activeNodeId)
 	}
 
-	if !nodeMapNodes[1].GetID().Cmp(bannedNodeId) {
-		t.Errorf("Unexpected node ID for node 1:\r\tGot: %d\r\tExpected: %d",
+	id1, err := id.Unmarshal(def.Nodes[1].ID)
+	if err != nil {
+		t.Error("Failed to unmarshal ID")
+	}
+	if !id1.Cmp(bannedNodeId) {
+		t.Errorf("Unexpected node ID for node 1:\n\tGot: %d\n\tExpected: %d",
 			nodeMapNodes[1].GetID(), bannedNodeId)
 	}
 
-	if nodeMapNodes[0].GetStatus() != node.Active {
-		t.Errorf("Unexpected status for node 0:\r\tGot: %s\r\tExpected: %s",
-			nodeMapNodes[0].GetStatus().String(), node.Banned.String())
+	id2, err := id.Unmarshal(def.Nodes[2].ID)
+	if err != nil {
+		t.Error("Failed to unmarshal ID")
+	}
+	if !id2.Cmp(altNodeID) {
+		t.Errorf("Unexpected node ID for node 2:\n\tGot: %d\n\tExpected: %d",
+			nodeMapNodes[2].GetID(), altNodeID)
 	}
 
-	if nodeMapNodes[1].GetStatus() != node.Banned {
-		t.Errorf("Unexpected status for node 1:\r\tGot: %s\r\tExpected: %s",
-			nodeMapNodes[1].GetStatus().String(), node.Banned.String())
+	banned := 0
+	for _, n := range nodeMapNodes {
+		if n.GetStatus() == node.Banned {
+			banned++
+		}
+	}
+	if banned != 1 {
+		t.Error("Should only be one banned node")
 	}
 	//endregion
 
diff --git a/cmd/poll.go b/cmd/poll.go
index 8ceb647b81161b95315d5d404c308c5928da50a7..be65a0c3048a19f2cb8f0866dff8afd8df85992f 100644
--- a/cmd/poll.go
+++ b/cmd/poll.go
@@ -13,25 +13,19 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/signature"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
 	"gitlab.com/elixxir/primitives/version"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/xx_network/comms/connect"
-	"net"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
 	"sync/atomic"
 )
 
-// The placeholder for the host in the Gateway address that is used to indicate
-// to permissioning to replace it with the Node's host.
-const gatewayReplaceIpPlaceholder = "CHANGE_TO_PUBLIC_IP"
-
 // Server->Permissioning unified poll function
-func (m *RegistrationImpl) Poll(msg *pb.PermissioningPoll, auth *connect.Auth,
-	serverAddress string) (*pb.PermissionPollResponse, error) {
+func (m *RegistrationImpl) Poll(msg *pb.PermissioningPoll, auth *connect.Auth) (*pb.PermissionPollResponse, error) {
 
 	// Initialize the response
 	response := &pb.PermissionPollResponse{}
@@ -69,11 +63,15 @@ func (m *RegistrationImpl) Poll(msg *pb.PermissioningPoll, auth *connect.Auth,
 
 	activity := current.Activity(msg.Activity)
 
-	// Increment the Node's poll count
-	n.IncrementNumPolls()
+	// check that the activity is not error and then poll, do not count error
+	// polls so erring nodes are not issues rounds
+	if activity != current.ERROR {
+		// Increment the Node's poll count
+		n.IncrementNumPolls()
+	}
 
-	//update ip addresses if nessessary
-	err := checkIPAddresses(m, n, msg, auth.Sender, serverAddress)
+	// update ip addresses if necessary
+	err := checkIPAddresses(m, n, msg, auth.Sender)
 	if err != nil {
 		err = errors.WithMessage(err, "Failed to update IP addresses")
 		return response, err
@@ -153,19 +151,14 @@ func (m *RegistrationImpl) Poll(msg *pb.PermissioningPoll, auth *connect.Auth,
 	if updateNotification.ToActivity == current.ERROR {
 		updateNotification.Error = msg.Error
 	}
-	err = m.State.SendUpdateNotification(updateNotification)
-	if err!=nil{
-		jww.WARN.Printf("Failed to send update notification, " +
-			"is the update thread running?")
-		n.GetPollingLock().Unlock()
-	}
+	updateNotification.ClientErrors = msg.ClientErrors
 
 	// Update occurred, report it to the control thread
-	return response, err
+	return response, m.State.SendUpdateNotification(updateNotification)
 }
 
 // PollNdf handles the client polling for an updated NDF
-func (m *RegistrationImpl) PollNdf(theirNdfHash []byte, auth *connect.Auth) ([]byte, error) {
+func (m *RegistrationImpl) PollNdf(theirNdfHash []byte) ([]byte, error) {
 
 	// Ensure the NDF is ready to be returned
 	regComplete := atomic.LoadUint32(m.NdfReady)
@@ -173,27 +166,14 @@ func (m *RegistrationImpl) PollNdf(theirNdfHash []byte, auth *connect.Auth) ([]b
 		return nil, errors.New(ndf.NO_NDF)
 	}
 
-	// Handle client request
-	if !auth.IsAuthenticated || auth.Sender.IsDynamicHost() {
-		// Do not return NDF if client hash matches
-		if isSame := m.State.GetPartialNdf().CompareHash(theirNdfHash); isSame {
-			return nil, nil
-		}
-
-		// Send the json of the client
-		jww.TRACE.Printf("Returning a new NDF to client!")
-		jww.TRACE.Printf("Sending the following ndf: %v", m.State.GetPartialNdf().Get())
-		return m.State.GetPartialNdf().Get().Marshal()
-	}
-
 	// Do not return NDF if backend hash matches
-	if isSame := m.State.GetFullNdf().CompareHash(theirNdfHash); isSame {
+	if isSame := m.State.GetPartialNdf().CompareHash(theirNdfHash); isSame {
 		return nil, nil
 	}
 
 	//Send the json of the ndf
 	jww.TRACE.Printf("Returning a new NDF to a back-end server!")
-	return m.State.GetFullNdf().Get().Marshal()
+	return m.State.GetPartialNdf().Get().Marshal()
 }
 
 // checkVersion checks if the PermissioningPoll message server and gateway
@@ -324,37 +304,11 @@ func verifyError(msg *pb.PermissioningPoll, n *node.State, m *RegistrationImpl)
 	return nil
 }
 
-// updateGatewayAdvertisedAddress checks if the Gateway's address host is set to
-// gatewayReplaceIpPlaceholder. If it is, then it is replaced with the Node's
-// host while retaining the Gateway's port.
-func updateGatewayAdvertisedAddress(gatewayAddress, nodeAddress string) (string, error) {
-	if gatewayAddress == "" {
-		return gatewayAddress, nil
-	}
-
-	gwAddr, gwPort, err := net.SplitHostPort(gatewayAddress)
-	if err != nil {
-		return "", errors.Errorf("Error parsing Gateway address: %v", err)
-	}
-
-	if gwAddr == gatewayReplaceIpPlaceholder {
-		nAddr, _, err := net.SplitHostPort(nodeAddress)
-		if err != nil {
-			return "", errors.Errorf("Error parsing Node address: %v", err)
-		}
+func checkIPAddresses(m *RegistrationImpl, n *node.State,
+	msg *pb.PermissioningPoll, nodeHost *connect.Host) error {
 
-		gatewayAddress = net.JoinHostPort(nAddr, gwPort)
-	}
-
-	return gatewayAddress, nil
-}
-
-func checkIPAddresses(m *RegistrationImpl, n *node.State, msg *pb.PermissioningPoll, nodeHost *connect.Host, nodeAddress string) error {
-	// Check if the Gateway address needs to be updated
-	gatewayAddress, err := updateGatewayAdvertisedAddress(msg.GatewayAddress, nodeAddress)
-	if err != nil {
-		return err
-	}
+	// Pull the addresses out of the message
+	gatewayAddress, nodeAddress := msg.GatewayAddress, msg.ServerAddress
 
 	// Update server and gateway addresses in state, if necessary
 	nodeUpdate := n.UpdateNodeAddresses(nodeAddress)
@@ -366,11 +320,11 @@ func checkIPAddresses(m *RegistrationImpl, n *node.State, msg *pb.PermissioningP
 	// If state required changes, then check the NDF
 	if nodeUpdate || gatewayUpdate {
 
-		jww.TRACE.Printf("UPDATING gateway and node update: %s, %s", nodeAddress,
+		jww.TRACE.Printf("UPDATING gateway and node update: %s, %s", msg.ServerAddress,
 			gatewayAddress)
 
 		// Update address information in Storage
-		err = storage.PermissioningDb.UpdateNodeAddresses(nodeHost.GetId(), nodeAddress, gatewayAddress)
+		err := storage.PermissioningDb.UpdateNodeAddresses(nodeHost.GetId(), nodeAddress, gatewayAddress)
 		if err != nil {
 			return err
 		}
@@ -381,21 +335,21 @@ func checkIPAddresses(m *RegistrationImpl, n *node.State, msg *pb.PermissioningP
 		if nodeUpdate {
 			nodeHost.UpdateAddress(nodeAddress)
 			n.SetConnectivity(node.PortUnknown)
-			if err = updateNdfNodeAddr(n.GetID(), nodeAddress, currentNDF); err != nil {
+			if err := updateNdfNodeAddr(n.GetID(), nodeAddress, currentNDF); err != nil {
 				m.NDFLock.Unlock()
 				return err
 			}
 		}
 
 		if gatewayUpdate {
-			if err = updateNdfGatewayAddr(n.GetID(), gatewayAddress, currentNDF); err != nil {
+			if err := updateNdfGatewayAddr(n.GetID(), gatewayAddress, currentNDF); err != nil {
 				m.NDFLock.Unlock()
 				return err
 			}
 		}
 
 		// Update the internal state with the newly-updated ndf
-		if err = m.State.UpdateNdf(currentNDF); err != nil {
+		if err := m.State.UpdateNdf(currentNDF); err != nil {
 			m.NDFLock.Unlock()
 			return err
 		}
diff --git a/cmd/poll_test.go b/cmd/poll_test.go
index cb4a8b46510dbc12790b3a45c2e6ec6b51ebc8a4..9719726d4a982e236a00591ad03db0e40e826a82 100644
--- a/cmd/poll_test.go
+++ b/cmd/poll_test.go
@@ -11,19 +11,19 @@ import (
 	"fmt"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/comms/registration"
-	"gitlab.com/elixxir/crypto/signature"
-	"gitlab.com/elixxir/crypto/signature/rsa"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
 	"gitlab.com/elixxir/primitives/states"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/primitives/version"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/storage/round"
 	"gitlab.com/elixxir/registration/testkeys"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"gitlab.com/xx_network/primitives/utils"
 	"sync"
 	"sync/atomic"
 	"testing"
@@ -44,7 +44,7 @@ func TestRegistrationImpl_Poll(t *testing.T) {
 	testString := "test"
 	// Start registration server
 	testParams.KeyPath = testkeys.GetCAKeyPath()
-	impl, err := StartRegistration(testParams, nil)
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Errorf("Unable to start registration: %+v", err)
 	}
@@ -103,7 +103,7 @@ func TestRegistrationImpl_Poll(t *testing.T) {
 	n := impl.State.GetNodeMap().GetNode(testID)
 	n.SetConnectivity(node.PortSuccessful)
 
-	response, err := impl.Poll(testMsg, testAuth, "0.0.0.0:11420")
+	response, err := impl.Poll(testMsg, testAuth)
 	if err != nil {
 		t.Errorf("Unexpected error polling: %+v", err)
 	}
@@ -135,7 +135,7 @@ func TestRegistrationImpl_PollNoNdf(t *testing.T) {
 	}
 	// Start registration server
 	ndfReady := uint32(0)
-	state, err := storage.NewState(pk, "", "")
+	state, err := storage.NewState(pk, 8)
 	if err != nil {
 		t.Errorf("Unable to create state: %+v", err)
 	}
@@ -151,7 +151,7 @@ func TestRegistrationImpl_PollNoNdf(t *testing.T) {
 
 	dummyMessage := &pb.PermissioningPoll{}
 
-	_, err = impl.Poll(dummyMessage, nil, "")
+	_, err = impl.Poll(dummyMessage, nil)
 	if err == nil || err.Error() != ndf.NO_NDF {
 		t.Errorf("Unexpected error polling: %+v", err)
 	}
@@ -163,7 +163,7 @@ func TestRegistrationImpl_PollFailAuth(t *testing.T) {
 
 	// Start registration server
 	ndfReady := uint32(1)
-	state, err := storage.NewState(getTestKey(), "", "")
+	state, err := storage.NewState(getTestKey(), 8)
 	if err != nil {
 		t.Errorf("Unable to create state: %+v", err)
 	}
@@ -194,7 +194,7 @@ func TestRegistrationImpl_PollFailAuth(t *testing.T) {
 
 	dummyMessage := &pb.PermissioningPoll{}
 
-	_, err = impl.Poll(dummyMessage, testAuth, "0.0.0.0:11420")
+	_, err = impl.Poll(dummyMessage, testAuth)
 	if err == nil || err.Error() != connect.AuthError(testAuth.Sender.GetId()).Error() {
 		t.Errorf("Unexpected error polling: %+v", err)
 	}
@@ -218,12 +218,11 @@ func TestRegistrationImpl_PollNdf(t *testing.T) {
 	storage.PopulateNodeRegistrationCodes(infos)
 
 	RegParams = testParams
-	udbId := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}
-	RegParams.udbId = udbId
+	udbId := id.NewIdFromUInt(5, id.User, t)
+	RegParams.udbId = udbId.Marshal()
 	RegParams.minimumNodes = 3
-	fmt.Println("-A")
 	// Start registration server
-	impl, err := StartRegistration(RegParams, nil)
+	impl, err := StartRegistration(RegParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
@@ -269,20 +268,20 @@ func TestRegistrationImpl_PollNdf(t *testing.T) {
 	}
 
 	l.Lock()
-	observedNDFBytes, err := impl.PollNdf(nil, &connect.Auth{})
+	observedNDFBytes, err := impl.PollNdf(nil)
 	l.Unlock()
 	if err != nil {
 		t.Errorf("failed to update ndf: %v", err)
 	}
 
-	observedNDF, _, err := ndf.DecodeNDF(string(observedNDFBytes))
+	observedNDF, err := ndf.Unmarshal(observedNDFBytes)
 	if err != nil {
 		t.Errorf("Could not decode ndf: %v\nNdf output: %s", err,
 			string(observedNDFBytes))
 	}
 
 	fmt.Printf("\n\n\nndf: %v\n\n\n", observedNDF.Nodes)
-	if bytes.Compare(observedNDF.UDB.ID, udbId) != 0 {
+	if bytes.Compare(observedNDF.UDB.ID, udbId.Marshal()) != 0 {
 		t.Errorf("Failed to set udbID. Expected: %v, \nRecieved: %v, \nNdf: %+v",
 			udbId, observedNDF.UDB.ID, observedNDF)
 	}
@@ -311,12 +310,12 @@ func TestRegistrationImpl_PollNdf_NoNDF(t *testing.T) {
 	storage.PopulateNodeRegistrationCodes(infos)
 	RegParams = testParams
 	//Setup udb configurations
-	udbId := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}
-	RegParams.udbId = udbId
+	udbId := id.NewIdFromUInt(5, id.User, t)
+	RegParams.udbId = udbId.Marshal()
 	RegParams.minimumNodes = 3
 
 	// Start registration server
-	impl, err := StartRegistration(testParams, nil)
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
@@ -335,7 +334,7 @@ func TestRegistrationImpl_PollNdf_NoNDF(t *testing.T) {
 	//Make a client ndf hash that is not up to date
 	clientNdfHash := []byte("test")
 
-	_, err = impl.PollNdf(clientNdfHash, &connect.Auth{})
+	_, err = impl.PollNdf(clientNdfHash)
 	if err == nil {
 		t.Error("Expected error path, should not have an ndf ready")
 	}
@@ -358,7 +357,7 @@ func TestPoll_BannedNode(t *testing.T) {
 	testString := "test"
 	// Start registration server
 	testParams.KeyPath = testkeys.GetCAKeyPath()
-	impl, err := StartRegistration(testParams, nil)
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Errorf("Unable to start registration: %+v", err)
 	}
@@ -409,7 +408,7 @@ func TestPoll_BannedNode(t *testing.T) {
 		t.Errorf(err.Error())
 	}
 
-	_, err = impl.Poll(testMsg, testAuth, "")
+	_, err = impl.Poll(testMsg, testAuth)
 	if err != nil {
 		return
 	}
@@ -892,7 +891,7 @@ func TestVerifyError(t *testing.T) {
 	}
 	// Start registration server
 	ndfReady := uint32(0)
-	state, err := storage.NewState(pk, "", "")
+	state, err := storage.NewState(pk, 8)
 	if err != nil {
 		t.Errorf("Unable to create state: %+v", err)
 	}
@@ -945,7 +944,7 @@ func TestVerifyError(t *testing.T) {
 	_ = nsm.AddNode(errNodeId, "", "", "", 0)
 	n := nsm.GetNode(errNodeId)
 	rsm := round.NewStateMap()
-	s, _ := rsm.AddRound(id.Round(0), 4, 5*time.Minute, connect.NewCircuit([]*id.ID{errNodeId}))
+	s, _ := rsm.AddRound(id.Round(0), 4, 8, 5*time.Minute, connect.NewCircuit([]*id.ID{errNodeId}))
 	_ = n.SetRound(s)
 
 	err = verifyError(msg, n, impl)
@@ -953,42 +952,3 @@ func TestVerifyError(t *testing.T) {
 		t.Error("Failed to verify error")
 	}
 }
-
-// Tests that updateGatewayAdvertisedAddress() returns the gatewayAddress when
-// no replacements need to be made.
-func TestUpdateGatewayAdvertisedAddress(t *testing.T) {
-	gatewayAddress := "0.0.0.0:22840"
-	nodeAddress := "192.168.1.1:11420"
-
-	testAddress, err := updateGatewayAdvertisedAddress(gatewayAddress, nodeAddress)
-
-	if err != nil {
-		t.Errorf("updateGatewayAdvertisedAddress() produced an unexpected error."+
-			"\n\texpected: %v\n\treceived: %v", nil, err)
-	}
-
-	if testAddress != gatewayAddress {
-		t.Errorf("updateGatewayAdvertisedAddress() did not return the correct address."+
-			"\n\texpected: %v\n\treceived: %v", gatewayAddress, testAddress)
-	}
-}
-
-// Tests that updateGatewayAdvertisedAddress() returns the nodeAddress with the
-// gatewayAddress port when the gatewayReplaceIpPlaceholder is used.
-func TestUpdateGatewayAdvertisedAddress_Update(t *testing.T) {
-	gatewayAddress := gatewayReplaceIpPlaceholder + ":22840"
-	nodeAddress := "192.168.1.1:11420"
-	expectedAddress := "192.168.1.1:22840"
-
-	testAddress, err := updateGatewayAdvertisedAddress(gatewayAddress, nodeAddress)
-
-	if err != nil {
-		t.Errorf("updateGatewayAdvertisedAddress() produced an unexpected error."+
-			"\n\texpected: %v\n\treceived: %v", nil, err)
-	}
-
-	if testAddress != expectedAddress {
-		t.Errorf("updateGatewayAdvertisedAddress() did not return the correct address."+
-			"\n\texpected: %v\n\treceived: %v", expectedAddress, testAddress)
-	}
-}
diff --git a/cmd/registration.go b/cmd/registration.go
index 22257cbc461fd6c3ffd415ee3176ec437c347c7d..46bb05ee41f70c5f8d59e9043baafe75143f7790 100644
--- a/cmd/registration.go
+++ b/cmd/registration.go
@@ -9,88 +9,58 @@
 package cmd
 
 import (
-	"crypto"
 	"crypto/rand"
-	"crypto/sha256"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/crypto/signature/rsa"
+	"gitlab.com/elixxir/crypto/hash"
 	"gitlab.com/elixxir/registration/storage"
-	"sync/atomic"
-	"time"
+	"gitlab.com/xx_network/crypto/signature/rsa"
 )
 
-const (
-	defaultMaxRegistrationAttempts   = uint64(500)
-	defaultRegistrationCountDuration = time.Hour * 24
-)
+var rateLimitErr = errors.New("Too many client registrations. Try again later")
 
 // Handle registration attempt by a Client
-func (m *RegistrationImpl) RegisterUser(registrationCode, pubKey string) (
-	signature []byte, err error) {
-
-	// Check for pre-existing registration for this public key
+// Returns rsa signature and error
+func (m *RegistrationImpl) RegisterUser(regCode string, pubKey string, receptionKey string) ([]byte, []byte, error) {
+	jww.INFO.Printf("RegisterUser %s", pubKey)
+	// Check for pre-existing registration for this public key first
 	if user, err := storage.PermissioningDb.GetUser(pubKey); err == nil && user != nil {
 		jww.INFO.Printf("Previous registration found for %s", pubKey)
-	} else {
-		// Check database to verify given registration code
-		jww.INFO.Printf("Attempting to use registration code %+v...",
-			registrationCode)
-		err = storage.PermissioningDb.UseCode(registrationCode)
+	} else if regCode != "" {
+		// Fail early for non-valid reg codes
+		err = storage.PermissioningDb.UseCode(regCode)
 		if err != nil {
-			// Check if the max registration attempts have been reached
-			if atomic.LoadUint64(m.registrationsRemaining) >= m.maxRegistrationAttempts {
-				// Invalid registration code, return an error
-				return make([]byte, 0), errors.Errorf(
-					"Error validating registration code: %+v", err)
-			} else {
-				atomic.AddUint64(m.registrationsRemaining, 1)
-				jww.INFO.Printf("Incremented registration counter to %+v (max %v)",
-					atomic.LoadUint64(m.registrationsRemaining), m.maxRegistrationAttempts)
-			}
-		}
-
-		// Record the user public key for duplicate registration support
-		err = storage.PermissioningDb.InsertUser(pubKey)
-		if err != nil {
-			jww.WARN.Printf("Unable to store user: %+v",
-				errors.New(err.Error()))
+			jww.INFO.Printf("RegisterUser error: %+v", err)
+			return nil, nil, err
 		}
+	} else if regCode == "" && !m.registrationLimiting.Add(1) {
+		// Rate limited, fail early
+		jww.INFO.Printf("RegisterUser error: %+v", rateLimitErr)
+		return nil, nil, rateLimitErr
 	}
 
 	// Use hardcoded keypair to sign Client-provided public key
 	//Create a hash, hash the pubKey and then truncate it
-	h := sha256.New()
+	h, _ := hash.NewCMixHash()
 	h.Write([]byte(pubKey))
-	data := h.Sum(nil)
-	sig, err := rsa.Sign(rand.Reader, m.State.GetPrivateKey(), crypto.SHA256, data, nil)
+	transmissionSig, err := rsa.Sign(rand.Reader, m.State.GetPrivateKey(), hash.CMixHash, h.Sum(nil), nil)
 	if err != nil {
-		return make([]byte, 0), errors.Errorf(
+		jww.INFO.Printf("RegisterUser error: can't sign pubkey")
+		return make([]byte, 0), make([]byte, 0), errors.Errorf(
 			"Unable to sign client public key: %+v", err)
 	}
 
-	// Return signed public key to Client
-	jww.INFO.Printf("Registration for code %+v complete!", registrationCode)
-	return sig, nil
-}
-
-// This has to be part of RegistrationImpl and has to return an error because
-// of the way our comms are structured
-func (m *RegistrationImpl) GetCurrentClientVersion() (version string, err error) {
-	clientVersionLock.RLock()
-	defer clientVersionLock.RUnlock()
-	return clientVersion, nil
-}
-
-// registrationCapacityRestRunner sets the registrations remaining to zero when
-// a ticker occurs.
-func (m *RegistrationImpl) registrationCapacityRestRunner(ticker *time.Ticker, done chan bool) {
-	for {
-		select {
-		case <-done:
-			return
-		case <-ticker.C:
-			atomic.StoreUint64(m.registrationsRemaining, 0)
-		}
+	h.Reset()
+	h.Write([]byte(receptionKey))
+	receptionSig, err := rsa.Sign(rand.Reader, m.State.GetPrivateKey(), hash.CMixHash, h.Sum(nil), nil)
+	// Record the user public key for duplicate registration support
+	err = storage.PermissioningDb.InsertUser(pubKey, receptionKey)
+	if err != nil {
+		jww.WARN.Printf("Unable to store user: %+v",
+			errors.New(err.Error()))
 	}
+
+	// Return signed public key to Client
+	jww.DEBUG.Printf("RegisterUser for code [%s] complete!", regCode)
+	return transmissionSig, receptionSig, nil
 }
diff --git a/cmd/registration_test.go b/cmd/registration_test.go
index 6959c6b4c6f8839252ea5f430303d8fe732a182f..2f03ae42be626d0b9a98dd3be92ba1c9f2495160 100644
--- a/cmd/registration_test.go
+++ b/cmd/registration_test.go
@@ -10,12 +10,12 @@ import (
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	nodeComms "gitlab.com/elixxir/comms/node"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/primitives/version"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/testkeys"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
 	"os"
 	"sync"
 	"testing"
@@ -62,18 +62,19 @@ func TestMain(m *testing.M) {
 	}
 
 	testParams = Params{
-		Address:                   permAddr,
-		CertPath:                  testkeys.GetCACertPath(),
-		KeyPath:                   testkeys.GetCAKeyPath(),
-		NdfOutputPath:             testkeys.GetNDFPath(),
-		publicAddress:             permAddr,
-		maxRegistrationAttempts:   5,
-		registrationCountDuration: time.Hour,
-		minimumNodes:              3,
-		minGatewayVersion:         minGatewayVersion,
-		minServerVersion:          minServerVersion,
-	}
-	nodeComm = nodeComms.StartNode(&id.TempGateway, nodeAddr, nodeComms.NewImplementation(), nodeCert, nodeKey)
+		Address:           permAddr,
+		CertPath:          testkeys.GetCACertPath(),
+		KeyPath:           testkeys.GetCAKeyPath(),
+		NdfOutputPath:     testkeys.GetNDFPath(),
+		publicAddress:     permAddr,
+		udbCertPath:       testkeys.GetUdbCertPath(),
+		userRegCapacity:   5,
+		userRegLeakPeriod: time.Hour,
+		minimumNodes:      3,
+		minGatewayVersion: minGatewayVersion,
+		minServerVersion:  minServerVersion,
+	}
+	nodeComm = nodeComms.StartNode(&id.TempGateway, nodeAddr, 0, nodeComms.NewImplementation(), nodeCert, nodeKey)
 
 	runFunc := func() int {
 		code := m.Run()
@@ -84,17 +85,18 @@ func TestMain(m *testing.M) {
 	os.Exit(runFunc())
 }
 
-//Error path: Test an insertion on an empty database
+// Error path: Test an insertion on an empty database
 func TestEmptyDataBase(t *testing.T) {
-	//Start the registration server
+	// Start the registration server
 	testParams := Params{
-		CertPath:                  testkeys.GetCACertPath(),
-		KeyPath:                   testkeys.GetCAKeyPath(),
-		maxRegistrationAttempts:   5,
-		registrationCountDuration: time.Hour,
+		CertPath:          testkeys.GetCACertPath(),
+		KeyPath:           testkeys.GetCAKeyPath(),
+		udbCertPath:       testkeys.GetUdbCertPath(),
+		userRegLeakPeriod: time.Hour,
+		userRegCapacity:   5,
 	}
 	// Start registration server
-	impl, err := StartRegistration(testParams, nil)
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Errorf(err.Error())
 	}
@@ -108,7 +110,7 @@ func TestEmptyDataBase(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//using node cert as gateway cert
+	// using node cert as gateway cert
 	err = impl.RegisterNode([]byte("test"), nodeAddr, string(nodeCert),
 		nodeAddr, string(nodeCert), "AAA")
 	if err == nil {
@@ -123,7 +125,7 @@ func TestEmptyDataBase(t *testing.T) {
 func TestRegCodeExists_InsertRegCode(t *testing.T) {
 	// Start registration server
 	testParams.Address = "0.0.0.0:5901"
-	impl, err := StartRegistration(testParams, nil)
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
@@ -138,7 +140,7 @@ func TestRegCodeExists_InsertRegCode(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Insert a sample regCode
+	// Insert a sample regCode
 	applicationId := uint64(10)
 	newNode := &storage.Node{
 		Code:          "AAAA",
@@ -150,7 +152,7 @@ func TestRegCodeExists_InsertRegCode(t *testing.T) {
 	if err != nil {
 		t.Errorf("Failed to insert client reg code %+v", err)
 	}
-	//Register a node with that regCode
+	// Register a node with that regCode
 	testSalt := []byte("testtesttesttesttesttesttesttest")
 	err = impl.RegisterNode(testSalt, nodeAddr, string(nodeCert),
 		nodeAddr, string(nodeCert), newNode.Code)
@@ -159,10 +161,10 @@ func TestRegCodeExists_InsertRegCode(t *testing.T) {
 	}
 }
 
-//Happy Path:  Insert a reg code along with a node
+// Happy Path:  Insert a reg code along with a node
 func TestRegCodeExists_RegUser(t *testing.T) {
-	//Initialize an implementation and the permissioning server
-	impl, err := StartRegistration(testParams, nil)
+	// Initialize an implementation and the permissioning server
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Errorf("Unable to start: %+v", err)
 	}
@@ -175,26 +177,27 @@ func TestRegCodeExists_RegUser(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Insert regcodes into it
+	// Insert regcodes into it
 	err = storage.PermissioningDb.InsertClientRegCode("AAAA", 100)
 	if err != nil {
 		t.Errorf("Failed to insert client reg code %+v", err)
 	}
 
-	//Attempt to register a user
-	sig, err := impl.RegisterUser("AAAA", string(nodeKey))
+	// Attempt to register a user
+	sig, receptionSig, err := impl.RegisterUser("AAAA", string(nodeKey), string(nodeKey))
 
 	if err != nil {
 		t.Errorf("Failed to register a node when it should have worked: %+v", err)
 	}
 
-	if sig == nil {
-		t.Errorf("Failed to sign public key, recieved %+v as a signature", sig)
+	if sig == nil || receptionSig == nil {
+		t.Errorf("Failed to sign public key, recieved %+v as a signature & %+v as a receptionSignature",
+			sig, receptionSig)
 	}
 	impl.Comms.Shutdown()
 }
 
-//Attempt to register a node after the
+// Attempt to register a node after the
 func TestCompleteRegistration_HappyPath(t *testing.T) {
 	// Initialize the database
 	var err error
@@ -207,7 +210,7 @@ func TestCompleteRegistration_HappyPath(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Insert a sample regCode
+	// Insert a sample regCode
 	infos := make([]node.Info, 0)
 	infos = append(infos, node.Info{RegCode: "BBBB", Order: "0"})
 
@@ -215,7 +218,7 @@ func TestCompleteRegistration_HappyPath(t *testing.T) {
 	localParams := testParams
 	localParams.minimumNodes = 1
 	// Start registration server
-	impl, err := StartRegistration(localParams, nil)
+	impl, err := StartRegistration(localParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
@@ -241,7 +244,7 @@ func TestCompleteRegistration_HappyPath(t *testing.T) {
 	}
 }
 
-//Error path: test that trying to register with the same reg code fails
+// Error path: test that trying to register with the same reg code fails
 func TestDoubleRegistration(t *testing.T) {
 	// Initialize the database
 	var err error
@@ -254,7 +257,7 @@ func TestDoubleRegistration(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Create reg codes and populate the database
+	// Create reg codes and populate the database
 	infos := make([]node.Info, 0)
 	infos = append(infos, node.Info{RegCode: "AAAA", Order: "0"},
 		node.Info{RegCode: "BBBB", Order: "1"},
@@ -263,17 +266,17 @@ func TestDoubleRegistration(t *testing.T) {
 	RegParams = testParams
 
 	// Start registration server
-	impl, err := StartRegistration(testParams, nil)
+	impl, err := StartRegistration(testParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
 	}
 	defer impl.Comms.Shutdown()
 
-	//Create a second node to register
-	nodeComm2 := nodeComms.StartNode(&id.TempGateway, "0.0.0.0:6901", nodeComms.NewImplementation(), nodeCert, nodeKey)
+	// Create a second node to register
+	nodeComm2 := nodeComms.StartNode(&id.TempGateway, "0.0.0.0:6901", 0, nodeComms.NewImplementation(), nodeCert, nodeKey)
 	defer nodeComm2.Shutdown()
-	//Register 1st node
+	// Register 1st node
 	testSalt := []byte("testtesttesttesttesttesttesttest")
 	err = impl.RegisterNode(testSalt, nodeAddr, string(nodeCert),
 		nodeAddr, string(nodeCert), "BBBB")
@@ -281,7 +284,7 @@ func TestDoubleRegistration(t *testing.T) {
 		t.Errorf("Expected happy path, recieved error: %+v", err)
 	}
 
-	//Register 2nd node
+	// Register 2nd node
 	err = impl.RegisterNode(testSalt, "0.0.0.0:6901", string(nodeCert),
 		"0.0.0.0:6901", string(nodeCert), "BBBB")
 	if err != nil {
@@ -291,7 +294,7 @@ func TestDoubleRegistration(t *testing.T) {
 	t.Errorf("Expected happy path, recieved error: %+v", err)
 }
 
-//Happy path: attempt to register 2 nodes
+// Happy path: attempt to register 2 nodes
 func TestTopology_MultiNodes(t *testing.T) {
 	// Initialize the database
 	var err error
@@ -304,7 +307,7 @@ func TestTopology_MultiNodes(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Create reg codes and populate the database
+	// Create reg codes and populate the database
 	infos := make([]node.Info, 0)
 	infos = append(infos, node.Info{RegCode: "AAAA", Order: "0"},
 		node.Info{RegCode: "BBBB", Order: "1"},
@@ -316,20 +319,20 @@ func TestTopology_MultiNodes(t *testing.T) {
 	localParams.minimumNodes = 2
 
 	// Start registration server
-	impl, err := StartRegistration(localParams, nil)
+	impl, err := StartRegistration(localParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
 	}
 	defer impl.Comms.Shutdown()
 
-	//Create a second node to register
-	nodeComm2 := nodeComms.StartNode(&id.TempGateway, "0.0.0.0:6901", nodeComms.NewImplementation(), nodeCert, nodeKey)
-	//Kill the connections for the next test
+	// Create a second node to register
+	nodeComm2 := nodeComms.StartNode(&id.TempGateway, "0.0.0.0:6901", 0, nodeComms.NewImplementation(), nodeCert, nodeKey)
+	// Kill the connections for the next test
 	defer nodeComm2.Shutdown()
 	go func() {
 		testSalt := []byte("testtesttesttesttesttesttesttest")
-		//Register 1st node
+		// Register 1st node
 		err = impl.RegisterNode(testSalt,
 			nodeAddr, string(nodeCert),
 			nodeAddr, string(nodeCert), "BBBB")
@@ -337,7 +340,7 @@ func TestTopology_MultiNodes(t *testing.T) {
 			t.Errorf("Expected happy path, recieved error: %+v", err)
 		}
 
-		//Register 2nd node
+		// Register 2nd node
 		err = impl.RegisterNode(testSalt,
 			"0.0.0.0:6901", string(gatewayCert),
 			"0.0.0.0:6901", string(gatewayCert), "CCCC")
@@ -367,7 +370,7 @@ func TestRegistrationImpl_CheckNodeRegistration(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Create reg codes and populate the database
+	// Create reg codes and populate the database
 	infos := make([]node.Info, 0)
 	infos = append(infos, node.Info{RegCode: "AAAA", Order: "0"},
 		node.Info{RegCode: "BBBB", Order: "1"},
@@ -379,18 +382,18 @@ func TestRegistrationImpl_CheckNodeRegistration(t *testing.T) {
 	localParams.minimumNodes = 2
 
 	// Start registration server
-	impl, err := StartRegistration(localParams, nil)
+	impl, err := StartRegistration(localParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
 	}
-	//Kill the connections for the next test
+	// Kill the connections for the next test
 	defer impl.Comms.Shutdown()
 
 	// Craft registered node id
 	testNodeID := id.NewIdFromString("A", id.Node, t)
 
-	//Register 1st node
+	// Register 1st node
 	err = impl.RegisterNode(testNodeID.Marshal(),
 		nodeAddr, string(nodeCert),
 		nodeAddr, string(nodeCert), "BBBB")
@@ -438,7 +441,7 @@ func TestCheckRegistration_NilMsg(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Create reg codes and populate the database
+	// Create reg codes and populate the database
 	infos := make([]node.Info, 0)
 	infos = append(infos, node.Info{RegCode: "AAAA", Order: "0"},
 		node.Info{RegCode: "BBBB", Order: "1"},
@@ -450,18 +453,18 @@ func TestCheckRegistration_NilMsg(t *testing.T) {
 	localParams.minimumNodes = 2
 
 	// Start registration server
-	impl, err := StartRegistration(localParams, nil)
+	impl, err := StartRegistration(localParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
 	}
-	//Kill the connections for the next test
+	// Kill the connections for the next test
 	defer impl.Comms.Shutdown()
 
 	// Craft registered node id
 	testNodeID := id.NewIdFromString("A", id.Node, t)
 
-	//Register 1st node
+	// Register 1st node
 	err = impl.RegisterNode(testNodeID.Marshal(),
 		nodeAddr, string(nodeCert),
 		nodeAddr, string(nodeCert), "BBBB")
@@ -488,7 +491,7 @@ func TestCheckRegistration_InvalidID(t *testing.T) {
 		t.Errorf("%+v", err)
 	}
 
-	//Create reg codes and populate the database
+	// Create reg codes and populate the database
 	infos := make([]node.Info, 0)
 	infos = append(infos, node.Info{RegCode: "AAAA", Order: "0"},
 		node.Info{RegCode: "BBBB", Order: "1"},
@@ -500,18 +503,18 @@ func TestCheckRegistration_InvalidID(t *testing.T) {
 	localParams.minimumNodes = 2
 
 	// Start registration server
-	impl, err := StartRegistration(localParams, nil)
+	impl, err := StartRegistration(localParams)
 	if err != nil {
 		t.Errorf(err.Error())
 		return
 	}
-	//Kill the connections for the next test
+	// Kill the connections for the next test
 	defer impl.Comms.Shutdown()
 
 	// Craft registered node id
 	testNodeID := id.NewIdFromString("A", id.Node, t)
 
-	//Register 1st node
+	// Register 1st node
 	err = impl.RegisterNode(testNodeID.Marshal(),
 		nodeAddr, string(nodeCert),
 		nodeAddr, string(nodeCert), "BBBB")
@@ -530,23 +533,6 @@ func TestCheckRegistration_InvalidID(t *testing.T) {
 	}
 }
 
-func TestRegistrationImpl_GetCurrentClientVersion(t *testing.T) {
-	impl, err := StartRegistration(testParams, nil)
-	if err != nil {
-		t.Errorf(err.Error())
-		return
-	}
-	testVersion := "0.0.0a"
-	setClientVersion(testVersion)
-	ver, err := impl.GetCurrentClientVersion()
-	if err != nil {
-		t.Error(err)
-	}
-	if ver != testVersion {
-		t.Errorf("Version was %+v, expected %+v", ver, testVersion)
-	}
-}
-
 // Test a case that should pass validation
 func TestValidateClientVersion_Success(t *testing.T) {
 	err := validateVersion("0.0.0a")
@@ -584,17 +570,18 @@ func TestValidateClientVersion_Failure(t *testing.T) {
 func TestRegCodeExists_RegUser_Timer(t *testing.T) {
 
 	testParams2 := Params{
-		Address:                   "0.0.0.0:5905",
-		CertPath:                  testkeys.GetCACertPath(),
-		KeyPath:                   testkeys.GetCAKeyPath(),
-		NdfOutputPath:             testkeys.GetNDFPath(),
-		publicAddress:             "0.0.0.0:5905",
-		maxRegistrationAttempts:   4,
-		registrationCountDuration: 3 * time.Second,
+		Address:           "0.0.0.0:5905",
+		CertPath:          testkeys.GetCACertPath(),
+		KeyPath:           testkeys.GetCAKeyPath(),
+		NdfOutputPath:     testkeys.GetNDFPath(),
+		udbCertPath:       testkeys.GetUdbCertPath(),
+		publicAddress:     "0.0.0.0:5905",
+		userRegCapacity:   4,
+		userRegLeakPeriod: 3 * time.Second,
 	}
 
 	// Start registration server
-	impl, err := StartRegistration(testParams2, make(chan bool))
+	impl, err := StartRegistration(testParams2)
 	if err != nil {
 		t.Errorf(err.Error())
 	}
@@ -609,38 +596,38 @@ func TestRegCodeExists_RegUser_Timer(t *testing.T) {
 	}
 
 	// Attempt to register a user
-	_, err = impl.RegisterUser("b", "B")
+	_, _, err = impl.RegisterUser("", "B", "C")
 	if err != nil {
 		t.Errorf("Failed to register a user when it should have worked: %+v", err)
 	}
 
 	// Attempt to register a user
-	_, err = impl.RegisterUser("c", "C")
+	_, _, err = impl.RegisterUser("", "C", "D")
 	if err != nil {
 		t.Errorf("Failed to register a user when it should have worked: %+v", err)
 	}
 
 	// Attempt to register a user
-	_, err = impl.RegisterUser("d", "D")
+	_, _, err = impl.RegisterUser("", "D", "E")
 	if err != nil {
 		t.Errorf("Failed to register a user when it should have worked: %+v", err)
 	}
 
 	// Attempt to register a user
-	_, err = impl.RegisterUser("e", "E")
+	_, _, err = impl.RegisterUser("", "E", "F")
 	if err != nil {
 		t.Errorf("Failed to register a user when it should have worked: %+v", err)
 	}
 
 	// Attempt to register a user
-	_, err = impl.RegisterUser("f", "F")
+	_, _, err = impl.RegisterUser("", "F", "G")
 	if err == nil {
 		t.Errorf("Did not fail to register a user when it should not have worked: %+v", err)
 	}
 
-	time.Sleep(testParams2.registrationCountDuration)
+	time.Sleep(testParams2.userRegLeakPeriod)
 	// Attempt to register a user
-	_, err = impl.RegisterUser("g", "G")
+	_, _, err = impl.RegisterUser("", "G", "H")
 	if err != nil {
 		t.Errorf("Failed to register a user when it should have worked: %+v", err)
 	}
diff --git a/cmd/root.go b/cmd/root.go
index a5ec26086e47a37f78b99d48a74646ef45411536..a44619a582bd1e3147316db52a71c9930bc533c5 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -16,12 +16,13 @@ import (
 	"github.com/spf13/cobra"
 	jww "github.com/spf13/jwalterweatherman"
 	"github.com/spf13/viper"
+	"gitlab.com/elixxir/client/interfaces/contact"
 	"gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/primitives/version"
 	"gitlab.com/elixxir/registration/scheduling"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/utils"
 	"net"
 	"os"
 	"path"
@@ -43,6 +44,10 @@ var (
 	disablePermissioning bool
 	disabledNodesPath    string
 
+	// Storage of registration codes from file so it can be loaded from disableRegCodes
+	regCodeInfos    []node.Info
+	disableRegCodes bool
+
 	// Duration between polls of the disabled Node list for updates.
 	disabledNodesPollDuration time.Duration
 )
@@ -76,22 +81,10 @@ var rootCmd = &cobra.Command{
 		ndfOutputPath := viper.GetString("ndfOutputPath")
 		setClientVersion(viper.GetString("clientVersion"))
 		ipAddr := viper.GetString("publicAddress")
-		//Get Notification Server address and cert Path
+		// Get Notification Server address and cert Path
 		nsCertPath := viper.GetString("nsCertPath")
 		nsAddress := viper.GetString("nsAddress")
 		publicAddress := fmt.Sprintf("%s:%d", ipAddr, viper.GetInt("port"))
-		roundIdPath := viper.GetString("roundIdPath")
-		updateIdPath := viper.GetString("updateIdPath")
-
-		maxRegistrationAttempts := viper.GetUint64("maxRegistrationAttempts")
-		if maxRegistrationAttempts == 0 {
-			maxRegistrationAttempts = defaultMaxRegistrationAttempts
-		}
-
-		registrationCountDuration := viper.GetDuration("registrationCountDuration")
-		if registrationCountDuration == 0 {
-			registrationCountDuration = defaultRegistrationCountDuration
-		}
 
 		// Set up database connection
 		rawAddr := viper.GetString("dbAddress")
@@ -119,7 +112,7 @@ var rootCmd = &cobra.Command{
 		// Populate Node registration codes into the database
 		RegCodesFilePath := viper.GetString("regCodesFilePath")
 		if RegCodesFilePath != "" {
-			regCodeInfos, err := node.LoadInfo(RegCodesFilePath)
+			regCodeInfos, err = node.LoadInfo(RegCodesFilePath)
 			if err != nil {
 				jww.ERROR.Printf("Failed to load registration codes from the "+
 					"file %s: %+v", RegCodesFilePath, err)
@@ -134,10 +127,14 @@ var rootCmd = &cobra.Command{
 		ClientRegCodes = viper.GetStringSlice("clientRegCodes")
 		storage.PopulateClientRegistrationCodes(ClientRegCodes, 1000)
 
-		udbId := make([]byte, 32)
-		udbId[len(udbId)-1] = byte(viper.GetInt("udbID"))
+		// Get user discovery ID and DH public key from contact file
+		udbId, udbDhPubKey := readUdContact(viper.GetString("udContactPath"))
+
+		// Get UDB cert path and address
+		udbCertPath := viper.GetString("udbCertPath")
+		udbAddress := viper.GetString("udbAddress")
 
-		//load the scheduling params file as a string
+		// load the scheduling params file as a string
 		SchedulingConfigPath := viper.GetString("schedulingConfigPath")
 		SchedulingConfig, err := utils.ReadFile(SchedulingConfigPath)
 		if err != nil {
@@ -159,6 +156,13 @@ var rootCmd = &cobra.Command{
 				minServerVersionString, err)
 		}
 
+		minClientVersionString := viper.GetString("minClientVersion")
+		minClientVersion, err := version.ParseVersion(minClientVersionString)
+		if err != nil {
+			jww.FATAL.Panicf("Could not parse minClientVersion %#v: %+v",
+				minClientVersionString, err)
+		}
+
 		// Get the amount of time to wait for scheduling to end
 		// This should default to 10 seconds in StartRegistration if not set
 		schedulingKillTimeout, err := time.ParseDuration(
@@ -176,35 +180,55 @@ var rootCmd = &cobra.Command{
 
 		disableGatewayPing := viper.GetBool("disableGatewayPing")
 
+		userRegLeakPeriodString := viper.GetString("userRegLeakPeriod")
+		var userRegLeakPeriod time.Duration
+		if userRegLeakPeriodString != "" {
+			// specified, so try to parse
+			userRegLeakPeriod, err = time.ParseDuration(userRegLeakPeriodString)
+			if err != nil {
+				jww.FATAL.Panicf("Could not parse duration: %+v", err)
+			}
+		} else {
+			// use default
+			userRegLeakPeriod = time.Hour * 24
+		}
+		userRegCapacity := viper.GetUint32("userRegCapacity")
+		if userRegCapacity == 0 {
+			// use default
+			userRegCapacity = 1000
+		}
+
 		// Populate params
 		RegParams = Params{
-			Address:                   localAddress,
-			CertPath:                  certPath,
-			KeyPath:                   keyPath,
-			NdfOutputPath:             ndfOutputPath,
-			cmix:                      *cmix,
-			e2e:                       *e2e,
-			publicAddress:             publicAddress,
-			NsAddress:                 nsAddress,
-			NsCertPath:                nsCertPath,
-			maxRegistrationAttempts:   maxRegistrationAttempts,
-			registrationCountDuration: registrationCountDuration,
-			schedulingKillTimeout:     schedulingKillTimeout,
-			closeTimeout:              closeTimeout,
-			udbId:                     udbId,
-			minimumNodes:              viper.GetUint32("minimumNodes"),
-			minGatewayVersion:         minGatewayVersion,
-			minServerVersion:          minServerVersion,
-			roundIdPath:               roundIdPath,
-			updateIdPath:              updateIdPath,
-			disableGatewayPing:        disableGatewayPing,
+			Address:               localAddress,
+			CertPath:              certPath,
+			KeyPath:               keyPath,
+			NdfOutputPath:         ndfOutputPath,
+			cmix:                  *cmix,
+			e2e:                   *e2e,
+			publicAddress:         publicAddress,
+			NsAddress:             nsAddress,
+			NsCertPath:            nsCertPath,
+			schedulingKillTimeout: schedulingKillTimeout,
+			closeTimeout:          closeTimeout,
+			udbId:                 udbId,
+			udbDhPubKey:           udbDhPubKey,
+			udbCertPath:           udbCertPath,
+			udbAddress:            udbAddress,
+			minimumNodes:          viper.GetUint32("minimumNodes"),
+			minGatewayVersion:     minGatewayVersion,
+			minServerVersion:      minServerVersion,
+			minClientVersion:      minClientVersion,
+			disableGatewayPing:    disableGatewayPing,
+			userRegLeakPeriod:     userRegLeakPeriod,
+			userRegCapacity:       userRegCapacity,
+			addressSpace:          viper.GetUint32("addressSpace"),
 		}
 
 		jww.INFO.Println("Starting Permissioning Server...")
 
 		// Start registration server
-		quitRegistrationCapacity := make(chan bool)
-		impl, err := StartRegistration(RegParams, quitRegistrationCapacity)
+		impl, err := StartRegistration(RegParams)
 		if err != nil {
 			jww.FATAL.Panicf(err.Error())
 		}
@@ -328,12 +352,6 @@ var rootCmd = &cobra.Command{
 				jww.ERROR.Print("couldn't stop round creation!")
 			}
 
-			// Try a non-blocking send for the registration capacity
-			select {
-			case quitRegistrationCapacity <- true:
-			default:
-			}
-
 			bannedNodeTrackerQuitChan <- struct{}{}
 
 			// Prevent node updates after round creation stops
@@ -412,13 +430,16 @@ func init() {
 	rootCmd.Flags().BoolVar(&noTLS, "noTLS", false,
 		"Runs without TLS enabled")
 
+	rootCmd.Flags().BoolVar(&disableRegCodes, "disableRegCodes", false,
+		"Automatically provide registration codes to Nodes. (For testing only)")
+
 	rootCmd.Flags().StringP("close-timeout", "t", "60s",
-		("Amount of time to wait for rounds to stop running after" +
-			" receiving the SIGUSR1 and SIGTERM signals"))
+		"Amount of time to wait for rounds to stop running after"+
+			" receiving the SIGUSR1 and SIGTERM signals")
 
 	rootCmd.Flags().StringP("kill-timeout", "k", "60s",
-		("Amount of time to wait for round creation to stop after" +
-			" receiving the SIGUSR2 and SIGTERM signals"))
+		"Amount of time to wait for round creation to stop after"+
+			" receiving the SIGUSR2 and SIGTERM signals")
 
 	rootCmd.Flags().BoolVarP(&disablePermissioning, "disablePermissioning", "",
 		false, "Disables registration server checking for ndf updates")
@@ -435,11 +456,25 @@ func init() {
 		jww.FATAL.Panicf("could not bind flag: %+v", err)
 	}
 
+	err = viper.BindPFlag("schedulingKillTimeout",
+		rootCmd.Flags().Lookup("kill-timeout"))
+	if err != nil {
+		jww.FATAL.Panicf("could not bind flag: %+v", err)
+	}
+
+	rootCmd.Flags().String("udContactPath", "",
+		"Location of the user discovery contact file.")
+
+	err = viper.BindPFlag("udContactPath", rootCmd.Flags().Lookup("udContactPath"))
+	if err != nil {
+		jww.FATAL.Panicf("could not bind flag: %+v", err)
+	}
+
 }
 
 // initConfig reads in config file and ENV variables if set.
 func initConfig() {
-	//Use default config location if none is passed
+	// Use default config location if none is passed
 	if cfgFile == "" {
 		// Find home directory.
 		home, err := homedir.Dir()
@@ -556,7 +591,10 @@ func initLog() {
 
 		// Create log file, overwrites if existing
 		logPath := viper.GetString("logPath")
-		logFile, err := os.Create(logPath)
+		fullLogPath, _ := utils.ExpandPath(logPath)
+		logFile, err := os.OpenFile(fullLogPath,
+			os.O_CREATE|os.O_WRONLY|os.O_APPEND,
+			0644)
 		if err != nil {
 			jww.WARN.Println("Invalid or missing log path, default path used.")
 		} else {
@@ -564,3 +602,28 @@ func initLog() {
 		}
 	}
 }
+
+// readUdContact reads and unmarshal the contact from file and returns the
+// marshaled ID and DH public key.
+func readUdContact(filePath string) ([]byte, []byte) {
+	if filePath == "" {
+		return nil, nil
+	}
+
+	data, err := utils.ReadFile(filePath)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to read contact file: %+v", err)
+	}
+
+	c, err := contact.Unmarshal(data)
+	if err != nil {
+		jww.FATAL.Panicf("Failed to unmarshal contact: %+v", err)
+	}
+
+	dhPubKeyJson, err := c.DhPubKey.MarshalJSON()
+	if err != nil {
+		jww.FATAL.Panicf("Failed to marshal contact DH public key: %+v", err)
+	}
+
+	return c.ID.Marshal(), dhPubKeyJson
+}
diff --git a/cmd/version.go b/cmd/version.go
index 18588db0a2f7ec8e655d28a9ce28e266575685a5..848188f02b942e09a501c2fadb75fa83e1bb4717 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -10,12 +10,13 @@ package cmd
 
 import (
 	"fmt"
+
 	"github.com/spf13/cobra"
-	"gitlab.com/elixxir/primitives/utils"
+	"gitlab.com/xx_network/primitives/utils"
 )
 
 // Change this value to set the version for this build
-const currentVersion = "1.5.0"
+const currentVersion = "2.0.0"
 
 func printVersion() {
 	fmt.Printf("xx network Permissioning Server v%s -- %s\n\n",
diff --git a/cmd/version_vars.go b/cmd/version_vars.go
index bcb4238480295112b9c6299c4440dbd6ee15ee27..7911f58eda25589cbfc023f274a6355615000de2 100644
--- a/cmd/version_vars.go
+++ b/cmd/version_vars.go
@@ -1,10 +1,10 @@
 // Code generated by go generate; DO NOT EDIT.
 // This file was generated by robots at
-// 2021-01-28 10:47:07.899147 -0600 CST m=+0.063060483
+// 2021-03-10 10:12:36.159679 -0800 PST m=+0.033523506
 package cmd
 
-const GITVERSION = `853bcda Merge branch 'XX-2875/ClientErrMaster' into 'master'`
-const SEMVER = "1.5.0"
+const GITVERSION = `93d59af update deps`
+const SEMVER = "2.0.0"
 const DEPENDENCIES = `module gitlab.com/elixxir/registration
 
 go 1.13
@@ -14,26 +14,23 @@ require (
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/go-sql-driver/mysql v1.5.0 // indirect
 	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
-	github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
 	github.com/jinzhu/gorm v1.9.12
 	github.com/jinzhu/now v1.1.1 // indirect
 	github.com/lib/pq v1.5.2 // indirect
 	github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
 	github.com/mitchellh/go-homedir v1.1.0
-	github.com/mitchellh/mapstructure v1.3.0 // indirect
-	github.com/pelletier/go-toml v1.7.0 // indirect
 	github.com/pkg/errors v0.9.1
 	github.com/smartystreets/assertions v1.1.0 // indirect
-	github.com/spf13/afero v1.2.2 // indirect
-	github.com/spf13/cast v1.3.1 // indirect
-	github.com/spf13/cobra v1.0.0
+	github.com/spf13/cobra v1.1.1
 	github.com/spf13/jwalterweatherman v1.1.0
-	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/spf13/viper v1.7.0
-	gitlab.com/elixxir/comms v0.0.0-20201119183624-cd133e949837
-	gitlab.com/elixxir/crypto v0.0.5
-	gitlab.com/elixxir/primitives v0.0.0-20201007171034-21ce972dc81d
-	gitlab.com/xx_network/comms v0.0.0-20200918162019-06b733db60e6
+	github.com/spf13/viper v1.7.1
+	gitlab.com/elixxir/client v1.2.1-0.20210222224029-4300043d7ce8
+	gitlab.com/elixxir/comms v0.0.4-0.20210309193245-64181ff10b68
+	gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2
+	gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b
+	gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01
+	gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96
+	gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a
 )
 
 replace google.golang.org/grpc => github.com/grpc/grpc-go v1.27.1
diff --git a/go.mod b/go.mod
index caef220e101dc3f3ff5566d62110a317ae0955d2..9d8d546e1db743e56bb18de2835e648a000b6def 100644
--- a/go.mod
+++ b/go.mod
@@ -7,26 +7,23 @@ require (
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/go-sql-driver/mysql v1.5.0 // indirect
 	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
-	github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
 	github.com/jinzhu/gorm v1.9.12
 	github.com/jinzhu/now v1.1.1 // indirect
 	github.com/lib/pq v1.5.2 // indirect
 	github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
 	github.com/mitchellh/go-homedir v1.1.0
-	github.com/mitchellh/mapstructure v1.3.0 // indirect
-	github.com/pelletier/go-toml v1.7.0 // indirect
 	github.com/pkg/errors v0.9.1
 	github.com/smartystreets/assertions v1.1.0 // indirect
-	github.com/spf13/afero v1.2.2 // indirect
-	github.com/spf13/cast v1.3.1 // indirect
-	github.com/spf13/cobra v1.0.0
+	github.com/spf13/cobra v1.1.1
 	github.com/spf13/jwalterweatherman v1.1.0
-	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/spf13/viper v1.7.0
-	gitlab.com/elixxir/comms v0.0.0-20201119183624-cd133e949837
-	gitlab.com/elixxir/crypto v0.0.5
-	gitlab.com/elixxir/primitives v0.0.0-20201007171034-21ce972dc81d
-	gitlab.com/xx_network/comms v0.0.0-20200918162019-06b733db60e6
+	github.com/spf13/viper v1.7.1
+	gitlab.com/elixxir/client v1.2.1-0.20210222224029-4300043d7ce8
+	gitlab.com/elixxir/comms v0.0.4-0.20210311180506-28ae742c5e35
+	gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2
+	gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b
+	gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01
+	gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96
+	gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a
 )
 
 replace google.golang.org/grpc => github.com/grpc/grpc-go v1.27.1
diff --git a/go.sum b/go.sum
index 275a833e05e175ae4c94c53a5296e6e402bb491e..627e6e5fb860aaaf1d8b8a80fd83b129ed6abb3f 100644
--- a/go.sum
+++ b/go.sum
@@ -17,9 +17,10 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/badoux/checkmail v1.2.1 h1:TzwYx5pnsV6anJweMx2auXdekBwGr/yt1GgalIx9nBQ=
+github.com/badoux/checkmail v1.2.1/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -27,9 +28,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@@ -87,6 +86,8 @@ github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -96,6 +97,8 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -106,7 +109,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -153,9 +155,12 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -165,9 +170,10 @@ github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw=
 github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
+github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -185,23 +191,26 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.3.0 h1:iDwIio/3gk2QtLLEsqU5lInaMzos0hDTz8a6lazSFVw=
-github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
+github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nyaruka/phonenumbers v1.0.60 h1:nnAcNwmZflhegiImm6MkvjlRRyoaSw1ox/jGPAewWTg=
+github.com/nyaruka/phonenumbers v1.0.60/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
-github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
+github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@@ -224,6 +233,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
 github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@@ -231,14 +241,14 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg=
+github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
-github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
+github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
+github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@@ -246,13 +256,15 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
@@ -260,27 +272,66 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-gitlab.com/elixxir/comms v0.0.0-20200707210150-b8ebd0951d23/go.mod h1:OsWMZ1O/R9fOkm+PoHnR3rkXfFtipGoPs73FuKuurHY=
-gitlab.com/elixxir/comms v0.0.0-20201119183624-cd133e949837 h1:ZKh1kIdkP31vtV6QIpXCwz5TmfEXg7suuwcfu9lv7xs=
-gitlab.com/elixxir/comms v0.0.0-20201119183624-cd133e949837/go.mod h1:L2Va13j2AbQkpkveOQmNzrQD37uI5NKeBhYH+LWMOx0=
-gitlab.com/elixxir/crypto v0.0.0-20200707005343-97f868cbd930/go.mod h1:LHBAaEf48a0/AjU118rjoworH0LgXifhAqmNX3ZRvME=
-gitlab.com/elixxir/crypto v0.0.0-20200731174640-0503cf80524a h1:peZpulfSqLSceA5ovtzQ5MPgQt4YbJY8FzpV2S2Nrhc=
-gitlab.com/elixxir/crypto v0.0.0-20200731174640-0503cf80524a/go.mod h1:LHBAaEf48a0/AjU118rjoworH0LgXifhAqmNX3ZRvME=
-gitlab.com/elixxir/crypto v0.0.5 h1:QS/3PEA6Hni61r6YAV8IfneKydtZjcC5E4ZLUPrEypc=
-gitlab.com/elixxir/crypto v0.0.5/go.mod h1:PFeyONfhulnM72O2wROslwhNadtnyvKAD2QLtQzAifM=
-gitlab.com/elixxir/primitives v0.0.0-20200706165052-9fe7a4fb99a3/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
-gitlab.com/elixxir/primitives v0.0.0-20200708185800-a06e961280e6 h1:7xLD8w5qAKN1YqG2UiMiN3rODUACyQME83uDlVhvWLo=
-gitlab.com/elixxir/primitives v0.0.0-20200708185800-a06e961280e6/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
-gitlab.com/elixxir/primitives v0.0.0-20200929195204-dd3970d93573/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
-gitlab.com/elixxir/primitives v0.0.0-20201007171034-21ce972dc81d h1:UTB9Aayt7UyaBMxanP3HjWFz7PhcY8U8lgBHZ+97dr4=
-gitlab.com/elixxir/primitives v0.0.0-20201007171034-21ce972dc81d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
-gitlab.com/xx_network/comms v0.0.0-20200916172635-6ab807c3c820 h1:vdozJQgrnznmHJLS38aOXprLKZXClnHJ9ljY/70aWuI=
-gitlab.com/xx_network/comms v0.0.0-20200916172635-6ab807c3c820/go.mod h1:J+GJ6fn71a4xnYVvbcrhtvWSOQIqqhaGcaej5xB3/JY=
-gitlab.com/xx_network/comms v0.0.0-20200918162019-06b733db60e6 h1:qBR6vf6Mkmv00LBcQm3Rd1+RreVcbzsYqJCGDXVGc84=
-gitlab.com/xx_network/comms v0.0.0-20200918162019-06b733db60e6/go.mod h1:J+GJ6fn71a4xnYVvbcrhtvWSOQIqqhaGcaej5xB3/JY=
+github.com/zeebo/assert v0.0.0-20181109011804-10f827ce2ed6/go.mod h1:yssERNPivllc1yU3BvpjYI5BUW+zglcz6QWqeVRL5t0=
+github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
+github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/blake3 v0.0.4 h1:vtZ4X8B2lKXZFg2Xyg6Wo36mvmnJvc2VQYTtA4RDCkI=
+github.com/zeebo/blake3 v0.0.4/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny2i34=
+github.com/zeebo/blake3 v0.1.0 h1:sP3n5SxSbzU8x4Svc4ZcQv7SmQOqCkiKBeAZWP+hePo=
+github.com/zeebo/blake3 v0.1.0/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny2i34=
+github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05 h1:4pW5fMvVkrgkMXdvIsVRRTs69DWYA8uNNQsu1stfVKU=
+github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05/go.mod h1:Gr+78ptB0MwXxm//LBaEvBiaXY7hXJ6KGe2V32X2F6E=
+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/client v1.2.1-0.20210222224029-4300043d7ce8 h1:2j6FUBt0uAab+WOD2Wvu0rVSgJU0EklBpsfV7lv3eEg=
+gitlab.com/elixxir/client v1.2.1-0.20210222224029-4300043d7ce8/go.mod h1:zMjfKGqIBi/rYeWA9ca/Vxhr6a3yGywbURWUutBJMok=
+gitlab.com/elixxir/comms v0.0.4-0.20210218234550-f2e03b19bdb2 h1:p5GunVi5sP9atTw3DKBkgV6k3eR9iTyI6m9GbUr8hhA=
+gitlab.com/elixxir/comms v0.0.4-0.20210218234550-f2e03b19bdb2/go.mod h1:GCbfPWB7VF5ZeDsLBCwfy0JiquG4OK6gsRjaIS66+yg=
+gitlab.com/elixxir/comms v0.0.4-0.20210309193245-64181ff10b68 h1:HR45PZyVl+gvksIKoHPCxFndhOpBT6z3rl7vaa3BaAc=
+gitlab.com/elixxir/comms v0.0.4-0.20210309193245-64181ff10b68/go.mod h1:96cMuVVlarB+I6nuFKdq4zCagQkbhVK/MUzRk3yOymI=
+gitlab.com/elixxir/comms v0.0.4-0.20210311180506-28ae742c5e35 h1:t/ILeoWel5Im+zLQUX2FIroZvrfAkxOaL3DCA8enKcE=
+gitlab.com/elixxir/comms v0.0.4-0.20210311180506-28ae742c5e35/go.mod h1:96cMuVVlarB+I6nuFKdq4zCagQkbhVK/MUzRk3yOymI=
+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.20210216174551-f806f79610eb h1:aPcrTC0QdrPqz4NgoAt5sfXt/+EFrNUwIns0s0VCQmg=
+gitlab.com/elixxir/crypto v0.0.7-0.20210216174551-f806f79610eb/go.mod h1:CLP8kULKW9A5oZHQ1zMCx4swMhBw2IMO68z4U/FkvcU=
+gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2 h1:JMbUxcOjFpdCBUMZS5g8CWfNdPJ6pP8xsAZbnLj66jc=
+gitlab.com/elixxir/crypto v0.0.7-0.20210309193114-8a6225c667e2/go.mod h1:TMZMB24OsjF6y3LCyBMzDucbOx1cGQCCeuKV9lJA/DU=
+gitlab.com/elixxir/ekv v0.1.4 h1:NLVMwsFEKArWcsDHu2DbXlm9374iSgn7oIA3rVSsvjc=
+gitlab.com/elixxir/ekv v0.1.4/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.20210216174458-2a23825c1eb1 h1:BfcaQtKgIbafExdHkeKIJ5XEGe9MvUiv+yg9u7jwqhY=
+gitlab.com/elixxir/primitives v0.0.3-0.20210216174458-2a23825c1eb1/go.mod h1:Wpz7WGZ/CpO6oHNmVTgTNBETTRXi40arWjom1uwu/1s=
+gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b h1:TswWfqiZqsdPLeWsfe7VJHMlV01W792kRHGYfYwb2Lk=
+gitlab.com/elixxir/primitives v0.0.3-0.20210309193003-ef42ebb4800b/go.mod h1:/e3a4KPqmA9V22qKSZ9prfYYNzIzvLI8xh7noVV091w=
+gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
+gitlab.com/xx_network/comms v0.0.4-0.20210216174438-0790d1f1f225 h1:ZVxPD76xDLdTSGY2w/aGRMiiry7SauD8sq4c+see6aM=
+gitlab.com/xx_network/comms v0.0.4-0.20210216174438-0790d1f1f225/go.mod h1:e7dy2wznC4U4bG/U3xFGYYsnnd8zHOhoSxzFkGPQYX4=
+gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01 h1:f93iz7mTHt3r37O97vaQD8otohihLN3OnAEEbDGQdVs=
+gitlab.com/xx_network/comms v0.0.4-0.20210309192940-6b7fb39b4d01/go.mod h1:aNPRHmPssXc1JMJ83DAknT2C2iMgKL1wH3//AqQrhQc=
+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.20210216174356-e81e1ddf8fb7 h1:vyL+m7D7w+RgMPARzcKCR8UMGC2foqNU6cSb1J6Dkis=
+gitlab.com/xx_network/crypto v0.0.5-0.20210216174356-e81e1ddf8fb7/go.mod h1:8J+/VBcMlBj2sZuSDaVKI/i58awFZ5Zdb4JdEwGVrqo=
+gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96 h1:VZGJNhuU6YunKyK4MbNZf25UxQsmU1bH5SnbK93tI7Q=
+gitlab.com/xx_network/crypto v0.0.5-0.20210309192854-cf32117afb96/go.mod h1:TtaHpuX0lcuTTtcq+pz+lMusjyTgvSohIHFOlVwN1uU=
+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.20210215192713-e32335847d4f h1:0wFEYIHuPkWJuDkbDXNrwC5yGwkd7Mugt2BwcTqQbFY=
+gitlab.com/xx_network/primitives v0.0.4-0.20210215192713-e32335847d4f/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36 h1:41qeW7XB9Rllsi6fe37+eaQCLjTGchpvcJqwEvZxeXE=
+gitlab.com/xx_network/primitives v0.0.4-0.20210219231511-983054dbee36/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a h1:Ume9QbJ4GoJh7v5yg/YVDjowJHx/VFeOC/A4PJZUm9g=
+gitlab.com/xx_network/primitives v0.0.4-0.20210309173740-eb8cd411334a/go.mod h1:9imZHvYwNFobxueSvVtHneZLk9wTK7HQTzxPm+zhFhE=
+gitlab.com/xx_network/ring v0.0.2 h1:TlPjlbFdhtJrwvRgIg4ScdngMTaynx/ByHBRZiXCoL0=
+gitlab.com/xx_network/ring v0.0.2/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -293,13 +344,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0 h1:eIYIE7EC5/Wv5Kbz8bJPaq+TN3kq3W8S+LSm62vM0DY=
 golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -328,12 +383,14 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d h1:dOiJ2n2cMwGLce/74I/QHMbnpk5GfY7InR8rczoMqRM=
+golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -354,11 +411,20 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
+golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
+golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -366,6 +432,8 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -388,6 +456,8 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -405,11 +475,12 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
 google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200514193133-8feb7f20f2a2/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200709005830-7a2ca40e9dc3 h1:JwLN1jVnmIsfE4HkDVe2AblFAbo0Z+4cjteDSOnv6oE=
-google.golang.org/genproto v0.0.0-20200709005830-7a2ca40e9dc3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3 h1:sg8vLDNIxFPHTchfhH1E3AI32BL3f23oie38xUWnJM8=
+google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1 h1:Zk6zlGXdtYdcY5TL+VrbTfmifvk3VvsXopCpszsHPBA=
+google.golang.org/genproto v0.0.0-20210105202744-fe13368bc0e1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -429,9 +500,13 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -443,6 +518,8 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/scheduling/nodeStateChange.go b/scheduling/nodeStateChange.go
index 4d10f5a60bd0f881d416a8a1eaef0a7e16c8a1b9..007cc7d1d1b8a407d8dba919b4d21cff4e70b64d 100644
--- a/scheduling/nodeStateChange.go
+++ b/scheduling/nodeStateChange.go
@@ -11,13 +11,13 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/signature"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/storage/round"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
 
@@ -39,6 +39,9 @@ func HandleNodeUpdates(update node.UpdateNotification, pool *waitingPool, state
 	if roundErrored {
 		return nil
 	}
+	if update.ClientErrors != nil && len(update.ClientErrors) > 0 {
+		r.AppendClientErrors(update.ClientErrors)
+	}
 	//ban the node if it is supposed to be banned
 	if update.ToStatus == node.Banned {
 		if hasRound {
@@ -275,23 +278,7 @@ func killRound(state *storage.NetworkState, r *round.State,
 		jww.WARN.Printf("Could not insert round error: %+v", err)
 		err = nil
 	}
-
-	// fix a potential error case where a node crashes after the round is
-	// created but before it updates to precomputing and then gets stuck
-	topology := r.GetTopology()
-	for i := 0; i < topology.Len(); i++ {
-		nid := topology.GetNodeAtIndex(i)
-		n := state.GetNodeMap().GetNode(nid)
-		if n != nil {
-			if n.GetActivity() == current.WAITING {
-				hasRound, rNode := n.GetCurrentRound()
-				if hasRound && rNode.GetRoundID() == r.GetRoundID() {
-					n.ClearRound()
-					pool.Add(n)
-				}
-			}
-		}
-	}
+	state.GetNodeMap().GetNodeStates()
 
 	return err
 }
diff --git a/scheduling/nodeStateChange_test.go b/scheduling/nodeStateChange_test.go
index 4456b3d08ab039967369d68c23949919d9db987f..4148c58a2102add5f20a638b15d4287d6636a2bb 100644
--- a/scheduling/nodeStateChange_test.go
+++ b/scheduling/nodeStateChange_test.go
@@ -8,13 +8,13 @@ package scheduling
 import (
 	"crypto/rand"
 	"gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/signature/rsa"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/storage/round"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
 	"strconv"
 	"testing"
 	"time"
@@ -27,10 +27,15 @@ func TestHandleNodeStateChance_Waiting(t *testing.T) {
 		BatchSize:      32,
 		RandomOrdering: false,
 	}
+	var err error
+	storage.PermissioningDb, _, err = storage.NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -46,7 +51,10 @@ func TestHandleNodeStateChance_Waiting(t *testing.T) {
 		}
 	}
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	// Set a round for the node in order to fully test the code path for
 	//  a waiting transition
@@ -81,7 +89,7 @@ func TestHandleNodeStateChance_Waiting_SetNodeToOnline(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -97,7 +105,10 @@ func TestHandleNodeStateChance_Waiting_SetNodeToOnline(t *testing.T) {
 		}
 	}
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	// Set a round for the node in order to fully test the code path for
 	//  a waiting transition
@@ -138,7 +149,7 @@ func TestHandleNodeStateChance_Standby(t *testing.T) {
 	}
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -155,9 +166,12 @@ func TestHandleNodeStateChance_Standby(t *testing.T) {
 	}
 	circuit := connect.NewCircuit(nodeList)
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
-	roundState, err := testState.GetRoundMap().AddRound(roundID, testParams.BatchSize, 5*time.Minute, circuit)
+	roundState, err := testState.GetRoundMap().AddRound(roundID, testParams.BatchSize, 8, 5*time.Minute, circuit)
 	if err != nil {
 		t.Errorf("Failed to add round: %v", err)
 	}
@@ -211,7 +225,7 @@ func TestHandleNodeStateChance_Standby_NoRound(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -270,7 +284,7 @@ func TestHandleNodeUpdates_Completed(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -287,9 +301,12 @@ func TestHandleNodeUpdates_Completed(t *testing.T) {
 	}
 	circuit := connect.NewCircuit(nodeList)
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
-	roundState, err := testState.GetRoundMap().AddRound(roundID, testParams.BatchSize, 5*time.Minute, circuit)
+	roundState, err := testState.GetRoundMap().AddRound(roundID, testParams.BatchSize, 8, 5*time.Minute, circuit)
 	if err != nil {
 		t.Errorf("Failed to add round: %v", err)
 	}
@@ -341,7 +358,7 @@ func TestHandleNodeUpdates_Completed_NoRound(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -386,9 +403,15 @@ func TestHandleNodeUpdates_Error(t *testing.T) {
 		RandomOrdering: false,
 	}
 
+	var err error
+	storage.PermissioningDb, _, err = storage.NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -404,7 +427,10 @@ func TestHandleNodeUpdates_Error(t *testing.T) {
 		}
 	}
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 	topology := connect.NewCircuit(nodeList)
 
 	// Set a round for the node in order to fully test the code path for
@@ -427,7 +453,6 @@ func TestHandleNodeUpdates_Error(t *testing.T) {
 	}
 	testState.GetNodeMap().GetNode(testUpdate.Node).GetPollingLock().Lock()
 
-	storage.PermissioningDb, _, err = storage.NewDatabase("", "", "", "", "")
 	testTracker := NewRoundTracker()
 
 	err = HandleNodeUpdates(testUpdate, testPool, testState, 0, testTracker)
@@ -446,7 +471,7 @@ func TestHandleNodeUpdates_BannedNode(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -535,7 +560,7 @@ func TestKillRound(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -578,75 +603,6 @@ func TestKillRound(t *testing.T) {
 	}
 }
 
-// Happy path
-func TestKillRound_NodeCrash(t *testing.T) {
-	testParams := Params{
-		TeamSize:       5,
-		BatchSize:      32,
-		RandomOrdering: false,
-	}
-
-	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-
-	testState, err := storage.NewState(privKey, "", "")
-	if err != nil {
-		t.Errorf("Failed to create test state: %v", err)
-		t.FailNow()
-	}
-
-	testPool := NewWaitingPool()
-
-	// Build mock nodes and place in map
-	nodeList := make([]*id.ID, testParams.TeamSize)
-	nodeStateList := make([]*node.State, testParams.TeamSize)
-	for i := uint64(0); i < uint64(len(nodeList)); i++ {
-		nodeList[i] = id.NewIdFromUInt(i, id.Node, t)
-		err := testState.GetNodeMap().AddNode(nodeList[i], strconv.Itoa(int(i)), "", "", 0)
-		if err != nil {
-			t.Errorf("Couldn't add node: %v", err)
-			t.FailNow()
-		}
-
-		// Add node to pool
-		ns := testState.GetNodeMap().GetNode(nodeList[i])
-		if i == 0 {
-			_, _, _ = ns.Update(current.WAITING)
-			roundState := round.NewState_Testing(42, 6, nil, t)
-			_ = ns.SetRound(roundState)
-		} else {
-			nodeStateList = append(nodeStateList, ns)
-			testPool.Add(ns)
-		}
-	}
-
-	topology := connect.NewCircuit(nodeList)
-
-	r := round.NewState_Testing(42, 0, topology, t)
-
-	re := &mixmessages.RoundError{
-		Id:     0,
-		NodeId: nil,
-		Error:  "test",
-	}
-
-	tesTracker := NewRoundTracker()
-
-	err = killRound(testState, r, re, tesTracker, testPool)
-	if err != nil {
-		t.Errorf("Unexpected error in happy path: %v", err)
-	}
-
-	if !testPool.pool.Has(testState.GetNodeMap().GetNode(nodeList[0])) {
-		t.Errorf("The node was not added to the pool.")
-	}
-
-	_, currentRound := testState.GetNodeMap().GetNode(nodeList[0]).GetCurrentRound()
-
-	if currentRound != nil {
-		t.Errorf("The round was not cleared.")
-	}
-}
-
 // Tests that the Precomputing case of HandleNodeUpdates produces the correct
 // error when there is no round.
 func TestHandleNodeUpdates_Precomputing_RoundError(t *testing.T) {
@@ -658,7 +614,7 @@ func TestHandleNodeUpdates_Precomputing_RoundError(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -706,7 +662,7 @@ func TestHandleNodeUpdates_Realtime(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -722,7 +678,10 @@ func TestHandleNodeUpdates_Realtime(t *testing.T) {
 		}
 	}
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	// Set a round for the node in order to fully test the code path for
 	//  a waiting transition
@@ -756,7 +715,7 @@ func TestHandleNodeUpdates_Realtime_RoundError(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -805,7 +764,7 @@ func TestHandleNodeUpdates_Realtime_UpdateError(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -821,7 +780,10 @@ func TestHandleNodeUpdates_Realtime_UpdateError(t *testing.T) {
 		}
 	}
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	// Set a round for the node in order to fully test the code path for
 	//  a waiting transition
@@ -859,7 +821,7 @@ func TestHandleNodeUpdates_RoundErrored(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -875,7 +837,10 @@ func TestHandleNodeUpdates_RoundErrored(t *testing.T) {
 		}
 	}
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	// Set a round for the node in order to fully test the code path for
 	//  a waiting transition
@@ -910,7 +875,7 @@ func TestHandleNodeUpdates_NOT_STARTED(t *testing.T) {
 
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
 
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
diff --git a/scheduling/params.go b/scheduling/params.go
index 04c9b8d538d8fcbdbc443d226cf750668e8ef4a5..556dee4fa6bd7c349d3394c807d207dad97342c0 100644
--- a/scheduling/params.go
+++ b/scheduling/params.go
@@ -8,9 +8,9 @@ package scheduling
 // Contains the scheduling params object and the internal protoround object
 
 import (
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
 
diff --git a/scheduling/permute_test.go b/scheduling/permute_test.go
index 1961449dae62846576931babc631079c6a714c67..15285b6c2272a66142368259f85ae5b96a38e2f5 100644
--- a/scheduling/permute_test.go
+++ b/scheduling/permute_test.go
@@ -71,7 +71,7 @@ func TestPermute(t *testing.T) {
 func factorial(n int) int {
 	factVal := 1
 	if n < 0 {
-		fmt.Print("Factorial of negative number doesn't exist.")
+		fmt.Println("Factorial of negative number doesn't exist.")
 	} else {
 		for i := 1; i <= n; i++ {
 			factVal *= i
diff --git a/scheduling/pool_test.go b/scheduling/pool_test.go
index 636a2e40d3cd707242924f1ab69d2c87fd974294..f2c59d12919080074dbe29ad0a36e1a5145f662a 100644
--- a/scheduling/pool_test.go
+++ b/scheduling/pool_test.go
@@ -8,10 +8,10 @@ package scheduling
 import (
 	"crypto/rand"
 	"github.com/golang-collections/collections/set"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
 	"reflect"
 	"testing"
 	"time"
@@ -338,7 +338,7 @@ func setupNode(t *testing.T, testState *storage.NetworkState, newId uint64) *nod
 func setupNodeMap(t *testing.T) *storage.NetworkState {
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
diff --git a/scheduling/roundTracker.go b/scheduling/roundTracker.go
index b7f2ca8a314e915fc5dcf12d1988a45c930e6437..e8b43de4cf5476d0ff2b6b2607183cdac2e254c6 100644
--- a/scheduling/roundTracker.go
+++ b/scheduling/roundTracker.go
@@ -9,7 +9,7 @@
 package scheduling
 
 import (
-	"gitlab.com/elixxir/primitives/id"
+	"gitlab.com/xx_network/primitives/id"
 	"sync"
 )
 
diff --git a/scheduling/roundTracker_test.go b/scheduling/roundTracker_test.go
index a7272026a9452a02146b8c1095773dea35c9725b..3019fee382aa80eb62486c98ecf4545861bfda02 100644
--- a/scheduling/roundTracker_test.go
+++ b/scheduling/roundTracker_test.go
@@ -7,7 +7,7 @@
 package scheduling
 
 import (
-	"gitlab.com/elixxir/primitives/id"
+	"gitlab.com/xx_network/primitives/id"
 	"math/rand"
 	"reflect"
 	"testing"
diff --git a/scheduling/schedule.go b/scheduling/schedule.go
index 3b2d9f252bf2616c218cef081e5392ccb5f27961..dd1f80d50f8e2dc4b2a7d639c26bdc4355e65952 100644
--- a/scheduling/schedule.go
+++ b/scheduling/schedule.go
@@ -12,12 +12,12 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/crypto/signature"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/storage/round"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/primitives/id"
 	"sync/atomic"
 	"time"
 )
@@ -124,6 +124,12 @@ func scheduler(params Params, state *storage.NetworkState, killchan chan chan st
 
 	}()
 
+	unstickerQuitChan := make(chan struct{})
+	// begin the thread that takes nodes stuck in waiting out of waiting
+	go func() {
+		UnstickNodes(state, pool, params.RoundTimeout*time.Second, unstickerQuitChan)
+	}()
+
 	var killed chan struct{}
 
 	iterationsCount := uint32(0)
@@ -144,6 +150,7 @@ func scheduler(params Params, state *storage.NetworkState, killchan chan chan st
 		select {
 		// Receive a signal to kill the scheduler
 		case killed = <-killchan:
+			// Also kill the unsticker
 			jww.WARN.Printf("Scheduler has recived a kill signal, exit process has begun")
 		// When we get a node update, move past the select statement
 		case update = <-state.GetNodeUpdateChannel():
@@ -210,8 +217,11 @@ func scheduler(params Params, state *storage.NetworkState, killchan chan chan st
 		// If the scheduler is to be killed and no rounds are in progress,
 		// kill the scheduler
 		if killed != nil && roundTracker.Len() == 0 {
+			// Stop round creation
 			close(newRoundChan)
 			jww.WARN.Printf("Scheduler is exiting due to kill signal")
+			// Also kill the unsticking thread
+			unstickerQuitChan <- struct{}{}
 			killed <- struct{}{}
 			return nil
 		}
diff --git a/scheduling/schedule_test.go b/scheduling/schedule_test.go
index 4a7a91b27e346cdc6e26c81d54833d8f408d65a7..35e5d3036d573056d428f1fc757d6dfcb807ac8b 100644
--- a/scheduling/schedule_test.go
+++ b/scheduling/schedule_test.go
@@ -2,13 +2,13 @@ package scheduling
 
 import (
 	"encoding/json"
-	"gitlab.com/elixxir/crypto/signature/rsa"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/testkeys"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
 	"reflect"
 	"strconv"
 	"testing"
@@ -41,7 +41,7 @@ func TestScheduler_NonRandom(t *testing.T) {
 			"PermissioningKey is %+v", err, pk)
 	}
 	// Start registration server
-	state, err := storage.NewState(pk, "", "")
+	state, err := storage.NewState(pk, 8)
 	if err != nil {
 		t.Errorf("Unable to create state: %+v", err)
 	}
diff --git a/scheduling/secureCreateRound.go b/scheduling/secureCreateRound.go
index 6b2408c003199f94356f4016d68b458d891e33c7..0c2acfae2b0d7ea2a166ff529b5b0994c955c3e4 100644
--- a/scheduling/secureCreateRound.go
+++ b/scheduling/secureCreateRound.go
@@ -4,10 +4,10 @@ import (
 	"github.com/golang-collections/collections/set"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
 	"time"
 )
 
diff --git a/scheduling/secureCreateRound_test.go b/scheduling/secureCreateRound_test.go
index b63a641fe4448c2e0978de087a55b82e1b29652c..49bdcf4c4da129997152a57ae7ddab59eff9f221 100644
--- a/scheduling/secureCreateRound_test.go
+++ b/scheduling/secureCreateRound_test.go
@@ -2,11 +2,10 @@ package scheduling
 
 import (
 	"crypto/rand"
-	"fmt"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
 	mathRand "math/rand"
 
 	"strconv"
@@ -29,7 +28,7 @@ func TestCreateRound(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -52,7 +51,10 @@ func TestCreateRound(t *testing.T) {
 		testpool.Add(nodeState)
 	}
 
-	roundID := testState.GetRoundID()
+	roundID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	_, err = createSecureRound(testParams, testpool, roundID, testState, nil)
 	if err != nil {
@@ -74,7 +76,7 @@ func TestCreateRound_Error_NotEnoughForTeam(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -127,7 +129,7 @@ func TestCreateRound_Error_NotEnoughForThreshold(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -182,7 +184,7 @@ func TestCreateRound_EfficientTeam_AllRegions(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -281,7 +283,7 @@ func TestCreateRound_EfficientTeam_RandomRegions(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -331,8 +333,6 @@ func TestCreateRound_EfficientTeam_RandomRegions(t *testing.T) {
 	}
 
 	duration := time.Now().Sub(start)
-	fmt.Printf("CreateRound took: %v\n", duration)
-
 	expectedDuration := int64(40)
 
 	// Check that it did not take an excessive amount of time
diff --git a/scheduling/simpleCreateRound.go b/scheduling/simpleCreateRound.go
index e813bbca6c896d4e95ebab730ba0998791bd13c5..6bf6349dc186443f21381c377b4e75b79b0c1873 100644
--- a/scheduling/simpleCreateRound.go
+++ b/scheduling/simpleCreateRound.go
@@ -9,10 +9,10 @@ import (
 	"github.com/golang-collections/collections/set"
 	"github.com/pkg/errors"
 	"gitlab.com/elixxir/crypto/shuffle"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
 	"strconv"
 )
 
diff --git a/scheduling/simpleCreateRound_test.go b/scheduling/simpleCreateRound_test.go
index 59e1beca5128301d181d9c94794c06914e5dbbbd..d33801ede33c5833a3014c1a1e484c154bd6ca40 100644
--- a/scheduling/simpleCreateRound_test.go
+++ b/scheduling/simpleCreateRound_test.go
@@ -7,11 +7,11 @@ package scheduling
 
 import (
 	"crypto/rand"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
 	mathRand "math/rand"
 	"reflect"
 	"strconv"
@@ -32,7 +32,7 @@ func TestCreateRound_NonRandom(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -105,7 +105,7 @@ func TestCreateRound_Random(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -167,7 +167,7 @@ func TestCreateRound_BadOrdering(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -218,7 +218,7 @@ func TestCreateRound_RandomOrdering(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -292,7 +292,7 @@ func TestCreateSimpleRound_SemiOptimal(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -401,7 +401,7 @@ func TestCreateSimpleRound_SemiOptimal_BadRegion(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
diff --git a/scheduling/startRound.go b/scheduling/startRound.go
index 95cbcd17a8acfdcee9960564e830ed9bbbbf0a9b..01e3bf5b298260e1c868ca7db55ba41fdd95e80b 100644
--- a/scheduling/startRound.go
+++ b/scheduling/startRound.go
@@ -6,8 +6,10 @@
 package scheduling
 
 import (
+	"fmt"
 	"github.com/pkg/errors"
 	"github.com/spf13/jwalterweatherman"
+	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/round"
@@ -18,7 +20,7 @@ import (
 //  node and network states in order to begin the round
 func startRound(round protoRound, state *storage.NetworkState, roundTracker *RoundTracker) (*round.State, error) {
 	// Add the round to the manager
-	r, err := state.GetRoundMap().AddRound(round.ID, round.BatchSize, round.ResourceQueueTimeout,
+	r, err := state.GetRoundMap().AddRound(round.ID, round.BatchSize, state.GetAddressSpaceSize(), round.ResourceQueueTimeout,
 		round.Topology)
 	if err != nil {
 		err = errors.WithMessagef(err, "Failed to create new round %v", round.ID)
@@ -54,5 +56,12 @@ func startRound(round protoRound, state *storage.NetworkState, roundTracker *Rou
 	// Add round to active set of rounds
 	roundTracker.AddActiveRound(r.GetRoundID())
 
+	//print the round to the log
+	roundPrnt := fmt.Sprintf("Scheduling round %d with nodes: ", round.ID)
+	for i := 0; i < round.Topology.Len(); i++ {
+		roundPrnt += fmt.Sprintf("\n\t (%d/%d) %s", i+1, round.Topology.Len(), round.Topology.GetNodeAtIndex(i))
+	}
+	jww.DEBUG.Println(roundPrnt)
+
 	return r, nil
 }
diff --git a/scheduling/startRound_test.go b/scheduling/startRound_test.go
index 38d60d709646f2d46b73f461a6529e0270ddf3db..cf61f93fae96435cde0f9e10eb4de013c7f1cb86 100644
--- a/scheduling/startRound_test.go
+++ b/scheduling/startRound_test.go
@@ -7,12 +7,12 @@ package scheduling
 
 import (
 	"crypto/rand"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/elixxir/registration/storage"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/storage/round"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
 	"testing"
 )
 
@@ -29,9 +29,15 @@ func TestStartRound(t *testing.T) {
 		NodeCleanUpInterval: 3,
 	}
 
+	var err error
+	storage.PermissioningDb, _, err = storage.NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -91,9 +97,15 @@ func TestStartRound_BadState(t *testing.T) {
 		NodeCleanUpInterval: 3,
 	}
 
+	var err error
+	storage.PermissioningDb, _, err = storage.NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
@@ -160,7 +172,7 @@ func TestStartRound_BadNode(t *testing.T) {
 
 	// Build network state
 	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
-	testState, err := storage.NewState(privKey, "", "")
+	testState, err := storage.NewState(privKey, 8)
 	if err != nil {
 		t.Errorf("Failed to create test state: %v", err)
 		t.FailNow()
diff --git a/scheduling/trackRounds.go b/scheduling/trackRounds.go
index bdd539a4335f024e3cdff9dae243bcf3bd4ad930..dcfbb4337b5b59fcd6787223383fe5bc719dadcb 100644
--- a/scheduling/trackRounds.go
+++ b/scheduling/trackRounds.go
@@ -28,6 +28,7 @@ func trackRounds(params Params, state *storage.NetworkState, pool *waitingPool,
 		notUpdating := make([]string, 0)
 		goodNode := make([]string, 0)
 		noContact := make([]string, 0)
+		banned := make([]string, 0)
 
 		precompRounds := make([]*round.State, 0)
 		queuedRounds := make([]*round.State, 0)
@@ -43,6 +44,13 @@ func trackRounds(params Params, state *storage.NetworkState, pool *waitingPool,
 		nodeStates := state.GetNodeMap().GetNodeStates()
 
 		for _, nodeState := range nodeStates {
+
+			if nodeState.IsBanned(){
+				bStr := fmt.Sprintf("\tNode %s (AppID: %v) is banned ", nodeState.GetID(), nodeState.GetAppID())
+				banned = append(banned, bStr)
+				continue
+			}
+
 			switch nodeState.GetActivity() {
 			case current.WAITING:
 				waitingNodes++
@@ -136,6 +144,7 @@ func trackRounds(params Params, state *storage.NetworkState, pool *waitingPool,
 		jww.INFO.Printf("Nodes without recent poll: %v", len(noPoll))
 		jww.INFO.Printf("Nodes without recent update: %v", len(notUpdating))
 		jww.INFO.Printf("Normally operating nodes: %v", len(nodeStates)-len(noPoll)-len(notUpdating))
+		jww.INFO.Printf("Banned nodes: %v", len(banned))
 		jww.INFO.Printf("")
 
 		if len(goodNode) > 0 {
@@ -169,6 +178,14 @@ func trackRounds(params Params, state *storage.NetworkState, pool *waitingPool,
 			jww.INFO.Printf("")
 		}
 
+		if len(banned)>0{
+			jww.INFO.Printf("Banned nodes:")
+			for _, s := range banned{
+				jww.INFO.Print(s)
+			}
+			jww.INFO.Printf("")
+		}
+
 		allRounds := precompRounds
 		allRounds = append(allRounds, queuedRounds...)
 		allRounds = append(allRounds, realtimeRounds...)
diff --git a/scheduling/unstickNodes.go b/scheduling/unstickNodes.go
new file mode 100644
index 0000000000000000000000000000000000000000..3c2eec73053383d3f391bff38fc7120234489784
--- /dev/null
+++ b/scheduling/unstickNodes.go
@@ -0,0 +1,45 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+// Contains a fix for a bug where nodes get stuck in waiting
+package scheduling
+
+import (
+	"gitlab.com/elixxir/primitives/current"
+	"gitlab.com/elixxir/registration/storage"
+	"gitlab.com/elixxir/registration/storage/node"
+	"time"
+)
+
+// Detect nodes that are stuck on waiting with no current round running
+// Then, add them back to the active pool so they can run rounds again
+func unstickNodes(state *storage.NetworkState, pool *waitingPool, roundTimeout time.Duration) {
+	// Check states of all nodes
+	nodeStates := state.GetNodeMap().GetNodeStates()
+	for i := range nodeStates {
+		thisNode := nodeStates[i]
+		_, thisRound := nodeStates[i].GetCurrentRound()
+		// add the node back to the waiting pool if certain conditions are met
+		if thisRound != nil && time.Since(thisRound.GetLastUpdate()) > 2*roundTimeout && thisNode.GetStatus() == node.Active && thisNode.GetActivity() == current.WAITING {
+			thisNode.ClearRound()
+			pool.Add(thisNode)
+		}
+	}
+}
+
+// Runner that unsticks nodes periodically
+// Exported methods should have only exported types for params! Right?
+func UnstickNodes(state *storage.NetworkState, pool *waitingPool, roundTimeout time.Duration, quitChan chan struct{}) {
+	unstickNodeTicker := time.NewTicker(2 * roundTimeout)
+	for cont := true; cont; {
+		select {
+		case <-unstickNodeTicker.C:
+			unstickNodes(state, pool, roundTimeout)
+		case <-quitChan:
+			cont = false
+		}
+	}
+}
diff --git a/storage/registrationDb.go b/storage/clientDb.go
similarity index 67%
rename from storage/registrationDb.go
rename to storage/clientDb.go
index c527b3162b060e7400f4dc972232606a5c9b74c4..cf05621bc2f14be1ee4fbda3a25d12328bcbb08f 100644
--- a/storage/registrationDb.go
+++ b/storage/clientDb.go
@@ -4,7 +4,8 @@
 // All rights reserved.                                                        /
 ////////////////////////////////////////////////////////////////////////////////
 
-// Handles the database ORM for clients
+// Handles the DatabaseImpl for client-related functionality
+//+build !stateless
 
 package storage
 
@@ -14,20 +15,20 @@ import (
 )
 
 // Inserts client registration code with given number of uses
-func (m *DatabaseImpl) InsertClientRegCode(code string, uses int) error {
+func (d *DatabaseImpl) InsertClientRegCode(code string, uses int) error {
 	jww.INFO.Printf("Inserting code %s, %d uses remaining", code, uses)
-	return m.db.Create(&RegistrationCode{
+	return d.db.Create(&RegistrationCode{
 		Code:          code,
 		RemainingUses: uses,
 	}).Error
 }
 
 // If client registration code is valid, decrements remaining uses
-func (m *DatabaseImpl) UseCode(code string) error {
+func (d *DatabaseImpl) UseCode(code string) error {
 	// Look up given registration code
 	regCode := RegistrationCode{}
 	jww.INFO.Printf("Attempting to use code %s...", code)
-	err := m.db.First(&regCode, "code = ?", code).Error
+	err := d.db.First(&regCode, "code = ?", code).Error
 	if err != nil {
 		// Unable to find code, return error
 		return err
@@ -40,7 +41,7 @@ func (m *DatabaseImpl) UseCode(code string) error {
 
 	// Decrement remaining uses by one
 	regCode.RemainingUses -= 1
-	err = m.db.Save(&regCode).Error
+	err = d.db.Save(&regCode).Error
 	if err != nil {
 		return err
 	}
@@ -50,17 +51,18 @@ func (m *DatabaseImpl) UseCode(code string) error {
 	return nil
 }
 
-// Gets User from the database
-func (m *DatabaseImpl) GetUser(publicKey string) (*User, error) {
+// Gets User from the Database
+func (d *DatabaseImpl) GetUser(publicKey string) (*User, error) {
 	user := &User{}
-	result := m.db.First(&user, "public_key = ?", publicKey)
+	result := d.db.First(&user, "public_key = ?", publicKey)
 	return user, result.Error
 }
 
-// Inserts User into the database
-func (m *DatabaseImpl) InsertUser(publicKey string) error {
+// Inserts User into the Database
+func (d *DatabaseImpl) InsertUser(publicKey, receptionKey string) error {
 	user := &User{
-		PublicKey: publicKey,
+		PublicKey:    publicKey,
+		ReceptionKey: receptionKey,
 	}
-	return m.db.Create(user).Error
+	return d.db.Create(user).Error
 }
diff --git a/storage/registrationMap.go b/storage/clientMap.go
similarity index 88%
rename from storage/registrationMap.go
rename to storage/clientMap.go
index 9e89442db1c235fc40ae3e47a7d4ed1cd45dcf52..5f228a914cad2ebd46f1f615a492d60b036d653e 100644
--- a/storage/registrationMap.go
+++ b/storage/clientMap.go
@@ -4,7 +4,7 @@
 // All rights reserved.                                                        /
 ////////////////////////////////////////////////////////////////////////////////
 
-// Handles the Map backend for registration codes
+// Handles the MapImpl for client-based functionality
 
 package storage
 
@@ -58,16 +58,17 @@ func (m *MapImpl) UseCode(code string) error {
 
 // Gets User from the map
 func (m *MapImpl) GetUser(publicKey string) (*User, error) {
-	if ok := m.users[publicKey]; ok {
+	if rk, ok := m.users[publicKey]; ok {
 		return &User{
-			PublicKey: publicKey,
+			PublicKey:    publicKey,
+			ReceptionKey: rk,
 		}, nil
 	}
 	return nil, errors.New("user does not exist")
 }
 
 // Inserts User into the map
-func (m *MapImpl) InsertUser(publicKey string) error {
-	m.users[publicKey] = true
+func (m *MapImpl) InsertUser(publicKey, receptionKey string) error {
+	m.users[publicKey] = receptionKey
 	return nil
 }
diff --git a/storage/registrationMap_test.go b/storage/clientMap_test.go
similarity index 94%
rename from storage/registrationMap_test.go
rename to storage/clientMap_test.go
index 9f4829401a3be0ebf853dd9e40055e5f1f494368..c28e7ad8736fe9f82a3ab5a6d40db31af3f71d90 100644
--- a/storage/registrationMap_test.go
+++ b/storage/clientMap_test.go
@@ -101,12 +101,12 @@ func TestMapImpl_UseCode_Invalid(t *testing.T) {
 // Happy path
 func TestMapImpl_InsertUser(t *testing.T) {
 	m := &MapImpl{
-		users: make(map[string]bool),
+		users: make(map[string]string),
 	}
 
 	testKey := "TEST"
-	_ = m.InsertUser(testKey)
-	if !m.users[testKey] {
+	_ = m.InsertUser(testKey, testKey)
+	if _, ok := m.users[testKey]; !ok {
 		t.Errorf("Insert failed to add the user!")
 	}
 }
@@ -114,11 +114,11 @@ func TestMapImpl_InsertUser(t *testing.T) {
 // Happy path
 func TestMapImpl_GetUser(t *testing.T) {
 	m := &MapImpl{
-		users: make(map[string]bool),
+		users: make(map[string]string),
 	}
 
 	testKey := "TEST"
-	m.users[testKey] = true
+	m.users[testKey] = testKey
 
 	user, err := m.GetUser(testKey)
 	if err != nil || user.PublicKey != testKey {
@@ -129,7 +129,7 @@ func TestMapImpl_GetUser(t *testing.T) {
 // Get user that does not exist
 func TestMapImpl_GetUserNotExists(t *testing.T) {
 	m := &MapImpl{
-		users: make(map[string]bool),
+		users: make(map[string]string),
 	}
 
 	testKey := "TEST"
diff --git a/storage/database.go b/storage/database.go
index 3df7dbcdb72034dfdc315cd093c2cd3f71e7f86a..c4118038085bdf90187eeb93437569ee2681a810 100644
--- a/storage/database.go
+++ b/storage/database.go
@@ -4,7 +4,8 @@
 // All rights reserved.                                                        /
 ////////////////////////////////////////////////////////////////////////////////
 
-// Handles high level database control
+// Handles Database backend functionality
+//+build !stateless
 
 package storage
 
@@ -13,223 +14,28 @@ import (
 	"github.com/jinzhu/gorm"
 	_ "github.com/jinzhu/gorm/dialects/postgres"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/registration/storage/node"
-	"sync"
 	"time"
 )
 
 // Struct implementing the Database Interface with an underlying DB
 type DatabaseImpl struct {
-	db *gorm.DB // Stored database connection
+	db *gorm.DB // Stored Database connection
 }
 
-// Struct implementing the Database Interface with an underlying Map
-type MapImpl struct {
-	clients           map[string]*RegistrationCode
-	nodes             map[string]*Node
-	users             map[string]bool
-	applications      map[uint64]*Application
-	nodeMetrics       map[uint64]*NodeMetric
-	nodeMetricCounter uint64
-	roundMetrics      map[uint64]*RoundMetric
-	mut               sync.Mutex
-}
-
-// Global variable for database interaction
-var PermissioningDb Storage
-
-type NodeRegistration interface {
-	// If Node registration code is valid, add Node information
-	RegisterNode(id *id.ID, salt []byte, code, serverAddr, serverCert,
-		gatewayAddress, gatewayCert string) error
-	// Update the Salt for a given Node ID
-	UpdateSalt(id *id.ID, salt []byte) error
-	// Get Node information for the given Node registration code
-	GetNode(code string) (*Node, error)
-	// Get Node information for the given Node ID
-	GetNodeById(id *id.ID) (*Node, error)
-	// Return all nodes in storage with the given Status
-	GetNodesByStatus(status node.Status) ([]*Node, error)
-	// Insert Application object along with associated unregistered Node
-	InsertApplication(application *Application, unregisteredNode *Node) error
-	// Insert NodeMetric object
-	InsertNodeMetric(metric *NodeMetric) error
-	// Insert RoundMetric object with associated topology
-	InsertRoundMetric(metric *RoundMetric, topology [][]byte) error
-	// Insert RoundError object
-	InsertRoundError(roundId id.Round, errStr string) error
-	UpdateNodeAddresses(id *id.ID, nodeAddr, gwAddr string) error
-}
-
-type ClientRegistration interface {
-	// Inserts Client registration code with given number of uses
-	InsertClientRegCode(code string, uses int) error
-	// If Client registration code is valid, decrements remaining uses
-	UseCode(code string) error
-	// Gets User from the database
-	GetUser(publicKey string) (*User, error)
-	// Inserts User into the database
-	InsertUser(publicKey string) error
-}
-
-// Interface database storage operations
-type Storage struct {
-	ClientRegistration
-	NodeRegistration
-}
-
-// Struct representing a RegistrationCode table in the database
-type RegistrationCode struct {
-	// Registration code acts as the primary key
-	Code string `gorm:"primary_key"`
-	// Remaining uses for the RegistrationCode
-	RemainingUses int
-}
-
-// Struct representing the User table in the database
-type User struct {
-	// User TLS public certificate in PEM string format
-	PublicKey string `gorm:"primary_key"`
-}
-
-// Struct representing the Node's Application table in the database
-type Application struct {
-	// The Application's unique ID
-	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:false"`
-	// Each Application has one Node
-	Node Node `gorm:"foreignkey:ApplicationId"`
-
-	// Node information
-	Name  string
-	Url   string
-	Blurb string
-	Other string
-
-	// Location string for the Node
-	Location string
-	// Geographic bin of the Node's location
-	GeoBin string
-	// GPS location of the Node
-	GpsLocation string
-	// Specifies the team the node was assigned
-	Team string
-	// Specifies which network the node is in
-	Network string
-
-	// Social media
-	Forum     string
-	Email     string
-	Twitter   string
-	Discord   string
-	Instagram string
-	Medium    string
-}
-
-// Struct representing the Node table in the database
-type Node struct {
-	// Registration code acts as the primary key
-	Code string `gorm:"primary_key"`
-	// Node order string, this is a tag used by the algorithm
-	Sequence string
-
-	// Unique Node ID
-	Id []byte `gorm:"UNIQUE_INDEX;default: null"`
-	// Salt used for generation of Node ID
-	Salt []byte
-	// Server IP address
-	ServerAddress string
-	// Gateway IP address
-	GatewayAddress string
-	// Node TLS public certificate in PEM string format
-	NodeCertificate string
-	// Gateway TLS public certificate in PEM string format
-	GatewayCertificate string
-
-	// Date/time that the node was registered
-	DateRegistered time.Time
-	// Node's network status
-	Status uint8 `gorm:"NOT NULL"`
-
-	// Unique ID of the Node's Application
-	ApplicationId uint64 `gorm:"UNIQUE_INDEX;NOT NULL;type:bigint REFERENCES applications(id)"`
-
-	// Each Node has many Node Metrics
-	NodeMetrics []NodeMetric `gorm:"foreignkey:NodeId;association_foreignkey:Id"`
-
-	// Each Node participates in many Rounds
-	Topologies []Topology `gorm:"foreignkey:NodeId;association_foreignkey:Id"`
-}
-
-// Struct representing Node Metrics table in the database
-type NodeMetric struct {
-	// Auto-incrementing primary key (Do not set)
-	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:true"`
-	// Node has many NodeMetrics
-	NodeId []byte `gorm:"INDEX;NOT NULL;type:bytea REFERENCES nodes(Id)"`
-	// Start time of monitoring period
-	StartTime time.Time `gorm:"NOT NULL"`
-	// End time of monitoring period
-	EndTime time.Time `gorm:"NOT NULL"`
-	// Number of pings responded to during monitoring period
-	NumPings uint64 `gorm:"NOT NULL"`
-}
-
-// Junction table for the many-to-many relationship between Nodes & RoundMetrics
-type Topology struct {
-	// Composite primary key
-	NodeId        []byte `gorm:"primary_key;type:bytea REFERENCES nodes(Id)"`
-	RoundMetricId uint64 `gorm:"INDEX;primary_key;type:bigint REFERENCES round_metrics(Id)"`
-
-	// Order in the topology of a Node for a given Round
-	Order uint8 `gorm:"NOT NULL"`
-}
-
-// Struct representing Round Metrics table in the database
-type RoundMetric struct {
-	// Unique ID of the round as assigned by the network
-	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:false"`
-
-	// Round timestamp information
-	PrecompStart  time.Time `gorm:"NOT NULL"`
-	PrecompEnd    time.Time `gorm:"NOT NULL"`
-	RealtimeStart time.Time `gorm:"NOT NULL"`
-	RealtimeEnd   time.Time `gorm:"NOT NULL;INDEX;"` // Index for TPS calc
-	BatchSize     uint32    `gorm:"NOT NULL"`
-
-	// Each RoundMetric has many Nodes participating in each Round
-	Topologies []Topology `gorm:"foreignkey:RoundMetricId;association_foreignkey:Id"`
-
-	// Each RoundMetric can have many Errors in each Round
-	RoundErrors []RoundError `gorm:"foreignkey:RoundMetricId;association_foreignkey:Id"`
-}
-
-// Struct representing Round Errors table in the database
-type RoundError struct {
-	// Auto-incrementing primary key (Do not set)
-	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:true"`
-
-	// ID of the round for a given run of the network
-	RoundMetricId uint64 `gorm:"INDEX;NOT NULL;type:bigint REFERENCES round_metrics(Id)"`
-
-	// String of error that occurred during the Round
-	Error string `gorm:"NOT NULL"`
-}
-
-// Initialize the Database interface with database backend
+// Initialize the database interface with Database backend
 // Returns a Storage interface, Close function, and error
 func NewDatabase(username, password, database, address,
 	port string) (Storage, func() error, error) {
 
 	var err error
 	var db *gorm.DB
-	//connect to the database if the correct information is provided
+	//connect to the Database if the correct information is provided
 	if address != "" && port != "" {
-		// Create the database connection
+		// Create the Database connection
 		connectString := fmt.Sprintf(
 			"host=%s port=%s user=%s dbname=%s sslmode=disable",
 			address, port, username, database)
-		// Handle empty database password
+		// Handle empty Database password
 		if len(password) > 0 {
 			connectString += fmt.Sprintf(" password=%s", password)
 		}
@@ -237,47 +43,35 @@ func NewDatabase(username, password, database, address,
 	}
 
 	// Return the map-backend interface
-	// in the event there is a database error or information is not provided
+	// in the event there is a Database error or information is not provided
 	if (address == "" || port == "") || err != nil {
 
 		if err != nil {
-			jww.WARN.Printf("Unable to initialize database backend: %+v", err)
+			jww.WARN.Printf("Unable to initialize Database backend: %+v", err)
 		} else {
 			jww.WARN.Printf("Database backend connection information not provided")
 		}
 
-		defer jww.INFO.Println("Map backend initialized successfully!")
-
-		return Storage{
-			ClientRegistration: ClientRegistration(&MapImpl{
-				clients: make(map[string]*RegistrationCode),
-				users:   make(map[string]bool),
-			}),
-			NodeRegistration: NodeRegistration(&MapImpl{
-				applications: make(map[uint64]*Application),
-				nodes:        make(map[string]*Node),
-				nodeMetrics:  make(map[uint64]*NodeMetric),
-				roundMetrics: make(map[uint64]*RoundMetric),
-			})}, func() error { return nil }, nil
+		return NewMap(), func() error { return nil }, nil
 	}
 
-	// Initialize the database logger
+	// Initialize the Database logger
 	db.SetLogger(jww.TRACE)
 	db.LogMode(true)
 
 	// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
 	db.DB().SetMaxIdleConns(10)
-	// SetMaxOpenConns sets the maximum number of open connections to the database.
+	// SetMaxOpenConns sets the maximum number of open connections to the Database.
 	db.DB().SetMaxOpenConns(100)
 	// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
 	db.DB().SetConnMaxLifetime(24 * time.Hour)
 
-	// Initialize the database schema
-	// WARNING: Order is important. Do not change without database testing
+	// Initialize the Database schema
+	// WARNING: Order is important. Do not change without Database testing
 	models := []interface{}{
-		&RegistrationCode{}, &User{},
+		&RegistrationCode{}, &User{}, &State{},
 		&Application{}, &Node{}, &RoundMetric{}, &Topology{}, &NodeMetric{},
-		&RoundError{},
+		&RoundError{}, EphemeralLength{},
 	}
 	for _, model := range models {
 		err = db.AutoMigrate(model).Error
@@ -286,46 +80,7 @@ func NewDatabase(username, password, database, address,
 		}
 	}
 
-	// Build the interface
-	di := &DatabaseImpl{
-		db: db,
-	}
-
 	jww.INFO.Println("Database backend initialized successfully!")
-	return Storage{
-		ClientRegistration: di,
-		NodeRegistration:   di,
-	}, db.Close, nil
+	return Storage{&DatabaseImpl{db: db}}, db.Close, nil
 
 }
-
-// Adds Client registration codes to the database
-func PopulateClientRegistrationCodes(codes []string, uses int) {
-	for _, code := range codes {
-		err := PermissioningDb.InsertClientRegCode(code, uses)
-		if err != nil {
-			jww.ERROR.Printf("Unable to populate Client registration code: %+v",
-				err)
-		}
-	}
-}
-
-// Adds Node registration codes to the database
-func PopulateNodeRegistrationCodes(infos []node.Info) {
-	// TODO: This will eventually need to be updated to intake applications too
-	i := 1
-	for _, info := range infos {
-		err := PermissioningDb.InsertApplication(&Application{
-			Id: uint64(i),
-		}, &Node{
-			Code:          info.RegCode,
-			Sequence:      info.Order,
-			ApplicationId: uint64(i),
-		})
-		if err != nil {
-			jww.ERROR.Printf("Unable to populate Node registration code: %+v",
-				err)
-		}
-		i++
-	}
-}
diff --git a/storage/disabledNodes.go b/storage/disabledNodes.go
index 1605c207cf91041546d1d26417bac64a34cbb573..e520a7ff33216a1f9c6d03befdc7dfbc70cd9232 100644
--- a/storage/disabledNodes.go
+++ b/storage/disabledNodes.go
@@ -14,9 +14,9 @@ import (
 	"github.com/golang-collections/collections/set"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
 	"strings"
 	"sync"
 	"time"
diff --git a/storage/disabledNodes_test.go b/storage/disabledNodes_test.go
index c4ec03c4dfefc0541beab48d942b725489f55eae..34bc05a67848571e0bf19c03ea5cdb50a1ec825f 100644
--- a/storage/disabledNodes_test.go
+++ b/storage/disabledNodes_test.go
@@ -9,9 +9,9 @@ package storage
 import (
 	"crypto/rand"
 	"github.com/golang-collections/collections/set"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/utils"
 	"os"
 	"reflect"
 	"strings"
diff --git a/storage/interface.go b/storage/interface.go
new file mode 100644
index 0000000000000000000000000000000000000000..489e7a49068c06eeb93b71e1ebf580aa092643b5
--- /dev/null
+++ b/storage/interface.go
@@ -0,0 +1,264 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2018 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+// Handles low level Database structures and interfaces
+
+package storage
+
+import (
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"time"
+)
+
+// Interface declaration for Storage methods
+type database interface {
+	// Permissioning methods
+	UpsertState(state *State) error
+	GetStateValue(key string) (string, error)
+	InsertNodeMetric(metric *NodeMetric) error
+	InsertRoundMetric(metric *RoundMetric, topology [][]byte) error
+	InsertRoundError(roundId id.Round, errStr string) error
+	GetLatestEphemeralLength() (*EphemeralLength, error)
+	GetEphemeralLengths() ([]*EphemeralLength, error)
+	InsertEphemeralLength(length *EphemeralLength) error
+
+	// Node methods
+	InsertApplication(application *Application, unregisteredNode *Node) error
+	RegisterNode(id *id.ID, salt []byte, code, serverAddr, serverCert,
+		gatewayAddress, gatewayCert string) error
+	UpdateSalt(id *id.ID, salt []byte) error
+	GetNode(code string) (*Node, error)
+	GetNodeById(id *id.ID) (*Node, error)
+	GetNodesByStatus(status node.Status) ([]*Node, error)
+	UpdateNodeAddresses(id *id.ID, nodeAddr, gwAddr string) error
+
+	// Client methods
+	InsertClientRegCode(code string, uses int) error
+	UseCode(code string) error
+	GetUser(publicKey string) (*User, error)
+	InsertUser(publicKey, receptionKey string) error
+}
+
+// Struct implementing the Database Interface with an underlying Map
+type MapImpl struct {
+	clients           map[string]*RegistrationCode
+	nodes             map[string]*Node
+	users             map[string]string
+	applications      map[uint64]*Application
+	nodeMetrics       map[uint64]*NodeMetric
+	nodeMetricCounter uint64
+	roundMetrics      map[uint64]*RoundMetric
+	states            map[string]string
+	ephemeralLengths  map[uint8]*EphemeralLength
+	mut               sync.Mutex
+}
+
+// Key-Value store used for persisting Permissioning State information
+type State struct {
+	Key   string `gorm:"primary_key"`
+	Value string `gorm:"NOT NULL"`
+}
+
+// Enumerates Keys in the State table
+const (
+	UpdateIdKey = "UpdateId"
+	RoundIdKey  = "RoundId"
+)
+
+// Struct representing a RegistrationCode table in the Database
+type RegistrationCode struct {
+	// Registration code acts as the primary key
+	Code string `gorm:"primary_key"`
+	// Remaining uses for the RegistrationCode
+	RemainingUses int
+}
+
+// Struct representing the User table in the Database
+type User struct {
+	// User TLS public certificate in PEM string format
+	PublicKey string `gorm:"primary_key"`
+	// User reception key in PEM string format
+	ReceptionKey string `gorm:"NOT NULL;UNIQUE"`
+}
+
+// Struct representing the Node's Application table in the Database
+type Application struct {
+	// The Application's unique ID
+	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:false"`
+	// Each Application has one Node
+	Node Node `gorm:"foreignkey:ApplicationId"`
+
+	// Node information
+	Name  string
+	Url   string
+	Blurb string
+	Other string
+
+	// Location string for the Node
+	Location string
+	// Geographic bin of the Node's location
+	GeoBin string
+	// GPS location of the Node
+	GpsLocation string
+	// Specifies the team the node was assigned
+	Team string
+	// Specifies which network the node is in
+	Network string
+
+	// Social media
+	Forum     string
+	Email     string
+	Twitter   string
+	Discord   string
+	Instagram string
+	Medium    string
+}
+
+// Struct representing the Node table in the Database
+type Node struct {
+	// Registration code acts as the primary key
+	Code string `gorm:"primary_key"`
+	// Node order string, this is a tag used by the algorithm
+	Sequence string
+
+	// Unique Node ID
+	Id []byte `gorm:"UNIQUE_INDEX;default: null"`
+	// Salt used for generation of Node ID
+	Salt []byte
+	// Server IP address
+	ServerAddress string
+	// Gateway IP address
+	GatewayAddress string
+	// Node TLS public certificate in PEM string format
+	NodeCertificate string
+	// Gateway TLS public certificate in PEM string format
+	GatewayCertificate string
+
+	// Date/time that the node was registered
+	DateRegistered time.Time
+	// Node's network status
+	Status uint8 `gorm:"NOT NULL"`
+
+	// Unique ID of the Node's Application
+	ApplicationId uint64 `gorm:"UNIQUE_INDEX;NOT NULL;type:bigint REFERENCES applications(id)"`
+
+	// Each Node has many Node Metrics
+	NodeMetrics []NodeMetric `gorm:"foreignkey:NodeId;association_foreignkey:Id"`
+
+	// Each Node participates in many Rounds
+	Topologies []Topology `gorm:"foreignkey:NodeId;association_foreignkey:Id"`
+}
+
+// Struct representing Node Metrics table in the Database
+type NodeMetric struct {
+	// Auto-incrementing primary key (Do not set)
+	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:true"`
+	// Node has many NodeMetrics
+	NodeId []byte `gorm:"INDEX;NOT NULL;type:bytea REFERENCES nodes(Id)"`
+	// Start time of monitoring period
+	StartTime time.Time `gorm:"NOT NULL"`
+	// End time of monitoring period
+	EndTime time.Time `gorm:"NOT NULL"`
+	// Number of pings responded to during monitoring period
+	NumPings uint64 `gorm:"NOT NULL"`
+}
+
+// Junction table for the many-to-many relationship between Nodes & RoundMetrics
+type Topology struct {
+	// Composite primary key
+	NodeId        []byte `gorm:"primary_key;type:bytea REFERENCES nodes(Id)"`
+	RoundMetricId uint64 `gorm:"INDEX;primary_key;type:bigint REFERENCES round_metrics(Id)"`
+
+	// Order in the topology of a Node for a given Round
+	Order uint8 `gorm:"NOT NULL"`
+}
+
+// Struct representing Round Metrics table in the Database
+type RoundMetric struct {
+	// Unique ID of the round as assigned by the network
+	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:false"`
+
+	// Round timestamp information
+	PrecompStart  time.Time `gorm:"NOT NULL"`
+	PrecompEnd    time.Time `gorm:"NOT NULL"`
+	RealtimeStart time.Time `gorm:"NOT NULL"`
+	RealtimeEnd   time.Time `gorm:"NOT NULL;INDEX;"` // Index for TPS calc
+	BatchSize     uint32    `gorm:"NOT NULL"`
+
+	// Each RoundMetric has many Nodes participating in each Round
+	Topologies []Topology `gorm:"foreignkey:RoundMetricId;association_foreignkey:Id"`
+
+	// Each RoundMetric can have many Errors in each Round
+	RoundErrors []RoundError `gorm:"foreignkey:RoundMetricId;association_foreignkey:Id"`
+}
+
+// Struct representing Round Errors table in the Database
+type RoundError struct {
+	// Auto-incrementing primary key (Do not set)
+	Id uint64 `gorm:"primary_key;AUTO_INCREMENT:true"`
+
+	// ID of the round for a given run of the network
+	RoundMetricId uint64 `gorm:"INDEX;NOT NULL;type:bigint REFERENCES round_metrics(Id)"`
+
+	// String of error that occurred during the Round
+	Error string `gorm:"NOT NULL"`
+}
+
+// Struct representing the validity period of an ephemeral ID length
+type EphemeralLength struct {
+	Length    uint8     `gorm:"primary_key;AUTO_INCREMENT:false"`
+	Timestamp time.Time `gorm:"NOT NULL;UNIQUE"`
+}
+
+// Initialize the database interface with Map backend
+func NewMap() Storage {
+	defer jww.INFO.Println("Map backend initialized successfully!")
+	return Storage{
+		&MapImpl{
+			applications:     make(map[uint64]*Application),
+			nodes:            make(map[string]*Node),
+			nodeMetrics:      make(map[uint64]*NodeMetric),
+			roundMetrics:     make(map[uint64]*RoundMetric),
+			clients:          make(map[string]*RegistrationCode),
+			users:            make(map[string]string),
+			states:           make(map[string]string),
+			ephemeralLengths: make(map[uint8]*EphemeralLength),
+		}}
+}
+
+// Adds Client registration codes to the Database
+func PopulateClientRegistrationCodes(codes []string, uses int) {
+	for _, code := range codes {
+		err := PermissioningDb.InsertClientRegCode(code, uses)
+		if err != nil {
+			jww.ERROR.Printf("Unable to populate Client registration code: %+v",
+				err)
+		}
+	}
+}
+
+// Adds Node registration codes to the Database
+func PopulateNodeRegistrationCodes(infos []node.Info) {
+	// TODO: This will eventually need to be updated to intake applications too
+	i := 1
+	for _, info := range infos {
+		err := PermissioningDb.InsertApplication(&Application{
+			Id: uint64(i),
+		}, &Node{
+			Code:          info.RegCode,
+			Sequence:      info.Order,
+			ApplicationId: uint64(i),
+		})
+		if err != nil {
+			jww.ERROR.Printf("Unable to populate Node registration code: %+v",
+				err)
+		}
+		i++
+	}
+}
diff --git a/storage/map.go b/storage/map.go
new file mode 100644
index 0000000000000000000000000000000000000000..ebe9cfcbd30be9c1174311fc9f3d57bd39e86fcc
--- /dev/null
+++ b/storage/map.go
@@ -0,0 +1,17 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2018 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+// Handles Map backend functionality
+//+build stateless
+
+package storage
+
+// Initialize the Database interface with Database backend
+// Returns a Storage interface, Close function, and error
+func NewDatabase(username, password, database, address,
+	port string) (Storage, func() error, error) {
+	return NewMap(), func() error { return nil }, nil
+}
diff --git a/storage/node/map.go b/storage/node/map.go
index de356cdbd366f0a0ab0a76ff8183d46a6186f90e..31e05b6093eec3d2af3210551b4c30e4ea221345 100644
--- a/storage/node/map.go
+++ b/storage/node/map.go
@@ -9,7 +9,7 @@ package node
 import (
 	"errors"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
+	"gitlab.com/xx_network/primitives/id"
 	"sync"
 	"time"
 )
diff --git a/storage/node/map_test.go b/storage/node/map_test.go
index e806a88411787512a997387981174eade09dfa74..e082ee1151946fe9d4b10b9f09cb3abd7dbeefb7 100644
--- a/storage/node/map_test.go
+++ b/storage/node/map_test.go
@@ -8,8 +8,8 @@ package node
 
 import (
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/registration/storage/round"
+	"gitlab.com/xx_network/primitives/id"
 	"math/rand"
 	"strings"
 	"testing"
diff --git a/storage/node/registration.go b/storage/node/registration.go
index 328437256de102ad2b947a324dcd757ffa35c6e0..8eafa2954c9ad495c338a5b0999bfd874a61283f 100644
--- a/storage/node/registration.go
+++ b/storage/node/registration.go
@@ -9,7 +9,7 @@ package node
 import (
 	"encoding/json"
 	"github.com/pkg/errors"
-	"gitlab.com/elixxir/primitives/utils"
+	"gitlab.com/xx_network/primitives/utils"
 )
 
 type Info struct {
diff --git a/storage/node/registration_test.go b/storage/node/registration_test.go
index 03f348cfec9674658046d9073b261052f2400c6d..2bf4ea1d1b8ba84d05dc33bd468e7b8e8a776e64 100644
--- a/storage/node/registration_test.go
+++ b/storage/node/registration_test.go
@@ -7,7 +7,7 @@
 package node
 
 import (
-	"gitlab.com/elixxir/primitives/utils"
+	"gitlab.com/xx_network/primitives/utils"
 	"os"
 	"reflect"
 	"testing"
diff --git a/storage/node/state.go b/storage/node/state.go
index 6e2c4edfe3397e16b95a0218445b04846a08ca44..f08310b5fc8b5270ae0edc5d36403a5d90bd6b00 100644
--- a/storage/node/state.go
+++ b/storage/node/state.go
@@ -9,10 +9,10 @@ package node
 import (
 	"github.com/pkg/errors"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/elixxir/registration/storage/round"
 	"gitlab.com/elixxir/registration/transition"
+	"gitlab.com/xx_network/primitives/id"
 	"sync"
 	"sync/atomic"
 	"testing"
diff --git a/storage/node/state_test.go b/storage/node/state_test.go
index c44fb15bfe986316cfdec99d7896d97fae32027b..330ec662301d9162554d7dddce2342fc98d1063a 100644
--- a/storage/node/state_test.go
+++ b/storage/node/state_test.go
@@ -8,9 +8,9 @@ package node
 
 import (
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/elixxir/registration/storage/round"
+	"gitlab.com/xx_network/primitives/id"
 	"math"
 	"reflect"
 	"strings"
@@ -24,6 +24,9 @@ func TestState_GetLastUpdate(t *testing.T) {
 	origTime := time.Now()
 	ns := State{}
 
+	// Sleep required due to low clock resolution on Windows
+	time.Sleep(1 * time.Millisecond)
+
 	_, _, err := ns.Update(current.WAITING)
 	if err != nil {
 		t.Errorf("Updating state failed: %v", err)
@@ -32,7 +35,7 @@ func TestState_GetLastUpdate(t *testing.T) {
 	newTime := ns.GetLastUpdate()
 
 	if origTime.After(newTime) || origTime.Equal(newTime) {
-		t.Errorf("origTime was after or euqal to newTime")
+		t.Errorf("origTime was after or equal to newTime")
 	}
 }
 
diff --git a/storage/node/updateNotification.go b/storage/node/updateNotification.go
index 6a939df7b9b3f8fa1ca205a6bd076621d6657cdc..758151783e859d850e16eb12ef520483479f8cd7 100644
--- a/storage/node/updateNotification.go
+++ b/storage/node/updateNotification.go
@@ -10,7 +10,7 @@ package node
 import (
 	"gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
+	"gitlab.com/xx_network/primitives/id"
 )
 
 // UpdateNotification structure used to notify the control thread that the
@@ -22,4 +22,5 @@ type UpdateNotification struct {
 	FromActivity current.Activity
 	ToActivity   current.Activity
 	Error        *mixmessages.RoundError
+	ClientErrors []*mixmessages.ClientError
 }
diff --git a/storage/nodeDb.go b/storage/nodeDb.go
new file mode 100644
index 0000000000000000000000000000000000000000..63c41915d8d533adc4d41b5bb396ef77adf6c271
--- /dev/null
+++ b/storage/nodeDb.go
@@ -0,0 +1,81 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2018 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+// Handles the DatabaseImpl for node-related functionality
+//+build !stateless
+
+package storage
+
+import (
+	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/id"
+	"time"
+)
+
+// Insert Application object along with associated unregistered Node
+func (d *DatabaseImpl) InsertApplication(application *Application, unregisteredNode *Node) error {
+	application.Node = *unregisteredNode
+	return d.db.Create(application).Error
+}
+
+// Update the Salt for a given Node ID
+func (d *DatabaseImpl) UpdateSalt(id *id.ID, salt []byte) error {
+	newNode := Node{
+		Salt: salt,
+	}
+	return d.db.First(&newNode, "id = ?", id.Marshal()).Update("salt", salt).Error
+}
+
+// If Node registration code is valid, add Node information
+func (d *DatabaseImpl) RegisterNode(id *id.ID, salt []byte, code, serverAddr, serverCert,
+	gatewayAddress, gatewayCert string) error {
+	newNode := Node{
+		Code:               code,
+		Id:                 id.Marshal(),
+		Salt:               salt,
+		ServerAddress:      serverAddr,
+		GatewayAddress:     gatewayAddress,
+		NodeCertificate:    serverCert,
+		GatewayCertificate: gatewayCert,
+		Status:             uint8(node.Active),
+		DateRegistered:     time.Now(),
+	}
+	return d.db.Model(&newNode).Update(&newNode).Error
+}
+
+// Get Node information for the given Node registration code
+func (d *DatabaseImpl) GetNode(code string) (*Node, error) {
+	newNode := &Node{}
+	err := d.db.First(&newNode, "code = ?", code).Error
+	return newNode, err
+}
+
+// Get Node information for the given Node ID
+func (d *DatabaseImpl) GetNodeById(id *id.ID) (*Node, error) {
+	newNode := &Node{}
+	err := d.db.First(&newNode, "id = ?", id.Marshal()).Error
+	return newNode, err
+}
+
+// Return all nodes in storage with the given Status
+func (d *DatabaseImpl) GetNodesByStatus(status node.Status) ([]*Node, error) {
+	var nodes []*Node
+	err := d.db.Where("status = ?", uint8(status)).Find(&nodes).Error
+	return nodes, err
+}
+
+// Update the address fields for the Node with the given id
+func (d *DatabaseImpl) UpdateNodeAddresses(id *id.ID, nodeAddr, gwAddr string) error {
+	newNode := &Node{
+		Id:             id.Marshal(),
+		ServerAddress:  nodeAddr,
+		GatewayAddress: gwAddr,
+	}
+	return d.db.Model(newNode).Where("id = ?", newNode.Id).Updates(map[string]interface{}{
+		"server_address":  nodeAddr,
+		"gateway_address": gwAddr,
+	}).Error
+}
diff --git a/storage/nodeMap.go b/storage/nodeMap.go
new file mode 100644
index 0000000000000000000000000000000000000000..95bf30130ec9e637b292835735ef677c8619743e
--- /dev/null
+++ b/storage/nodeMap.go
@@ -0,0 +1,156 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2018 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+// Handles the MapImpl for node-related functionality
+
+package storage
+
+import (
+	"bytes"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+	"time"
+)
+
+// Insert Application object along with associated unregistered Node
+func (m *MapImpl) InsertApplication(application *Application, unregisteredNode *Node) error {
+	m.mut.Lock()
+	defer m.mut.Unlock()
+
+	jww.INFO.Printf("Adding application: %d", application.Id)
+	jww.INFO.Printf("Adding node registration code: %s with Order Info: %s",
+		unregisteredNode.Code, unregisteredNode.Sequence)
+
+	// Enforce unique keys
+	if m.nodes[unregisteredNode.Code] != nil {
+		return errors.Errorf("node registration code %s already exists",
+			unregisteredNode.Code)
+	}
+	if m.applications[application.Id] != nil {
+		return errors.Errorf("application ID %d already exists",
+			application.Id)
+	}
+
+	m.nodes[unregisteredNode.Code] = unregisteredNode
+	m.applications[application.Id] = application
+	return nil
+}
+
+// Update the Salt for a given Node ID
+func (m *MapImpl) UpdateSalt(id *id.ID, salt []byte) error {
+	n, err := m.GetNodeById(id)
+	if err != nil {
+		return err
+	}
+
+	m.mut.Lock()
+	defer m.mut.Unlock()
+	n.Salt = salt
+
+	return nil
+}
+
+// If Node registration code is valid, add Node information
+func (m *MapImpl) RegisterNode(id *id.ID, salt []byte, code, serverAddress, serverCert,
+	gatewayAddress, gatewayCert string) error {
+	m.mut.Lock()
+	defer m.mut.Unlock()
+
+	jww.INFO.Printf("Attempting to register node with code: %s", code)
+	if info := m.nodes[code]; info != nil {
+		info.Id = id.Marshal()
+		info.Salt = salt
+		info.ServerAddress = serverAddress
+		info.GatewayCertificate = gatewayCert
+		info.GatewayAddress = gatewayAddress
+		info.NodeCertificate = serverCert
+		info.Status = uint8(node.Active)
+		info.DateRegistered = time.Now()
+		return nil
+	}
+	return errors.Errorf("unable to register node %s", code)
+
+}
+
+// Get Node information for the given Node registration code
+func (m *MapImpl) GetNode(code string) (*Node, error) {
+	m.mut.Lock()
+	defer m.mut.Unlock()
+
+	info := m.nodes[code]
+	if info == nil {
+		return nil, errors.Errorf("unable to get node %s", code)
+	}
+	return info, nil
+}
+
+// Get Node information for the given Node ID
+func (m *MapImpl) GetNodeById(id *id.ID) (*Node, error) {
+	m.mut.Lock()
+	defer m.mut.Unlock()
+
+	for _, v := range m.nodes {
+		if bytes.Compare(v.Id, id.Marshal()) == 0 {
+			return v, nil
+		}
+	}
+	return nil, errors.Errorf("unable to get node %s", id.String())
+}
+
+// Return all nodes in storage with the given Status
+func (m *MapImpl) GetNodesByStatus(status node.Status) ([]*Node, error) {
+	m.mut.Lock()
+	defer m.mut.Unlock()
+
+	nodes := make([]*Node, 0)
+	for _, v := range m.nodes {
+		if node.Status(v.Status) == status {
+			nodes = append(nodes, v)
+		}
+	}
+	return nodes, nil
+}
+
+// If Node registration code is valid, add Node information
+func (m *MapImpl) BannedNode(id *id.ID, t interface{}) error {
+	// Ensure we're called from a test only
+	switch t.(type) {
+	case *testing.T:
+	case *testing.M:
+	case *testing.B:
+	default:
+		jww.FATAL.Panicf("BannedNode permissioning map function called outside testing")
+	}
+
+	m.mut.Lock()
+	defer m.mut.Unlock()
+	for _, n := range m.nodes {
+		if bytes.Compare(n.Id, id.Bytes()) == 0 {
+			n.Status = uint8(node.Banned)
+			return nil
+		}
+	}
+	return errors.New("Node could not be found in map")
+}
+
+// Update the address fields for the Node with the given id
+func (m *MapImpl) UpdateNodeAddresses(id *id.ID, nodeAddr, gwAddr string) error {
+	m.mut.Lock()
+	defer m.mut.Unlock()
+
+	for _, v := range m.nodes {
+		if bytes.Compare(v.Id, id.Marshal()) == 0 {
+			v.GatewayAddress = gwAddr
+			v.ServerAddress = nodeAddr
+			return nil
+		}
+	}
+
+	return errors.Errorf("unable to update addresses for %s", id.String())
+}
diff --git a/storage/nodeMap_test.go b/storage/nodeMap_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..cbb420a868385fa5fb88d1d4c877adc96c2df27b
--- /dev/null
+++ b/storage/nodeMap_test.go
@@ -0,0 +1,298 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2018 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+package storage
+
+import (
+	"bytes"
+	"crypto/rand"
+	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/id"
+	"testing"
+)
+
+// Happy path
+func TestMapImpl_InsertApplication(t *testing.T) {
+	m := &MapImpl{
+		nodes:        make(map[string]*Node),
+		applications: make(map[uint64]*Application),
+	}
+
+	// Attempt to load in a valid code
+	applicationId := uint64(10)
+	newNode := &Node{
+		Code:          "TEST",
+		Sequence:      "BLARG",
+		ApplicationId: applicationId,
+	}
+	newApplication := &Application{Id: applicationId}
+	err := m.InsertApplication(newApplication, newNode)
+
+	// Verify the insert was successful
+	if err != nil || m.nodes[newNode.Code] == nil {
+		t.Errorf("Expected to successfully insert node registration code")
+	}
+
+	if m.nodes[newNode.Code].Sequence != newNode.Sequence {
+		t.Errorf("Order string incorret; Expected: %s, Recieved: %s",
+			newNode.Sequence, m.nodes[newNode.Code].Sequence)
+	}
+}
+
+// Error Path: Duplicate node registration code and application
+func TestMapImpl_InsertApplication_Duplicate(t *testing.T) {
+	m := &MapImpl{
+		nodes:        make(map[string]*Node),
+		applications: make(map[uint64]*Application),
+	}
+
+	// Load in a registration code
+	applicationId := uint64(10)
+	newNode := &Node{
+		Code:          "TEST",
+		Sequence:      "BLARG",
+		ApplicationId: applicationId,
+	}
+	newApplication := &Application{Id: applicationId}
+
+	// Attempt to load in a duplicate application
+	m.applications[applicationId] = newApplication
+	err := m.InsertApplication(newApplication, newNode)
+
+	// Verify the insert failed
+	if err == nil {
+		t.Errorf("Expected to fail inserting duplicate application")
+	}
+
+	// Attempt to load in a duplicate code
+	m.nodes[newNode.Code] = newNode
+	err = m.InsertApplication(newApplication, newNode)
+
+	// Verify the insert failed
+	if err == nil {
+		t.Errorf("Expected to fail inserting duplicate node registration code")
+	}
+}
+
+// Happy path
+func TestMapImpl_UpdateSalt(t *testing.T) {
+	testID := id.NewIdFromString("test", id.Node, t)
+	key := "testKey"
+	newSalt := make([]byte, 8)
+	_, _ = rand.Read(newSalt)
+
+	m := &MapImpl{
+		nodes: map[string]*Node{key: {Id: testID.Bytes(), Salt: []byte("b")}},
+	}
+
+	err := m.UpdateSalt(testID, newSalt)
+	if err != nil {
+		t.Errorf("Received unexpected error when upadting salt."+
+			"\n\terror: %v", err)
+	}
+
+	// Verify that the new salt matches the passed in salt
+	if !bytes.Equal(newSalt, m.nodes[key].Salt) {
+		t.Errorf("Node in map has unexpected salt."+
+			"\n\texpected: %d\n\treceived: %d", newSalt, m.nodes[key].Salt)
+	}
+}
+
+// Tests that MapImpl.UpdateSalt returns an error if no Node is found in the map
+// for the given ID.
+func TestMapImpl_UpdateSalt_NodeNotInMap(t *testing.T) {
+	testID := id.NewIdFromString("test", id.Node, t)
+	key := "testKey"
+	newSalt := make([]byte, 8)
+	_, _ = rand.Read(newSalt)
+
+	m := &MapImpl{
+		nodes: map[string]*Node{key: {Id: id.NewIdFromString("test3", id.Node, t).Bytes(), Salt: []byte("b")}},
+	}
+
+	err := m.UpdateSalt(testID, newSalt)
+	if err == nil {
+		t.Errorf("Did not receive an error when the Node does not exist in " +
+			"the map.")
+	}
+}
+
+// Happy path
+func TestMapImpl_RegisterNode(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	// Load in a registration code
+	code := "TEST"
+	cert := "cert"
+	gwCert := "gwcert"
+	addr := "addr"
+	gwAddr := "gwaddr"
+	m.nodes[code] = &Node{Code: code}
+
+	// Attempt to insert a node
+	err := m.RegisterNode(id.NewIdFromString("", id.Node, t), []byte("test"), code, addr,
+		cert, gwAddr, gwCert)
+
+	// Verify the insert was successful
+	if info := m.nodes[code]; err != nil || info.NodeCertificate != cert ||
+		info.GatewayCertificate != gwCert || info.ServerAddress != addr ||
+		info.GatewayAddress != gwAddr {
+		t.Errorf("Expected to successfully insert node information: %+v", info)
+	}
+}
+
+// Error path: Invalid registration code
+func TestMapImpl_RegisterNode_Invalid(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	// Do NOT load in a registration code
+	code := "TEST"
+
+	// Attempt to insert a node without an associated registration code
+	err := m.RegisterNode(id.NewIdFromString("", id.Node, t), []byte("test"), code, code,
+		code, code, code)
+
+	// Verify the insert failed
+	if err == nil {
+		t.Errorf("Expected to fail inserting node information without the" +
+			" correct registration code")
+	}
+}
+
+// Happy path
+func TestMapImpl_GetNode(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	// Load in a registration code
+	code := "TEST"
+	m.nodes[code] = &Node{Code: code}
+
+	// Check that the correct node is obtained
+	info, err := m.GetNode(code)
+	if err != nil || info.Code != code {
+		t.Errorf("Expected to be able to obtain correct node")
+	}
+}
+
+// Error path: Nonexistent registration code
+func TestMapImpl_GetNode_Invalid(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	// Check that no node is obtained from empty map
+	info, err := m.GetNode("TEST")
+	if err == nil || info != nil {
+		t.Errorf("Expected to not find the node")
+	}
+}
+
+// Happy path
+func TestMapImpl_GetNodeById(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	// Load in a registration code
+	code := "TEST"
+	testId := id.NewIdFromString(code, id.Node, t)
+	m.nodes[code] = &Node{Code: code, Id: testId.Marshal()}
+
+	// Check that the correct node is obtained
+	info, err := m.GetNodeById(testId)
+	if err != nil || info.Code != code {
+		t.Errorf("Expected to be able to obtain correct node")
+	}
+}
+
+// Error path: Nonexistent node id
+func TestMapImpl_GetNodeById_Invalid(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	testId := id.NewIdFromString("test", id.Node, t)
+
+	// Check that no node is obtained from empty map
+	info, err := m.GetNodeById(testId)
+	if err == nil || info != nil {
+		t.Errorf("Expected to not find the node")
+	}
+}
+
+// Happy path
+func TestMapImpl_GetNodesByStatus(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	// Should start off empty
+	nodes, err := m.GetNodesByStatus(node.Banned)
+	if err != nil {
+		t.Errorf("Unable to get nodes by status: %+v", err)
+	}
+	if len(nodes) > 0 {
+		t.Errorf("Unexpected nodes returned for status: %v", nodes)
+	}
+
+	// Add a banned node
+	code := "TEST"
+	m.nodes[code] = &Node{Code: code, Status: uint8(node.Banned)}
+
+	// Should have a result now
+	nodes, err = m.GetNodesByStatus(node.Banned)
+	if err != nil {
+		t.Errorf("Unable to get nodes by status: %+v", err)
+	}
+	if len(nodes) != 1 {
+		t.Errorf("Unexpected nodes returned for status: %v", nodes)
+	}
+
+	// Unban the node
+	m.nodes[code].Status = uint8(node.Active)
+
+	// Shouldn't get a result anymore
+	nodes, err = m.GetNodesByStatus(node.Banned)
+	if err != nil {
+		t.Errorf("Unable to get nodes by status: %+v", err)
+	}
+	if len(nodes) > 0 {
+		t.Errorf("Unexpected nodes returned for status: %v", nodes)
+	}
+}
+
+// Happy path
+func TestMapImpl_UpdateNodeAddresses(t *testing.T) {
+	m := &MapImpl{
+		nodes: make(map[string]*Node),
+	}
+
+	testString := "test"
+	testId := id.NewIdFromString(testString, id.Node, t)
+	testResult := "newAddr"
+	m.nodes[testString] = &Node{
+		Code:           testString,
+		Id:             testId.Marshal(),
+		ServerAddress:  testString,
+		GatewayAddress: testString,
+	}
+
+	err := m.UpdateNodeAddresses(testId, testResult, testResult)
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
+	if result := m.nodes[testString]; result.ServerAddress != testResult || result.GatewayAddress != testResult {
+		t.Errorf("Field values did not update correctly, got Node %s Gateway %s",
+			result.ServerAddress, result.GatewayAddress)
+	}
+}
diff --git a/storage/permissioningDb.go b/storage/permissioningDb.go
index fb804c0a2c25164adcaca219efe90b0866bc7319..5f7a06fa88a7a6f182f7c1151c9b0a38576ff21e 100644
--- a/storage/permissioningDb.go
+++ b/storage/permissioningDb.go
@@ -4,42 +4,72 @@
 // All rights reserved.                                                        /
 ////////////////////////////////////////////////////////////////////////////////
 
-// Handles the database ORM for nodes
+// Handles the DatabaseImpl for permissioning-based functionality
+//+build !stateless
 
 package storage
 
 import (
+	"github.com/jinzhu/gorm"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/registration/storage/node"
-	"time"
+	"gitlab.com/xx_network/primitives/id"
 )
 
-// Insert Application object along with associated unregistered Node
-func (m *DatabaseImpl) InsertApplication(application *Application, unregisteredNode *Node) error {
-	application.Node = *unregisteredNode
-	return m.db.Create(application).Error
+// Inserts the given State into Storage if it does not exist
+// Or updates the Database State if its value does not match the given State
+func (d *DatabaseImpl) UpsertState(state *State) error {
+	jww.TRACE.Printf("Attempting to insert State into DB: %+v", state)
+
+	// Build a transaction to prevent race conditions
+	return d.db.Transaction(func(tx *gorm.DB) error {
+		// Make a copy of the provided state
+		newState := *state
+
+		// Attempt to insert state into the Database,
+		// or if it already exists, replace state with the Database value
+		err := tx.FirstOrCreate(state, &State{Key: state.Key}).Error
+		if err != nil {
+			return err
+		}
+
+		// If state is already present in the Database, overwrite it with newState
+		if newState.Value != state.Value {
+			return tx.Save(newState).Error
+		}
+
+		// Commit
+		return nil
+	})
+}
+
+// Returns a State's value from Storage with the given key
+// Or an error if a matching State does not exist
+func (d *DatabaseImpl) GetStateValue(key string) (string, error) {
+	result := &State{Key: key}
+	err := d.db.Take(result).Error
+	jww.TRACE.Printf("Obtained State from DB: %+v", result)
+	return result.Value, err
 }
 
-// Insert NodeMetric object
-func (m *DatabaseImpl) InsertNodeMetric(metric *NodeMetric) error {
-	jww.TRACE.Printf("Attempting to insert node metric: %+v", metric)
-	return m.db.Create(metric).Error
+// Insert new NodeMetric object into Storage
+func (d *DatabaseImpl) InsertNodeMetric(metric *NodeMetric) error {
+	jww.TRACE.Printf("Attempting to insert NodeMetric into DB: %+v", metric)
+	return d.db.Create(metric).Error
 }
 
-// Insert RoundError object
-func (m *DatabaseImpl) InsertRoundError(roundId id.Round, errStr string) error {
+// Insert new RoundError object into Storage
+func (d *DatabaseImpl) InsertRoundError(roundId id.Round, errStr string) error {
 	roundErr := &RoundError{
 		RoundMetricId: uint64(roundId),
 		Error:         errStr,
 	}
-	jww.DEBUG.Printf("Attempting to insert round error: %+v", roundErr)
-	return m.db.Create(roundErr).Error
+	jww.TRACE.Printf("Attempting to insert RoundError into DB: %+v", roundErr)
+	return d.db.Create(roundErr).Error
 }
 
-// Insert RoundMetric object with associated topology
-func (m *DatabaseImpl) InsertRoundMetric(metric *RoundMetric, topology [][]byte) error {
+// Insert new RoundMetric object with associated topology into Storage
+func (d *DatabaseImpl) InsertRoundMetric(metric *RoundMetric, topology [][]byte) error {
 
 	// Build the Topology
 	metric.Topologies = make([]Topology, len(topology))
@@ -56,65 +86,28 @@ func (m *DatabaseImpl) InsertRoundMetric(metric *RoundMetric, topology [][]byte)
 	}
 
 	// Save the RoundMetric
-	jww.DEBUG.Printf("Attempting to insert round metric: %+v", metric)
-	return m.db.Create(metric).Error
-}
-
-// Update the Salt for a given Node ID
-func (m *DatabaseImpl) UpdateSalt(id *id.ID, salt []byte) error {
-	newNode := Node{
-		Salt: salt,
-	}
-	return m.db.First(&newNode, "id = ?", id.Marshal()).Update("salt", salt).Error
-}
-
-// If Node registration code is valid, add Node information
-func (m *DatabaseImpl) RegisterNode(id *id.ID, salt []byte, code, serverAddr, serverCert,
-	gatewayAddress, gatewayCert string) error {
-	newNode := &Node{
-		Code:               code,
-		Id:                 id.Marshal(),
-		Salt:               salt,
-		ServerAddress:      serverAddr,
-		GatewayAddress:     gatewayAddress,
-		NodeCertificate:    serverCert,
-		GatewayCertificate: gatewayCert,
-		Status:             uint8(node.Active),
-		DateRegistered:     time.Now(),
-	}
-	return m.db.Model(&newNode).Update(&newNode).Error
-}
-
-// Update the address fields for the Node with the given id
-func (m *DatabaseImpl) UpdateNodeAddresses(id *id.ID, nodeAddr, gwAddr string) error {
-	newNode := &Node{
-		Id:             id.Marshal(),
-		ServerAddress:  nodeAddr,
-		GatewayAddress: gwAddr,
-	}
-	return m.db.Model(newNode).Where("id = ?", newNode.Id).Updates(map[string]interface{}{
-		"server_address":  nodeAddr,
-		"gateway_address": gwAddr,
-	}).Error
+	jww.TRACE.Printf("Attempting to insert RoundMetric into DB: %+v", metric)
+	return d.db.Create(metric).Error
 }
 
-// Get Node information for the given Node registration code
-func (m *DatabaseImpl) GetNode(code string) (*Node, error) {
-	newNode := &Node{}
-	err := m.db.First(&newNode, "code = ?", code).Error
-	return newNode, err
+// Returns newest (and largest, by implication) EphemeralLength from Storage
+func (d *DatabaseImpl) GetLatestEphemeralLength() (*EphemeralLength, error) {
+	result := &EphemeralLength{}
+	err := d.db.Last(result).Error
+	jww.TRACE.Printf("Obtained latest EphemeralLength from DB: %+v", result)
+	return result, err
 }
 
-// Get Node information for the given Node ID
-func (m *DatabaseImpl) GetNodeById(id *id.ID) (*Node, error) {
-	newNode := &Node{}
-	err := m.db.First(&newNode, "id = ?", id.Marshal()).Error
-	return newNode, err
+// Returns all EphemeralLength from Storage
+func (d *DatabaseImpl) GetEphemeralLengths() ([]*EphemeralLength, error) {
+	var result []*EphemeralLength
+	err := d.db.Find(&result).Error
+	jww.TRACE.Printf("Obtained EphemeralLengths from DB: %+v", result)
+	return result, err
 }
 
-// Return all nodes in storage with the given Status
-func (m *DatabaseImpl) GetNodesByStatus(status node.Status) ([]*Node, error) {
-	var nodes []*Node
-	err := m.db.Where("status = ?", uint8(status)).Find(&nodes).Error
-	return nodes, err
+// Insert new EphemeralLength into Storage
+func (d *DatabaseImpl) InsertEphemeralLength(length *EphemeralLength) error {
+	jww.TRACE.Printf("Attempting to insert EphemeralLength into DB: %+v", length)
+	return d.db.Create(length).Error
 }
diff --git a/storage/permissioningMap.go b/storage/permissioningMap.go
index a48e3025b9aba70b2f96b6efdb76ac55d71dd16e..c2e3c0eae69c9c1c2cd13732386037c5b84df11f 100644
--- a/storage/permissioningMap.go
+++ b/storage/permissioningMap.go
@@ -4,45 +4,47 @@
 // All rights reserved.                                                        /
 ////////////////////////////////////////////////////////////////////////////////
 
-// Handles the Map backend for the permissioning server
+// Handles the MapImpl for permissioning-based functionality
 
 package storage
 
 import (
-	"bytes"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/registration/storage/node"
-	"testing"
+	"gitlab.com/xx_network/primitives/id"
 )
 
-// Insert Application object along with associated unregistered Node
-func (m *MapImpl) InsertApplication(application *Application, unregisteredNode *Node) error {
+// Inserts the given State into Storage if it does not exist
+// Or updates the Database State if its value does not match the given State
+func (m *MapImpl) UpsertState(state *State) error {
+	jww.TRACE.Printf("Attempting to insert State into Map: %+v", state)
+
 	m.mut.Lock()
 	defer m.mut.Unlock()
 
-	jww.INFO.Printf("Adding application: %d", application.Id)
-	jww.INFO.Printf("Adding node registration code: %s with Order Info: %s",
-		unregisteredNode.Code, unregisteredNode.Sequence)
+	m.states[state.Key] = state.Value
+	return nil
+}
 
-	// Enforce unique keys
-	if m.nodes[unregisteredNode.Code] != nil {
-		return errors.Errorf("node registration code %s already exists",
-			unregisteredNode.Code)
-	}
-	if m.applications[application.Id] != nil {
-		return errors.Errorf("application ID %d already exists",
-			application.Id)
+// Returns a State's value from Storage with the given key
+// Or an error if a matching State does not exist
+func (m *MapImpl) GetStateValue(key string) (string, error) {
+	m.mut.Lock()
+	defer m.mut.Unlock()
+
+	if val, ok := m.states[key]; ok {
+		jww.TRACE.Printf("Obtained State from Map: %+v", val)
+		return val, nil
 	}
 
-	m.nodes[unregisteredNode.Code] = unregisteredNode
-	m.applications[application.Id] = application
-	return nil
+	// NOTE: Other code depends on this error string
+	return "", errors.Errorf("Unable to locate state for key %s", key)
 }
 
-// Insert NodeMetric object
+// Insert new NodeMetric object into Storage
 func (m *MapImpl) InsertNodeMetric(metric *NodeMetric) error {
+	jww.TRACE.Printf("Attempting to insert NodeMetric into Map: %+v", metric)
+
 	m.mut.Lock()
 	defer m.mut.Unlock()
 
@@ -51,32 +53,28 @@ func (m *MapImpl) InsertNodeMetric(metric *NodeMetric) error {
 
 	// Add to map
 	metric.Id = m.nodeMetricCounter
-	jww.DEBUG.Printf("Attempting to insert node metric: %+v", metric)
 	m.nodeMetrics[m.nodeMetricCounter] = metric
 	return nil
 }
 
-// Insert RoundError object
+// Insert new RoundError object into Storage
 func (m *MapImpl) InsertRoundError(roundId id.Round, errStr string) error {
-	m.mut.Lock()
-	defer m.mut.Unlock()
 	rid := uint64(roundId)
+	roundErr := RoundError{
+		Id:            0, // Currently useless in MapImpl
+		RoundMetricId: rid,
+		Error:         errStr,
+	}
 
-	m.roundMetrics[rid].RoundErrors = append(
-		m.roundMetrics[rid].RoundErrors,
-		RoundError{
-			Id:            0, // Currently useless in MapImpl
-			RoundMetricId: rid,
-			Error:         errStr,
-		},
-	)
+	jww.TRACE.Printf("Attempting to insert RoundError into Map: %+v", roundErr)
+	m.mut.Lock()
+	m.roundMetrics[rid].RoundErrors = append(m.roundMetrics[rid].RoundErrors, roundErr)
+	m.mut.Unlock()
 	return nil
 }
 
-// Insert RoundMetric object with associated topology
+// Insert new RoundMetric object with associated topology into Storage
 func (m *MapImpl) InsertRoundMetric(metric *RoundMetric, topology [][]byte) error {
-	m.mut.Lock()
-	defer m.mut.Unlock()
 
 	// Build Topology objects
 	metric.Topologies = make([]Topology, len(topology))
@@ -94,119 +92,63 @@ func (m *MapImpl) InsertRoundMetric(metric *RoundMetric, topology [][]byte) erro
 	}
 
 	// Add to map
-	jww.DEBUG.Printf("Attempting to insert round metric: %+v", metric)
-	m.roundMetrics[metric.Id] = metric
-	return nil
-}
-
-// Update the Salt for a given Node ID
-func (m *MapImpl) UpdateSalt(id *id.ID, salt []byte) error {
-	n, err := m.GetNodeById(id)
-	if err != nil {
-		return err
-	}
-
+	jww.TRACE.Printf("Attempting to insert RoundMetric into Map: %+v", metric)
 	m.mut.Lock()
-	defer m.mut.Unlock()
-	n.Salt = salt
-
+	m.roundMetrics[metric.Id] = metric
+	m.mut.Unlock()
 	return nil
 }
 
-// If Node registration code is valid, add Node information
-func (m *MapImpl) RegisterNode(id *id.ID, salt []byte, code, serverAddress, serverCert,
-	gatewayAddress, gatewayCert string) error {
+// Returns newest (and largest, by implication) EphemeralLength from Storage
+func (m *MapImpl) GetLatestEphemeralLength() (*EphemeralLength, error) {
 	m.mut.Lock()
 	defer m.mut.Unlock()
 
-	jww.INFO.Printf("Attempting to register node with code: %s", code)
-	if info := m.nodes[code]; info != nil {
-		info.Id = id.Marshal()
-		info.Salt = salt
-		info.ServerAddress = serverAddress
-		info.GatewayCertificate = gatewayCert
-		info.GatewayAddress = gatewayAddress
-		info.NodeCertificate = serverCert
-		info.Status = uint8(node.Active)
-		return nil
+	if len(m.ephemeralLengths) == 0 {
+		return nil, errors.Errorf("Unable to locate any EphemeralLengths")
 	}
-	return errors.Errorf("unable to register node %s", code)
-
-}
-
-// Update the address fields for the Node with the given id
-func (m *MapImpl) UpdateNodeAddresses(id *id.ID, nodeAddr, gwAddr string) error {
-	m.mut.Lock()
-	defer m.mut.Unlock()
 
-	for _, v := range m.nodes {
-		if bytes.Compare(v.Id, id.Marshal()) == 0 {
-			v.GatewayAddress = gwAddr
-			v.ServerAddress = nodeAddr
-			return nil
+	largest := uint8(0)
+	for k := range m.ephemeralLengths {
+		if k > largest {
+			largest = k
 		}
 	}
-
-	return errors.Errorf("unable to update addresses for %s", id.String())
+	result := m.ephemeralLengths[largest]
+	jww.TRACE.Printf("Obtained latest EphemeralLength from Map: %+v", result)
+	return result, nil
 }
 
-// Get Node information for the given Node registration code
-func (m *MapImpl) GetNode(code string) (*Node, error) {
+// Returns all EphemeralLength from Storage
+func (m *MapImpl) GetEphemeralLengths() ([]*EphemeralLength, error) {
 	m.mut.Lock()
 	defer m.mut.Unlock()
 
-	info := m.nodes[code]
-	if info == nil {
-		return nil, errors.Errorf("unable to get node %s", code)
+	if len(m.ephemeralLengths) == 0 {
+		return nil, errors.Errorf("Unable to locate any EphemeralLengths")
 	}
-	return info, nil
-}
-
-// Get Node information for the given Node ID
-func (m *MapImpl) GetNodeById(id *id.ID) (*Node, error) {
-	m.mut.Lock()
-	defer m.mut.Unlock()
 
-	for _, v := range m.nodes {
-		if bytes.Compare(v.Id, id.Marshal()) == 0 {
-			return v, nil
-		}
+	result := make([]*EphemeralLength, len(m.ephemeralLengths))
+	i := 0
+	for _, v := range m.ephemeralLengths {
+		result[i] = v
+		i++
 	}
-	return nil, errors.Errorf("unable to get node %s", id.String())
+	jww.TRACE.Printf("Obtained EphemeralLengths from Map: %+v", result)
+	return result, nil
 }
 
-// Return all nodes in storage with the given Status
-func (m *MapImpl) GetNodesByStatus(status node.Status) ([]*Node, error) {
+// Insert new EphemeralLength into Storage
+func (m *MapImpl) InsertEphemeralLength(length *EphemeralLength) error {
+	jww.TRACE.Printf("Attempting to insert EphemeralLength into Map: %+v", length)
+
 	m.mut.Lock()
 	defer m.mut.Unlock()
 
-	nodes := make([]*Node, 0)
-	for _, v := range m.nodes {
-		if node.Status(v.Status) == status {
-			nodes = append(nodes, v)
-		}
-	}
-	return nodes, nil
-}
-
-// If Node registration code is valid, add Node information
-func (m *MapImpl) BannedNode(id *id.ID, t interface{}) error {
-	// Ensure we're called from a test only
-	switch t.(type) {
-	case *testing.T:
-	case *testing.M:
-	case *testing.B:
-	default:
-		jww.FATAL.Panicf("BannedNode permissioning map function called outside testing")
+	if m.ephemeralLengths[length.Length] != nil {
+		return errors.Errorf("ephemeral length %d already exists", length.Length)
 	}
 
-	m.mut.Lock()
-	defer m.mut.Unlock()
-	for _, n := range m.nodes {
-		if bytes.Compare(n.Id, id.Bytes()) == 0 {
-			n.Status = uint8(node.Banned)
-			return nil
-		}
-	}
-	return errors.New("Node could not be found in map")
+	m.ephemeralLengths[length.Length] = length
+	return nil
 }
diff --git a/storage/permissioningMap_test.go b/storage/permissioningMap_test.go
index dcdd485b28fb7040867ee44924211815f13b4765..5345977b80a1507168777dfa280638e7797f9488 100644
--- a/storage/permissioningMap_test.go
+++ b/storage/permissioningMap_test.go
@@ -7,22 +7,62 @@
 package storage
 
 import (
-	"bytes"
-	"crypto/rand"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/registration/storage/node"
+	"gitlab.com/xx_network/primitives/id"
+	"strings"
 	"testing"
 	"time"
 )
 
-// Hidden function for one-time unit testing database implementation
+// Hidden function for one-time unit testing Database implementation
 //func TestDatabaseImpl(t *testing.T) {
+//	jww.SetLogThreshold(jww.LevelTrace)
+//	jww.SetStdoutThreshold(jww.LevelTrace)
+//
 //	db, _, err := NewDatabase("cmix", "", "cmix_server", "0.0.0.0", "5432")
 //	if err != nil {
 //		t.Errorf(err.Error())
 //		return
 //	}
 //
+//	result, err := db.GetLatestEphemeralLength()
+//	if err != nil {
+//		t.Errorf(err.Error())
+//	}
+//	jww.INFO.Printf("%+v", result)
+//	result2, err := db.GetEphemeralLengths()
+//	if err != nil {
+//		t.Errorf(err.Error())
+//	}
+//	jww.INFO.Printf("%#v", result2)
+//
+//	err = db.UpsertState(&State{
+//		Key:   RoundIdKey,
+//		Value: "10",
+//	})
+//	if err != nil {
+//		t.Errorf(err.Error())
+//	}
+//
+//	val, err := db.GetStateValue(RoundIdKey)
+//	if err != nil {
+//		t.Errorf(err.Error())
+//	}
+//	jww.FATAL.Printf(val)
+//
+//	err = db.UpsertState(&State{
+//		Key:   RoundIdKey,
+//		Value: "20",
+//	})
+//	if err != nil {
+//		t.Errorf(err.Error())
+//	}
+//
+//	val, err = db.GetStateValue(RoundIdKey)
+//	if err != nil {
+//		t.Errorf(err.Error())
+//	}
+//	jww.FATAL.Printf(val)
+//
 //	testCode := "test"
 //	testId := id.NewIdFromString(testCode, id.Node, t)
 //	testAppId := uint64(10010)
@@ -177,284 +217,120 @@ func TestMapImpl_InsertRoundError(t *testing.T) {
 }
 
 // Happy path
-func TestMapImpl_InsertApplication(t *testing.T) {
-	m := &MapImpl{
-		nodes:        make(map[string]*Node),
-		applications: make(map[uint64]*Application),
-	}
+func TestMapImpl_InsertEphemeralLength(t *testing.T) {
+	m := &MapImpl{ephemeralLengths: make(map[uint8]*EphemeralLength)}
 
-	// Attempt to load in a valid code
-	applicationId := uint64(10)
-	newNode := &Node{
-		Code:          "TEST",
-		Sequence:      "BLARG",
-		ApplicationId: applicationId,
+	el := &EphemeralLength{
+		Length:    10,
+		Timestamp: time.Now(),
 	}
-	newApplication := &Application{Id: applicationId}
-	err := m.InsertApplication(newApplication, newNode)
-
-	// Verify the insert was successful
-	if err != nil || m.nodes[newNode.Code] == nil {
-		t.Errorf("Expected to successfully insert node registration code")
+	err := m.InsertEphemeralLength(el)
+	if err != nil {
+		t.Errorf("Unable to insert EphLen: %+v", err)
 	}
 
-	if m.nodes[newNode.Code].Sequence != newNode.Sequence {
-		t.Errorf("Order string incorret; Expected: %s, Recieved: %s",
-			newNode.Sequence, m.nodes[newNode.Code].Sequence)
+	if m.ephemeralLengths[el.Length] == nil {
+		t.Errorf("Expected to find inserted EphLen: %d", el.Length)
 	}
 }
 
-// Error Path: Duplicate node registration code and application
-func TestMapImpl_InsertApplication_Duplicate(t *testing.T) {
-	m := &MapImpl{
-		nodes:        make(map[string]*Node),
-		applications: make(map[uint64]*Application),
-	}
+// Error path
+func TestMapImpl_InsertEphemeralLengthErr(t *testing.T) {
+	m := &MapImpl{ephemeralLengths: make(map[uint8]*EphemeralLength)}
 
-	// Load in a registration code
-	applicationId := uint64(10)
-	newNode := &Node{
-		Code:          "TEST",
-		Sequence:      "BLARG",
-		ApplicationId: applicationId,
+	el := &EphemeralLength{
+		Length:    10,
+		Timestamp: time.Now(),
 	}
-	newApplication := &Application{Id: applicationId}
-
-	// Attempt to load in a duplicate application
-	m.applications[applicationId] = newApplication
-	err := m.InsertApplication(newApplication, newNode)
-
-	// Verify the insert failed
-	if err == nil {
-		t.Errorf("Expected to fail inserting duplicate application")
-	}
-
-	// Attempt to load in a duplicate code
-	m.nodes[newNode.Code] = newNode
-	err = m.InsertApplication(newApplication, newNode)
+	// Manually add duplicate entry
+	m.ephemeralLengths[el.Length] = el
 
-	// Verify the insert failed
+	err := m.InsertEphemeralLength(el)
 	if err == nil {
-		t.Errorf("Expected to fail inserting duplicate node registration code")
+		t.Errorf("Expected failure from duplicate EphLen!")
 	}
 }
 
 // Happy path
-func TestMapImpl_UpdateSalt(t *testing.T) {
-	testID := id.NewIdFromString("test", id.Node, t)
-	key := "testKey"
-	newSalt := make([]byte, 8)
-	_, _ = rand.Read(newSalt)
+func TestMapImpl_GetEphemeralLengths(t *testing.T) {
+	m := &MapImpl{ephemeralLengths: make(map[uint8]*EphemeralLength)}
+	testLen := 64
 
-	m := &MapImpl{
-		nodes: map[string]*Node{key: {Id: testID.Bytes(), Salt: []byte("b")}},
+	// Make a bunch of results to insert
+	for i := 0; i < testLen; i++ {
+		el := &EphemeralLength{
+			Length:    uint8(i),
+			Timestamp: time.Now(),
+		}
+		m.ephemeralLengths[el.Length] = el
 	}
 
-	err := m.UpdateSalt(testID, newSalt)
+	result, err := m.GetEphemeralLengths()
 	if err != nil {
-		t.Errorf("Received unexpected error when upadting salt."+
-			"\n\terror: %v", err)
+		t.Errorf("Unable to get all EphLen: %+v", err)
 	}
 
-	// Verify that the new salt matches the passed in salt
-	if !bytes.Equal(newSalt, m.nodes[key].Salt) {
-		t.Errorf("Node in map has unexpected salt."+
-			"\n\texpected: %d\n\treceived: %d", newSalt, m.nodes[key].Salt)
+	if len(result) != testLen {
+		t.Errorf("Didn't get correct number of EphLen, Got %d Expected %d", len(result), testLen)
 	}
 }
 
-// Tests that MapImpl.UpdateSalt returns an error if no Node is found in the map
-// for the given ID.
-func TestMapImpl_UpdateSalt_NodeNotInMap(t *testing.T) {
-	testID := id.NewIdFromString("test", id.Node, t)
-	key := "testKey"
-	newSalt := make([]byte, 8)
-	_, _ = rand.Read(newSalt)
-
-	m := &MapImpl{
-		nodes: map[string]*Node{key: {Id: id.NewIdFromString("test3", id.Node, t).Bytes(), Salt: []byte("b")}},
-	}
-
-	err := m.UpdateSalt(testID, newSalt)
-	if err == nil {
-		t.Errorf("Did not receive an error when the Node does not exist in " +
-			"the map.")
+// Error path
+func TestMapImpl_GetEphemeralLengthsErr(t *testing.T) {
+	m := &MapImpl{ephemeralLengths: make(map[uint8]*EphemeralLength)}
+	result, err := m.GetEphemeralLengths()
+	if result != nil || err == nil {
+		t.Errorf("Expected error getting bad EphLens!")
 	}
 }
 
 // Happy path
-func TestMapImpl_RegisterNode(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
-	}
-
-	// Load in a registration code
-	code := "TEST"
-	cert := "cert"
-	gwCert := "gwcert"
-	addr := "addr"
-	gwAddr := "gwaddr"
-	m.nodes[code] = &Node{Code: code}
-
-	// Attempt to insert a node
-	err := m.RegisterNode(id.NewIdFromString("", id.Node, t), []byte("test"), code, addr,
-		cert, gwAddr, gwCert)
-
-	// Verify the insert was successful
-	if info := m.nodes[code]; err != nil || info.NodeCertificate != cert ||
-		info.GatewayCertificate != gwCert || info.ServerAddress != addr ||
-		info.GatewayAddress != gwAddr {
-		t.Errorf("Expected to successfully insert node information: %+v", info)
-	}
-}
-
-// Error path: Invalid registration code
-func TestMapImpl_RegisterNode_Invalid(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
-	}
-
-	// Do NOT load in a registration code
-	code := "TEST"
+func TestMapImpl_GetLatestEphemeralLength(t *testing.T) {
+	m := &MapImpl{ephemeralLengths: make(map[uint8]*EphemeralLength)}
 
-	// Attempt to insert a node without an associated registration code
-	err := m.RegisterNode(id.NewIdFromString("", id.Node, t), []byte("test"), code, code,
-		code, code, code)
+	// Make a bunch of results to insert
+	maxLen := 50
+	for i := 0; i <= maxLen; i += 5 {
 
-	// Verify the insert failed
-	if err == nil {
-		t.Errorf("Expected to fail inserting node information without the" +
-			" correct registration code")
+		el := &EphemeralLength{
+			Length: uint8(i),
+			// Unlike the real world, decrease Timestamp as Length increases
+			// in order to ensure latest EphemeralLength is based on Length
+			Timestamp: time.Now().Add(time.Duration(-i) * time.Minute),
+		}
+		m.ephemeralLengths[el.Length] = el
 	}
-}
 
-// Happy path
-func TestMapImpl_UpdateNodeAddresses(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
-	}
-
-	testString := "test"
-	testId := id.NewIdFromString(testString, id.Node, t)
-	testResult := "newAddr"
-	m.nodes[testString] = &Node{
-		Code:           testString,
-		Id:             testId.Marshal(),
-		ServerAddress:  testString,
-		GatewayAddress: testString,
-	}
-
-	err := m.UpdateNodeAddresses(testId, testResult, testResult)
+	result, err := m.GetLatestEphemeralLength()
 	if err != nil {
-		t.Errorf(err.Error())
-	}
-
-	if result := m.nodes[testString]; result.ServerAddress != testResult || result.GatewayAddress != testResult {
-		t.Errorf("Field values did not update correctly, got Node %s Gateway %s",
-			result.ServerAddress, result.GatewayAddress)
-	}
-}
-
-// Happy path
-func TestMapImpl_GetNode(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
-	}
-
-	// Load in a registration code
-	code := "TEST"
-	m.nodes[code] = &Node{Code: code}
-
-	// Check that the correct node is obtained
-	info, err := m.GetNode(code)
-	if err != nil || info.Code != code {
-		t.Errorf("Expected to be able to obtain correct node")
-	}
-}
-
-// Error path: Nonexistent registration code
-func TestMapImpl_GetNode_Invalid(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
-	}
-
-	// Check that no node is obtained from empty map
-	info, err := m.GetNode("TEST")
-	if err == nil || info != nil {
-		t.Errorf("Expected to not find the node")
-	}
-}
-
-// Happy path
-func TestMapImpl_GetNodeById(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
+		t.Errorf("Unable to get latest EphLen: %+v", err)
 	}
 
-	// Load in a registration code
-	code := "TEST"
-	testId := id.NewIdFromString(code, id.Node, t)
-	m.nodes[code] = &Node{Code: code, Id: testId.Marshal()}
-
-	// Check that the correct node is obtained
-	info, err := m.GetNodeById(testId)
-	if err != nil || info.Code != code {
-		t.Errorf("Expected to be able to obtain correct node")
+	if result.Length != uint8(maxLen) {
+		t.Errorf("Latest EphLen incorrect: Got %d, expected %d", result.Length, maxLen)
 	}
 }
 
-// Error path: Nonexistent node id
-func TestMapImpl_GetNodeById_Invalid(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
-	}
-
-	testId := id.NewIdFromString("test", id.Node, t)
-
-	// Check that no node is obtained from empty map
-	info, err := m.GetNodeById(testId)
-	if err == nil || info != nil {
-		t.Errorf("Expected to not find the node")
+// Error path
+func TestMapImpl_GetLatestEphemeralLengthErr(t *testing.T) {
+	m := &MapImpl{ephemeralLengths: make(map[uint8]*EphemeralLength)}
+	result, err := m.GetLatestEphemeralLength()
+	if result != nil || err == nil {
+		t.Errorf("Expected error getting bad latest EphLen!")
 	}
 }
 
-// Happy path
-func TestMapImpl_GetNodesByStatus(t *testing.T) {
-	m := &MapImpl{
-		nodes: make(map[string]*Node),
-	}
-
-	// Should start off empty
-	nodes, err := m.GetNodesByStatus(node.Banned)
-	if err != nil {
-		t.Errorf("Unable to get nodes by status: %+v", err)
-	}
-	if len(nodes) > 0 {
-		t.Errorf("Unexpected nodes returned for status: %v", nodes)
-	}
-
-	// Add a banned node
-	code := "TEST"
-	m.nodes[code] = &Node{Code: code, Status: uint8(node.Banned)}
+// Test error path to ensure error message stays consistent
+func TestMapImpl_GetStateValue(t *testing.T) {
+	m := &MapImpl{states: make(map[string]string)}
 
-	// Should have a result now
-	nodes, err = m.GetNodesByStatus(node.Banned)
-	if err != nil {
-		t.Errorf("Unable to get nodes by status: %+v", err)
-	}
-	if len(nodes) != 1 {
-		t.Errorf("Unexpected nodes returned for status: %v", nodes)
+	_, err := m.GetStateValue("test")
+	if err == nil {
+		t.Errorf("Expected error getting bad state value!")
+		return
 	}
 
-	// Unban the node
-	m.nodes[code].Status = uint8(node.Active)
-
-	// Shouldn't get a result anymore
-	nodes, err = m.GetNodesByStatus(node.Banned)
-	if err != nil {
-		t.Errorf("Unable to get nodes by status: %+v", err)
-	}
-	if len(nodes) > 0 {
-		t.Errorf("Unexpected nodes returned for status: %v", nodes)
+	if !strings.Contains(err.Error(), "Unable to locate state for key") {
+		t.Errorf("Invalid error message getting bad state value: Got %s", err.Error())
 	}
 }
diff --git a/storage/round/map.go b/storage/round/map.go
index 010f2ceae1671e61e9bf980ce4afaeda72595a7a..bb8da291b289c571f04b98d981ffca2f40a1a46f 100644
--- a/storage/round/map.go
+++ b/storage/round/map.go
@@ -9,8 +9,8 @@ package round
 import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
 	"sync"
 	"testing"
 	"time"
@@ -31,7 +31,7 @@ func NewStateMap() *StateMap {
 }
 
 // Adds a new round state to the structure. Will not overwrite an existing one.
-func (rsm *StateMap) AddRound(id id.Round, batchsize uint32, resourceQueueTimeout time.Duration,
+func (rsm *StateMap) AddRound(id id.Round, batchsize, addressSpaceSize uint32, resourceQueueTimeout time.Duration,
 	topology *connect.Circuit) (*State, error) {
 	rsm.mux.Lock()
 	defer rsm.mux.Unlock()
@@ -40,7 +40,7 @@ func (rsm *StateMap) AddRound(id id.Round, batchsize uint32, resourceQueueTimeou
 		return nil, errors.New("cannot add a round which already exists")
 	}
 
-	rsm.rounds[id] = newState(id, batchsize, resourceQueueTimeout, topology, time.Now())
+	rsm.rounds[id] = newState(id, batchsize, addressSpaceSize, resourceQueueTimeout, topology, time.Now())
 
 	return rsm.rounds[id], nil
 }
diff --git a/storage/round/map_test.go b/storage/round/map_test.go
index b64d5175f643c207f03d7986f98c9a3f98a02ba9..d1a549e366906e6c4badbf3a9355607fc0e6c2a7 100644
--- a/storage/round/map_test.go
+++ b/storage/round/map_test.go
@@ -7,9 +7,9 @@
 package round
 
 import (
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
 	"testing"
 	"time"
 )
@@ -34,7 +34,7 @@ func TestStateMap_AddRound_Happy(t *testing.T) {
 
 	const numNodes = 5
 
-	rRtn, err := sm.AddRound(rid, 32, 5*time.Minute, buildMockTopology(numNodes, t))
+	rRtn, err := sm.AddRound(rid, 32, 8, 5*time.Minute, buildMockTopology(numNodes, t))
 
 	if err != nil {
 		t.Errorf("Error returned on valid addition of node: %s", err)
@@ -69,7 +69,7 @@ func TestStateMap_AddNode_Invalid(t *testing.T) {
 
 	sm.rounds[rid] = &State{state: states.FAILED}
 
-	rRtn, err := sm.AddRound(rid, 32, 5*time.Minute, buildMockTopology(numNodes, t))
+	rRtn, err := sm.AddRound(rid, 32, 8, 5*time.Minute, buildMockTopology(numNodes, t))
 
 	if err == nil {
 		t.Errorf("Error not returned on invalid addition of node: %s", err)
diff --git a/storage/round/roundInfoCopy.go b/storage/round/roundInfoCopy.go
index ad75e20fe1edd73e674bff8faff937ecdb4a04b0..6feaeedabd7907537778d62fa849c7e381ae0d5c 100644
--- a/storage/round/roundInfoCopy.go
+++ b/storage/round/roundInfoCopy.go
@@ -48,5 +48,6 @@ func CopyRoundInfo(ri *pb.RoundInfo) *pb.RoundInfo {
 		Timestamps:                 timestampsCopy,
 		ResourceQueueTimeoutMillis: ri.GetResourceQueueTimeoutMillis(),
 		Errors:                     errorsCopy,
+		AddressSpaceSize:           ri.GetAddressSpaceSize(),
 	}
 }
diff --git a/storage/round/state.go b/storage/round/state.go
index 529dd2bfcc6bade1fcf46a57243d8b265eedf7f2..7adfdee9f28444a7d4839e431381de109e16d65b 100644
--- a/storage/round/state.go
+++ b/storage/round/state.go
@@ -10,9 +10,9 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/xx_network/comms/connect"
+	"gitlab.com/xx_network/primitives/id"
 	"math"
 	"sync"
 	"testing"
@@ -36,6 +36,9 @@ type State struct {
 	// List of round errors received from nodes
 	roundErrors []*pb.RoundError
 
+	// List of client errors received from nodes
+	clientErrors []*pb.ClientError
+
 	roundComplete chan struct{}
 
 	lastUpdate time.Time
@@ -44,7 +47,7 @@ type State struct {
 }
 
 //creates a round state object
-func newState(id id.Round, batchsize uint32, resourceQueueTimeout time.Duration,
+func newState(id id.Round, batchsize, addressSpaceSize uint32, resourceQueueTimeout time.Duration,
 	topology *connect.Circuit, pendingTs time.Time) *State {
 
 	strTopology := make([][]byte, topology.Len())
@@ -68,6 +71,7 @@ func newState(id id.Round, batchsize uint32, resourceQueueTimeout time.Duration,
 			Topology:                   strTopology,
 			Timestamps:                 timestamps,
 			ResourceQueueTimeoutMillis: uint32(resourceQueueTimeout),
+			AddressSpaceSize:           addressSpaceSize,
 		},
 		topology:           topology,
 		state:              states.PENDING,
@@ -147,6 +151,7 @@ func (s *State) BuildRoundInfo() *pb.RoundInfo {
 	defer s.mux.RUnlock()
 
 	s.base.Errors = s.roundErrors
+	s.base.ClientErrors = s.clientErrors
 	s.base.State = uint32(s.state)
 
 	return CopyRoundInfo(s.base)
@@ -184,6 +189,14 @@ func (s *State) AppendError(roundError *pb.RoundError) {
 	s.roundErrors = append(s.roundErrors, roundError)
 }
 
+// Append a round error to our list of stored rounderrors
+func (s *State) AppendClientErrors(clientErrors []*pb.ClientError) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	s.clientErrors = append(s.clientErrors, clientErrors...)
+}
+
 //returns the channel used to stop the round timeout
 func (s *State) GetRoundCompletedChan() <-chan struct{} {
 	return s.roundComplete
diff --git a/storage/round/state_test.go b/storage/round/state_test.go
index 85f510bff39e691fe22862a1649b7d3f4acb6bcf..63980b01ded3475391b2fab2dbeda245b0c8afea 100644
--- a/storage/round/state_test.go
+++ b/storage/round/state_test.go
@@ -8,8 +8,8 @@ package round
 
 import (
 	"bytes"
-	"gitlab.com/elixxir/primitives/id"
 	"gitlab.com/elixxir/primitives/states"
+	"gitlab.com/xx_network/primitives/id"
 	"math"
 	"reflect"
 	"strings"
@@ -27,7 +27,10 @@ func TestState_GetLastUpdate(t *testing.T) {
 
 	topology := buildMockTopology(numNodes, t)
 	origTime := time.Now()
-	ns := newState(rid, batchSize, 5*time.Minute, topology, origTime)
+	ns := newState(rid, batchSize, 8, 5*time.Minute, topology, origTime)
+
+	// Sleep required due to low clock resolution on Windows
+	time.Sleep(1 * time.Millisecond)
 
 	err := ns.Update(states.PRECOMPUTING, time.Now())
 	if err != nil {
@@ -37,7 +40,7 @@ func TestState_GetLastUpdate(t *testing.T) {
 	newTime := ns.GetLastUpdate()
 
 	if origTime.After(newTime) || origTime.Equal(newTime) {
-		t.Errorf("origTime was after or euqal to newTime")
+		t.Errorf("origTime was after or equal to newTime")
 	}
 }
 
@@ -53,7 +56,7 @@ func TestNewState(t *testing.T) {
 
 	ts := time.Now()
 
-	ns := newState(rid, batchSize, 5*time.Minute, topology, ts)
+	ns := newState(rid, batchSize, 8, 5*time.Minute, topology, ts)
 
 	if len(ns.base.Timestamps) != int(states.NUM_STATES) {
 		t.Errorf("Length of timestamps list is incorrect: "+
@@ -130,7 +133,7 @@ func TestState_NodeIsReadyForTransition(t *testing.T) {
 
 	ts := time.Now()
 
-	ns := newState(rid, batchSize, 5*time.Minute, topology, ts)
+	ns := newState(rid, batchSize, 8, 5*time.Minute, topology, ts)
 
 	if ns.readyForTransition != 0 {
 		t.Errorf("readyForTransmission is incorrect; "+
@@ -166,7 +169,7 @@ func TestState_Update_Forward(t *testing.T) {
 
 	ts := time.Now()
 
-	ns := newState(rid, batchSize, 5*time.Minute, topology, ts)
+	ns := newState(rid, batchSize, 8, 5*time.Minute, topology, ts)
 
 	for i := states.PRECOMPUTING; i < states.NUM_STATES; i++ {
 		time.Sleep(1 * time.Millisecond)
@@ -201,7 +204,7 @@ func TestState_Update_Same(t *testing.T) {
 
 	ts := time.Now()
 
-	ns := newState(rid, batchSize, 5*time.Minute, topology, ts)
+	ns := newState(rid, batchSize, 8, 5*time.Minute, topology, ts)
 
 	for i := states.PENDING; i < states.NUM_STATES; i++ {
 		ns.state = i
@@ -245,7 +248,7 @@ func TestState_Update_Reverse(t *testing.T) {
 
 	ts := time.Now()
 
-	ns := newState(rid, batchSize, 5*time.Minute, topology, ts)
+	ns := newState(rid, batchSize, 8, 5*time.Minute, topology, ts)
 
 	for i := states.PRECOMPUTING; i < states.NUM_STATES; i++ {
 		ns.state = i
@@ -287,7 +290,7 @@ func TestState_BuildRoundInfo(t *testing.T) {
 
 	ts := time.Now()
 
-	ns := newState(rid, batchSize, 5*time.Minute, topology, ts)
+	ns := newState(rid, batchSize, 8, 5*time.Minute, topology, ts)
 
 	ns.state = states.FAILED
 
diff --git a/storage/state.go b/storage/state.go
index ffa96c7d60501c1be148607214b878bc61d193c9..7e4102eb6fe59ba1c0cecdb391ca7a9de74188e2 100644
--- a/storage/state.go
+++ b/storage/state.go
@@ -10,17 +10,20 @@ package storage
 
 import (
 	"github.com/golang-collections/collections/set"
+	"github.com/jinzhu/gorm"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/comms/network/dataStructures"
-	"gitlab.com/elixxir/crypto/signature"
-	"gitlab.com/elixxir/crypto/signature/rsa"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
 	"gitlab.com/elixxir/primitives/states"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/storage/round"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"strconv"
+	"strings"
 	"sync"
 	"time"
 )
@@ -32,19 +35,15 @@ type NetworkState struct {
 	// NetworkState parameters
 	privateKey *rsa.PrivateKey
 
-	// The ID of the current round
-	roundID *stateID
-
 	// Round state
-	rounds          *round.StateMap
-	roundUpdates    *dataStructures.Updates
-	roundUpdateID   *stateID
-	roundUpdateLock sync.Mutex
-	roundData       *dataStructures.Data
-	update          chan node.UpdateNotification // For triggering updates to top level
+	rounds       *round.StateMap
+	roundUpdates *dataStructures.Updates
+	roundData    *dataStructures.Data
+	update       chan node.UpdateNotification // For triggering updates to top level
 
 	// Node NetworkState
-	nodes *node.StateMap
+	nodes     *node.StateMap
+	updateMux sync.Mutex
 
 	// List of states of Nodes to be disabled
 	disabledNodesStates *disabledNodes
@@ -52,10 +51,13 @@ type NetworkState struct {
 	// NDF state
 	partialNdf *dataStructures.Ndf
 	fullNdf    *dataStructures.Ndf
+
+	// Address space size
+	addressSpaceSize uint32
 }
 
 // NewState returns a new NetworkState object.
-func NewState(pk *rsa.PrivateKey, roundIdPath, updateIdPath string) (*NetworkState, error) {
+func NewState(pk *rsa.PrivateKey, addressSpaceSize uint32) (*NetworkState, error) {
 	fullNdf, err := dataStructures.NewNdf(&ndf.NetworkDefinition{})
 	if err != nil {
 		return nil, err
@@ -65,40 +67,54 @@ func NewState(pk *rsa.PrivateKey, roundIdPath, updateIdPath string) (*NetworkSta
 		return nil, err
 	}
 
-	// Create round ID
-	roundID, err := loadOrCreateStateID(roundIdPath, 1)
-	if err != nil {
-		return nil, errors.Errorf("Failed to load round ID from path: %+v", err)
+	state := &NetworkState{
+		rounds:           round.NewStateMap(),
+		roundUpdates:     dataStructures.NewUpdates(),
+		update:           make(chan node.UpdateNotification, updateBufferLength),
+		nodes:            node.NewStateMap(),
+		fullNdf:          fullNdf,
+		partialNdf:       partialNdf,
+		privateKey:       pk,
+		addressSpaceSize: addressSpaceSize,
 	}
 
-	// Create increment ID
-	updateRoundID, err := loadOrCreateStateID(updateIdPath, 0)
-	if err != nil {
-		return nil, errors.Errorf("Failed to load update ID from path: %+v", err)
+	// Obtain round & update Id from Storage
+	// Ignore not found in Storage errors, zero-value will be handled below
+	updateId, err := state.GetUpdateID()
+	if err != nil &&
+		!strings.Contains(err.Error(), gorm.ErrRecordNotFound.Error()) &&
+		!strings.Contains(err.Error(), "Unable to locate state for key") {
+		return nil, err
 	}
-
-	state := &NetworkState{
-		roundID:       roundID,
-		rounds:        round.NewStateMap(),
-		roundUpdates:  dataStructures.NewUpdates(),
-		update:        make(chan node.UpdateNotification, updateBufferLength),
-		nodes:         node.NewStateMap(),
-		fullNdf:       fullNdf,
-		partialNdf:    partialNdf,
-		privateKey:    pk,
-		roundUpdateID: updateRoundID,
+	roundId, err := state.GetRoundID()
+	if err != nil &&
+		!strings.Contains(err.Error(), gorm.ErrRecordNotFound.Error()) &&
+		!strings.Contains(err.Error(), "Unable to locate state for key") {
+		return nil, err
 	}
 
 	// Updates are handled in the uint space, as a result, the designator for
 	// update 0 also designates that no updates are known by the server. To
 	// avoid this collision, permissioning will skip this update as well.
-	if updateRoundID.get() == 0 {
-		// Insert dummy update
+	if updateId == 0 {
+		// Set update Id to start at 0
+		err = state.setId(UpdateIdKey, 0)
+		if err != nil {
+			return nil, err
+		}
+		// Then insert a dummy and increment to 1
 		err = state.AddRoundUpdate(&pb.RoundInfo{})
 		if err != nil {
 			return nil, err
 		}
 	}
+	if roundId == 0 {
+		// Set round Id to start at 1
+		err = state.setId(RoundIdKey, 1)
+		if err != nil {
+			return nil, err
+		}
+	}
 
 	return state, nil
 }
@@ -121,12 +137,11 @@ func (s *NetworkState) GetUpdates(id int) ([]*pb.RoundInfo, error) {
 // AddRoundUpdate creates a copy of the round before inserting it into
 // roundUpdates.
 func (s *NetworkState) AddRoundUpdate(r *pb.RoundInfo) error {
-	s.roundUpdateLock.Lock()
-	defer s.roundUpdateLock.Unlock()
+	s.updateMux.Lock()
+	defer s.updateMux.Unlock()
 
 	roundCopy := round.CopyRoundInfo(r)
-
-	updateID, err := s.roundUpdateID.increment()
+	updateID, err := s.IncrementUpdateID()
 	if err != nil {
 		return err
 	}
@@ -194,6 +209,11 @@ func (s *NetworkState) GetNodeMap() *node.StateMap {
 	return s.nodes
 }
 
+// GetAddressSpaceSize returns the address space size
+func (s *NetworkState) GetAddressSpaceSize() uint32 {
+	return s.addressSpaceSize
+}
+
 // NodeUpdateNotification sends a notification to the control thread of an
 // update to a nodes state.
 func (s *NetworkState) SendUpdateNotification(nun node.UpdateNotification) error {
@@ -210,16 +230,68 @@ func (s *NetworkState) GetNodeUpdateChannel() <-chan node.UpdateNotification {
 	return s.update
 }
 
-// IncrementRoundID increments the round ID in a thread safe manner. If an error
-// occurs while updating the ID file, then it is returned.
+// Helper to increment the RoundId or UpdateId depending on the given key
+// FIXME: Get and set should be coupled to avoid race conditions
+func (s *NetworkState) increment(key string) (uint64, error) {
+	oldIdStr, err := PermissioningDb.GetStateValue(key)
+	if err != nil {
+		return 0, errors.Errorf("Unable to obtain current %s: %+v", key, err)
+	}
+
+	oldId, err := strconv.ParseUint(oldIdStr, 10, 64)
+	if err != nil {
+		return 0, errors.Errorf("Unable to parse current %s: %+v", key, err)
+	}
+
+	return oldId, s.setId(key, oldId+1)
+}
+
+// Helper to set the roundId or updateId value
+func (s *NetworkState) setId(key string, newVal uint64) error {
+	err := PermissioningDb.UpsertState(&State{
+		Key:   key,
+		Value: strconv.FormatUint(newVal, 10),
+	})
+	if err != nil {
+		return errors.Errorf("Unable to update current round ID: %+v", err)
+	}
+	return nil
+}
+
+// Helper to return the RoundId or UpdateId depending on the given key
+func (s *NetworkState) get(key string) (uint64, error) {
+	roundIdStr, err := PermissioningDb.GetStateValue(key)
+	if err != nil {
+		return 0, errors.Errorf("Unable to obtain current %s: %+v", key, err)
+	}
+
+	roundId, err := strconv.ParseUint(roundIdStr, 10, 64)
+	if err != nil {
+		return 0, errors.Errorf("Unable to parse current %s: %+v", key, err)
+	}
+	return roundId, nil
+}
+
+// IncrementRoundID increments the round ID
 func (s *NetworkState) IncrementRoundID() (id.Round, error) {
-	roundID, err := s.roundID.increment()
-	return id.Round(roundID), err
+	roundId, err := s.increment(RoundIdKey)
+	return id.Round(roundId), err
+}
+
+// IncrementUpdateID increments the update ID
+func (s *NetworkState) IncrementUpdateID() (uint64, error) {
+	return s.increment(UpdateIdKey)
+}
+
+// GetRoundID returns the round ID
+func (s *NetworkState) GetRoundID() (id.Round, error) {
+	roundId, err := s.get(RoundIdKey)
+	return id.Round(roundId), err
 }
 
-// GetRoundID returns the round ID in a thread safe manner.
-func (s *NetworkState) GetRoundID() id.Round {
-	return id.Round(s.roundID.get())
+// GetRoundID returns the update ID
+func (s *NetworkState) GetUpdateID() (uint64, error) {
+	return s.get(UpdateIdKey)
 }
 
 // CreateDisabledNodes generates and sets a disabledNodes object that will track
diff --git a/storage/stateID.go b/storage/stateID.go
deleted file mode 100644
index a7a315fadb660021da1c8e96da5d2edcdc7bb669..0000000000000000000000000000000000000000
--- a/storage/stateID.go
+++ /dev/null
@@ -1,89 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-
-// Package storage defines the structure which creates and tracks the RoundID.
-// It only allows itself to incremented forward by 1.
-package storage
-
-import (
-	"github.com/pkg/errors"
-	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/primitives/utils"
-	"strconv"
-	"strings"
-	"sync"
-)
-
-// roundID structure contains the current ID and the file path to store it.
-type stateID struct {
-	id   uint64
-	path string
-	sync.RWMutex
-}
-
-// loadOrCreateStateID loads a new round ID from the specified file path and
-// returns a new roundID with that ID. If no path is provided or the file does
-// not exist, then the ID is set to startId.
-func loadOrCreateStateID(path string, startId uint64) (*stateID, error) {
-	// Skip reading from the file if no path is provided or file does not exist
-	if path != "" && utils.FileExists(path) {
-		roundIdBytes, err := utils.ReadFile(path)
-		if err != nil {
-			return nil, errors.Errorf("Could not load ID from file: %+v", err)
-		}
-		roundIdString := strings.TrimSpace(string(roundIdBytes))
-		startId, err = strconv.ParseUint(roundIdString, 10, 64)
-		if err != nil {
-			return nil, errors.Errorf("Could not convert ID to uint: %+v", err)
-		}
-	} else {
-		jww.WARN.Printf("Could not open state ID path %s because file does "+
-			"not exist, reading ID from file skipped. state ID set to %d.",
-			path, startId)
-	}
-
-	return &stateID{
-		id:   startId,
-		path: path,
-	}, nil
-}
-
-// increment increments the ID by one, saves the new ID to file, and returns the
-// previous ID. This function is thread safe. The internal value is updated only
-// after the file write succeeds. If no path is provided, then only the ID in
-// memory is updated.
-func (rid *stateID) increment() (uint64, error) {
-	rid.Lock()
-	defer rid.Unlock()
-
-	oldID := rid.id
-	newID := rid.id + 1
-
-	// Skip updating the file if no path is provided
-	if rid.path != "" {
-		// Convert the incremented ID to a string and write to file
-		idBytes := []byte(strconv.FormatUint(newID, 10))
-		err := utils.WriteFile(rid.path, idBytes, utils.FilePerms, utils.DirPerms)
-		if err != nil {
-			return 0, errors.WithMessagef(err, "can't update to %d", newID)
-		}
-	} else {
-		jww.WARN.Printf("The state ID path is empty, updating ID file skipped.")
-	}
-
-	// Update the ID in memory
-	rid.id = newID
-
-	return oldID, nil
-}
-
-// get returns the current ID.
-func (rid *stateID) get() uint64 {
-	rid.RLock()
-	defer rid.RUnlock()
-
-	return rid.id
-}
diff --git a/storage/stateID_test.go b/storage/stateID_test.go
deleted file mode 100644
index 92a5cb7470fcbe6a1f2d1add013337302833250e..0000000000000000000000000000000000000000
--- a/storage/stateID_test.go
+++ /dev/null
@@ -1,376 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// Copyright © 2020 Privategrity Corporation                                   /
-//                                                                             /
-// All rights reserved.                                                        /
-////////////////////////////////////////////////////////////////////////////////
-package storage
-
-import (
-	"gitlab.com/elixxir/primitives/utils"
-	"os"
-	"strconv"
-	"testing"
-	"time"
-)
-
-// Tests that loadOrCreateStateID() correctly reads the ID from file and
-// constructs the roundID with the correct values.
-func TestLoadRoundID(t *testing.T) {
-	expectedPath := "testRoundID.txt"
-	expectedID := uint64(9843)
-	idString := []byte(strconv.FormatUint(expectedID, 10))
-
-	defer func() {
-		err := os.RemoveAll(expectedPath)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-	}()
-
-	err := utils.WriteFile(expectedPath, idString, utils.FilePerms, utils.DirPerms)
-	if err != nil {
-		t.Fatalf("Failed to write test file: %+v", err)
-	}
-
-	testSID, err := loadOrCreateStateID(expectedPath, 0)
-	if err != nil {
-		t.Errorf("loadOrCreateStateID() produced an unexpected error: %+v", err)
-	}
-
-	if expectedID != testSID.id {
-		t.Errorf("loadOrCreateStateID() returned a roundID with an incorrect ID."+
-			"\n\t expected: %+v\n\treceived: %+v", expectedID, testSID.id)
-	}
-
-	if expectedPath != testSID.path {
-		t.Errorf("loadOrCreateStateID() returned a roundID with an incorrect path."+
-			"\n\t expected: %+v\n\treceived: %+v", expectedPath, testSID.path)
-	}
-}
-
-// Tests that loadOrCreateStateID() sets the ID to 0 when no path is provided.
-func TestLoadRoundID_EmptyPath(t *testing.T) {
-	expectedPath := ""
-	expectedID := uint64(0)
-
-	testSID, err := loadOrCreateStateID(expectedPath, 0)
-	if err != nil {
-		t.Errorf("loadOrCreateStateID() produced an unexpected error: %+v", err)
-	}
-
-	if expectedID != testSID.id {
-		t.Errorf("loadOrCreateStateID() returned a roundID with an incorrect ID."+
-			"\n\t expected: %+v\n\treceived: %+v", expectedID, testSID.id)
-	}
-
-	if expectedPath != testSID.path {
-		t.Errorf("loadOrCreateStateID() returned a roundID with an incorrect path."+
-			"\n\t expected: %+v\n\treceived: %+v", expectedPath, testSID.path)
-	}
-}
-
-// Tests that loadOrCreateStateID() returns an error when the file does not contain a
-// uint64.
-func TestLoadRoundID_FileContentError(t *testing.T) {
-	expectedPath := "testRoundID.txt"
-	expectedError := "Could not convert ID to uint: strconv.ParseUint: " +
-		"parsing \"test\": invalid syntax"
-
-	defer func() {
-		err := os.RemoveAll(expectedPath)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-	}()
-
-	err := utils.WriteFile(expectedPath, []byte("test"), utils.FilePerms, utils.DirPerms)
-	if err != nil {
-		t.Fatalf("Failed to write test file: %+v", err)
-	}
-
-	_, err = loadOrCreateStateID(expectedPath, 0)
-	if err == nil {
-		t.Errorf("loadOrCreateStateID() did not produce the expected error."+
-			"\n\texpected: %+v\n\treceived: %+v", expectedError, err)
-	}
-}
-
-// Tests that increment() increments the ID the correct number of times and
-// saves the value to file.
-func TestRoundID_Increment(t *testing.T) {
-	testID := uint64(9843)
-	testPath := "testRoundID.txt"
-	incrementAmount := uint64(10)
-	testSID := stateID{
-		id:   testID,
-		path: testPath,
-	}
-
-	defer func() {
-		err := os.RemoveAll(testPath)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-	}()
-
-	for i := uint64(0); i < incrementAmount; i++ {
-		oldID, err := testSID.increment()
-		if err != nil {
-			t.Errorf("increment() produced an unexpected error on index %d: "+
-				"%+v", i, err)
-		}
-
-		// Test that the correct old ID was returned
-		if oldID != testID+i {
-			t.Errorf("increment() did not return the correct old ID."+
-				"\n\texpected: %+v\n\treceived: %+v", testID+i, oldID)
-		}
-
-		// Test that the ID in memory was correctly incremented
-		if testSID.id != testID+i+1 {
-			t.Errorf("increment() did not increment the ID in memory correctly."+
-				"\n\texpected: %+v\n\treceived: %+v", testID+i+1, testSID.id)
-		}
-
-		// Test that the ID on disk was correctly incremented
-		sidBytes, err := utils.ReadFile(testPath)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-		idUint, err := strconv.ParseUint(string(sidBytes), 10, 64)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-		if idUint != testID+i+1 {
-			t.Errorf("increment() did not increment the ID on disk "+
-				"correctly.\n\texpected: %+v\n\treceived: %+v",
-				testID+i+1, idUint)
-		}
-	}
-}
-
-// Tests that increment() increments the internal ID the correct number of times
-// but skips writing to file when an empty path is provided.
-func TestRoundID_Increment_EmptyPath(t *testing.T) {
-	testID := uint64(9843)
-	testPath := ""
-	incrementAmount := uint64(10)
-	testSID := stateID{
-		id:   testID,
-		path: testPath,
-	}
-
-	for i := uint64(0); i < incrementAmount; i++ {
-		oldID, err := testSID.increment()
-		if err != nil {
-			t.Errorf("increment() produced an unexpected error on "+
-				"index %d: %+v", i, err)
-		}
-
-		// Test that the correct old ID was returned
-		if oldID != testID+i {
-			t.Errorf("increment() did not return the correct old ID."+
-				"\n\texpected: %+v\n\treceived: %+v", testID+i, oldID)
-		}
-
-		// Test that the ID in memory was correctly incremented
-		if testSID.id != testID+i+1 {
-			t.Errorf("increment() did not increment the ID in memory correctly."+
-				"\n\texpected: %+v\n\treceived: %+v",
-				testID+i+1, testSID.id)
-		}
-	}
-}
-
-// Tests that increment() returns an error for an invalid file path and that the ID
-// is not updated on error.
-func TestRoundID_Increment_FileError(t *testing.T) {
-	testID := uint64(9843)
-	testPath := "~a/testRoundID.txt"
-	testSID := stateID{
-		id:   testID,
-		path: testPath,
-	}
-
-	defer func() {
-		err := os.RemoveAll(testPath)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-	}()
-
-	_, err := testSID.increment()
-	if err == nil {
-		t.Errorf("increment() did not produce an error on an invalid path.")
-	}
-
-	if testSID.id != testID {
-		t.Errorf("increment() unexpectedly incremented the ID on error."+
-			"\n\texpected: %+v\n\treceived: %+v", testID, testSID.id)
-	}
-}
-
-// Tests that increment() blocks when the thread is locked.
-func TestRoundID_Increment_Lock(t *testing.T) {
-	expectedID := uint64(9843)
-	testSID := stateID{
-		id:   expectedID,
-		path: "testRoundID.txt",
-	}
-
-	result := make(chan bool)
-
-	testSID.Lock()
-
-	go func() {
-		_, _ = testSID.increment()
-		result <- true
-	}()
-
-	select {
-	case <-result:
-		t.Errorf("increment() did not correctly lock the thread.")
-	case <-time.After(time.Second):
-		return
-	}
-}
-
-// Tests that get() returns the correct value.
-func TestRoundID_Get(t *testing.T) {
-	expectedID := uint64(9843)
-	testSID := stateID{
-		id:   expectedID,
-		path: "testRoundID.txt",
-	}
-
-	testID := testSID.get()
-
-	if expectedID != testID {
-		t.Errorf("get() returned an incorrect ID."+
-			"\n\texpected: %+v\n\treceived: %+v", expectedID, testID)
-	}
-}
-
-// Tests that get() blocks when the thread is locked.
-func TestRoundID_Get_Lock(t *testing.T) {
-	expectedID := uint64(9843)
-	testSID := stateID{
-		id:   expectedID,
-		path: "testRoundID.txt",
-	}
-
-	result := make(chan bool)
-
-	testSID.Lock()
-
-	go func() {
-		_ = testSID.get()
-		result <- true
-	}()
-
-	select {
-	case <-result:
-		t.Errorf("get() did not correctly lock the thread.")
-	case <-time.After(time.Second):
-		return
-	}
-}
-
-// Tests that calling loadOrCreateStateID() multiple times on a previously
-// incremented ID files results in the correct ID. This simulates what the ID
-// file will do in integration.
-func TestRoundID_IntegrationSim(t *testing.T) {
-	testID := uint64(9843)
-	testPath := "testRoundID.txt"
-	idString := []byte(strconv.FormatUint(testID, 10))
-	incrementAmount := uint64(10)
-	idTracker := testID
-
-	defer func() {
-		err := os.RemoveAll(testPath)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-	}()
-
-	err := utils.WriteFile(testPath, idString, utils.FilePerms, utils.DirPerms)
-	if err != nil {
-		t.Fatalf("Failed to write test file: %+v", err)
-	}
-
-	for i := 0; i < 5; i++ {
-		testSID, err2 := loadOrCreateStateID(testPath, 0)
-		if err2 != nil {
-			t.Errorf("loadOrCreateStateID() produced an unexpected error at "+
-				"index %d: %+v", i, err2)
-		}
-
-		if testSID.id != idTracker {
-			t.Errorf("loadOrCreateStateID() produced a state ID with an "+
-				"incorrect ID at index %d.\n\texpected: %+v\n\treceived: %+v",
-				i, idTracker, testSID.id)
-		}
-
-		for j := uint64(0); j < incrementAmount; j++ {
-			_, err = testSID.increment()
-			if err != nil {
-				t.Errorf("increment() produced an unexpected error on index "+
-					"%d: %+v", j, err)
-			}
-		}
-
-		idTracker += incrementAmount
-
-		if testSID.id != idTracker {
-			t.Errorf("increment() did not increment the id correctly at index "+
-				"%d.\n\texpected: %+v\n\treceived: %+v",
-				i, idTracker, testSID.id)
-		}
-	}
-}
-
-// Tests that calling loadOrCreateStateID() multiple times on a previously
-// incremented ID files results in the correct ID. This simulates what the ID
-// file will do in integration.
-func TestRoundID_IntegrationSim_NoFile(t *testing.T) {
-	testPath := "testRoundID.txt"
-	incrementAmount := uint64(10)
-	idTracker := uint64(0)
-
-	defer func() {
-		err := os.RemoveAll(testPath)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-	}()
-
-	for i := 0; i < 5; i++ {
-		testSID, err2 := loadOrCreateStateID(testPath, 0)
-		if err2 != nil {
-			t.Errorf("loadOrCreateStateID() produced an unexpected error at "+
-				"index %d: %+v", i, err2)
-		}
-
-		if testSID.id != idTracker {
-			t.Errorf("loadOrCreateStateID() produced a state ID with an "+
-				"incorrect ID at index %d.\n\texpected: %+v\n\treceived: %+v",
-				i, idTracker, testSID.id)
-		}
-
-		for j := uint64(0); j < incrementAmount; j++ {
-			_, err := testSID.increment()
-			if err != nil {
-				t.Errorf("increment() produced an unexpected error on index "+
-					"%d: %+v", j, err)
-			}
-		}
-
-		idTracker += incrementAmount
-
-		if testSID.id != idTracker {
-			t.Errorf("increment() did not increment the id correctly at index "+
-				"%d.\n\texpected: %+v\n\treceived: %+v",
-				i, idTracker, testSID.id)
-		}
-	}
-}
diff --git a/storage/state_test.go b/storage/state_test.go
index 4d3b80379419450c4470c0fc49f45e68eeda9eff..9eb840f617f8cdb1065a8b914b338c1c012ac272 100644
--- a/storage/state_test.go
+++ b/storage/state_test.go
@@ -12,14 +12,14 @@ import (
 	"github.com/pkg/errors"
 	pb "gitlab.com/elixxir/comms/mixmessages"
 	"gitlab.com/elixxir/comms/network/dataStructures"
-	"gitlab.com/elixxir/crypto/signature"
-	"gitlab.com/elixxir/crypto/signature/rsa"
 	"gitlab.com/elixxir/primitives/current"
-	"gitlab.com/elixxir/primitives/id"
-	"gitlab.com/elixxir/primitives/ndf"
-	"gitlab.com/elixxir/primitives/utils"
 	"gitlab.com/elixxir/registration/storage/node"
 	"gitlab.com/elixxir/registration/storage/round"
+	"gitlab.com/xx_network/comms/signature"
+	"gitlab.com/xx_network/crypto/signature/rsa"
+	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/ndf"
+	"gitlab.com/xx_network/primitives/utils"
 	mrand "math/rand"
 	"os"
 	"reflect"
@@ -44,6 +44,11 @@ func TestNewState(t *testing.T) {
 		t.Fatalf("Failed to generate new NDF:\n%v", err)
 	}
 
+	PermissioningDb, _, err = NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
 	// Generate private RSA key
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 	if err != nil {
@@ -51,7 +56,7 @@ func TestNewState(t *testing.T) {
 	}
 
 	// Generate new NetworkState
-	state, err := NewState(privateKey, "", "")
+	state, err := NewState(privateKey, 8)
 	if err != nil {
 		t.Errorf("NewState() produced an unexpected error:\n%v", err)
 	}
@@ -104,6 +109,12 @@ func TestNewState_PrivateKeyError(t *testing.T) {
 		"signature: Unable to sign message: crypto/rsa: key size too small " +
 		"for PSS signature"
 
+	var err error
+	PermissioningDb, _, err = NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
 	// Generate private RSA key
 	privateKey, err := rsa.GenerateKey(rand.Reader, 128)
 	if err != nil {
@@ -111,12 +122,12 @@ func TestNewState_PrivateKeyError(t *testing.T) {
 	}
 
 	// Generate new NetworkState
-	state, err := NewState(privateKey, "", "")
+	state, err := NewState(privateKey, 8)
 
 	// Test NewState() output
 	if err == nil || err.Error() != expectedErr {
 		t.Errorf("NewState() did not produce an error when expected."+
-			"\n\texpected: %s\n\treceived: %s", expectedErr, err.Error())
+			"\n\texpected: %s\n\treceived: %s", expectedErr, err)
 	}
 	if state != nil {
 		t.Errorf("NewState() unexpedly produced a non-nil NetworkState when an error was produced."+
@@ -225,8 +236,6 @@ func TestNetworkState_AddRoundUpdate(t *testing.T) {
 		t.Fatalf("%+v", err)
 	}
 
-	state.roundUpdateID.id = 1
-
 	// Call AddRoundUpdate()
 	err = state.AddRoundUpdate(testRoundInfo)
 	if err != nil {
@@ -274,6 +283,11 @@ func TestNetworkState_AddRoundUpdate_Error(t *testing.T) {
 	expectedErr := "Could not add round update 1 for round 0 due to failed " +
 		"signature: Unable to sign message: crypto/rsa: key size too small for " +
 		"PSS signature"
+	var err error
+	PermissioningDb, _, err = NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	// Generate new NetworkState
 	state, _, err := generateTestNetworkState()
@@ -491,7 +505,7 @@ func generateTestNetworkState() (*NetworkState, *rsa.PrivateKey, error) {
 	}
 
 	// Generate new NetworkState using the private key
-	state, err := NewState(privateKey, "", "")
+	state, err := NewState(privateKey, 8)
 	if err != nil {
 		return state, privateKey, fmt.Errorf("NewState() produced an unexpected error:\n+%v", err)
 	}
@@ -502,14 +516,23 @@ func generateTestNetworkState() (*NetworkState, *rsa.PrivateKey, error) {
 // Tests that IncrementRoundID() increments the ID correctly.
 func TestNetworkState_IncrementRoundID(t *testing.T) {
 	testID := uint64(9843)
+	var err error
+	PermissioningDb, _, err = NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+		t.FailNow()
+	}
+	err = PermissioningDb.UpsertState(&State{
+		Key:   RoundIdKey,
+		Value: fmt.Sprintf("%d", testID),
+	})
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
 	testPath := "testRoundID.txt"
 	incrementAmount := uint64(10)
-	testState := NetworkState{
-		roundID: &stateID{
-			id:   testID,
-			path: testPath,
-		},
-	}
+	testState := NetworkState{}
 
 	defer func() {
 		err := os.RemoveAll(testPath)
@@ -536,14 +559,27 @@ func TestNetworkState_IncrementRoundID(t *testing.T) {
 // Tests that GetRoundID() returns the correct value.
 func TestNetworkState_GetRoundID(t *testing.T) {
 	expectedID := id.Round(9843)
-	testState := NetworkState{
-		roundID: &stateID{
-			id:   uint64(expectedID),
-			path: "testRoundID.txt",
-		},
+
+	var err error
+	PermissioningDb, _, err = NewDatabase("", "", "", "", "")
+	if err != nil {
+		t.Errorf(err.Error())
+		t.FailNow()
 	}
+	err = PermissioningDb.UpsertState(&State{
+		Key:   RoundIdKey,
+		Value: fmt.Sprintf("%d", expectedID),
+	})
+	if err != nil {
+		t.Errorf(err.Error())
+	}
+
+	testState := NetworkState{}
 
-	testID := testState.GetRoundID()
+	testID, err := testState.GetRoundID()
+	if err != nil {
+		t.Errorf(err.Error())
+	}
 
 	if expectedID != testID {
 		t.Errorf("GetRoundID() returned an incorrect ID."+
diff --git a/storage/storage.go b/storage/storage.go
new file mode 100644
index 0000000000000000000000000000000000000000..040b32293b2026a11d719564f2f705a0df74ff6b
--- /dev/null
+++ b/storage/storage.go
@@ -0,0 +1,26 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 Privategrity Corporation                                   /
+//                                                                             /
+// All rights reserved.                                                        /
+////////////////////////////////////////////////////////////////////////////////
+
+// Handles the high level storage API.
+// This layer merges the business logic layer and the database layer
+
+package storage
+
+import "testing"
+
+// Global variable for Database interaction
+var PermissioningDb Storage
+
+// API for the storage layer
+type Storage struct {
+	// Stored Database interface
+	database
+}
+
+// Test use only function for exposing MapImpl
+func (s *Storage) GetMapImpl(t *testing.T) *MapImpl {
+	return s.database.(*MapImpl)
+}
diff --git a/testkeys/clientNDF.json b/testkeys/clientNDF.json
index 2b6a3f9e8daaf8b0a8ec2a29df15c1eef468f08a..5bdc6807f3710f2d36d6f1b4b58f35595e0d2d4c 100644
--- a/testkeys/clientNDF.json
+++ b/testkeys/clientNDF.json
@@ -82,4 +82,3 @@
     "Generator":"2"
   }
 }
-jhwpnRhQlA9L4oWlndWbbYgESFx6d/ZHCiLVs/k83S5zkk1WJejMI3mK/CG5lYg7lgO411OzM9rCic36l5EeVoZW7xVG8Qyh834rj+Q6QWxxgrW9UQoAIrEQ3Mm4LuyhBRolTEUNSx+1jwKLja1IaZGV2F06qq8LeaqbqFFlanuTc+5ns1OD2nvtoqL+OTRAM6e3+0LYA9wR5RmA7KG10ZnIdG9v3TV0frD/A1n2QLNoFus8XTy54U5vt3+UiVbMsGGoUDWCg8xHaGiaHj5j30eQDxU17GUiAurorRMUnH/1UMD3NLUtw2NfhuWqikR/a8KG4xqyJ3RgqpOWCa9luX0QzSyo/lANbpTjFHPXi1JNXPfT7808d74KXr/lGBEYfxLXoABmS8GyUwb6sScnNAnbnWUL+9J++rPZ3OVlfTjUFRbGPZi1alVdzX3qevTKIJ+bUUhjSHBA3wOrhalIaquFOZHI7o/jaGdtmdVILRqga+7B04vy0K7guEp+xcRZqXT5VV878r92pJEv9+nAlLAohhn0OFEXxyB+47mVkmCqoC0UJb2s+ZjVU4Pwf/x6/Qu71IVKQLnoipsY7cUV2IDdBwlGjocg5ImMijVw+lthbs8bk0G6m9owmTAOEI4ux1M/4ykKM1lzm/JCYq0o4ULig0nVy52m9Y/snmGCV/8=
\ No newline at end of file
diff --git a/testkeys/keypath.go b/testkeys/keypath.go
index 194a8f7b5d934607a48252fe1ab86e4f885ee07f..a343cc7536f498fd9bb146c0a5b8ef5e8dddfeae 100644
--- a/testkeys/keypath.go
+++ b/testkeys/keypath.go
@@ -55,6 +55,10 @@ func GetNDFPath() string {
 	return filepath.Join(getDirForFile(), "ndf.json")
 }
 
+func GetUdbCertPath() string {
+	return filepath.Join(getDirForFile(), "udb.crt")
+}
+
 func GetClientPublicKey() string {
 	return filepath.Join(getDirForFile(), "cmix.public_key.pem")
 }
diff --git a/testkeys/udb.crt b/testkeys/udb.crt
new file mode 100644
index 0000000000000000000000000000000000000000..4b622ae0f74ee4c5ec47a74fa28d29193b3f3361
--- /dev/null
+++ b/testkeys/udb.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDOTCCAiGgAwIBAgIUC0PgcjZfa9ApbtSoRzr0y3vx4vAwDQYJKoZIhvcNAQEL
+BQAwLDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdFbGl4eGly
+MB4XDTIwMTAyNzE4MjMwMVoXDTMwMTAyNTE4MjMwMVowLDELMAkGA1UEBhMCVVMx
+CzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdFbGl4eGlyMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAoVdpRzLj/+zexaf2+1ZvY8uOwvdhyYAGvvi//znlL3jv
+ZpTD8ltQ3v50LIBrcmyTvsEE//iKrL0nd1PHZB/jD/PGRzC5ssnYfXL+YwsHQrbk
+mgWQdmbAfiLE5ZBK89zoV39ojNqWj47pQkQ1gsZnnS8pqCOtv9rCZIkKATQx1/hA
+gUxMrkO1M61zuKtwJFnRfct/UaQSZtIs37fpvKJzNy4Ok8kkplMHg2VWI7+99Cqb
+UexnNbOq7pPSnewt+wC4pcxeR3Bi6JBkHWpntsOdzIfSLNqhXvnnzurMwscjsfnj
+KJjsx0UZClZHP3AO2i3qvqz7ytGGzthAuEfWqH1vdwIDAQABo1MwUTAdBgNVHQ4E
+FgQUcxnSaGW3Vn1Xd13g1SBfYMPFSVIwHwYDVR0jBBgwFoAUcxnSaGW3Vn1Xd13g
+1SBfYMPFSVIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFkpc
+dp3hXNOt+szMXbEryCARrpvy1VUMjAEZd/sqAA/eXgq8h2cNe0Hkjm25iMPTNiJQ
+6+nOng3HJixqxPjZDw/GgvwYPRGDulwyOe2p9gHa3Ib8PvPL9ZET6BqqXF0VZ5S2
+rvbjBOHTAhx0ydJVPmW4vTaoK7vOqaoqEgMw14IUjGYBzIcqdW78Aj5HFlgI8g+w
+wceLuCmH2idoh55fEq1jczSbq1S4GEtoM0Yx7zBNXnBb30YThIJ652smkdrTsIxV
+jrKOtzZ/EMp7qE96S0HRz0S4EjPUHC7d/v1WB3pVv5XSoFbn2N7ZUMRFgDlvSeyu
+rnLb1hgS8uEk5jJzLA==
+-----END CERTIFICATE-----
\ No newline at end of file