Repository: prebid/prebid-server
Pull request #2191 (2022-03-18 18:20:06)

Test Hardening For TickerTask
Refactored the TickerTask tests to better avoid race conditions, per https://github.com/prebid/prebid-server/issues/2190.


Patch:
diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go
index 9ece01db..2c29e69a 100644
--- a/adapters/adkernelAdn/adkernelAdn.go
+++ b/adapters/adkernelAdn/adkernelAdn.go
@@ -15,8 +15,6 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
-const defaultDomain string = "tag.adkernel.com"
-
 type adkernelAdnAdapter struct {
 	EndpointTemplate *template.Template
 }
@@ -103,7 +101,6 @@ func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkern
 			res[impExt] = make([]openrtb2.Imp, 0)
 		}
 		res[impExt] = append(res[impExt], imp)
-
 	}
 	return res, errors
 }
@@ -212,12 +209,8 @@ func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext
 
 // Builds enpoint url based on adapter-specific pub settings from imp.ext
 func (adapter *adkernelAdnAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdkernelAdn) (string, error) {
-	reqHost := defaultDomain
-	if params.Host != "" {
-		reqHost = params.Host
-	}
 	pubIDStr := strconv.Itoa(params.PublisherID)
-	endpointParams := macros.EndpointTemplateParams{Host: reqHost, PublisherID: pubIDStr}
+	endpointParams := macros.EndpointTemplateParams{PublisherID: pubIDStr}
 	return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams)
 }
 
diff --git a/amp/parse.go b/amp/parse.go
index 05e43aca..1ce19a76 100644
--- a/amp/parse.go
+++ b/amp/parse.go
@@ -9,7 +9,7 @@ import (
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 )
 
-// Params defines the paramters of an AMP request.
+// Params defines the parameters of an AMP request.
 type Params struct {
 	Account         string
 	CanonicalURL    string
@@ -31,7 +31,7 @@ type Size struct {
 	Width          int64
 }
 
-// ParseParams parses the AMP paramters from a HTTP request.
+// ParseParams parses the AMP parameters from a HTTP request.
 func ParseParams(httpRequest *http.Request) (Params, error) {
 	query := httpRequest.URL.Query()
 
@@ -104,7 +104,7 @@ func chooseConsent(consent, gdprConsent string) string {
 		return consent
 	}
 
-	// Fallback to 'gdpr_consent' for compatability until it's no longer used. This was our original
+	// Fallback to 'gdpr_consent' for compatibility until it's no longer used. This was our original
 	// implementation before the same AMP macro was reused for CCPA.
 	return gdprConsent
 }
diff --git a/config/accounts.go b/config/accounts.go
index 0e6c4114..ae9d656e 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -13,15 +13,24 @@ const (
 
 // Account represents a publisher account configuration
 type Account struct {
-	ID                 string      `mapstructure:"id" json:"id"`
-	Disabled           bool        `mapstructure:"disabled" json:"disabled"`
-	CacheTTL           DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
-	EventsEnabled      bool        `mapstructure:"events_enabled" json:"events_enabled"`
-	CCPA               AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
-	GDPR               AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
-	DebugAllow         bool        `mapstructure:"debug_allow" json:"debug_allow"`
-	DefaultIntegration string      `mapstructure:"default_integration" json:"default_integration"`
-	Events             Events      `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
+	ID                      string      `mapstructure:"id" json:"id"`
+	Disabled                bool        `mapstructure:"disabled" json:"disabled"`
+	CacheTTL                DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
+	EventsEnabled           bool        `mapstructure:"events_enabled" json:"events_enabled"`
+	CCPA                    AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
+	GDPR                    AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
+	DebugAllow              bool        `mapstructure:"debug_allow" json:"debug_allow"`
+	DefaultIntegration      string      `mapstructure:"default_integration" json:"default_integration"`
+	CookieSync              CookieSync  `mapstructure:"cookie_sync" json:"cookie_sync"`
+	Events                  Events      `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
+	TruncateTargetAttribute *int        `mapstructure:"truncate_target_attr" json:"truncate_target_attr"`
+}
+
+// CookieSync represents the account-level defaults for the cookie sync endpoint.
+type CookieSync struct {
+	DefaultLimit    int  `mapstructure:"default_limit" json:"default_limit"`
+	MaxLimit        int  `mapstructure:"max_limit" json:"max_limit"`
+	DefaultCoopSync bool `mapstructure:"default_coop_sync" json:"default_coop_sync"`
 }
 
 // AccountCCPA represents account-specific CCPA configuration
diff --git a/config/config.go b/config/config.go
index 57c9b5a6..9324acce 100644
--- a/config/config.go
+++ b/config/config.go
@@ -819,7 +819,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1")
 	v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json")
 	v.SetDefault("adapters.adkernel.endpoint", "https://pbs.adksrv.com/hb?zone={{.ZoneID}}")
-	v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}")
+	v.SetDefault("adapters.adkerneladn.endpoint", "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}")
 	v.SetDefault("adapters.adman.endpoint", "http://pub.admanmedia.com/?c=o&m=ortb")
 	v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx")
 	v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}")
diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go
index 079348be..12adc8ad 100644
--- a/endpoints/cookie_sync.go
+++ b/endpoints/cookie_sync.go
@@ -8,17 +8,21 @@ import (
 	"io/ioutil"
 	"net/http"
 	"strconv"
+	"strings"
 
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
+	accountService "github.com/prebid/prebid-server/account"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/privacy"
 	"github.com/prebid/prebid-server/privacy/ccpa"
 	gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/usersync"
 )
 
@@ -28,6 +32,8 @@ var (
 	errCookieSyncGDPRConsentMissing                = errors.New("gdpr_consent is required if gdpr=1")
 	errCookieSyncGDPRConsentMissingSignalAmbiguous = errors.New("gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request")
 	errCookieSyncInvalidBiddersType                = errors.New("invalid bidders type. must either be a string '*' or a string array of bidders")
+	errCookieSyncAccountBlocked                    = errors.New("account is disabled, please reach out to the prebid server host")
+	errCookieSyncAccountInvalid                    = errors.New("account must be valid if provided, please reach out to the prebid server host")
 )
 
 var cookieSyncBidderFilterAllowAll = usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude)
@@ -38,6 +44,7 @@ func NewCookieSyncEndpoint(
 	gdprPermissions gdpr.Permissions,
 	metrics metrics.MetricsEngine,
 	pbsAnalytics analytics.PBSAnalyticsModule,
+	accountsFetcher stored_requests.AccountFetcher,
 	bidders map[string]openrtb_ext.BidderName) HTTPRouterHandler {
 
 	bidderHashSet := make(map[string]struct{}, len(bidders))
@@ -46,38 +53,38 @@ func NewCookieSyncEndpoint(
 	}
 
 	return &cookieSyncEndpoint{
-		chooser:          usersync.NewChooser(syncersByBidder),
-		config:           config.UserSync,
-		hostCookieConfig: &config.HostCookie,
+		chooser: usersync.NewChooser(syncersByBidder),
+		config:  config,
 		privacyConfig: usersyncPrivacyConfig{
 			gdprConfig:      config.GDPR,
 			gdprPermissions: gdprPermissions,
 			ccpaEnforce:     config.CCPA.Enforce,
 			bidderHashSet:   bidderHashSet,
 		},
-		metrics:      metrics,
-		pbsAnalytics: pbsAnalytics,
+		metrics:         metrics,
+		pbsAnalytics:    pbsAnalytics,
+		accountsFetcher: accountsFetcher,
 	}
 }
 
 type cookieSyncEndpoint struct {
-	chooser          usersync.Chooser
-	config           config.UserSync
-	hostCookieConfig *config.HostCookie
-	privacyConfig    usersyncPrivacyConfig
-	metrics          metrics.MetricsEngine
-	pbsAnalytics     analytics.PBSAnalyticsModule
+	chooser         usersync.Chooser
+	config          *config.Configuration
+	privacyConfig   usersyncPrivacyConfig
+	metrics         metrics.MetricsEngine
+	pbsAnalytics    analytics.PBSAnalyticsModule
+	accountsFetcher stored_requests.AccountFetcher
 }
 
 func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 	request, privacyPolicies, err := c.parseRequest(r)
 	if err != nil {
-		c.metrics.RecordCookieSync(metrics.CookieSyncBadRequest)
+		c.writeParseRequestErrorMetrics(err)
 		c.handleError(w, err, http.StatusBadRequest)
 		return
 	}
 
-	cookie := usersync.ParseCookieFromRequest(r, c.hostCookieConfig)
+	cookie := usersync.ParseCookieFromRequest(r, &c.config.HostCookie)
 
 	result := c.chooser.Choose(request, cookie)
 	switch result.Status {
@@ -125,6 +132,11 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 		}
 	}
 
+	request, err = c.setLimit(request)
+	if err != nil {
+		return usersync.Request{}, privacy.Policies{}, err
+	}
+
 	privacyPolicies := privacy.Policies{
 		GDPR: gdprPrivacy.Policy{
 			Signal:  gdprString,
@@ -154,8 +166,8 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 	rx := usersync.Request{
 		Bidders: request.Bidders,
 		Cooperative: usersync.Cooperative{
-			Enabled:        (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.Cooperative.EnabledByDefault),
-			PriorityGroups: c.config.Cooperative.PriorityGroups,
+			Enabled:        (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault),
+			PriorityGroups: c.config.UserSync.Cooperative.PriorityGroups,
 		},
 		Limit: request.Limit,
 		Privacy: usersyncPrivacy{
@@ -169,6 +181,39 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 	return rx, privacyPolicies, nil
 }
 
+func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) {
+	switch err {
+	case errCookieSyncAccountBlocked:
+		c.metrics.RecordCookieSync(metrics.CookieSyncAccountBlocked)
+	case errCookieSyncAccountInvalid:
+		c.metrics.RecordCookieSync(metrics.CookieSyncAccountInvalid)
+	default:
+		c.metrics.RecordCookieSync(metrics.CookieSyncBadRequest)
+	}
+}
+
+func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest) (cookieSyncRequest, error) {
+	if request.Account != "" {
+		accountInfo, errs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account)
+		if len(errs) > 0 {
+			return request, combineErrors(errs)
+		}
+		if request.Limit <= 0 {
+			request.Limit = accountInfo.CookieSync.DefaultLimit
+		}
+		if request.Limit <= 0 || request.Limit > accountInfo.CookieSync.MaxLimit {
+			request.Limit = accountInfo.CookieSync.MaxLimit
+		}
+		if request.Limit < 0 {
+			request.Limit = 0
+		}
+		if request.CooperativeSync == nil {
+			request.CooperativeSync = &accountInfo.CookieSync.DefaultCoopSync
+		}
+	}
+	return request, nil
+}
+
 func parseTypeFilter(request *cookieSyncRequestFilterSettings) (usersync.SyncTypeFilter, error) {
 	syncTypeFilter := usersync.SyncTypeFilter{
 		IFrame:   cookieSyncBidderFilterAllowAll,
@@ -237,6 +282,23 @@ func (c *cookieSyncEndpoint) handleError(w http.ResponseWriter, err error, httpS
 	})
 }
 
+func combineErrors(errs []error) error {
+	var errorStrings []string
+	for _, err := range errs {
+		// preserve knowledge of special account errors
+		switch errortypes.ReadCode(err) {
+		case errortypes.BlacklistedAcctErrorCode:
+			return errCookieSyncAccountBlocked
+		case errortypes.AcctRequiredErrorCode:
+			return errCookieSyncAccountInvalid
+		}
+
+		errorStrings = append(errorStrings, err.Error())
+	}
+	combinedErrors := strings.Join(errorStrings, " ")
+	return errors.New(combinedErrors)
+}
+
 func (c *cookieSyncEndpoint) writeBidderMetrics(biddersEvaluated []usersync.BidderEvaluation) {
 	for _, bidder := range biddersEvaluated {
 		switch bidder.Status {
@@ -319,6 +381,7 @@ type cookieSyncRequest struct {
 	Limit           int                              `json:"limit"`
 	CooperativeSync *bool                            `json:"coopSync"`
 	FilterSettings  *cookieSyncRequestFilterSettings `json:"filterSettings"`
+	Account         string                           `json:"account"`
 }
 
 type cookieSyncRequestFilterSettings struct {
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 074f6739..42caa3a7 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -940,7 +940,7 @@ func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.Cont
 
 func validateNativePlacementType(pt native1.PlacementType, impIndex int) error {
 	if pt == 0 {
-		// Placement Type is only reccomended, not required.
+		// Placement Type is only recommended, not required.
 		return nil
 	}
 	if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 08fd3117..80820ac1 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -158,7 +158,7 @@ type AuctionRequest struct {
 	GlobalPrivacyControlHeader string
 	ImpExtInfoMap              map[string]ImpExtInfo
 
-	// LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests
+	// LegacyLabels is included here for temporary compatibility with cleanOpenRTBRequests
 	// in HoldAuction until we get to factoring it away. Do not use for anything new.
 	LegacyLabels   metrics.Labels
 	FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData
@@ -287,7 +287,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 				errs = append(errs, cacheErrs...)
 			}
 
-			targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory)
+			targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute)
 
 		}
 		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs)
diff --git a/exchange/targeting.go b/exchange/targeting.go
index f00e43d2..00008d29 100644
--- a/exchange/targeting.go
+++ b/exchange/targeting.go
@@ -35,7 +35,7 @@ type targetData struct {
 // The one exception is the `hb_cache_id` key. Since our APIs explicitly document cache keys to be on a "best effort" basis,
 // it's ok if those stay in the auction. For now, this method implements a very naive cache strategy.
 // In the future, we should implement a more clever retry & backoff strategy to balance the success rate & performance.
-func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMapping map[string]string) {
+func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMapping map[string]string, truncateTargetAttr *int) {
 	for impId, topBidsPerImp := range auc.winningBidsByBidder {
 		overallWinner := auc.winningBids[impId]
 		for bidderName, topBidPerBidder := range topBidsPerImp {
@@ -43,38 +43,38 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 
 			targets := make(map[string]string, 10)
 			if cpm, ok := auc.roundedPrices[topBidPerBidder]; ok {
-				targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, bidderName, isOverallWinner, truncateTargetAttr)
 			}
-			targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(bidderName), bidderName, isOverallWinner)
+			targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(bidderName), bidderName, isOverallWinner, truncateTargetAttr)
 			if hbSize := makeHbSize(topBidPerBidder.bid); hbSize != "" {
-				targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if cacheID, ok := auc.cacheIds[topBidPerBidder.bid]; ok {
-				targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if vastID, ok := auc.vastCacheIds[topBidPerBidder.bid]; ok {
-				targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if targData.includeFormat {
-				targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBidPerBidder.bidType), bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBidPerBidder.bidType), bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			if targData.cacheHost != "" {
-				targData.addKeys(targets, openrtb_ext.HbConstantCacheHostKey, targData.cacheHost, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbConstantCacheHostKey, targData.cacheHost, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if targData.cachePath != "" {
-				targData.addKeys(targets, openrtb_ext.HbConstantCachePathKey, targData.cachePath, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbConstantCachePathKey, targData.cachePath, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			if deal := topBidPerBidder.bid.DealID; len(deal) > 0 {
-				targData.addKeys(targets, openrtb_ext.HbDealIDConstantKey, deal, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbDealIDConstantKey, deal, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			if isApp {
-				targData.addKeys(targets, openrtb_ext.HbEnvKey, openrtb_ext.HbEnvKeyApp, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbEnvKey, openrtb_ext.HbEnvKeyApp, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if len(categoryMapping) > 0 {
-				targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBidPerBidder.bid.ID], bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBidPerBidder.bid.ID], bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			topBidPerBidder.bidTargets = targets
@@ -82,9 +82,18 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 	}
 }
 
-func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool) {
+func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool, truncateTargetAttr *int) {
+	var maxLength int
+	if truncateTargetAttr != nil {
+		maxLength = *truncateTargetAttr
+		if maxLength < 0 {
+			maxLength = MaxKeyLength
+		}
+	} else {
+		maxLength = MaxKeyLength
+	}
 	if targData.includeBidderKeys {
-		keys[key.BidderKey(bidderName, MaxKeyLength)] = value
+		keys[key.BidderKey(bidderName, maxLength)] = value
 	}
 	if targData.includeWinners && overallWinner {
 		keys[string(key)] = value
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 9d16143f..b70907ea 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -300,6 +300,8 @@ const (
 	CookieSyncBadRequest            CookieSyncStatus = "bad_request"
 	CookieSyncOptOut                CookieSyncStatus = "opt_out"
 	CookieSyncGDPRHostCookieBlocked CookieSyncStatus = "gdpr_blocked_host_cookie"
+	CookieSyncAccountBlocked        CookieSyncStatus = "acct_blocked"
+	CookieSyncAccountInvalid        CookieSyncStatus = "acct_invalid"
 )
 
 // CookieSyncStatuses returns possible cookie sync statuses.
@@ -404,7 +406,7 @@ type MetricsEngine interface {
 	RecordStoredDataError(labels StoredDataLabels)
 	RecordPrebidCacheRequestTime(success bool, length time.Duration)
 	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
-	RecordTimeoutNotice(sucess bool)
+	RecordTimeoutNotice(success bool)
 	RecordRequestPrivacy(privacy PrivacyLabels)
 	RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName)
 }
diff --git a/openrtb_ext/imp_adkernelAdn.go b/openrtb_ext/imp_adkernelAdn.go
index 803f3fa4..fee22318 100644
--- a/openrtb_ext/imp_adkernelAdn.go
+++ b/openrtb_ext/imp_adkernelAdn.go
@@ -2,6 +2,5 @@ package openrtb_ext
 
 // ExtImpAdkernelAdn defines the contract for bidrequest.imp[i].ext.adkernelAdn
 type ExtImpAdkernelAdn struct {
-	PublisherID int    `json:"pubId"`
-	Host        string `json:"host,omitempty"`
+	PublisherID int `json:"pubId"`
 }
diff --git a/router/router.go b/router/router.go
index e49f88b1..eb887c02 100644
--- a/router/router.go
+++ b/router/router.go
@@ -235,7 +235,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R
 	r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(bidderInfos, defaultAliases))
 	r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(bidderInfos, cfg.Adapters, defaultAliases))
 	r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases))
-	r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, activeBidders).Handle)
+	r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, accounts, activeBidders).Handle)
 	r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse))
 	r.GET("/", serveIndex)
 	r.Handler("GET", "/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev))
diff --git a/static/bidder-params/adkernelAdn.json b/static/bidder-params/adkernelAdn.json
index 6c1afe11..84448245 100644
--- a/static/bidder-params/adkernelAdn.json
+++ b/static/bidder-params/adkernelAdn.json
@@ -9,10 +9,6 @@
         "type": "integer",
         "minimum": 1,
         "description": "Publisher Id to use."
-      },
-      "host": {
-        "type": "string",
-        "description": "Network host to send request"
       }
   },
   "required": ["pubId"]


Test patch:
diff --git a/adapters/adkernelAdn/adkernelAdn_test.go b/adapters/adkernelAdn/adkernelAdn_test.go
index 687f5a94..945a415f 100644
--- a/adapters/adkernelAdn/adkernelAdn_test.go
+++ b/adapters/adkernelAdn/adkernelAdn_test.go
@@ -11,7 +11,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderAdkernelAdn, config.Adapter{
-		Endpoint: "http://{{.Host}}/rtbpub?account={{.PublisherID}}"})
+		Endpoint: "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json
index 71df95b7..2a6db14d 100644
--- a/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json
+++ b/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json
@@ -36,7 +36,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=102",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=102",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json
index 1b5472df..6df3e0f1 100644
--- a/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json
+++ b/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json
@@ -29,7 +29,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json
index 848d0c3d..e60aa06c 100644
--- a/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json
+++ b/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json
@@ -33,7 +33,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=102",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=102",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json b/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json
index 950a6c28..ae71ff41 100644
--- a/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json
+++ b/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json
@@ -26,7 +26,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json b/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json
index a7b3e251..8a25aae0 100644
--- a/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json
+++ b/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json
@@ -26,7 +26,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json b/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json
index 59296dc6..37a5a36d 100644
--- a/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json
+++ b/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json
@@ -37,7 +37,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
             "app": {
             "bundle": "com.rovia.angrybirds",
diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go
index 62bcc08d..7bd476b3 100644
--- a/adapters/infoawarebidder_test.go
+++ b/adapters/infoawarebidder_test.go
@@ -173,7 +173,7 @@ func TestImpFiltering(t *testing.T) {
 		}
 
 		// Extra MakeRequests() call check: our mockBidder returns an adapter request for every imp
-		assert.Len(t, actualAdapterRequests, test.expectedImpLen, "Test failed. Incorrect lenght of filtered imps: %s", test.description)
+		assert.Len(t, actualAdapterRequests, test.expectedImpLen, "Test failed. Incorrect length of filtered imps: %s", test.description)
 	}
 }
 
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index 8cbba3ec..263f8b1f 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -2,6 +2,7 @@ package endpoints
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"io"
 	"net/http"
@@ -12,6 +13,7 @@ import (
 
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -34,6 +36,7 @@ func TestNewCookieSyncEndpoint(t *testing.T) {
 		configCCPAEnforce = true
 		metrics           = metrics.MetricsEngineMock{}
 		analytics         = MockAnalytics{}
+		fetcher           = FakeAccountsFetcher{}
 		bidders           = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")}
 	)
 
@@ -48,21 +51,27 @@ func TestNewCookieSyncEndpoint(t *testing.T) {
 		&gdprPerms,
 		&metrics,
 		&analytics,
+		&fetcher,
 		bidders,
 	)
 
 	expected := &cookieSyncEndpoint{
-		chooser:          usersync.NewChooser(syncersByBidder),
-		config:           configUserSync,
-		hostCookieConfig: &configHostCookie,
+		chooser: usersync.NewChooser(syncersByBidder),
+		config: &config.Configuration{
+			UserSync:   configUserSync,
+			HostCookie: configHostCookie,
+			GDPR:       configGDPR,
+			CCPA:       config.CCPA{Enforce: configCCPAEnforce},
+		},
 		privacyConfig: usersyncPrivacyConfig{
 			gdprConfig:      configGDPR,
 			gdprPermissions: &gdprPerms,
 			ccpaEnforce:     configCCPAEnforce,
 			bidderHashSet:   map[string]struct{}{"bidderA": {}, "bidderB": {}},
 		},
-		metrics:      &metrics,
-		pbsAnalytics: &analytics,
+		metrics:         &metrics,
+		pbsAnalytics:    &analytics,
+		accountsFetcher: &fetcher,
 	}
 
 	assert.Equal(t, expected, endpoint)
@@ -238,8 +247,8 @@ func TestCookieSyncHandle(t *testing.T) {
 		writer := httptest.NewRecorder()
 
 		endpoint := cookieSyncEndpoint{
-			chooser:          FakeChooser{Result: test.givenChooserResult},
-			hostCookieConfig: &config.HostCookie{},
+			chooser: FakeChooser{Result: test.givenChooserResult},
+			config:  &config.Configuration{},
 			privacyConfig: usersyncPrivacyConfig{
 				gdprConfig: config.GDPR{
 					Enabled:      true,
@@ -263,14 +272,15 @@ func TestCookieSyncParseRequest(t *testing.T) {
 	expectedCCPAParsedPolicy, _ := ccpa.Policy{Consent: "1NYN"}.Parse(map[string]struct{}{})
 
 	testCases := []struct {
-		description      string
-		givenConfig      config.UserSync
-		givenBody        io.Reader
-		givenGDPRConfig  config.GDPR
-		givenCCPAEnabled bool
-		expectedError    string
-		expectedPrivacy  privacy.Policies
-		expectedRequest  usersync.Request
+		description          string
+		givenConfig          config.UserSync
+		givenBody            io.Reader
+		givenGDPRConfig      config.GDPR
+		givenCCPAEnabled     bool
+		givenAccountRequired bool
+		expectedError        string
+		expectedPrivacy      privacy.Policies
+		expectedRequest      usersync.Request
 	}{
 		{
 			description: "Complete Request",
@@ -647,18 +657,121 @@ func TestCookieSyncParseRequest(t *testing.T) {
 			givenCCPAEnabled: true,
 			expectedError:    "Failed to read request body",
 		},
+		{
+			description: "Account Defaults - Max Limit + Default Coop",
+			givenBody: strings.NewReader(`{` +
+				`"bidders":["a", "b"],` +
+				`"limit":42,` +
+				`"account":"TestAccount"` +
+				`}`),
+			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
+			givenCCPAEnabled: true,
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedPrivacy: privacy.Policies{},
+			expectedRequest: usersync.Request{
+				Bidders: []string{"a", "b"},
+				Cooperative: usersync.Cooperative{
+					Enabled:        true,
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+				Limit: 30,
+				Privacy: usersyncPrivacy{
+					gdprSignal: gdpr.SignalAmbiguous,
+				},
+				SyncTypeFilter: usersync.SyncTypeFilter{
+					IFrame:   usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+					Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+				},
+			},
+		},
+		{
+			description: "Account Defaults - DefaultLimit",
+			givenBody: strings.NewReader(`{` +
+				`"bidders":["a", "b"],` +
+				`"account":"TestAccount"` +
+				`}`),
+			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
+			givenCCPAEnabled: true,
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					EnabledByDefault: false,
+					PriorityGroups:   [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedPrivacy: privacy.Policies{},
+			expectedRequest: usersync.Request{
+				Bidders: []string{"a", "b"},
+				Cooperative: usersync.Cooperative{
+					Enabled:        true,
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+				Limit: 20,
+				Privacy: usersyncPrivacy{
+					gdprSignal: gdpr.SignalAmbiguous,
+				},
+				SyncTypeFilter: usersync.SyncTypeFilter{
+					IFrame:   usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+					Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+				},
+			},
+		},
+		{
+			description: "Account Defaults - Error",
+			givenBody: strings.NewReader(`{` +
+				`"bidders":["a", "b"],` +
+				`"account":"DisabledAccount"` +
+				`}`),
+			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
+			givenCCPAEnabled: true,
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					EnabledByDefault: false,
+					PriorityGroups:   [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedPrivacy: privacy.Policies{},
+			expectedRequest: usersync.Request{
+				Bidders: []string{"a", "b"},
+				Cooperative: usersync.Cooperative{
+					Enabled:        true,
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+				Limit: 20,
+				Privacy: usersyncPrivacy{
+					gdprSignal: gdpr.SignalAmbiguous,
+				},
+				SyncTypeFilter: usersync.SyncTypeFilter{
+					IFrame:   usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+					Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+				},
+			},
+			expectedError:        errCookieSyncAccountBlocked.Error(),
+			givenAccountRequired: true,
+		},
 	}
 
 	for _, test := range testCases {
 		httpRequest := httptest.NewRequest("POST", "/cookiesync", test.givenBody)
 
 		endpoint := cookieSyncEndpoint{
-			config: test.givenConfig,
+			config: &config.Configuration{
+				UserSync:        test.givenConfig,
+				AccountRequired: test.givenAccountRequired,
+			},
 			privacyConfig: usersyncPrivacyConfig{
 				gdprConfig:  test.givenGDPRConfig,
 				ccpaEnforce: test.givenCCPAEnabled,
 			},
+			accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{
+				"TestAccount":     json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`),
+				"DisabledAccount": json.RawMessage(`{"disabled":true}`),
+			}},
 		}
+		assert.NoError(t, endpoint.config.MarshalAccountDefaults())
 		request, privacyPolicies, err := endpoint.parseRequest(httpRequest)
 
 		if test.expectedError == "" {
@@ -673,6 +786,185 @@ func TestCookieSyncParseRequest(t *testing.T) {
 	}
 }
 
+func TestSetLimit(t *testing.T) {
+	coopSync := true
+
+	testCases := []struct {
+		description     string
+		givenRequest    cookieSyncRequest
+		givenConfig     config.UserSync
+		expectedError   string
+		expectedRequest cookieSyncRequest
+	}{
+		{
+			description: "Test that Max Limit and Default Coop Sync are Applied",
+			givenRequest: cookieSyncRequest{
+				Account: "TestAccount",
+				Limit:   42,
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "TestAccount",
+				Limit:           30,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Test that Default Limit is Applied",
+			givenRequest: cookieSyncRequest{
+				Account: "TestAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "TestAccount",
+				Limit:           20,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Test that max limit is applied when given limit is zero",
+			givenRequest: cookieSyncRequest{
+				Account: "ZeroLimitAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "ZeroLimitAccount",
+				Limit:           30,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Negative Value Check",
+			givenRequest: cookieSyncRequest{
+				Account: "NegativeLimitAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "NegativeLimitAccount",
+				Limit:           0,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Error Handler",
+			givenRequest: cookieSyncRequest{
+				Account: "DisabledAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account: "DisabledAccount",
+			},
+			expectedError: errCookieSyncAccountBlocked.Error(),
+		},
+	}
+
+	for _, test := range testCases {
+		endpoint := cookieSyncEndpoint{
+			config: &config.Configuration{
+				UserSync: test.givenConfig,
+			},
+			accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{
+				"TestAccount":          json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`),
+				"DisabledAccount":      json.RawMessage(`{"disabled":true}`),
+				"ZeroLimitAccount":     json.RawMessage(`{"cookie_sync": {"default_limit": 0, "max_limit": 30, "default_coop_sync": true}}`),
+				"NegativeLimitAccount": json.RawMessage(`{"cookie_sync": {"default_limit": -1, "max_limit": -1, "default_coop_sync": true}}`),
+			}},
+		}
+		assert.NoError(t, endpoint.config.MarshalAccountDefaults())
+		request, err := endpoint.setLimit(test.givenRequest)
+
+		if test.expectedError == "" {
+			assert.NoError(t, err, test.description)
+			assert.Equal(t, test.expectedRequest, request, test.description)
+		} else {
+			assert.EqualError(t, err, test.expectedError, test.description)
+		}
+	}
+}
+
+func TestWriteParseRequestErrorMetrics(t *testing.T) {
+	err := errors.New("anyError")
+
+	mockAnalytics := MockAnalytics{}
+	mockAnalytics.On("LogCookieSyncObject", mock.Anything)
+	writer := httptest.NewRecorder()
+
+	endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics}
+	endpoint.handleError(writer, err, 418)
+
+	assert.Equal(t, writer.Code, 418)
+	assert.Equal(t, writer.Body.String(), "anyError\n")
+	mockAnalytics.AssertCalled(t, "LogCookieSyncObject", &analytics.CookieSyncObject{
+		Status:       418,
+		Errors:       []error{err},
+		BidderStatus: []*analytics.CookieSyncBidder{},
+	})
+}
+
+func TestCookieSyncWriteParseRequestErrorMetrics(t *testing.T) {
+	testCases := []struct {
+		description     string
+		err             error
+		setExpectations func(*metrics.MetricsEngineMock)
+	}{
+		{
+			description: "Account Blocked",
+			err:         errCookieSyncAccountBlocked,
+			setExpectations: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordCookieSync", metrics.CookieSyncAccountBlocked).Once()
+			},
+		},
+		{
+			description: "Account Invalid",
+			err:         errCookieSyncAccountInvalid,
+			setExpectations: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordCookieSync", metrics.CookieSyncAccountInvalid).Once()
+			},
+		},
+		{
+			description: "No Special Case",
+			err:         errors.New("any error"),
+			setExpectations: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once()
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		mockMetrics := metrics.MetricsEngineMock{}
+		test.setExpectations(&mockMetrics)
+
+		endpoint := &cookieSyncEndpoint{metrics: &mockMetrics}
+		endpoint.writeParseRequestErrorMetrics(test.err)
+
+		mockMetrics.AssertExpectations(t)
+	}
+}
+
 func TestParseTypeFilter(t *testing.T) {
 	testCases := []struct {
 		description    string
@@ -1242,6 +1534,50 @@ func TestUsersyncPrivacyCCPAAllowsBidderSync(t *testing.T) {
 	}
 }
 
+func TestCombineErrors(t *testing.T) {
+	testCases := []struct {
+		description    string
+		givenErrorList []error
+		expectedError  error
+	}{
+		{
+			description:    "No errors given",
+			givenErrorList: []error{},
+			expectedError:  errors.New(""),
+		},
+		{
+			description:    "One error given",
+			givenErrorList: []error{errors.New("Error #1")},
+			expectedError:  errors.New("Error #1"),
+		},
+		{
+			description:    "Multiple errors given",
+			givenErrorList: []error{errors.New("Error #1"), errors.New("Error #2")},
+			expectedError:  errors.New("Error #1 Error #2"),
+		},
+		{
+			description:    "Special Case: blocked (rejected via block list)",
+			givenErrorList: []error{&errortypes.BlacklistedAcct{}},
+			expectedError:  errCookieSyncAccountBlocked,
+		},
+		{
+			description:    "Special Case: invalid (rejected via allow list)",
+			givenErrorList: []error{&errortypes.AcctRequired{}},
+			expectedError:  errCookieSyncAccountInvalid,
+		},
+		{
+			description:    "Special Case: multiple special cases, first one wins",
+			givenErrorList: []error{&errortypes.BlacklistedAcct{}, &errortypes.AcctRequired{}},
+			expectedError:  errCookieSyncAccountBlocked,
+		},
+	}
+
+	for _, test := range testCases {
+		combinedErrors := combineErrors(test.givenErrorList)
+		assert.Equal(t, test.expectedError, combinedErrors, test.description)
+	}
+}
+
 type FakeChooser struct {
 	Result usersync.Result
 }
@@ -1321,6 +1657,17 @@ func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCore
 	return args.Bool(0), args.Bool(1), args.Bool(2), args.Error(3)
 }
 
+type FakeAccountsFetcher struct {
+	AccountData map[string]json.RawMessage
+}
+
+func (f FakeAccountsFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	if account, ok := f.AccountData[accountID]; ok {
+		return account, nil
+	}
+	return nil, []error{errors.New("Account not found")}
+}
+
 // ErrReader returns an io.Reader that returns 0, err from all Read calls. This is added in
 // Go 1.16. Copied here for now until we switch over.
 func ErrReader(err error) io.Reader {
diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go
index 7110e188..9be25f44 100644
--- a/endpoints/info/bidders_test.go
+++ b/endpoints/info/bidders_test.go
@@ -179,7 +179,7 @@ func TestBiddersHandler(t *testing.T) {
 		expectedHeaders http.Header
 	}{
 		{
-			description:     "No Query Paramters - Backwards Compatability",
+			description:     "No Query Parameters - Backwards Compatibility",
 			givenURL:        "/info/bidders",
 			expectedStatus:  http.StatusOK,
 			expectedBody:    `["a","b","x","y"]`,
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 48e054c7..f2726de0 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -247,6 +247,7 @@ type TargetingTestData struct {
 	IsApp                      bool
 	CategoryMapping            map[string]string
 	ExpectedBidTargetsByBidder map[string]map[openrtb_ext.BidderName]map[string]string
+	TruncateTargetAttr         *int
 }
 
 var bid123 *openrtb2.Bid = &openrtb2.Bid{
@@ -261,6 +262,9 @@ var bid084 *openrtb2.Bid = &openrtb2.Bid{
 	Price: 0.84,
 }
 
+var truncateTargetAttrValueLess int = 10
+var truncateTargetAttrValueGreater int = 25
+var truncateTargetAttrValueNegative int = -1
 var TargetingTests []TargetingTestData = []TargetingTestData{
 	{
 		Description: "Targeting winners only (most basic targeting example)",
@@ -291,6 +295,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				openrtb_ext.BidderRubicon: {},
 			},
 		},
+		TruncateTargetAttr: nil,
 	},
 	{
 		Description: "Targeting on bidders only",
@@ -324,6 +329,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				},
 			},
 		},
+		TruncateTargetAttr: nil,
 	},
 	{
 		Description: "Full basic targeting with hd_format",
@@ -364,6 +370,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				},
 			},
 		},
+		TruncateTargetAttr: nil,
 	},
 	{
 		Description: "Cache and deal targeting test",
@@ -410,6 +417,109 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				},
 			},
 		},
+		TruncateTargetAttr: nil,
+	},
+	{
+		Description: "Truncate Targeting Attribute value is given and is less than const MaxKeyLength",
+		TargetData: targetData{
+			priceGranularity:  openrtb_ext.PriceGranularityFromString("med"),
+			includeBidderKeys: true,
+		},
+		Auction: auction{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				"ImpId-1": {
+					openrtb_ext.BidderAppnexus: {
+						bid:     bid123,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+					openrtb_ext.BidderRubicon: {
+						bid:     bid084,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+		},
+		ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{
+			"ImpId-1": {
+				openrtb_ext.BidderAppnexus: {
+					"hb_bidder_": "appnexus",
+					"hb_pb_appn": "1.20",
+				},
+				openrtb_ext.BidderRubicon: {
+					"hb_bidder_": "rubicon",
+					"hb_pb_rubi": "0.80",
+				},
+			},
+		},
+		TruncateTargetAttr: &truncateTargetAttrValueLess,
+	},
+	{
+		Description: "Truncate Targeting Attribute value is given and is greater than const MaxKeyLength",
+		TargetData: targetData{
+			priceGranularity:  openrtb_ext.PriceGranularityFromString("med"),
+			includeBidderKeys: true,
+		},
+		Auction: auction{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				"ImpId-1": {
+					openrtb_ext.BidderAppnexus: {
+						bid:     bid123,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+					openrtb_ext.BidderRubicon: {
+						bid:     bid084,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+		},
+		ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{
+			"ImpId-1": {
+				openrtb_ext.BidderAppnexus: {
+					"hb_bidder_appnexus": "appnexus",
+					"hb_pb_appnexus":     "1.20",
+				},
+				openrtb_ext.BidderRubicon: {
+					"hb_bidder_rubicon": "rubicon",
+					"hb_pb_rubicon":     "0.80",
+				},
+			},
+		},
+		TruncateTargetAttr: &truncateTargetAttrValueGreater,
+	},
+	{
+		Description: "Truncate Targeting Attribute value is given and is negative",
+		TargetData: targetData{
+			priceGranularity:  openrtb_ext.PriceGranularityFromString("med"),
+			includeBidderKeys: true,
+		},
+		Auction: auction{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				"ImpId-1": {
+					openrtb_ext.BidderAppnexus: {
+						bid:     bid123,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+					openrtb_ext.BidderRubicon: {
+						bid:     bid084,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+		},
+		ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{
+			"ImpId-1": {
+				openrtb_ext.BidderAppnexus: {
+					"hb_bidder_appnexus": "appnexus",
+					"hb_pb_appnexus":     "1.20",
+				},
+				openrtb_ext.BidderRubicon: {
+					"hb_bidder_rubicon": "rubicon",
+					"hb_pb_rubicon":     "0.80",
+				},
+			},
+		},
+		TruncateTargetAttr: &truncateTargetAttrValueNegative,
 	},
 }
 
@@ -433,7 +543,7 @@ func TestSetTargeting(t *testing.T) {
 		}
 		auc.winningBids = winningBids
 		targData := test.TargetData
-		targData.setTargeting(auc, test.IsApp, test.CategoryMapping)
+		targData.setTargeting(auc, test.IsApp, test.CategoryMapping, test.TruncateTargetAttr)
 		for imp, targetsByBidder := range test.ExpectedBidTargetsByBidder {
 			for bidder, expected := range targetsByBidder {
 				assert.Equal(t,
diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go
index 1198d0bb..13ac1f5c 100644
--- a/privacy/scrubber_test.go
+++ b/privacy/scrubber_test.go
@@ -37,7 +37,7 @@ func TestScrubDevice(t *testing.T) {
 		geo         ScrubStrategyGeo
 	}{
 		{
-			description: "All Strageties - None",
+			description: "All Strategies - None",
 			expected:    device,
 			id:          ScrubStrategyDeviceIDNone,
 			ipv4:        ScrubStrategyIPV4None,
@@ -45,7 +45,7 @@ func TestScrubDevice(t *testing.T) {
 			geo:         ScrubStrategyGeoNone,
 		},
 		{
-			description: "All Strageties - Strictest",
+			description: "All Strategies - Strictest",
 			expected: &openrtb2.Device{
 				DIDMD5:   "",
 				DIDSHA1:  "",
diff --git a/stored_requests/data/by_id/accounts/test.json b/stored_requests/data/by_id/accounts/test.json
index 76bafff7..07492f75 100644
--- a/stored_requests/data/by_id/accounts/test.json
+++ b/stored_requests/data/by_id/accounts/test.json
@@ -10,5 +10,10 @@
     },
     "events": {
         "enabled": true
+    },
+    "cookie_sync": {
+        "default_limt": 20,
+        "max_limit": 50,
+        "default_coop_sync": true
     }
 }
diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go
index 27551c9a..c02eec15 100644
--- a/util/task/ticker_task_test.go
+++ b/util/task/ticker_task_test.go
@@ -1,6 +1,7 @@
 package task_test
 
 import (
+	"sync"
 	"testing"
 	"time"
 
@@ -9,55 +10,78 @@ import (
 )
 
 type MockRunner struct {
-	RunCount int
+	ExpectationMet chan struct{}
+	actualCalls    int
+	expectedCalls  int
+	mutex          sync.Mutex
 }
 
-func (mcc *MockRunner) Run() error {
-	mcc.RunCount++
-	return nil
+func NewMockRunner(expectedCalls int) *MockRunner {
+	return &MockRunner{
+		ExpectationMet: make(chan struct{}),
+		expectedCalls:  expectedCalls,
+	}
 }
 
-func TestStartWithSingleRun(t *testing.T) {
-	// Setup:
-	runner := &MockRunner{RunCount: 0}
-	interval := 0 * time.Millisecond
-	ticker := task.NewTickerTask(interval, runner)
+func (m *MockRunner) Run() error {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	m.actualCalls++
 
-	// Execute:
-	ticker.Start()
-	time.Sleep(10 * time.Millisecond)
+	if m.expectedCalls == m.actualCalls {
+		close(m.ExpectationMet)
+	}
 
-	// Verify:
-	assert.Equal(t, runner.RunCount, 1, "runner should have run one time")
+	return nil
 }
 
-func TestStartWithPeriodicRun(t *testing.T) {
-	// Setup:
-	runner := &MockRunner{RunCount: 0}
-	interval := 10 * time.Millisecond
+func (m *MockRunner) RunCount() int {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	return m.actualCalls
+}
+
+func TestStartWithSingleRun(t *testing.T) {
+	// Setup Initial Run Only:
+	expectedRuns := 1
+	runner := NewMockRunner(expectedRuns)
+	interval := 0 * time.Millisecond // forces a single run
 	ticker := task.NewTickerTask(interval, runner)
 
 	// Execute:
 	ticker.Start()
-	time.Sleep(25 * time.Millisecond)
-	ticker.Stop()
 
 	// Verify:
-	assert.Equal(t, runner.RunCount, 3, "runner should have run three times")
+	select {
+	case <-runner.ExpectationMet:
+	case <-time.After(250 * time.Millisecond):
+		assert.Failf(t, "Runner Calls", "expected %v calls, observed %v calls", expectedRuns, runner.RunCount())
+	}
+
+	// Verify No Additional Runs:
+	time.Sleep(50 * time.Millisecond)
+	assert.Equal(t, expectedRuns, runner.RunCount(), "runner should not run after Stop is called")
 }
 
-func TestStop(t *testing.T) {
-	// Setup:
-	runner := &MockRunner{RunCount: 0}
+func TestStartWithPeriodicRun(t *testing.T) {
+	// Setup Initial Run + One Periodic Run:
+	expectedRuns := 2
+	runner := NewMockRunner(expectedRuns)
 	interval := 10 * time.Millisecond
 	ticker := task.NewTickerTask(interval, runner)
 
 	// Execute:
 	ticker.Start()
-	time.Sleep(25 * time.Millisecond)
-	ticker.Stop()
-	time.Sleep(25 * time.Millisecond) // wait in case stop failed so additional runs can happen
 
-	// Verify:
-	assert.Equal(t, runner.RunCount, 3, "runner should have run three times")
+	// Verify Expected Runs:
+	select {
+	case <-runner.ExpectationMet:
+		ticker.Stop()
+	case <-time.After(250 * time.Millisecond):
+		assert.Failf(t, "Runner Calls", "expected %v calls, observed %v calls", expectedRuns, runner.RunCount())
+	}
+
+	// Verify No Additional Runs After Stop:
+	time.Sleep(50 * time.Millisecond)
+	assert.Equal(t, expectedRuns, runner.RunCount(), "runner should not run after Stop is called")
 }