[ads] Add confirmation_tokens and payment_tokens database tables (#35341)

This commit is contained in:
Terry Mancey
2026-04-07 21:45:12 -05:00
committed by GitHub
parent b232a8d93a
commit 8d1957d2e8
69 changed files with 1006 additions and 2 deletions
@@ -129,6 +129,8 @@ static_library("internal") {
"account/tokens/confirmation_tokens/confirmation_token_info.h",
"account/tokens/confirmation_tokens/confirmation_tokens.cc",
"account/tokens/confirmation_tokens/confirmation_tokens.h",
"account/tokens/confirmation_tokens/confirmation_tokens_database_table.cc",
"account/tokens/confirmation_tokens/confirmation_tokens_database_table.h",
"account/tokens/confirmation_tokens/confirmation_tokens_util.cc",
"account/tokens/confirmation_tokens/confirmation_tokens_util.h",
"account/tokens/confirmation_tokens/confirmation_tokens_value_util.cc",
@@ -141,6 +143,8 @@ static_library("internal") {
"account/tokens/payment_tokens/payment_token_value_util.h",
"account/tokens/payment_tokens/payment_tokens.cc",
"account/tokens/payment_tokens/payment_tokens.h",
"account/tokens/payment_tokens/payment_tokens_database_table.cc",
"account/tokens/payment_tokens/payment_tokens_database_table.h",
"account/tokens/token_generator.cc",
"account/tokens/token_generator.h",
"account/tokens/token_generator_interface.h",
@@ -0,0 +1,233 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_database_table.h"
#include <cstddef>
#include <cstdint>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/strings/string_util.h"
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_token_info.h"
#include "brave/components/brave_ads/core/internal/common/challenge_bypass_ristretto/public_key.h"
#include "brave/components/brave_ads/core/internal/common/challenge_bypass_ristretto/unblinded_token.h"
#include "brave/components/brave_ads/core/internal/common/database/database_column_util.h"
#include "brave/components/brave_ads/core/internal/common/database/database_table_util.h"
#include "brave/components/brave_ads/core/internal/common/database/database_transaction_util.h"
#include "brave/components/brave_ads/core/internal/common/logging_util.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h"
namespace brave_ads::database::table {
namespace {
constexpr char kTableName[] = "confirmation_tokens";
void BindColumnTypes(const mojom::DBActionInfoPtr& mojom_db_action) {
CHECK(mojom_db_action);
mojom_db_action->bind_column_types = {
mojom::DBBindColumnType::kString, // unblinded_token
mojom::DBBindColumnType::kString, // public_key
mojom::DBBindColumnType::kString // signature
};
}
size_t BindColumns(const mojom::DBActionInfoPtr& mojom_db_action,
const ConfirmationTokenList& confirmation_tokens) {
CHECK(mojom_db_action);
CHECK(!confirmation_tokens.empty());
size_t row_count = 0;
int32_t index = 0;
for (const auto& confirmation_token : confirmation_tokens) {
BindColumnString(
mojom_db_action, index++,
confirmation_token.unblinded_token.EncodeBase64().value_or(""));
BindColumnString(mojom_db_action, index++,
confirmation_token.public_key.EncodeBase64().value_or(""));
BindColumnString(mojom_db_action, index++,
confirmation_token.signature_base64);
++row_count;
}
return row_count;
}
ConfirmationTokenInfo FromMojomRow(const mojom::DBRowInfoPtr& mojom_db_row) {
CHECK(mojom_db_row);
ConfirmationTokenInfo confirmation_token;
confirmation_token.unblinded_token =
cbr::UnblindedToken(ColumnString(mojom_db_row, 0));
confirmation_token.public_key = cbr::PublicKey(ColumnString(mojom_db_row, 1));
confirmation_token.signature_base64 = ColumnString(mojom_db_row, 2);
return confirmation_token;
}
void GetAllCallback(
GetConfirmationTokensCallback callback,
mojom::DBTransactionResultInfoPtr mojom_db_transaction_result) {
if (!IsTransactionSuccessful(mojom_db_transaction_result)) {
BLOG(0, "Failed to get confirmation tokens");
return std::move(callback).Run(/*success=*/false,
/*confirmation_tokens=*/{});
}
CHECK(mojom_db_transaction_result->rows_union);
ConfirmationTokenList confirmation_tokens;
for (auto& mojom_db_row :
mojom_db_transaction_result->rows_union->get_rows()) {
confirmation_tokens.push_back(FromMojomRow(mojom_db_row));
}
std::move(callback).Run(/*success=*/true, std::move(confirmation_tokens));
}
} // namespace
void ConfirmationTokens::Save(const ConfirmationTokenList& confirmation_tokens,
ResultCallback callback) {
if (confirmation_tokens.empty()) {
return std::move(callback).Run(/*success=*/true);
}
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
Insert(mojom_db_transaction, confirmation_tokens);
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
std::move(callback));
}
void ConfirmationTokens::Insert(
const mojom::DBTransactionInfoPtr& mojom_db_transaction,
const ConfirmationTokenList& confirmation_tokens) {
CHECK(mojom_db_transaction);
if (confirmation_tokens.empty()) {
return;
}
mojom::DBActionInfoPtr mojom_db_action = mojom::DBActionInfo::New();
mojom_db_action->type = mojom::DBActionInfo::Type::kExecuteWithBindings;
mojom_db_action->sql = BuildInsertSql(mojom_db_action, confirmation_tokens);
mojom_db_transaction->actions.push_back(std::move(mojom_db_action));
}
void ConfirmationTokens::Delete(const ConfirmationTokenInfo& confirmation_token,
ResultCallback callback) {
const std::optional<std::string> unblinded_token_base64 =
confirmation_token.unblinded_token.EncodeBase64();
if (!unblinded_token_base64) {
BLOG(0, "Failed to delete confirmation token");
return std::move(callback).Run(/*success=*/false);
}
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
Execute(mojom_db_transaction, R"(
DELETE FROM
$1
WHERE
unblinded_token = '$2')",
{kTableName, *unblinded_token_base64});
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
std::move(callback));
}
void ConfirmationTokens::DeleteAll(ResultCallback callback) {
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
Execute(mojom_db_transaction, R"(
DELETE FROM
$1)",
{kTableName});
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
std::move(callback));
}
void ConfirmationTokens::GetAll(GetConfirmationTokensCallback callback) const {
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
mojom::DBActionInfoPtr mojom_db_action = mojom::DBActionInfo::New();
mojom_db_action->type = mojom::DBActionInfo::Type::kExecuteQueryWithBindings;
mojom_db_action->sql = base::ReplaceStringPlaceholders(
R"(
SELECT
unblinded_token,
public_key,
signature
FROM
$1)",
{kTableName}, nullptr);
BindColumnTypes(mojom_db_action);
mojom_db_transaction->actions.push_back(std::move(mojom_db_action));
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
base::BindOnce(&GetAllCallback, std::move(callback)));
}
void ConfirmationTokens::Create(
const mojom::DBTransactionInfoPtr& mojom_db_transaction) {
CHECK(mojom_db_transaction);
Execute(mojom_db_transaction, R"(
CREATE TABLE confirmation_tokens (
unblinded_token TEXT NOT NULL PRIMARY KEY ON CONFLICT REPLACE,
public_key TEXT NOT NULL,
signature TEXT NOT NULL
))");
}
void ConfirmationTokens::Migrate(
const mojom::DBTransactionInfoPtr& mojom_db_transaction,
int to_version) {
CHECK(mojom_db_transaction);
switch (to_version) {
case 56: {
Create(mojom_db_transaction);
break;
}
default: {
// No migration needed.
break;
}
}
}
///////////////////////////////////////////////////////////////////////////////
std::string ConfirmationTokens::BuildInsertSql(
const mojom::DBActionInfoPtr& mojom_db_action,
const ConfirmationTokenList& confirmation_tokens) const {
CHECK(mojom_db_action);
CHECK(!confirmation_tokens.empty());
const size_t row_count = BindColumns(mojom_db_action, confirmation_tokens);
return base::ReplaceStringPlaceholders(
R"(
INSERT INTO $1 (
unblinded_token,
public_key,
signature
) VALUES $2)",
{kTableName, BuildBindColumnPlaceholders(/*column_count=*/3U, row_count)},
nullptr);
}
} // namespace brave_ads::database::table
@@ -0,0 +1,52 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_ACCOUNT_TOKENS_CONFIRMATION_TOKENS_CONFIRMATION_TOKENS_DATABASE_TABLE_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_ACCOUNT_TOKENS_CONFIRMATION_TOKENS_CONFIRMATION_TOKENS_DATABASE_TABLE_H_
#include <string>
#include "base/functional/callback.h"
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_token_info.h"
#include "brave/components/brave_ads/core/internal/database/database_table_interface.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom-forward.h"
#include "brave/components/brave_ads/core/public/ads_callback.h"
namespace brave_ads::database::table {
using GetConfirmationTokensCallback =
base::OnceCallback<void(bool success,
ConfirmationTokenList confirmation_tokens)>;
// Persists unspent confirmation tokens that are pending assignment to an ad
// confirmation. Tokens are loaded from this table on startup to populate the
// in-memory cache, and written here on every add or removal.
class ConfirmationTokens final : public TableInterface {
public:
void Save(const ConfirmationTokenList& confirmation_tokens,
ResultCallback callback);
void Insert(const mojom::DBTransactionInfoPtr& mojom_db_transaction,
const ConfirmationTokenList& confirmation_tokens);
void Delete(const ConfirmationTokenInfo& confirmation_token,
ResultCallback callback);
void DeleteAll(ResultCallback callback);
void GetAll(GetConfirmationTokensCallback callback) const;
void Create(const mojom::DBTransactionInfoPtr& mojom_db_transaction) override;
void Migrate(const mojom::DBTransactionInfoPtr& mojom_db_transaction,
int to_version) override;
private:
std::string BuildInsertSql(
const mojom::DBActionInfoPtr& mojom_db_action,
const ConfirmationTokenList& confirmation_tokens) const;
};
} // namespace brave_ads::database::table
#endif // BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_ACCOUNT_TOKENS_CONFIRMATION_TOKENS_CONFIRMATION_TOKENS_DATABASE_TABLE_H_
@@ -0,0 +1,147 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_database_table.h"
#include "base/test/test_future.h"
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_token_info.h"
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/test/confirmation_tokens_test_util.h"
#include "brave/components/brave_ads/core/internal/common/test/test_base.h"
#include "testing/gmock/include/gmock/gmock.h"
// npm run test -- brave_unit_tests --filter=BraveAds*
namespace brave_ads::database::table {
class BraveAdsConfirmationTokensDatabaseTableTest : public test::TestBase {
protected:
ConfirmationTokens database_table_;
};
TEST_F(BraveAdsConfirmationTokensDatabaseTableTest,
SaveEmptyConfirmationTokens) {
// Act
base::test::TestFuture<bool> save_test_future;
database_table_.Save({}, save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Assert
base::test::TestFuture<bool, ConfirmationTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, ConfirmationTokenList>());
const auto [success, confirmation_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_THAT(confirmation_tokens, ::testing::IsEmpty());
}
TEST_F(BraveAdsConfirmationTokensDatabaseTableTest, SaveConfirmationTokens) {
// Act
base::test::TestFuture<bool> save_test_future;
database_table_.Save(test::BuildConfirmationTokens(/*count=*/7),
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Assert
base::test::TestFuture<bool, ConfirmationTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, ConfirmationTokenList>());
const auto [success, confirmation_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ(test::BuildConfirmationTokens(/*count=*/7), confirmation_tokens);
}
TEST_F(BraveAdsConfirmationTokensDatabaseTableTest,
SaveDuplicateConfirmationTokens) {
// Arrange
base::test::TestFuture<bool> save_test_future;
database_table_.Save(test::BuildConfirmationTokens(/*count=*/1),
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> duplicate_test_future;
database_table_.Save(test::BuildConfirmationTokens(/*count=*/1),
duplicate_test_future.GetCallback());
ASSERT_TRUE(duplicate_test_future.Take());
// Assert
base::test::TestFuture<bool, ConfirmationTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, ConfirmationTokenList>());
const auto [success, confirmation_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ(test::BuildConfirmationTokens(/*count=*/1), confirmation_tokens);
}
TEST_F(BraveAdsConfirmationTokensDatabaseTableTest, DeleteConfirmationToken) {
// Arrange
base::test::TestFuture<bool> save_test_future;
database_table_.Save(test::BuildConfirmationTokens(/*count=*/2),
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> delete_test_future;
database_table_.Delete(test::BuildConfirmationTokens(/*count=*/2).front(),
delete_test_future.GetCallback());
ASSERT_TRUE(delete_test_future.Take());
// Assert
base::test::TestFuture<bool, ConfirmationTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, ConfirmationTokenList>());
const auto [success, confirmation_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ((ConfirmationTokenList{
test::BuildConfirmationTokens(/*count=*/2).back()}),
confirmation_tokens);
}
TEST_F(BraveAdsConfirmationTokensDatabaseTableTest,
DoNotDeleteMissingConfirmationToken) {
// Arrange
base::test::TestFuture<bool> save_test_future;
database_table_.Save(test::BuildConfirmationTokens(/*count=*/1),
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> delete_test_future;
database_table_.Delete(test::BuildConfirmationTokens(/*count=*/2).back(),
delete_test_future.GetCallback());
ASSERT_TRUE(delete_test_future.Take());
// Assert
base::test::TestFuture<bool, ConfirmationTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, ConfirmationTokenList>());
const auto [success, confirmation_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ(test::BuildConfirmationTokens(/*count=*/1), confirmation_tokens);
}
TEST_F(BraveAdsConfirmationTokensDatabaseTableTest,
DeleteAllConfirmationTokens) {
// Arrange
base::test::TestFuture<bool> save_test_future;
database_table_.Save(test::BuildConfirmationTokens(/*count=*/7),
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> delete_all_test_future;
database_table_.DeleteAll(delete_all_test_future.GetCallback());
ASSERT_TRUE(delete_all_test_future.Take());
// Assert
base::test::TestFuture<bool, ConfirmationTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, ConfirmationTokenList>());
const auto [success, confirmation_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_THAT(confirmation_tokens, ::testing::IsEmpty());
}
} // namespace brave_ads::database::table
@@ -0,0 +1,265 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_tokens_database_table.h"
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/strings/string_util.h"
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_token_info.h"
#include "brave/components/brave_ads/core/internal/common/challenge_bypass_ristretto/public_key.h"
#include "brave/components/brave_ads/core/internal/common/challenge_bypass_ristretto/unblinded_token.h"
#include "brave/components/brave_ads/core/internal/common/database/database_column_util.h"
#include "brave/components/brave_ads/core/internal/common/database/database_table_util.h"
#include "brave/components/brave_ads/core/internal/common/database/database_transaction_util.h"
#include "brave/components/brave_ads/core/internal/common/logging_util.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h"
#include "brave/components/brave_ads/core/public/account/confirmations/confirmation_type.h"
#include "brave/components/brave_ads/core/public/ad_units/ad_type.h"
namespace brave_ads::database::table {
namespace {
constexpr char kTableName[] = "payment_tokens";
void BindColumnTypes(const mojom::DBActionInfoPtr& mojom_db_action) {
CHECK(mojom_db_action);
mojom_db_action->bind_column_types = {
mojom::DBBindColumnType::kString, // transaction_id
mojom::DBBindColumnType::kString, // unblinded_token
mojom::DBBindColumnType::kString, // public_key
mojom::DBBindColumnType::kString, // confirmation_type
mojom::DBBindColumnType::kString // ad_type
};
}
size_t BindColumns(const mojom::DBActionInfoPtr& mojom_db_action,
const PaymentTokenList& payment_tokens) {
CHECK(mojom_db_action);
CHECK(!payment_tokens.empty());
size_t row_count = 0;
int32_t index = 0;
for (const auto& payment_token : payment_tokens) {
BindColumnString(mojom_db_action, index++, payment_token.transaction_id);
BindColumnString(mojom_db_action, index++,
payment_token.unblinded_token.EncodeBase64().value_or(""));
BindColumnString(mojom_db_action, index++,
payment_token.public_key.EncodeBase64().value_or(""));
BindColumnString(mojom_db_action, index++,
ToString(payment_token.confirmation_type));
BindColumnString(mojom_db_action, index++, ToString(payment_token.ad_type));
++row_count;
}
return row_count;
}
PaymentTokenInfo FromMojomRow(const mojom::DBRowInfoPtr& mojom_db_row) {
CHECK(mojom_db_row);
PaymentTokenInfo payment_token;
payment_token.transaction_id = ColumnString(mojom_db_row, 0);
payment_token.unblinded_token =
cbr::UnblindedToken(ColumnString(mojom_db_row, 1));
payment_token.public_key = cbr::PublicKey(ColumnString(mojom_db_row, 2));
payment_token.confirmation_type =
ToMojomConfirmationType(ColumnString(mojom_db_row, 3));
payment_token.ad_type = ToMojomAdType(ColumnString(mojom_db_row, 4));
return payment_token;
}
void GetAllCallback(
GetPaymentTokensCallback callback,
mojom::DBTransactionResultInfoPtr mojom_db_transaction_result) {
if (!IsTransactionSuccessful(mojom_db_transaction_result)) {
BLOG(0, "Failed to get payment tokens");
return std::move(callback).Run(/*success=*/false, /*payment_tokens=*/{});
}
CHECK(mojom_db_transaction_result->rows_union);
PaymentTokenList payment_tokens;
for (auto& mojom_db_row :
mojom_db_transaction_result->rows_union->get_rows()) {
payment_tokens.push_back(FromMojomRow(mojom_db_row));
}
std::move(callback).Run(/*success=*/true, std::move(payment_tokens));
}
} // namespace
void PaymentTokens::Save(const PaymentTokenList& payment_tokens,
ResultCallback callback) {
if (payment_tokens.empty()) {
return std::move(callback).Run(/*success=*/true);
}
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
Insert(mojom_db_transaction, payment_tokens);
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
std::move(callback));
}
void PaymentTokens::Insert(
const mojom::DBTransactionInfoPtr& mojom_db_transaction,
const PaymentTokenList& payment_tokens) {
CHECK(mojom_db_transaction);
if (payment_tokens.empty()) {
return;
}
mojom::DBActionInfoPtr mojom_db_action = mojom::DBActionInfo::New();
mojom_db_action->type = mojom::DBActionInfo::Type::kExecuteWithBindings;
mojom_db_action->sql = BuildInsertSql(mojom_db_action, payment_tokens);
mojom_db_transaction->actions.push_back(std::move(mojom_db_action));
}
void PaymentTokens::Delete(const PaymentTokenInfo& payment_token,
ResultCallback callback) {
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
Execute(mojom_db_transaction, R"(
DELETE FROM
$1
WHERE
transaction_id = '$2')",
{kTableName, payment_token.transaction_id});
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
std::move(callback));
}
void PaymentTokens::Delete(const PaymentTokenList& payment_tokens,
ResultCallback callback) {
if (payment_tokens.empty()) {
return std::move(callback).Run(/*success=*/true);
}
std::vector<std::string> transaction_ids;
transaction_ids.reserve(payment_tokens.size());
for (const auto& payment_token : payment_tokens) {
transaction_ids.push_back("'" + payment_token.transaction_id + "'");
}
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
Execute(mojom_db_transaction, R"(
DELETE FROM
$1
WHERE
transaction_id IN ($2))",
{kTableName, base::JoinString(transaction_ids, ", ")});
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
std::move(callback));
}
void PaymentTokens::DeleteAll(ResultCallback callback) {
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
Execute(mojom_db_transaction, R"(
DELETE FROM
$1)",
{kTableName});
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
std::move(callback));
}
void PaymentTokens::GetAll(GetPaymentTokensCallback callback) const {
mojom::DBTransactionInfoPtr mojom_db_transaction =
mojom::DBTransactionInfo::New();
mojom::DBActionInfoPtr mojom_db_action = mojom::DBActionInfo::New();
mojom_db_action->type = mojom::DBActionInfo::Type::kExecuteQueryWithBindings;
mojom_db_action->sql = base::ReplaceStringPlaceholders(
R"(
SELECT
transaction_id,
unblinded_token,
public_key,
confirmation_type,
ad_type
FROM
$1)",
{kTableName}, nullptr);
BindColumnTypes(mojom_db_action);
mojom_db_transaction->actions.push_back(std::move(mojom_db_action));
RunTransaction(FROM_HERE, std::move(mojom_db_transaction),
base::BindOnce(&GetAllCallback, std::move(callback)));
}
void PaymentTokens::Create(
const mojom::DBTransactionInfoPtr& mojom_db_transaction) {
CHECK(mojom_db_transaction);
Execute(mojom_db_transaction, R"(
CREATE TABLE payment_tokens (
transaction_id TEXT NOT NULL PRIMARY KEY ON CONFLICT REPLACE,
unblinded_token TEXT NOT NULL,
public_key TEXT NOT NULL,
confirmation_type TEXT NOT NULL,
ad_type TEXT NOT NULL
))");
}
void PaymentTokens::Migrate(
const mojom::DBTransactionInfoPtr& mojom_db_transaction,
int to_version) {
CHECK(mojom_db_transaction);
switch (to_version) {
case 56: {
Create(mojom_db_transaction);
break;
}
default: {
// No migration needed.
break;
}
}
}
///////////////////////////////////////////////////////////////////////////////
std::string PaymentTokens::BuildInsertSql(
const mojom::DBActionInfoPtr& mojom_db_action,
const PaymentTokenList& payment_tokens) const {
CHECK(mojom_db_action);
CHECK(!payment_tokens.empty());
const size_t row_count = BindColumns(mojom_db_action, payment_tokens);
return base::ReplaceStringPlaceholders(
R"(
INSERT INTO $1 (
transaction_id,
unblinded_token,
public_key,
confirmation_type,
ad_type
) VALUES $2)",
{kTableName, BuildBindColumnPlaceholders(/*column_count=*/5U, row_count)},
nullptr);
}
} // namespace brave_ads::database::table
@@ -0,0 +1,49 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_ACCOUNT_TOKENS_PAYMENT_TOKENS_PAYMENT_TOKENS_DATABASE_TABLE_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_ACCOUNT_TOKENS_PAYMENT_TOKENS_PAYMENT_TOKENS_DATABASE_TABLE_H_
#include <string>
#include "base/functional/callback.h"
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_token_info.h"
#include "brave/components/brave_ads/core/internal/database/database_table_interface.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom-forward.h"
#include "brave/components/brave_ads/core/public/ads_callback.h"
namespace brave_ads::database::table {
using GetPaymentTokensCallback =
base::OnceCallback<void(bool success, PaymentTokenList payment_tokens)>;
// Persists unspent payment tokens that have been earned via ad confirmations
// and are awaiting redemption. Tokens are loaded from this table on startup to
// populate the in-memory cache, and written here on every add or removal.
class PaymentTokens final : public TableInterface {
public:
void Save(const PaymentTokenList& payment_tokens, ResultCallback callback);
void Insert(const mojom::DBTransactionInfoPtr& mojom_db_transaction,
const PaymentTokenList& payment_tokens);
void Delete(const PaymentTokenInfo& payment_token, ResultCallback callback);
void Delete(const PaymentTokenList& payment_tokens, ResultCallback callback);
void DeleteAll(ResultCallback callback);
void GetAll(GetPaymentTokensCallback callback) const;
void Create(const mojom::DBTransactionInfoPtr& mojom_db_transaction) override;
void Migrate(const mojom::DBTransactionInfoPtr& mojom_db_transaction,
int to_version) override;
private:
std::string BuildInsertSql(const mojom::DBActionInfoPtr& mojom_db_action,
const PaymentTokenList& payment_tokens) const;
};
} // namespace brave_ads::database::table
#endif // BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_ACCOUNT_TOKENS_PAYMENT_TOKENS_PAYMENT_TOKENS_DATABASE_TABLE_H_
@@ -0,0 +1,193 @@
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_tokens_database_table.h"
#include "base/test/test_future.h"
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_token_info.h"
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/test/payment_tokens_test_util.h"
#include "brave/components/brave_ads/core/internal/common/test/test_base.h"
#include "testing/gmock/include/gmock/gmock.h"
// npm run test -- brave_unit_tests --filter=BraveAds*
namespace brave_ads::database::table {
class BraveAdsPaymentTokensDatabaseTableTest : public test::TestBase {
protected:
PaymentTokens database_table_;
};
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, SaveEmptyPaymentTokens) {
// Act
base::test::TestFuture<bool> save_test_future;
database_table_.Save({}, save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Assert
base::test::TestFuture<bool, PaymentTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, PaymentTokenList>());
const auto [success, payment_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_THAT(payment_tokens, ::testing::IsEmpty());
}
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, SavePaymentTokens) {
// Arrange
PaymentTokenInfo payment_token_1 = test::BuildPaymentToken();
payment_token_1.transaction_id = "foo";
PaymentTokenInfo payment_token_2 = test::BuildPaymentToken();
payment_token_2.transaction_id = "bar";
// Act
base::test::TestFuture<bool> save_test_future;
database_table_.Save({payment_token_1, payment_token_2},
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Assert
base::test::TestFuture<bool, PaymentTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, PaymentTokenList>());
const auto [success, payment_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ((PaymentTokenList{payment_token_1, payment_token_2}),
payment_tokens);
}
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, SaveDuplicatePaymentTokens) {
// Arrange
const PaymentTokenInfo payment_token = test::BuildPaymentToken();
base::test::TestFuture<bool> save_test_future;
database_table_.Save({payment_token}, save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> duplicate_test_future;
database_table_.Save({payment_token}, duplicate_test_future.GetCallback());
ASSERT_TRUE(duplicate_test_future.Take());
// Assert
base::test::TestFuture<bool, PaymentTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, PaymentTokenList>());
const auto [success, payment_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_THAT(payment_tokens, ::testing::SizeIs(1U));
}
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, DeletePaymentToken) {
// Arrange
PaymentTokenInfo payment_token_1 = test::BuildPaymentToken();
payment_token_1.transaction_id = "foo";
PaymentTokenInfo payment_token_2 = test::BuildPaymentToken();
payment_token_2.transaction_id = "bar";
base::test::TestFuture<bool> save_test_future;
database_table_.Save({payment_token_1, payment_token_2},
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> delete_test_future;
database_table_.Delete(payment_token_1, delete_test_future.GetCallback());
ASSERT_TRUE(delete_test_future.Take());
// Assert
base::test::TestFuture<bool, PaymentTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, PaymentTokenList>());
const auto [success, payment_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ((PaymentTokenList{payment_token_2}), payment_tokens);
}
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, DeletePaymentTokens) {
// Arrange
PaymentTokenInfo payment_token_1 = test::BuildPaymentToken();
payment_token_1.transaction_id = "foo";
PaymentTokenInfo payment_token_2 = test::BuildPaymentToken();
payment_token_2.transaction_id = "bar";
PaymentTokenInfo payment_token_3 = test::BuildPaymentToken();
payment_token_3.transaction_id = "baz";
base::test::TestFuture<bool> save_test_future;
database_table_.Save({payment_token_1, payment_token_2, payment_token_3},
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> delete_test_future;
database_table_.Delete({payment_token_1, payment_token_2},
delete_test_future.GetCallback());
ASSERT_TRUE(delete_test_future.Take());
// Assert
base::test::TestFuture<bool, PaymentTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, PaymentTokenList>());
const auto [success, payment_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ((PaymentTokenList{payment_token_3}), payment_tokens);
}
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, DeleteEmptyPaymentTokens) {
// Act & Assert
base::test::TestFuture<bool> delete_test_future;
database_table_.Delete(PaymentTokenList{}, delete_test_future.GetCallback());
EXPECT_TRUE(delete_test_future.Take());
}
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, DoNotDeleteMissingPaymentToken) {
// Arrange
PaymentTokenInfo payment_token = test::BuildPaymentToken();
payment_token.transaction_id = "foo";
base::test::TestFuture<bool> save_test_future;
database_table_.Save({payment_token}, save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
PaymentTokenInfo missing_payment_token = test::BuildPaymentToken();
missing_payment_token.transaction_id = "bar";
// Act
base::test::TestFuture<bool> delete_test_future;
database_table_.Delete(missing_payment_token,
delete_test_future.GetCallback());
ASSERT_TRUE(delete_test_future.Take());
// Assert
base::test::TestFuture<bool, PaymentTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, PaymentTokenList>());
const auto [success, payment_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_EQ((PaymentTokenList{payment_token}), payment_tokens);
}
TEST_F(BraveAdsPaymentTokensDatabaseTableTest, DeleteAllPaymentTokens) {
// Arrange
PaymentTokenInfo payment_token_1 = test::BuildPaymentToken();
payment_token_1.transaction_id = "foo";
PaymentTokenInfo payment_token_2 = test::BuildPaymentToken();
payment_token_2.transaction_id = "bar";
base::test::TestFuture<bool> save_test_future;
database_table_.Save({payment_token_1, payment_token_2},
save_test_future.GetCallback());
ASSERT_TRUE(save_test_future.Take());
// Act
base::test::TestFuture<bool> delete_all_test_future;
database_table_.DeleteAll(delete_all_test_future.GetCallback());
ASSERT_TRUE(delete_all_test_future.Take());
// Assert
base::test::TestFuture<bool, PaymentTokenList> get_all_test_future;
database_table_.GetAll(
get_all_test_future.GetCallback<bool, PaymentTokenList>());
const auto [success, payment_tokens] = get_all_test_future.Take();
EXPECT_TRUE(success);
EXPECT_THAT(payment_tokens, ::testing::IsEmpty());
}
} // namespace brave_ads::database::table
@@ -10,6 +10,7 @@
#include "base/check.h"
#include "base/check_op.h"
#include "base/debug/crash_logging.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/task/task_traits.h"
@@ -8,8 +8,8 @@
namespace brave_ads::database {
inline constexpr int kVersionNumber = 55;
inline constexpr int kCompatibleVersionNumber = 55;
inline constexpr int kVersionNumber = 56;
inline constexpr int kCompatibleVersionNumber = 56;
// If the database version number is less than or equal to this value, the
// database will be razed and recreated during migration. This should be updated
@@ -11,6 +11,8 @@
#include "base/location.h"
#include "brave/components/brave_ads/core/internal/account/confirmations/queue/confirmation_queue_database_table.h"
#include "brave/components/brave_ads/core/internal/account/deposits/deposits_database_table.h"
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_database_table.h"
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_tokens_database_table.h"
#include "brave/components/brave_ads/core/internal/account/transactions/transactions_database_table.h"
#include "brave/components/brave_ads/core/internal/common/database/database_transaction_util.h"
#include "brave/components/brave_ads/core/internal/creatives/campaigns_database_table.h"
@@ -37,6 +39,12 @@ void Create(const mojom::DBTransactionInfoPtr& mojom_db_transaction) {
table::ConfirmationQueue confirmation_queue_database_table;
confirmation_queue_database_table.Create(mojom_db_transaction);
table::ConfirmationTokens confirmation_tokens_database_table;
confirmation_tokens_database_table.Create(mojom_db_transaction);
table::PaymentTokens payment_tokens_database_table;
payment_tokens_database_table.Create(mojom_db_transaction);
table::AdEvents ad_events_database_table;
ad_events_database_table.Create(mojom_db_transaction);
@@ -12,6 +12,8 @@
#include "base/location.h"
#include "brave/components/brave_ads/core/internal/account/confirmations/queue/confirmation_queue_database_table.h"
#include "brave/components/brave_ads/core/internal/account/deposits/deposits_database_table.h"
#include "brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_database_table.h"
#include "brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_tokens_database_table.h"
#include "brave/components/brave_ads/core/internal/account/transactions/transactions_database_table.h"
#include "brave/components/brave_ads/core/internal/common/database/database_table_util.h"
#include "brave/components/brave_ads/core/internal/common/database/database_transaction_util.h"
@@ -114,6 +116,12 @@ void MigrateToVersion(const mojom::DBTransactionInfoPtr& mojom_db_transaction,
table::ConfirmationQueue confirmation_queue_database_table;
confirmation_queue_database_table.Migrate(mojom_db_transaction, to_version);
table::ConfirmationTokens confirmation_tokens_database_table;
confirmation_tokens_database_table.Migrate(mojom_db_transaction, to_version);
table::PaymentTokens payment_tokens_database_table;
payment_tokens_database_table.Migrate(mojom_db_transaction, to_version);
table::AdEvents ad_events_database_table;
ad_events_database_table.Migrate(mojom_db_transaction, to_version);
+2
View File
@@ -73,6 +73,7 @@ source_set("brave_ads_unit_tests") {
"//brave/components/brave_ads/core/internal/account/statement/statement_util_unittest.cc",
"//brave/components/brave_ads/core/internal/account/test/account_observer_mock.cc",
"//brave/components/brave_ads/core/internal/account/test/account_observer_mock.h",
"//brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_database_table_unittest.cc",
"//brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_unittest.cc",
"//brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_util_unittest.cc",
"//brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/confirmation_tokens_value_util_unittest.cc",
@@ -80,6 +81,7 @@ source_set("brave_ads_unit_tests") {
"//brave/components/brave_ads/core/internal/account/tokens/confirmation_tokens/test/confirmation_tokens_test_util.h",
"//brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_token_util_unittest.cc",
"//brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_token_value_util_unittest.cc",
"//brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_tokens_database_table_unittest.cc",
"//brave/components/brave_ads/core/internal/account/tokens/payment_tokens/payment_tokens_unittest.cc",
"//brave/components/brave_ads/core/internal/account/tokens/payment_tokens/test/payment_tokens_test_util.cc",
"//brave/components/brave_ads/core/internal/account/tokens/payment_tokens/test/payment_tokens_test_util.h",
@@ -23,6 +23,37 @@ EXPECTED_ROW_COUNT = 4
# TODO(https://github.com/brave/brave-browser/issues/40017): Add foreign key
# support.
# Valid values for columns that have a constrained set of values parsed via
# strict enum converters (i.e. those that trigger NOTREACHED on unknown input).
# Keys are (table_name, column_name); use None as the table_name to match the
# column in any table.
COLUMN_VALID_VALUES = {
# ConfirmationType: parsed via ToMojomConfirmationType which NOTREACHes on
# unknown values.
(None, 'confirmation_type'): [
'click', 'dismiss', 'view', 'served', 'landed', 'bookmark', 'flag',
'upvote', 'downvote', 'conversion', 'interaction', 'media_play',
'media_25', 'media_100'
],
# confirmation_queue.type is a ConfirmationType despite the generic name.
('confirmation_queue', 'type'): [
'click', 'dismiss', 'view', 'served', 'landed', 'bookmark', 'flag',
'upvote', 'downvote', 'conversion', 'interaction', 'media_play',
'media_25', 'media_100'
],
# AdType: parsed via ToMojomAdType which NOTREACHes on unknown values.
(None, 'ad_type'): [
'ad_notification', 'new_tab_page_ad', 'search_result_ad'
],
# ad_events.type and ad_history.type are AdType despite the generic name.
('ad_events', 'type'): [
'ad_notification', 'new_tab_page_ad', 'search_result_ad'
],
('ad_history', 'type'): [
'ad_notification', 'new_tab_page_ad', 'search_result_ad'
],
}
def generate_mock_string(length):
characters = string.ascii_letters + string.digits
@@ -52,8 +83,19 @@ def generate_mock_chrome_webkit_timestamp():
return chrome_webkit_delta_in_seconds * MILLISECONDS_IN_SECOND
def get_column_valid_values(table_name, column_name):
# Check for a table-specific override first, then fall back to a
# column-name-only match (None key).
return (COLUMN_VALID_VALUES.get((table_name, column_name))
or COLUMN_VALID_VALUES.get((None, column_name)))
def generate_mock_column_with_random_test_data(connection, table_name,
column_name, column_type):
valid_values = get_column_valid_values(table_name, column_name)
if valid_values:
return secrets.choice(valid_values)
if column_type in ('INTEGER', 'INT', 'NUMERIC'):
mock_column = secrets.randbelow(100)
elif column_type == 'TEXT':