From 64dba94c0526b722351ba000b60151faeb2d5e78 Mon Sep 17 00:00:00 2001 From: chyngyz Date: Fri, 15 Mar 2024 17:20:36 +0600 Subject: [PATCH] Set correct PublicKey path --- .../bitcoincore/models/PublicKey.kt | 2 +- .../rbf/ReplacementTransactionBuilder.kt | 21 +-- .../bitcoincore/storage/CoreDatabase.kt | 3 +- .../storage/migrations/Migration_18_19.kt | 121 ++++++++++++++++++ 4 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/migrations/Migration_18_19.kt diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/models/PublicKey.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/models/PublicKey.kt index 84a6adb5..b0aded6d 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/models/PublicKey.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/models/PublicKey.kt @@ -37,7 +37,7 @@ open class PublicKey() { } constructor(account: Int, index: Int, external: Boolean, publicKey: ByteArray, publicKeyHash: ByteArray) : this() { - this.path = "$account/${if (external) 1 else 0}/$index" + this.path = "$account/${if (external) 0 else 1}/$index" this.account = account this.index = index this.external = external diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/rbf/ReplacementTransactionBuilder.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/rbf/ReplacementTransactionBuilder.kt index 884fd34e..b2088fe4 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/rbf/ReplacementTransactionBuilder.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/rbf/ReplacementTransactionBuilder.kt @@ -6,7 +6,6 @@ import io.horizontalsystems.bitcoincore.core.IStorage import io.horizontalsystems.bitcoincore.core.PluginManager import io.horizontalsystems.bitcoincore.extensions.toReversedByteArray import io.horizontalsystems.bitcoincore.extensions.toReversedHex -import io.horizontalsystems.bitcoincore.managers.PublicKeyManager import io.horizontalsystems.bitcoincore.managers.UnspentOutputProvider import io.horizontalsystems.bitcoincore.models.Address import io.horizontalsystems.bitcoincore.models.PublicKey @@ -102,23 +101,9 @@ class ReplacementTransactionBuilder( originalInputs.map { inputWithPreviousOutput -> val previousOutput = inputWithPreviousOutput.previousOutput ?: throw BuildError.InvalidTransaction("No previous output of original transaction") - val prevOutputPublicKey = previousOutput.publicKeyPath?.let { path -> - val parts = path.split("/").map { it.toInt() } - if (parts.size != 3) throw PublicKeyManager.Error.InvalidPath - val account = parts[0] - val change = if (parts[1] == 0) 1 else 0 // change was incorrectly set in PublicKey - val index = parts[2] - val fixedPath = "$account/$change/$index" - - publicKeyManager.getPublicKeyByPath(fixedPath) - } ?: throw BuildError.InvalidTransaction("No public key of original transaction") - mutableTransaction.addInput( - inputToSign( - previousOutput = previousOutput, - prevOutputPublicKey = prevOutputPublicKey, - sequence = incrementedSequence(inputWithPreviousOutput) - ) - ) + val publicKey = previousOutput.publicKeyPath?.let { publicKeyManager.getPublicKeyByPath(it) } + ?: throw BuildError.InvalidTransaction("No public key of original transaction") + mutableTransaction.addInput(inputToSign(previousOutput = previousOutput, publicKey, incrementedSequence(inputWithPreviousOutput))) } } diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/CoreDatabase.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/CoreDatabase.kt index dedf4ea4..ee516298 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/CoreDatabase.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/CoreDatabase.kt @@ -10,7 +10,7 @@ import android.content.Context import io.horizontalsystems.bitcoincore.models.* import io.horizontalsystems.bitcoincore.storage.migrations.* -@Database(version = 18, exportSchema = false, entities = [ +@Database(version = 19, exportSchema = false, entities = [ BlockchainState::class, PeerAddress::class, BlockHash::class, @@ -49,6 +49,7 @@ abstract class CoreDatabase : RoomDatabase() { return Room.databaseBuilder(context, CoreDatabase::class.java, dbName) .allowMainThreadQueries() .addMigrations( + Migration_18_19, Migration_17_18, Migration_16_17, Migration_15_16, diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/migrations/Migration_18_19.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/migrations/Migration_18_19.kt new file mode 100644 index 00000000..be0991c1 --- /dev/null +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/storage/migrations/Migration_18_19.kt @@ -0,0 +1,121 @@ +package io.horizontalsystems.bitcoincore.storage.migrations + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import android.util.Log +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import io.horizontalsystems.bitcoincore.managers.PublicKeyManager + +object Migration_18_19 : Migration(18, 19) { + + override fun migrate(database: SupportSQLiteDatabase) { + try { + database.beginTransaction() + + migratePublicKeyPath(database) + migrateTransactionOutput(database) + migrateBlockHashPublicKey(database) + + database.setTransactionSuccessful() + } catch (error: Throwable) { + Log.e("e", "error in migration", error) + } finally { + database.endTransaction() + } + } + + private fun migratePublicKeyPath(database: SupportSQLiteDatabase) { + var cursor = database.query("SELECT * FROM `PublicKey`") + val publicKeyPathIndex = cursor.getColumnIndex("path") + + if (publicKeyPathIndex >= 0) { + + while (cursor.moveToNext()) { + val path = cursor.getString(publicKeyPathIndex) + val fixedPath = fixedPath(path) + + database.update( + /* table = */ "PublicKey", + /* conflictAlgorithm = */ SQLiteDatabase.CONFLICT_IGNORE, + /* values = */ ContentValues().apply { put("path", "tmp-$fixedPath") }, + /* whereClause = */ "path = ?", + /* whereArgs = */ arrayOf(path) + ) + } + + cursor = database.query("SELECT * FROM `PublicKey`") + + while (cursor.moveToNext()) { + val path = cursor.getString(publicKeyPathIndex) + val fixedPath = path.removePrefix("tmp-") + + database.update( + /* table = */ "PublicKey", + /* conflictAlgorithm = */ SQLiteDatabase.CONFLICT_IGNORE, + /* values = */ ContentValues().apply { put("path", fixedPath) }, + /* whereClause = */ "path = ?", + /* whereArgs = */ arrayOf(path) + ) + } + } + } + + private fun migrateTransactionOutput(database: SupportSQLiteDatabase) { + val cursor = database.query("SELECT * FROM `TransactionOutput` WHERE publicKeyPath IS NOT NULL") + val publicKeyPathIndex = cursor.getColumnIndex("publicKeyPath") + val transactionHashIndex = cursor.getColumnIndex("transactionHash") + val indexIndex = cursor.getColumnIndex("index") + + if (publicKeyPathIndex >= 0 && transactionHashIndex >= 0 && indexIndex >= 0) { + while (cursor.moveToNext()) { + val transactionHash = cursor.getBlob(transactionHashIndex) + val index = cursor.getString(indexIndex) + val path = cursor.getString(publicKeyPathIndex) + val fixedPath = fixedPath(path) + + database.update( + /* table = */ "TransactionOutput", + /* conflictAlgorithm = */ SQLiteDatabase.CONFLICT_IGNORE, + /* values = */ ContentValues().apply { put("publicKeyPath", fixedPath) }, + /* whereClause = */ "transactionHash = ? AND `index` = ?", + /* whereArgs = */ arrayOf(transactionHash, index) + ) + } + } + } + + private fun migrateBlockHashPublicKey(database: SupportSQLiteDatabase) { + val cursor = database.query("SELECT * FROM `BlockHashPublicKey` WHERE publicKeyPath IS NOT NULL") + val publicKeyPathIndex = cursor.getColumnIndex("publicKeyPath") + val blockHashIndex = cursor.getColumnIndex("blockHash") + + if (publicKeyPathIndex >= 0 && blockHashIndex >= 0) { + while (cursor.moveToNext()) { + val blockHash = cursor.getBlob(blockHashIndex) + val path = cursor.getString(publicKeyPathIndex) + val fixedPath = fixedPath(path) + + database.update( + /* table = */ "BlockHashPublicKey", + /* conflictAlgorithm = */ SQLiteDatabase.CONFLICT_IGNORE, + /* values = */ ContentValues().apply { + put("publicKeyPath", fixedPath) + }, + /* whereClause = */ "blockHash = ? AND publicKeyPath = ?", + /* whereArgs = */ arrayOf(blockHash, path) + ) + } + } + } + + private fun fixedPath(path: String): String { + val parts = path.split("/").map { it.toInt() } + if (parts.size != 3) throw PublicKeyManager.Error.InvalidPath + val account = parts[0] + val change = if (parts[1] == 0) 1 else 0 + val index = parts[2] + return "$account/$change/$index" + } + +}