Handle duplicate UTXOs (#4296)

* Handle duplicate UTXOs

* Add logging

* Add log for exception in handleDuplicateSpendingInfoDb

Co-authored-by: Chris Stewart <stewart.chris1234@gmail.com>
This commit is contained in:
rorp 2022-04-29 06:21:41 -07:00 committed by GitHub
parent 486fa36d2c
commit 0c6c9180ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 44 additions and 8 deletions

View File

@ -1,6 +1,5 @@
package org.bitcoins.server
import akka.{Done, NotUsed}
import akka.actor.ActorSystem
import akka.dispatch.Dispatchers
import akka.http.scaladsl.model.ws.Message
@ -12,6 +11,7 @@ import akka.stream.scaladsl.{
Source,
SourceQueueWithComplete
}
import akka.{Done, NotUsed}
import org.bitcoins.asyncutil.AsyncUtil
import org.bitcoins.chain.blockchain.ChainHandler
import org.bitcoins.chain.config.ChainAppConfig
@ -25,6 +25,7 @@ import org.bitcoins.core.api.node.{
NodeType
}
import org.bitcoins.core.util.TimeUtil
import org.bitcoins.core.wallet.rescan.RescanState
import org.bitcoins.dlc.node.DLCNode
import org.bitcoins.dlc.node.config.DLCNodeAppConfig
import org.bitcoins.dlc.wallet._
@ -36,16 +37,11 @@ import org.bitcoins.rpc.BitcoindException.InWarmUp
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.config.{BitcoindRpcAppConfig, ZmqConfig}
import org.bitcoins.server.routes.{BitcoinSServerRunner, CommonRoutes, Server}
import org.bitcoins.server.util.{
BitcoinSAppScalaDaemon,
CallbackUtil,
ServerBindings,
WebsocketUtil,
WsServerConfig
}
import org.bitcoins.server.util._
import org.bitcoins.tor.config.TorAppConfig
import org.bitcoins.wallet._
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.SpendingInfoDAO
import java.time.Instant
import scala.concurrent.duration.DurationInt
@ -201,6 +197,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
_ <- callbacksF
node <- startedNodeF
_ <- startedTorConfigF
wallet <- configuredWalletF
_ <- handleDuplicateSpendingInfoDb(wallet)
_ <- node.sync()
} yield {
logger.info(
@ -326,6 +324,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
dlcWalletCallbacks = WebsocketUtil.buildDLCWalletCallbacks(wsQueue)
_ = dlcConf.addCallbacks(dlcWalletCallbacks)
_ <- startedTorConfigF
_ <- handleDuplicateSpendingInfoDb(wallet)
} yield {
logger.info(s"Done starting Main!")
()
@ -542,6 +541,30 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
tuple
}
private def handleDuplicateSpendingInfoDb(wallet: Wallet): Future[Unit] = {
val spendingInfoDAO = SpendingInfoDAO()
for {
rescanNeeded <- spendingInfoDAO.hasDuplicates()
_ <-
if (rescanNeeded) {
logger.warn("Found duplicate UTXOs. Rescanning...")
wallet
.rescanNeutrinoWallet(startOpt = None,
endOpt = None,
addressBatchSize = wallet.discoveryBatchSize,
useCreationTime = true)
.recover { case scala.util.control.NonFatal(exn) =>
logger.error(s"Failed to handleDuplicateSpendingInfoDb rescan",
exn)
RescanState.RescanDone
}
} else {
Future.successful(RescanState.RescanDone)
}
_ <- spendingInfoDAO.createOutPointsIndexIfNeeded()
} yield ()
}
}
object BitcoinSServerMain extends BitcoinSAppScalaDaemon {

View File

@ -574,6 +574,19 @@ case class SpendingInfoDAO()(implicit
.map(_ => ts.map(_.copyWithState(TxoState.Reserved)))
}
def createOutPointsIndexIfNeeded(): Future[Unit] = {
val query =
sqlu"CREATE UNIQUE INDEX IF NOT EXISTS utxo_outpoints ON txo_spending_info (tx_outpoint)"
safeDatabase.run(query).map(_ => ())
}
def hasDuplicates(): Future[Boolean] = {
val query =
sql"SELECT EXISTS (SELECT tx_outpoint, COUNT(*) FROM txo_spending_info GROUP BY tx_outpoint HAVING COUNT(*) > 1)"
.as[Boolean]
safeDatabase.run(query).map(_.headOption.getOrElse(false))
}
private def findScriptPubKeysAction(ids: Seq[Long]): DBIOAction[
Map[Long, ScriptPubKeyDb],
NoStream,