Skip to content

Commit

Permalink
fix: fix issues with setting numintervals for leo (#2651)
Browse files Browse the repository at this point in the history
* fix: allow meaningful zero for issuer configuration in order api for premium

* fix: remove irrelevant fields

* fix: use better method for finding item

* fix: implement truncating logic

* test: add tests

* test: add tests
  • Loading branch information
pavelbrm committed Sep 9, 2024
1 parent d35e027 commit be56e4a
Show file tree
Hide file tree
Showing 12 changed files with 558 additions and 188 deletions.
70 changes: 49 additions & 21 deletions services/skus/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,31 +240,33 @@ func (s *Service) CreateOrderItemCredentials(ctx context.Context, orderID, itemI
return model.ErrOrderNotPaid
}

var orderItem *OrderItem
for _, item := range order.Items {
if item.ID == itemID {
orderItem = &item
break
}
item, ok := order.HasItem(itemID)
if !ok {
return errItemDoesNotExist
}

if orderItem == nil {
return errItemDoesNotExist
nbcreds := len(blindedCreds)
if nbcreds == 0 {
return model.ErrTLV2InvalidCredNum
}

if err := s.doCredentialsExist(ctx, requestID, orderItem, blindedCreds); err != nil {
if err := s.doCredentialsExist(ctx, requestID, item, blindedCreds[0]); err != nil {
if errors.Is(err, errCredsAlreadySubmitted) {
return nil
}

return err
}

if err := checkNumBlindedCreds(order, orderItem, len(blindedCreds)); err != nil {
// Check if truncation is necessary for in case of a Leo order with 8*192=1536 nbcreds.
// If yes, then truncate credentials to the desired number, 576.
creds := truncateTLV2BCreds(order, item, nbcreds, blindedCreds)

if err := checkNumBlindedCreds(order, item, len(creds)); err != nil {
return err
}

issuerID, err := encodeIssuerID(order.MerchantID, orderItem.SKU)
issuerID, err := encodeIssuerID(order.MerchantID, item.SKU)
if err != nil {
return errorutils.Wrap(err, "error encoding issuer name")
}
Expand All @@ -275,10 +277,10 @@ func (s *Service) CreateOrderItemCredentials(ctx context.Context, orderID, itemI
}

metadata := &Metadata{
ItemID: orderItem.ID,
ItemID: item.ID,
OrderID: order.ID,
IssuerID: issuer.ID,
CredentialType: orderItem.CredentialType,
CredentialType: item.CredentialType,
}

associatedData, err := json.Marshal(metadata)
Expand All @@ -292,46 +294,46 @@ func (s *Service) CreateOrderItemCredentials(ctx context.Context, orderID, itemI
{
IssuerType: issuerID,
IssuerCohort: defaultCohort,
BlindedTokens: blindedCreds,
BlindedTokens: creds,
AssociatedData: associatedData,
},
},
}

if err := s.Datastore.InsertSigningOrderRequestOutbox(ctx, requestID, order.ID, orderItem.ID, signReq); err != nil {
if err := s.Datastore.InsertSigningOrderRequestOutbox(ctx, requestID, order.ID, item.ID, signReq); err != nil {
return fmt.Errorf("error inserting signing order request outbox orderID %s: %w", order.ID, err)
}

return nil
}

func (s *Service) doCredentialsExist(ctx context.Context, requestID uuid.UUID, item *model.OrderItem, blindedCreds []string) error {
func (s *Service) doCredentialsExist(ctx context.Context, requestID uuid.UUID, item *model.OrderItem, firstBCred string) error {
switch item.CredentialType {
case timeLimitedV2:
// NOTE: There was a possible race condition that would allow exceeding limits on the number of cred batches.
// The condition is currently mitigated by:
// - checking the number of active batches before accepting a request to create creds;
// - checking the number of active batches before inserting the signed creds.

return s.doTLV2Exist(ctx, requestID, item, blindedCreds)
return s.doTLV2Exist(ctx, requestID, item, firstBCred)
default:
return s.doCredsExist(ctx, item)
}
}

func (s *Service) doTLV2Exist(ctx context.Context, reqID uuid.UUID, item *model.OrderItem, bcreds []string) error {
func (s *Service) doTLV2Exist(ctx context.Context, reqID uuid.UUID, item *model.OrderItem, firstBCred string) error {
now := time.Now()

return s.doTLV2ExistTxTime(ctx, s.Datastore.RawDB(), reqID, item, bcreds, now, now)
return s.doTLV2ExistTxTime(ctx, s.Datastore.RawDB(), reqID, item, firstBCred, now, now)
}

func (s *Service) doTLV2ExistTxTime(ctx context.Context, dbi sqlx.QueryerContext, reqID uuid.UUID, item *model.OrderItem, bcreds []string, from, to time.Time) error {
func (s *Service) doTLV2ExistTxTime(ctx context.Context, dbi sqlx.QueryerContext, reqID uuid.UUID, item *model.OrderItem, firstBCred string, from, to time.Time) error {
if item.CredentialType != timeLimitedV2 {
return model.ErrUnsupportedCredType
}

// Check TLV2 to see if we have credentials signed that match incoming blinded tokens.
report, err := s.tlv2Repo.GetCredSubmissionReport(ctx, dbi, item.OrderID, item.ID, reqID, bcreds...)
report, err := s.tlv2Repo.GetCredSubmissionReport(ctx, dbi, item.OrderID, item.ID, reqID, firstBCred)
if err != nil {
return err
}
Expand Down Expand Up @@ -805,3 +807,29 @@ func checkTLV2BatchLimit(lim, nact int) error {

return nil
}

func truncateTLV2BCreds(ord *model.Order, item *model.OrderItem, nSrcCreds int, srcCreds []string) []string {
result := srcCreds
if targetn, ok := shouldTruncateTLV2Creds(ord, item, nSrcCreds); ok {
result = srcCreds[:targetn]
}

return result
}

// shouldTruncateTLV2Creds reports whether supplied blinded tokens should be truncated.
//
// At present, the function is only concerned with Leo.
func shouldTruncateTLV2Creds(ord *model.Order, item *model.OrderItem, ncreds int) (int, bool) {
if !item.IsLeo() {
return 0, false
}

const target = 3 * 192

if ncreds <= target {
return 0, false
}

return target, true
}
195 changes: 195 additions & 0 deletions services/skus/credentials_noint_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package skus

import (
"strconv"
"testing"

uuid "github.com/satori/go.uuid"
should "github.com/stretchr/testify/assert"

"github.com/brave-intl/bat-go/libs/datastore"

"github.com/brave-intl/bat-go/services/skus/model"
)

func TestCheckTLV2BatchLimit(t *testing.T) {
Expand Down Expand Up @@ -52,3 +58,192 @@ func TestCheckTLV2BatchLimit(t *testing.T) {
})
}
}

func TestTruncateTLV2BCreds(t *testing.T) {
type tcGiven struct {
ord *model.Order
item *model.OrderItem
srcCreds []string
}

type testCase struct {
name string
given tcGiven
exp []string
}

tests := []testCase{
{
name: "no_trunc_not_leo",
given: tcGiven{
ord: &model.Order{
ID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
Metadata: datastore.Metadata{
"numIntervals": int(33),
"numPerInterval": int(2),
},
},
item: &model.OrderItem{
ID: uuid.Must(uuid.FromString("f100ded0-0000-4000-a000-000000000000")),
OrderID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
SKU: "brave-vpn-premium",
},
srcCreds: genNumStrings(2 * 33),
},
exp: genNumStrings(2 * 33),
},

{
name: "no_trunc_leo_3_192",
given: tcGiven{
ord: &model.Order{
ID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
Metadata: datastore.Metadata{
"numIntervals": int(3),
"numPerInterval": int(192),
},
},
item: &model.OrderItem{
ID: uuid.Must(uuid.FromString("f100ded0-0000-4000-a000-000000000000")),
OrderID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
SKU: "brave-leo-premium",
},
srcCreds: genNumStrings(3 * 192),
},
exp: genNumStrings(3 * 192),
},

{
name: "trunc_leo_8_192",
given: tcGiven{
ord: &model.Order{
ID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
Metadata: datastore.Metadata{
"numIntervals": int(8),
"numPerInterval": int(192),
},
},
item: &model.OrderItem{
ID: uuid.Must(uuid.FromString("f100ded0-0000-4000-a000-000000000000")),
OrderID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
SKU: "brave-leo-premium",
},
srcCreds: genNumStrings(8 * 192),
},
exp: genNumStrings(3 * 192),
},
}

for i := range tests {
tc := tests[i]

t.Run(tc.name, func(t *testing.T) {
actual := truncateTLV2BCreds(tc.given.ord, tc.given.item, len(tc.given.srcCreds), tc.given.srcCreds)
should.Equal(t, tc.exp, actual)
})
}
}

func TestShouldTruncateTLV2Creds(t *testing.T) {
type tcGiven struct {
ord *model.Order
item *model.OrderItem
ncreds int
}

type tcExpected struct {
val int
ok bool
}

type testCase struct {
name string
given tcGiven
exp tcExpected
}

tests := []testCase{
{
name: "not_leo",
given: tcGiven{
ord: &model.Order{
ID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
Metadata: datastore.Metadata{
"numIntervals": int(33),
"numPerInterval": int(2),
},
},
item: &model.OrderItem{
ID: uuid.Must(uuid.FromString("f100ded0-0000-4000-a000-000000000000")),
OrderID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
SKU: "brave-vpn-premium",
},
ncreds: 2 * 33,
},
exp: tcExpected{},
},

{
name: "false_3_192",
given: tcGiven{
ord: &model.Order{
ID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
Metadata: datastore.Metadata{
"numIntervals": int(3),
"numPerInterval": int(192),
},
},
item: &model.OrderItem{
ID: uuid.Must(uuid.FromString("f100ded0-0000-4000-a000-000000000000")),
OrderID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
SKU: "brave-leo-premium",
},
ncreds: 3 * 192,
},
exp: tcExpected{},
},

{
name: "true_8_192",
given: tcGiven{
ord: &model.Order{
ID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
Metadata: datastore.Metadata{
"numIntervals": int(8),
"numPerInterval": int(192),
},
},
item: &model.OrderItem{
ID: uuid.Must(uuid.FromString("f100ded0-0000-4000-a000-000000000000")),
OrderID: uuid.Must(uuid.FromString("facade00-0000-4000-a000-000000000000")),
SKU: "brave-leo-premium",
},
ncreds: 8 * 192,
},
exp: tcExpected{
val: 3 * 192,
ok: true,
},
},
}

for i := range tests {
tc := tests[i]

t.Run(tc.name, func(t *testing.T) {
actual, ok := shouldTruncateTLV2Creds(tc.given.ord, tc.given.item, tc.given.ncreds)
should.Equal(t, tc.exp.ok, ok)
should.Equal(t, tc.exp.val, actual)
})
}
}

func genNumStrings(n int) []string {
result := make([]string, n)

for i := 0; i < n; i++ {
result[i] = strconv.Itoa(i)
}

return result
}
Loading

0 comments on commit be56e4a

Please sign in to comment.