Skip to content

Commit 61e9c48

Browse files
authored
Merge pull request #916 from gobitfly/BEDS-448/mobile-bundle-updater
Mobile App Bundle Backend
2 parents 63c6a5c + 9a7d701 commit 61e9c48

File tree

7 files changed

+193
-18
lines changed

7 files changed

+193
-18
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package commands
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/gobitfly/beaconchain/pkg/commons/db"
9+
"github.com/gobitfly/beaconchain/pkg/commons/log"
10+
11+
"github.com/pkg/errors"
12+
)
13+
14+
type AppBundleCommand struct {
15+
FlagSet *flag.FlagSet
16+
Config appBundleCommandConfig
17+
}
18+
19+
type appBundleCommandConfig struct {
20+
DryRun bool
21+
Force bool // bypass summary confirm
22+
BundleURL string
23+
BundleVersionCode int64
24+
NativeVersionCode int64
25+
TargetInstalls int64
26+
}
27+
28+
func (s *AppBundleCommand) ParseCommandOptions() {
29+
s.FlagSet.Int64Var(&s.Config.BundleVersionCode, "version-code", 0, "Version code of that bundle (Default: Next)")
30+
s.FlagSet.Int64Var(&s.Config.NativeVersionCode, "min-native-version", 0, "Minimum required native version (Default: Current)")
31+
s.FlagSet.Int64Var(&s.Config.TargetInstalls, "target-installs", -1, "How many people to roll out to (Default: All)")
32+
s.FlagSet.StringVar(&s.Config.BundleURL, "bundle-url", "", "URL to bundle that contains the update, bundle.zip")
33+
s.FlagSet.BoolVar(&s.Config.Force, "force", false, "Skips summary and confirmation")
34+
}
35+
36+
func (s *AppBundleCommand) Run() error {
37+
if s.Config.BundleURL == "" {
38+
s.showHelp()
39+
return errors.New("Please provide a valid bundle URL via --bundle-url")
40+
}
41+
if s.Config.BundleVersionCode == 0 {
42+
fileName := strings.Split(s.Config.BundleURL, "/")
43+
if len(fileName) == 0 {
44+
return errors.New("Invalid bundle URL")
45+
}
46+
47+
split := strings.Split(fileName[len(fileName)-1], "_")
48+
if len(split) < 2 {
49+
return errors.New("Invalid bundle URL")
50+
}
51+
52+
// split[1] is the version code
53+
_, err := fmt.Sscanf(split[1], "%d", &s.Config.BundleVersionCode)
54+
if err != nil {
55+
return errors.Wrap(err, "Error parsing version code")
56+
}
57+
}
58+
if s.Config.NativeVersionCode <= 0 {
59+
err := db.ReaderDb.Get(&s.Config.NativeVersionCode, "SELECT MAX(min_native_version) FROM mobile_app_bundles")
60+
if err != nil {
61+
return errors.Wrap(err, "Error getting max native version")
62+
}
63+
}
64+
65+
if s.Config.TargetInstalls < 0 {
66+
s.Config.TargetInstalls = -1
67+
}
68+
69+
if !s.Config.Force {
70+
// Summary
71+
log.Infof("=== Bundle Summary ===")
72+
log.Infof("Bundle URL: %s", s.Config.BundleURL)
73+
log.Infof("Bundle Version Code: %d", s.Config.BundleVersionCode)
74+
log.Infof("Minimum Native Version: %d", s.Config.NativeVersionCode)
75+
if s.Config.TargetInstalls == -1 {
76+
log.Infof("Target Installs: All")
77+
} else {
78+
log.Infof("Target Installs: %d", s.Config.TargetInstalls)
79+
}
80+
log.Infof("======================\n")
81+
82+
// ask for y/n input
83+
log.Infof("Do you want to add this bundle? (y/n)\n")
84+
var input string
85+
_, err := fmt.Scanln(&input)
86+
if err != nil {
87+
return errors.Wrap(err, "Error reading input")
88+
}
89+
90+
if input != "y" {
91+
log.Infof("Bundle not added\n")
92+
return nil
93+
}
94+
}
95+
96+
if s.Config.DryRun {
97+
log.Infof("Dry run, not adding bundle\n")
98+
return nil
99+
}
100+
101+
_, err := db.WriterDb.Exec("INSERT INTO mobile_app_bundles (bundle_url, bundle_version, min_native_version, target_count) VALUES ($1, $2, $3, $4)", s.Config.BundleURL, s.Config.BundleVersionCode, s.Config.NativeVersionCode, s.Config.TargetInstalls)
102+
if err != nil {
103+
return errors.Wrap(err, "Error inserting app bundle")
104+
}
105+
106+
log.Infof("Bundle added successfully")
107+
return nil
108+
}
109+
110+
func (s *AppBundleCommand) showHelp() {
111+
log.Infof("Usage: app_bundle [options]")
112+
log.Infof("Options:")
113+
log.Infof(" --version-code int\tVersion code of that bundle")
114+
log.Infof(" --min-native-version int\tMinimum required native version (Default: Current)")
115+
log.Infof(" --target-installs int\tHow many people to roll out to (Default: All)")
116+
log.Infof(" --bundle-url string\tURL to bundle that contains the update, bundle.zip")
117+
}

backend/cmd/misc/commands/validator_stats_partition.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ func (s *StatsMigratorCommand) ParseCommandOptions() {
4343
s.FlagSet.IntVar(&s.NumberOfPartitions, "partitions", 0, "Number of partitions. Recommended 2 - 128 for PostgreSQL 15")
4444
}
4545

46-
func (s *StatsMigratorCommand) StartStatsPartitionCommand() error {
46+
func (s *StatsMigratorCommand) Run() error {
4747
if s.CurrentTable == "" {
48-
showHelp()
48+
s.showHelp()
4949
return errors.New("Please specify a valid current-table name via --current-table")
5050
}
5151
if s.DestinationTable == "" {
52-
showHelp()
52+
s.showHelp()
5353
return errors.New("Please specify a valid destination-table name via --destination-table")
5454
}
5555
if s.NumberOfPartitions <= 0 {
56-
showHelp()
56+
s.showHelp()
5757
return errors.New("Please specify a valid number of partitions via --partitions. Number of partitions must be > 0")
5858
}
5959

@@ -64,7 +64,7 @@ func (s *StatsMigratorCommand) StartStatsPartitionCommand() error {
6464
return nil
6565
}
6666

67-
func showHelp() {
67+
func (s *StatsMigratorCommand) showHelp() {
6868
log.Infof("Usage: %s --current-table=validator_stats --destination-table=validator_stats_partitioned --partitions=64\n", "validator_stats_partition")
6969
log.Infof("Usage: %s --current-table=validator_stats --destination-table=validator_stats_partitioned --partitions=64 --drop-existing\n", "validator_stats_partition")
7070
log.Infof("Usage: %s --current-table=validator_stats --destination-table=validator_stats_partitioned --partitions=64 --drop-existing --batch-size=20000 --sleep-between-batches=1s --rename-destination-on-complete=true\n", "validator_stats_partition")

backend/cmd/misc/main.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ func Run() {
7676
FlagSet: fs,
7777
}
7878

79+
appBundleCommand := commands.AppBundleCommand{
80+
FlagSet: fs,
81+
}
82+
7983
configPath := fs.String("config", "config/default.config.yml", "Path to the config file")
80-
fs.StringVar(&opts.Command, "command", "", "command to run, available: updateAPIKey, applyDbSchema, initBigtableSchema, epoch-export, debug-rewards, debug-blocks, clear-bigtable, index-old-eth1-blocks, update-aggregation-bits, historic-prices-export, index-missing-blocks, export-epoch-missed-slots, migrate-last-attestation-slot-bigtable, export-genesis-validators, update-block-finalization-sequentially, nameValidatorsByRanges, export-stats-totals, export-sync-committee-periods, export-sync-committee-validator-stats, partition-validator-stats, migrate-app-purchases, collect-notifications, collect-user-db-notifications")
84+
fs.StringVar(&opts.Command, "command", "", "command to run, available: updateAPIKey, applyDbSchema, initBigtableSchema, epoch-export, debug-rewards, debug-blocks, clear-bigtable, index-old-eth1-blocks, update-aggregation-bits, historic-prices-export, index-missing-blocks, export-epoch-missed-slots, migrate-last-attestation-slot-bigtable, export-genesis-validators, update-block-finalization-sequentially, nameValidatorsByRanges, export-stats-totals, export-sync-committee-periods, export-sync-committee-validator-stats, partition-validator-stats, migrate-app-purchases, collect-notifications, collect-user-db-notifications, app-bundle")
8185
fs.Uint64Var(&opts.StartEpoch, "start-epoch", 0, "start epoch")
8286
fs.Uint64Var(&opts.EndEpoch, "end-epoch", 0, "end epoch")
8387
fs.Uint64Var(&opts.User, "user", 0, "user id")
@@ -101,6 +105,7 @@ func Run() {
101105
versionFlag := fs.Bool("version", false, "Show version and exit")
102106

103107
statsPartitionCommand.ParseCommandOptions()
108+
appBundleCommand.ParseCommandOptions()
104109
_ = fs.Parse(os.Args[2:])
105110

106111
if *versionFlag {
@@ -461,7 +466,10 @@ func Run() {
461466
err = fixExecTransactionsCount()
462467
case "partition-validator-stats":
463468
statsPartitionCommand.Config.DryRun = opts.DryRun
464-
err = statsPartitionCommand.StartStatsPartitionCommand()
469+
err = statsPartitionCommand.Run()
470+
case "app-bundle":
471+
appBundleCommand.Config.DryRun = opts.DryRun
472+
err = appBundleCommand.Run()
465473
case "fix-ens":
466474
err = fixEns(erigonClient)
467475
case "fix-ens-addresses":

backend/pkg/api/data_access/app.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,42 @@ func (d *DataAccessService) AddMobilePurchase(tx *sql.Tx, userID uint64, payment
110110
}
111111

112112
func (d *DataAccessService) GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64) (*t.MobileAppBundleStats, error) {
113-
// @TODO data access
114-
return d.dummy.GetLatestBundleForNativeVersion(ctx, nativeVersion)
113+
var bundle t.MobileAppBundleStats
114+
err := d.alloyReader.Get(&bundle, `
115+
WITH
116+
latest_native AS (
117+
SELECT max(min_native_version) as max_native_version
118+
FROM mobile_app_bundles
119+
),
120+
latest_bundle AS (
121+
SELECT
122+
bundle_version,
123+
bundle_url,
124+
delivered_count,
125+
COALESCE(target_count, -1) as target_count
126+
FROM mobile_app_bundles
127+
WHERE min_native_version <= $1
128+
ORDER BY bundle_version DESC
129+
LIMIT 1
130+
)
131+
SELECT
132+
COALESCE(latest_bundle.bundle_version, 0) as bundle_version,
133+
COALESCE(latest_bundle.bundle_url, '') as bundle_url,
134+
COALESCE(latest_bundle.target_count, -1) as target_count,
135+
COALESCE(latest_bundle.delivered_count, 0) as delivered_count,
136+
latest_native.max_native_version
137+
FROM latest_native
138+
LEFT JOIN latest_bundle ON TRUE;`,
139+
nativeVersion,
140+
)
141+
if errors.Is(err, sql.ErrNoRows) {
142+
return nil, ErrNotFound
143+
}
144+
145+
return &bundle, err
115146
}
116147

117-
func (d *DataAccessService) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error {
118-
// @TODO data access
119-
return d.dummy.IncrementBundleDeliveryCount(ctx, bundleVerison)
148+
func (d *DataAccessService) IncrementBundleDeliveryCount(ctx context.Context, bundleVersion uint64) error {
149+
_, err := d.alloyWriter.Exec("UPDATE mobile_app_bundles SET delivered_count = COALESCE(delivered_count, 0) + 1 WHERE bundle_version = $1", bundleVersion)
150+
return err
120151
}

backend/pkg/api/handlers/internal.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ func (h *HandlerService) InternalGetMobileLatestBundle(w http.ResponseWriter, r
529529
var data types.MobileBundleData
530530
data.HasNativeUpdateAvailable = stats.MaxNativeVersion > nativeVersion
531531
// if given bundle version is smaller than the latest and delivery count is less than target count, return the latest bundle
532-
if force || (bundleVersion < stats.LatestBundleVersion && (stats.TargetCount == 0 || stats.DeliveryCount < stats.TargetCount)) {
532+
if force || (bundleVersion < stats.LatestBundleVersion && (stats.TargetCount == -1 || stats.DeliveryCount < stats.TargetCount)) {
533533
data.BundleUrl = stats.BundleUrl
534534
}
535535
response := types.GetMobileLatestBundleResponse{

backend/pkg/api/types/data_access.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,9 @@ type HealthzData struct {
231231
// Mobile structs
232232

233233
type MobileAppBundleStats struct {
234-
LatestBundleVersion uint64
235-
BundleUrl string
236-
TargetCount uint64 // coalesce to 0 if column is null
237-
DeliveryCount uint64
238-
MaxNativeVersion uint64 // the max native version of the whole table for the given environment
234+
LatestBundleVersion uint64 `db:"bundle_version"`
235+
BundleUrl string `db:"bundle_url"`
236+
TargetCount int64 `db:"target_count"` // coalesce to -1 if column is null
237+
DeliveryCount int64 `db:"delivered_count"`
238+
MaxNativeVersion uint64 `db:"max_native_version"` // the max native version of the whole table for the given environment
239239
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- +goose Up
2+
-- +goose StatementBegin
3+
4+
CREATE TABLE IF NOT EXISTS mobile_app_bundles (
5+
bundle_version INT NOT NULL PRIMARY KEY,
6+
bundle_url TEXT NOT NULL,
7+
min_native_version INT NOT NULL,
8+
target_count INT,
9+
delivered_count INT NOT NULL DEFAULT 0
10+
);
11+
12+
-- +goose StatementEnd
13+
14+
-- +goose Down
15+
-- +goose StatementBegin
16+
17+
DROP TABLE IF EXISTS mobile_app_bundles;
18+
19+
-- +goose StatementEnd

0 commit comments

Comments
 (0)