Support for big SPKs (#4084)

* Support for big SPKs

* Support for big SPKs

* Bump number of migrations

* Fix other test case

* cleanup

* cleanup

* fix psql migrations

Co-authored-by: Chris Stewart <stewart.chris1234@gmail.com>
This commit is contained in:
rorp 2022-02-14 05:22:34 -08:00 committed by GitHub
parent 3931897e7f
commit 4d85b7a3d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 139 additions and 12 deletions

View File

@ -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)
}

View File

@ -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()

View File

@ -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))
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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]] =

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}