package database import ( "fmt" "log/slog" ) type migrateFn func(*DB) error var dbMigrate = [...]migrateFn{v1, v2, v3} func MigrateDb(db *DB) error { version := getSchemaVersion(db) if version < 0 { return fmt.Errorf("[DB] can't get database schema version") } else if version == 0 { slog.Warn("[DB] No database schema version found, creating schema version 1") } else { slog.Info(fmt.Sprintf("[DB] Current database schema version: %d", version)) } for ; version < len(dbMigrate); version++ { tx, err := db.Begin() if err != nil { return err } migrateFn := dbMigrate[version] slog.Info(fmt.Sprintf("[DB] Migrating database schema version %d", version+1)) if err := migrateFn(db); err != nil { if err := tx.Rollback(); err != nil { return err } return err } if err := setSchemaVersion(db, version+1); err != nil { return err } if err := tx.Commit(); err != nil { return err } } return nil } func getSchemaVersion(db *DB) int { _, err := db.Exec(` CREATE TABLE IF NOT EXISTS tbl_schema_ver ( version INT(5) UNSIGNED NOT NULL )`) if err != nil { return -1 } version := 0 if err := db.Get(&version, `SELECT version FROM tbl_schema_ver`); err != nil { return -1 } return version } func setSchemaVersion(db *DB, version int) error { _, err := db.Exec(`DELETE FROM tbl_schema_ver`) if err != nil { return err } _, err = db.Exec(`INSERT INTO tbl_schema_ver (version) VALUES (?)`, version) return err } func v1(db *DB) error { slog.Debug("[DB] Migrating database schema version 1") // table: tbl_cron slog.Debug("[DB] Creating table: tbl_cron") _, err := db.Exec(` CREATE TABLE tbl_cron ( id INT(8) UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL DEFAULT '', slug VARCHAR(255) NOT NULL DEFAULT '', description VARCHAR(255) NOT NULL DEFAULT '', run_every INT(8) UNSIGNED NOT NULL DEFAULT 60 COMMENT 'in seconds', last_run INT(11) UNSIGNED NOT NULL DEFAULT 0, next_run INT(11) UNSIGNED NOT NULL DEFAULT 0, run_time FLOAT(7,3) UNSIGNED NOT NULL DEFAULT 0.000, cron_state TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, is_enabled TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, PRIMARY KEY (id) )`) if err != nil { return err } slog.Debug("[DB] Adding default cron jobs to table: tbl_cron") _, err = db.Exec(` INSERT INTO tbl_cron ( title, slug, description, run_every ) VALUES ( 'Delete old probe logs', 'delete_old_probe_logs', 'Delete old probe log from the database', 120 );`) if err != nil { return err } // table: tbl_node slog.Debug("[DB] Creating table: tbl_node") _, err = db.Exec(` CREATE TABLE tbl_node ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, protocol VARCHAR(6) NOT NULL DEFAULT 'http' COMMENT 'http | https', hostname VARCHAR(255) NOT NULL, port INT(6) UNSIGNED NOT NULL DEFAULT 0, is_tor TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, is_available TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, nettype VARCHAR(100) NOT NULL COMMENT 'mainnet | stagenet | testnet', height BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, adjusted_time BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, database_size BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, difficulty BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, version VARCHAR(200) NOT NULL DEFAULT '', uptime float(5,2) UNSIGNED NOT NULL DEFAULT 0.00, estimate_fee INT(9) UNSIGNED NOT NULL DEFAULT 0, ip_addr VARCHAR(200) NOT NULL, asn INT(9) UNSIGNED NOT NULL DEFAULT 0, asn_name VARCHAR(255) NOT NULL DEFAULT '', country VARCHAR(100) NOT NULL DEFAULT '', country_name VARCHAR(255) NOT NULL DEFAULT '', city VARCHAR(255) NOT NULL DEFAULT '', lat FLOAT NOT NULL DEFAULT 0 COMMENT 'latitude', lon FLOAT NOT NULL DEFAULT 0 COMMENT 'longitude', date_entered INT(11) UNSIGNED NOT NULL DEFAULT 0, last_checked INT(11) UNSIGNED NOT NULL DEFAULT 0, last_check_status TEXT DEFAULT NULL, cors_capable TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (id) )`) if err != nil { return err } // NOTE: If you need list of public nodes (for example to seed `tbl_node` // data for integration test), you can use `public_nodes` command from // `monero-wallet-cli` app. Eg testnet public nodes: // echo "public_nodes" | monero-wallet-cli --testnet --wallet-file=wallet --daemon-address=testnet.xmr.ditatompel.com:443 --password-file=pass_file // table: tbl_prober slog.Debug("[DB] Creating table: tbl_prober") _, err = db.Exec(` CREATE TABLE tbl_prober ( id INT(9) UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, api_key VARCHAR(36) NOT NULL, last_submit_ts INT(11) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (id) )`) if err != nil { return err } slog.Debug("[DB] Adding unique key to table: tbl_prober") _, err = db.Exec(`ALTER TABLE tbl_prober ADD UNIQUE KEY (api_key)`) if err != nil { return err } // table: tbl_probe_log slog.Debug("[DB] Creating table: tbl_probe_log") _, err = db.Exec(` CREATE TABLE tbl_probe_log ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, node_id INT(11) UNSIGNED NOT NULL DEFAULT 0, prober_id INT(9) UNSIGNED NOT NULL DEFAULT 0, is_available TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, height BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, adjusted_time BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, database_size BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, difficulty BIGINT(20) UNSIGNED NOT NULL DEFAULT 0, estimate_fee INT(9) UNSIGNED NOT NULL DEFAULT 0, date_checked INT(11) UNSIGNED NOT NULL DEFAULT 0, failed_reason TEXT NOT NULL DEFAULT '', fetch_runtime FLOAT(5,2) UNSIGNED NOT NULL DEFAULT 0.00, PRIMARY KEY (id) )`) if err != nil { return err } slog.Debug("[DB] Adding key to table: tbl_probe_log") _, err = db.Exec(`ALTER TABLE tbl_probe_log ADD KEY (node_id)`) if err != nil { return err } return nil } func v2(db *DB) error { slog.Debug("[DB] Migrating database schema version 2") // table: tbl_fee slog.Debug("[DB] Creating table: tbl_fee") _, err := db.Exec(` CREATE TABLE tbl_fee ( nettype VARCHAR(100) NOT NULL DEFAULT '', estimate_fee INT(9) UNSIGNED NOT NULL DEFAULT 0, node_count INT(9) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (nettype) )`) if err != nil { return err } slog.Debug("[DB] Adding default fee to table: tbl_fee") _, err = db.Exec(` INSERT INTO tbl_fee ( nettype, estimate_fee, node_count ) VALUES ( 'mainnet', 0, 0 ), ( 'stagenet', 0, 0 ), ( 'testnet', 0, 0 );`) if err != nil { return err } slog.Debug("[DB] Adding majority fee cron jobs to table: tbl_cron") _, err = db.Exec(` INSERT INTO tbl_cron ( title, slug, description, run_every ) VALUES ( 'Calculate majority fee', 'calculate_majority_fee', 'Calculate majority Monero fee', 300 );`) if err != nil { return err } return nil } func v3(db *DB) error { slog.Debug("[DB] Migrating database schema version 3") // table: tbl_node slog.Debug("[DB] Adding additional columns to tbl_node") _, err := db.Exec(` ALTER TABLE tbl_node ADD COLUMN ipv6_only TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER cors_capable, ADD COLUMN ip_addresses TEXT NOT NULL DEFAULT '' AFTER cors_capable;`) if err != nil { return err } return nil }