Example #1
0
def update_database(db: sqlite3.Connection) -> None:
    # This will error if the database has not been created correctly with the metadata.
    version = _get_migration(db)

    from . import migrations
    with db:
        if version == 22:
            migrations.migration_0023_add_wallet_events.execute(db)
            version += 1
        if version == 23:
            migrations.migration_0024_account_transactions.execute(db)
            version += 1
        if version == 24:
            migrations.migration_0025_invoices.execute(db)
            version += 1
        if version == 25:
            migrations.migration_0026_txo_coinbase_flag.execute(db)
            version += 1

        if version != MIGRATION_CURRENT:
            db.rollback()
            assert version == MIGRATION_CURRENT, \
                f"Expected migration {MIGRATION_CURRENT}, got {version}"

    _ensure_matching_migration(db, MIGRATION_CURRENT)
def execute(conn: sqlite3.Connection) -> None:
    # Ensure that for all transactions in block position 0, all outputs for those transactions
    # have the IS_COINBASE flag.
    conn.execute("UPDATE TransactionOutputs "
        f"SET flags=flags|{TransactionOutputFlag.IS_COINBASE} "
        "WHERE tx_hash in (SELECT tx_hash FROM Transactions WHERE block_position = 0)")

    date_updated = int(time.time())
    conn.execute("UPDATE WalletData SET value=?, date_updated=? WHERE key=?",
        [json.dumps(MIGRATION),date_updated,"migration"])
Example #3
0
def execute(conn: sqlite3.Connection) -> None:
    conn.execute("CREATE TABLE IF NOT EXISTS WalletEvents ("
                 "event_id INTEGER PRIMARY KEY,"
                 "event_type INTEGER NOT NULL,"
                 "event_flags INTEGER NOT NULL,"
                 "account_id INTEGER,"
                 "date_created INTEGER NOT NULL,"
                 "date_updated INTEGER NOT NULL,"
                 "FOREIGN KEY(account_id) REFERENCES Accounts (account_id)"
                 ")")

    date_updated = int(time.time())
    conn.execute("UPDATE WalletData SET value=?, date_updated=? WHERE key=?",
                 [json.dumps(MIGRATION), date_updated, "migration"])

    # Inject seed backup reminders for every existing account (can actually only be one for now).
    account_rows = list(conn.execute("SELECT * FROM Accounts"))
    wallet_event_id = 1
    for account_row in account_rows:
        conn.execute(
            "INSERT INTO WalletEvents (event_id, event_type, event_flags, "
            "account_id, date_created, date_updated) VALUES (?, ?, ?, ?, ?, ?)",
            (wallet_event_id, WalletEventType.SEED_BACKUP_REMINDER,
             WalletEventFlag.UNREAD | WalletEventFlag.FEATURED, account_row[0],
             date_updated, date_updated))
        wallet_event_id += 1

    if wallet_event_id > 1:
        conn.execute(
            "INSERT INTO WalletData (key, value, date_created, date_updated) VALUES "
            "(?, ?, ?, ?)",
            ("next_wallet_event_id", json.dumps(wallet_event_id), date_updated,
             date_updated))
Example #4
0
def _get_migration(db: sqlite3.Connection) -> int:
    cursor = db.execute("SELECT value FROM WalletData WHERE key='migration'")
    row = cursor.fetchone()
    if row is None:
        raise DatabaseMigrationError(
            "wallet database migration metadata not present")
    return json.loads(row[0])
Example #5
0
    def _ensure_journal_mode(self, connection: sqlite3.Connection) -> None:
        with self._lock:
            cursor = connection.execute(f"PRAGMA journal_mode;")
            journal_mode = cursor.fetchone()[0]
            if journal_mode.upper() == self.JOURNAL_MODE.value:
                return

            self._logger.debug(
                "Switching database from journal mode %s to journal mode %s",
                journal_mode.upper(), self.JOURNAL_MODE.value)

            time_start = time.time()
            attempt = 1
            delay = 0.05
            while True:
                try:
                    cursor = connection.execute(
                        f"PRAGMA journal_mode={self.JOURNAL_MODE.value};")
                except sqlite3.OperationalError:
                    time_delta = time.time() - time_start
                    if time_delta < 10.0:
                        delay = min(delay, max(0.05, 10.0 - time_delta))
                        time.sleep(delay)
                        self._logger.warning(
                            "Database %s pragma attempt %d at %ds",
                            self.JOURNAL_MODE.value, attempt, time_delta)
                        delay *= 2
                        attempt += 1
                        continue
                    raise
                else:
                    journal_mode = cursor.fetchone()[0]
                    if journal_mode.upper() != self.JOURNAL_MODE.value:
                        self._logger.error(
                            "Database unable to switch from journal mode %s to journal mode %s",
                            self.JOURNAL_MODE.value, journal_mode.upper())
                        return
                    break

            self._logger.debug("Database now in journal mode %s",
                               self.JOURNAL_MODE.value)
Example #6
0
def execute(conn: sqlite3.Connection) -> None:
    conn.execute("CREATE VIEW IF NOT EXISTS AccountTransactions (account_id, tx_hash) AS "
    "SELECT DISTINCT KI.account_id, TD.tx_hash FROM TransactionDeltas TD "
    "INNER JOIN KeyInstances KI USING(keyinstance_id)")

    # Switch the state constants over from a value to flags.
    conn.execute("UPDATE PaymentRequests SET state=8 WHERE state=3")
    conn.execute("UPDATE PaymentRequests SET state=4 WHERE state=2")
    conn.execute("UPDATE PaymentRequests SET state=2 WHERE state=1")
    conn.execute("UPDATE PaymentRequests SET state=1 WHERE state=0")

    date_updated = int(time.time())
    conn.execute("UPDATE WalletData SET value=?, date_updated=? WHERE key=?",
        [json.dumps(MIGRATION),date_updated,"migration"])
Example #7
0
def execute(conn: sqlite3.Connection) -> None:
    conn.execute("CREATE TABLE IF NOT EXISTS Invoices ("
                 "invoice_id INTEGER PRIMARY KEY,"
                 "account_id INTEGER NOT NULL,"
                 "tx_hash BLOB DEFAULT NULL,"
                 "payment_uri TEXT NOT NULL,"
                 "description TEXT NULL,"
                 "invoice_flags INTEGER NOT NULL,"
                 "value INTEGER NOT NULL,"
                 "invoice_data BLOB NOT NULL,"
                 "date_expires INTEGER DEFAULT NULL,"
                 "date_created INTEGER NOT NULL,"
                 "date_updated INTEGER NOT NULL,"
                 "FOREIGN KEY (account_id) REFERENCES Accounts (account_id),"
                 "FOREIGN KEY (tx_hash) REFERENCES Transactions (tx_hash)"
                 ")")

    # The unique constraint is also required for any upsert operation to work.
    # But really we added it to prevent people from making duplicate invoices.
    conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS "
                 "idx_Invoices_unique ON Invoices(payment_uri)")

    date_updated = int(time.time())
    conn.execute("UPDATE WalletData SET value=?, date_updated=? WHERE key=?",
                 [json.dumps(MIGRATION), date_updated, "migration"])
def execute(conn: sqlite3.Connection) -> None:
    date_created = int(time.time())
    conn.execute(
        "CREATE TABLE IF NOT EXISTS MasterKeys ("
        "masterkey_id INTEGER PRIMARY KEY,"
        "parent_masterkey_id INTEGER DEFAULT NULL,"
        "derivation_type INTEGER NOT NULL,"
        "derivation_data BLOB NOT NULL,"
        "date_created INTEGER NOT NULL,"
        "date_updated INTEGER NOT NULL,"
        "FOREIGN KEY(parent_masterkey_id) REFERENCES MasterKeys (masterkey_id)"
        ")")

    conn.execute(
        "CREATE TABLE IF NOT EXISTS Accounts ("
        "account_id INTEGER PRIMARY KEY,"
        "default_masterkey_id INTEGER DEFAULT NULL,"
        "default_script_type INTEGER NOT NULL,"
        "account_name TEXT NOT NULL,"
        "date_created INTEGER NOT NULL,"
        "date_updated INTEGER NOT NULL,"
        "FOREIGN KEY(default_masterkey_id) REFERENCES MasterKeys (masterkey_id)"
        ")")

    conn.execute(
        "CREATE TABLE IF NOT EXISTS KeyInstances ("
        "keyinstance_id INTEGER PRIMARY KEY,"
        "account_id INTEGER NOT NULL,"
        "masterkey_id INTEGER DEFAULT NULL,"
        "derivation_type INTEGER NOT NULL,"
        "derivation_data BLOB NOT NULL,"
        "script_type INTEGER NOT NULL,"
        "flags INTEGER NOT NULL,"
        "description TEXT DEFAULT NULL,"
        "date_created INTEGER NOT NULL,"
        "date_updated INTEGER NOT NULL,"
        "FOREIGN KEY(account_id) REFERENCES Accounts (account_id)" +
        "FOREIGN KEY(masterkey_id) REFERENCES MasterKeys (masterkey_id)" + ")")

    conn.execute("CREATE TABLE IF NOT EXISTS Transactions ("
                 "tx_hash BLOB PRIMARY KEY,"
                 "tx_data BLOB DEFAULT NULL,"
                 "proof_data BLOB DEFAULT NULL,"
                 "block_height INTEGER DEFAULT NULL,"
                 "block_position INTEGER DEFAULT NULL,"
                 "fee_value INTEGER DEFAULT NULL,"
                 "flags INTEGER NOT NULL DEFAULT 0,"
                 "description TEXT DEFAULT NULL,"
                 "date_created INTEGER NOT NULL,"
                 "date_updated INTEGER NOT NULL"
                 ")")

    conn.execute(
        "CREATE TABLE IF NOT EXISTS TransactionOutputs ("
        "tx_hash BLOB NOT NULL,"
        "tx_index INTEGER NOT NULL,"
        "value INTEGER NOT NULL,"
        "keyinstance_id INTEGER NOT NULL,"
        "flags INTEGER NOT NULL,"
        "date_created INTEGER NOT NULL,"
        "date_updated INTEGER NOT NULL,"
        "FOREIGN KEY (tx_hash) REFERENCES Transactions (tx_hash),"
        "FOREIGN KEY (keyinstance_id) REFERENCES KeyInstances (keyinstance_id)"
        ")")

    # The unique constraint is also required for any upsert operation to work.
    conn.execute(
        "CREATE UNIQUE INDEX IF NOT EXISTS "
        "idx_TransactionOutputs_unique ON TransactionOutputs(tx_hash, tx_index)"
    )

    conn.execute(
        "CREATE TABLE IF NOT EXISTS TransactionDeltas ("
        "keyinstance_id INTEGER NOT NULL,"
        "tx_hash BLOB NOT NULL,"
        "value_delta INTEGER NOT NULL,"
        "date_created INTEGER NOT NULL,"
        "date_updated INTEGER NOT NULL,"
        "FOREIGN KEY(tx_hash) REFERENCES Transactions (tx_hash),"
        "FOREIGN KEY(keyinstance_id) REFERENCES KeyInstances (keyinstance_id) "
        ")")

    # The unique constraint is also required for any upsert operation to work.
    conn.execute(
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_TransactionDeltas_unique "
        "ON TransactionDeltas(keyinstance_id, tx_hash)")

    conn.execute("CREATE TABLE IF NOT EXISTS WalletData ("
                 "key TEXT NOT NULL,"
                 "value TEXT NOT NULL,"
                 "date_created INTEGER NOT NULL,"
                 "date_updated INTEGER NOT NULL"
                 ")")

    conn.execute(
        "CREATE TABLE IF NOT EXISTS PaymentRequests ("
        "paymentrequest_id INTEGER PRIMARY KEY,"
        "keyinstance_id INTEGER NOT NULL,"
        "state INTEGER NOT NULL,"
        "description TEXT DEFAULT NULL,"
        "expiration INTEGER DEFAULT NULL,"
        "value INTEGER DEFAULT NULL,"
        "date_created INTEGER NOT NULL,"
        "date_updated INTEGER NOT NULL,"
        "FOREIGN KEY(keyinstance_id) REFERENCES KeyInstances (keyinstance_id) "
        ")")

    # The unique constraint is also required for any upsert operation to work.
    conn.execute(
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_WalletData_unique ON WalletData(key)"
    )

    conn.executemany(
        "INSERT INTO WalletData (key, value, date_created, date_updated) VALUES "
        "(?, ?, ?, ?)", [
            ["migration",
             json.dumps(MIGRATION), date_created, date_created],
            ["next_masterkey_id",
             json.dumps(1), date_created, date_created],
            ["next_account_id",
             json.dumps(1), date_created, date_created],
            ["next_keyinstance_id",
             json.dumps(1), date_created, date_created],
            [
                "next_paymentrequest_id",
                json.dumps(1), date_created, date_created
            ],
        ])