Fix: correct the transaction queries.

The new info that we receive during scanning broke some assumptions we were making in the queries.
This commit is contained in:
Kevin Gorham 2020-03-27 16:24:27 -04:00
parent 8808d9c58d
commit 82381c5381
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
3 changed files with 386 additions and 8 deletions

View File

@ -0,0 +1,345 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "d6e9b05e0607d399f821058adb43dc15",
"entities": [
{
"tableName": "transactions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id_tx` INTEGER, `txid` BLOB NOT NULL, `tx_index` INTEGER, `created` TEXT, `expiry_height` INTEGER, `block` INTEGER, `raw` BLOB, PRIMARY KEY(`id_tx`), FOREIGN KEY(`block`) REFERENCES `blocks`(`height`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id_tx",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "transactionId",
"columnName": "txid",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "transactionIndex",
"columnName": "tx_index",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "expiryHeight",
"columnName": "expiry_height",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "minedHeight",
"columnName": "block",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "raw",
"columnName": "raw",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id_tx"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "blocks",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"block"
],
"referencedColumns": [
"height"
]
}
]
},
{
"tableName": "blocks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`height` INTEGER, `hash` BLOB NOT NULL, `time` INTEGER NOT NULL, `sapling_tree` BLOB NOT NULL, PRIMARY KEY(`height`))",
"fields": [
{
"fieldPath": "height",
"columnName": "height",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "hash",
"columnName": "hash",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "saplingTree",
"columnName": "sapling_tree",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"height"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "received_notes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id_note` INTEGER, `tx` INTEGER NOT NULL, `output_index` INTEGER NOT NULL, `account` INTEGER NOT NULL, `value` INTEGER NOT NULL, `spent` INTEGER, `diversifier` BLOB NOT NULL, `rcm` BLOB NOT NULL, `nf` BLOB NOT NULL, `is_change` INTEGER NOT NULL, `memo` BLOB, PRIMARY KEY(`id_note`), FOREIGN KEY(`tx`) REFERENCES `transactions`(`id_tx`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`account`) REFERENCES `accounts`(`account`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`spent`) REFERENCES `transactions`(`id_tx`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id_note",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "transactionId",
"columnName": "tx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "outputIndex",
"columnName": "output_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "account",
"columnName": "account",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "spent",
"columnName": "spent",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "diversifier",
"columnName": "diversifier",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "rcm",
"columnName": "rcm",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "nf",
"columnName": "nf",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "isChange",
"columnName": "is_change",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "memo",
"columnName": "memo",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id_note"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "transactions",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"tx"
],
"referencedColumns": [
"id_tx"
]
},
{
"table": "accounts",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"account"
],
"referencedColumns": [
"account"
]
},
{
"table": "transactions",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"spent"
],
"referencedColumns": [
"id_tx"
]
}
]
},
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account` INTEGER, `extfvk` TEXT NOT NULL, `address` TEXT NOT NULL, PRIMARY KEY(`account`))",
"fields": [
{
"fieldPath": "account",
"columnName": "account",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "extendedFullViewingKey",
"columnName": "extfvk",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"account"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sent_notes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id_note` INTEGER, `tx` INTEGER NOT NULL, `output_index` INTEGER NOT NULL, `from_account` INTEGER NOT NULL, `address` TEXT NOT NULL, `value` INTEGER NOT NULL, `memo` BLOB, PRIMARY KEY(`id_note`), FOREIGN KEY(`tx`) REFERENCES `transactions`(`id_tx`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`from_account`) REFERENCES `accounts`(`account`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id_note",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "transactionId",
"columnName": "tx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "outputIndex",
"columnName": "output_index",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "account",
"columnName": "from_account",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "memo",
"columnName": "memo",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id_note"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": [
{
"table": "transactions",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"tx"
],
"referencedColumns": [
"id_tx"
]
},
{
"table": "accounts",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"from_account"
],
"referencedColumns": [
"account"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd6e9b05e0607d399f821058adb43dc15')"
]
}
}

View File

@ -26,7 +26,7 @@ import cash.z.wallet.sdk.entity.*
Account::class,
Sent::class
],
version = 4,
version = 5,
exportSchema = true
)
abstract class DerivedDataDb : RoomDatabase() {
@ -163,15 +163,15 @@ interface TransactionDao {
transactions.raw AS raw,
sent_notes.address AS toAddress,
CASE
WHEN transactions.raw IS NOT NULL THEN sent_notes.value
WHEN sent_notes.value IS NOT NULL THEN sent_notes.value
ELSE received_notes.value
end AS value,
CASE
WHEN transactions.raw IS NOT NULL THEN sent_notes.memo
WHEN sent_notes.memo IS NOT NULL THEN sent_notes.memo
ELSE received_notes.memo
end AS memo,
CASE
WHEN transactions.raw IS NOT NULL THEN sent_notes.id_note
WHEN sent_notes.id_note IS NOT NULL THEN sent_notes.id_note
ELSE received_notes.id_note
end AS noteId,
blocks.time AS blockTimeInSeconds
@ -207,15 +207,15 @@ interface TransactionDao {
transactions.raw AS raw,
sent_notes.address AS toAddress,
CASE
WHEN transactions.raw IS NOT NULL THEN sent_notes.value
WHEN sent_notes.value IS NOT NULL THEN sent_notes.value
ELSE received_notes.value
end AS value,
CASE
WHEN transactions.raw IS NOT NULL THEN sent_notes.memo
WHEN sent_notes.memo IS NOT NULL THEN sent_notes.memo
ELSE received_notes.memo
end AS memo,
CASE
WHEN transactions.raw IS NOT NULL THEN sent_notes.id_note
WHEN sent_notes.id_note IS NOT NULL THEN sent_notes.id_note
ELSE received_notes.id_note
end AS noteId,
blocks.time AS blockTimeInSeconds
@ -226,7 +226,8 @@ interface TransactionDao {
ON transactions.id_tx = sent_notes.tx
LEFT JOIN blocks
ON transactions.block = blocks.height
WHERE :blockRangeStart <= minedheight AND minedheight <= :blockRangeEnd
WHERE :blockRangeStart <= minedheight
AND minedheight <= :blockRangeEnd
ORDER BY ( minedheight IS NOT NULL ),
minedheight ASC,
blocktimeinseconds DESC,

View File

@ -40,6 +40,7 @@ open class PagedTransactionRepository(
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.addMigrations(MIGRATION_3_4)
.addMigrations(MIGRATION_4_3)
.addMigrations(MIGRATION_4_5)
.build(),
pageSize
)
@ -151,6 +152,37 @@ open class PagedTransactionRepository(
database.execSQL("PRAGMA foreign_keys = ON;")
}
}
private val MIGRATION_4_5 = object : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("PRAGMA foreign_keys = OFF;")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS received_notes_new (
id_note INTEGER PRIMARY KEY,
tx INTEGER NOT NULL,
output_index INTEGER NOT NULL,
account INTEGER NOT NULL,
diversifier BLOB NOT NULL,
value INTEGER NOT NULL,
rcm BLOB NOT NULL,
nf BLOB NOT NULL UNIQUE,
is_change INTEGER NOT NULL,
memo BLOB,
spent INTEGER,
FOREIGN KEY (tx) REFERENCES transactions(id_tx),
FOREIGN KEY (account) REFERENCES accounts(account),
FOREIGN KEY (spent) REFERENCES transactions(id_tx),
CONSTRAINT tx_output UNIQUE (tx, output_index)
); """.trimIndent()
)
database.execSQL("INSERT INTO received_notes_new SELECT * FROM received_notes;")
database.execSQL("DROP TABLE received_notes;")
database.execSQL("ALTER TABLE received_notes_new RENAME TO received_notes;")
database.execSQL("PRAGMA foreign_keys = ON;")
}
}
}
}