diff --git a/core/src/main/scala/org/bitcoins/core/api/wallet/db/ScriptPubKeyDb.scala b/core/src/main/scala/org/bitcoins/core/api/wallet/db/ScriptPubKeyDb.scala index 68ecce6bae..c1c3d45a59 100644 --- a/core/src/main/scala/org/bitcoins/core/api/wallet/db/ScriptPubKeyDb.scala +++ b/core/src/main/scala/org/bitcoins/core/api/wallet/db/ScriptPubKeyDb.scala @@ -2,8 +2,24 @@ package org.bitcoins.core.api.wallet.db import org.bitcoins.core.api.db.DbRowAutoInc import org.bitcoins.core.protocol.script.ScriptPubKey +import org.bitcoins.crypto.{CryptoUtil, Sha256Digest} -case class ScriptPubKeyDb(scriptPubKey: ScriptPubKey, id: Option[Long] = None) +case class ScriptPubKeyDb( + scriptPubKey: ScriptPubKey, + hash: Sha256Digest, + id: Option[Long] = None) extends DbRowAutoInc[ScriptPubKeyDb] { override def copyWithId(id: Long): ScriptPubKeyDb = copy(id = Option(id)) } + +object ScriptPubKeyDb { + + def apply(scriptPubKey: ScriptPubKey): ScriptPubKeyDb = + ScriptPubKeyDb( + scriptPubKey = scriptPubKey, + hash = hash(scriptPubKey) + ) + + def hash(scriptPubKey: ScriptPubKey): Sha256Digest = + CryptoUtil.sha256(scriptPubKey.bytes) +} diff --git a/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala b/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala index dd22e7033f..6329619074 100644 --- a/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala +++ b/db-commons-test/src/test/scala/org/bitcoins/db/DbManagementTest.scala @@ -106,13 +106,13 @@ class DbManagementTest extends BitcoinSAsyncTest with EmbeddedPg { val result = walletDbManagement.migrate() walletAppConfig.driver match { case SQLite => - val expected = 12 + val expected = 14 assert(result == expected) val flywayInfo = walletDbManagement.info() assert(flywayInfo.applied().length == expected) assert(flywayInfo.pending().length == 0) case PostgreSQL => - val expected = 10 + val expected = 12 assert(result == expected) val flywayInfo = walletDbManagement.info() diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/models/ScriptPubKeyDAOTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/models/ScriptPubKeyDAOTest.scala index b7558637c4..cfac541745 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/models/ScriptPubKeyDAOTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/models/ScriptPubKeyDAOTest.scala @@ -6,6 +6,7 @@ import org.bitcoins.core.script.constant.ScriptNumber import org.bitcoins.core.script.reserved._ import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKey} import org.bitcoins.testkit.fixtures.WalletDAOFixture +import scodec.bits.ByteVector import java.sql.SQLException @@ -95,4 +96,41 @@ class ScriptPubKeyDAOTest extends WalletDAOFixture { assert(found.head.scriptPubKey == pkh) } } + + it must "be able to store and find big spks" in { daos => + val scriptPubKeyDAO = daos.scriptPubKeyDAO + + val Size = 840011 // the size of the biggest script on testnet + + val big1 = RawScriptPubKey.fromAsmBytes(ByteVector.fill(Size)(0x55)) + assert(big1.bytes.size == Size + 5) + + val big2 = RawScriptPubKey.fromAsmBytes(ByteVector.fill(Size * 2)(0xaa)) + assert(big2.bytes.size == Size * 2 + 5) + + val multisig = MultiSignatureScriptPubKey( + 2, + Vector(ECPublicKey.freshPublicKey, ECPublicKey.freshPublicKey)) + + val pkh = P2PKHScriptPubKey(ECPublicKey.freshPublicKey) + + val spks = Vector( + EmptyScriptPubKey, + pkh, + big1, + multisig, + big2 + ).map(spk => ScriptPubKeyDb(spk)) + + for { + _ <- scriptPubKeyDAO.createAll(spks) + fromDb <- scriptPubKeyDAO.findAll() + fromDb1 <- scriptPubKeyDAO.findScriptPubKeys(fromDb.map(_.scriptPubKey)) + } yield { + assert(fromDb.sortBy(_.id) == fromDb1.sortBy(_.id)) + val actual = fromDb1.sortBy(_.id) + assert(actual.size == spks.size) + assert(actual.map(_.scriptPubKey) == spks.map(_.scriptPubKey)) + } + } } diff --git a/wallet/src/main/resources/postgresql/wallet/migration/V14__add_scriptpubkey_hash.sql b/wallet/src/main/resources/postgresql/wallet/migration/V14__add_scriptpubkey_hash.sql new file mode 100644 index 0000000000..7bcd6deb3b --- /dev/null +++ b/wallet/src/main/resources/postgresql/wallet/migration/V14__add_scriptpubkey_hash.sql @@ -0,0 +1,5 @@ +ALTER TABLE pub_key_scripts ADD COLUMN hash VARCHAR(64); + +ALTER TABLE pub_key_scripts DROP CONSTRAINT pub_key_scripts_script_pub_key_key; + +CREATE UNIQUE INDEX pub_key_scripts_hash_idx ON pub_key_scripts(hash); diff --git a/wallet/src/main/resources/sqlite/wallet/migration/V13__add_scriptpubkey_hash.sql b/wallet/src/main/resources/sqlite/wallet/migration/V13__add_scriptpubkey_hash.sql new file mode 100644 index 0000000000..a80aa9f4a6 --- /dev/null +++ b/wallet/src/main/resources/sqlite/wallet/migration/V13__add_scriptpubkey_hash.sql @@ -0,0 +1,3 @@ +ALTER TABLE "pub_key_scripts" ADD COLUMN hash VARCHAR(64); + +CREATE UNIQUE INDEX "pub_key_scripts_hash_idx" ON "pub_key_scripts"(hash); diff --git a/wallet/src/main/scala/org/bitcoins/wallet/internal/AddressHandling.scala b/wallet/src/main/scala/org/bitcoins/wallet/internal/AddressHandling.scala index d4ccfc55e2..449b7cc7a4 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/internal/AddressHandling.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/internal/AddressHandling.scala @@ -107,8 +107,7 @@ private[wallet] trait AddressHandling extends WalletLogger { override def watchScriptPubKey( scriptPubKey: ScriptPubKey): Future[ScriptPubKeyDb] = - scriptPubKeyDAO.createIfNotExists( - ScriptPubKeyDb(scriptPubKey = scriptPubKey)) + scriptPubKeyDAO.createIfNotExists(ScriptPubKeyDb(scriptPubKey)) /** Enumerates the public keys in this wallet */ protected[wallet] def listPubkeys(): Future[Vector[ECPublicKey]] = diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/ScriptPubKeyDAO.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/ScriptPubKeyDAO.scala index 5299e2eb8a..a149631f29 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/ScriptPubKeyDAO.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/models/ScriptPubKeyDAO.scala @@ -3,6 +3,7 @@ package org.bitcoins.wallet.models import org.bitcoins.core.api.wallet.db.ScriptPubKeyDb import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.script.ScriptType +import org.bitcoins.crypto.Sha256Digest import org.bitcoins.db.CRUDAutoInc import org.bitcoins.wallet.config.WalletAppConfig import slick.dbio.DBIOAction @@ -50,7 +51,8 @@ case class ScriptPubKeyDAO()(implicit /** Searches for the given set of spks and returns the ones that exist in the db */ def findScriptPubKeys( spks: Vector[ScriptPubKey]): Future[Vector[ScriptPubKeyDb]] = { - val query = table.filter(_.scriptPubKey.inSet(spks)) + val hashes = spks.map(ScriptPubKeyDb.hash) + val query = table.filter(_.hash.inSet(hashes)) safeDatabase.runVec(query.result) } @@ -58,17 +60,20 @@ case class ScriptPubKeyDAO()(implicit extends TableAutoInc[ScriptPubKeyDb](tag, schemaName, "pub_key_scripts") { def scriptPubKey: Rep[ScriptPubKey] = column("script_pub_key") + def scriptType: Rep[ScriptType] = column("script_type") - private type ScriptPubKeyTuple = (Option[Long], ScriptPubKey, ScriptType) + def hash: Rep[Sha256Digest] = column("hash") + + private type ScriptPubKeyTuple = + (Option[Long], ScriptPubKey, ScriptType, Sha256Digest) private val fromTuple: ScriptPubKeyTuple => ScriptPubKeyDb = { - case (id, scriptPubKey, scriptType) => + case (id, scriptPubKey, scriptType, hash) => require( scriptPubKey.scriptType == scriptType, s"script type must match it script: `${scriptPubKey.scriptType}` != `${scriptType}` ") - ScriptPubKeyDb(scriptPubKey, id) - + ScriptPubKeyDb(scriptPubKey, hash, id) } private val toTuple: ScriptPubKeyDb => Option[ScriptPubKeyTuple] = { @@ -76,11 +81,12 @@ case class ScriptPubKeyDAO()(implicit Some( (scriptPubKeyDb.id, scriptPubKeyDb.scriptPubKey, - scriptPubKeyDb.scriptPubKey.scriptType)) + scriptPubKeyDb.scriptPubKey.scriptType, + scriptPubKeyDb.hash)) } override def * = - (id.?, scriptPubKey, scriptType).<>(fromTuple, toTuple) + (id.?, scriptPubKey, scriptType, hash).<>(fromTuple, toTuple) } } diff --git a/wallet/src/main/scala/postgresql/wallet/migration/V15__compute_spk_hashes.scala b/wallet/src/main/scala/postgresql/wallet/migration/V15__compute_spk_hashes.scala new file mode 100644 index 0000000000..239deb34c8 --- /dev/null +++ b/wallet/src/main/scala/postgresql/wallet/migration/V15__compute_spk_hashes.scala @@ -0,0 +1,30 @@ +package postgresql.wallet.migration + +import org.bitcoins.core.api.wallet.db.ScriptPubKeyDb +import org.bitcoins.core.protocol.script.ScriptPubKey +import org.flywaydb.core.api.migration.{BaseJavaMigration, Context} + +class V15__compute_spk_hashes extends BaseJavaMigration { + + override def migrate(context: Context): Unit = { + val selectStatement = context.getConnection.createStatement() + try { + val rows = selectStatement.executeQuery( + "SELECT id, script_pub_key FROM pub_key_scripts") + while (rows.next()) { + val id = rows.getLong(1) + val hex = rows.getString(2) + val spk = ScriptPubKey(hex) + val hash = ScriptPubKeyDb.hash(spk) + val updateStatement = context.getConnection.prepareStatement( + "UPDATE pub_key_scripts SET hash=? WHERE id=?") + updateStatement.setString(1, hash.hex) + updateStatement.setLong(2, id) + try { + updateStatement.executeUpdate() + } finally updateStatement.close() + } + } finally selectStatement.close() + } + +} diff --git a/wallet/src/main/scala/sqlite/wallet/migration/V14__compute_spk_hashes.scala b/wallet/src/main/scala/sqlite/wallet/migration/V14__compute_spk_hashes.scala new file mode 100644 index 0000000000..6a5ef39347 --- /dev/null +++ b/wallet/src/main/scala/sqlite/wallet/migration/V14__compute_spk_hashes.scala @@ -0,0 +1,30 @@ +package sqlite.wallet.migration + +import org.bitcoins.core.api.wallet.db.ScriptPubKeyDb +import org.bitcoins.core.protocol.script.ScriptPubKey +import org.flywaydb.core.api.migration.{BaseJavaMigration, Context} + +class V14__compute_spk_hashes extends BaseJavaMigration { + + override def migrate(context: Context): Unit = { + val selectStatement = context.getConnection.createStatement() + try { + val rows = selectStatement.executeQuery( + "SELECT id, script_pub_key FROM pub_key_scripts") + while (rows.next()) { + val id = rows.getLong(1) + val hex = rows.getString(2) + val spk = ScriptPubKey(hex) + val hash = ScriptPubKeyDb.hash(spk) + val updateStatement = context.getConnection.prepareStatement( + "UPDATE pub_key_scripts SET hash=? WHERE id=?") + updateStatement.setString(1, hash.hex) + updateStatement.setLong(2, id) + try { + updateStatement.executeUpdate() + } finally updateStatement.close() + } + } finally selectStatement.close() + } + +}