2021 04 18 wallet received txo state (#2914)

* Add invariant to spendingInfoDb to that requires if the spendinginfodb is in a TxoState.spentStates, the SpendingInfoDb.spendingTxIdOpt is defined

* Remove unused SpendingInfoDAO.updateTxoState()

* Tighten up TxoState -> ReceivedState types we are using in the wallet. The ensures we are talking specific class of states (receiving a txo) rather than accounting for the case of spending and receiving since there is different information required for the spending states

* Add TxoState.ImmatureCoinbase to ReceivedStates
This commit is contained in:
Chris Stewart 2021-04-18 16:56:08 -05:00 committed by GitHub
parent 0d546f3b65
commit 238c083aad
3 changed files with 31 additions and 49 deletions

View file

@ -17,7 +17,7 @@ object TxoState extends StringFactory[TxoState] {
/** A coinbase output that has not reached maturity and cannot be spent yet.
* https://bitcoin.stackexchange.com/questions/1991/what-is-the-block-maturation-time
*/
final case object ImmatureCoinbase extends TxoState
final case object ImmatureCoinbase extends ReceivedState
/** Means we have received funds to this utxo, and they have not been confirmed in a block */
final case object BroadcastReceived extends ReceivedState
@ -55,7 +55,10 @@ object TxoState extends StringFactory[TxoState] {
Set(TxoState.ConfirmedReceived, TxoState.ConfirmedSpent)
val receivedStates: Set[TxoState] =
Set(PendingConfirmationsReceived, ConfirmedReceived, BroadcastReceived)
Set(PendingConfirmationsReceived,
ConfirmedReceived,
BroadcastReceived,
TxoState.ImmatureCoinbase)
val spentStates: Set[TxoState] =
Set(PendingConfirmationsSpent, TxoState.ConfirmedSpent, BroadcastSpent)

View file

@ -11,7 +11,7 @@ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
import org.bitcoins.core.util.TimeUtil
import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.core.wallet.utxo.TxoState._
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
import org.bitcoins.core.wallet.utxo.{AddressTag, ReceivedState, TxoState}
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.wallet._
@ -357,17 +357,18 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
* error if any (this is because we're operating on data we've
* already verified).
*/
private def processUtxo(
private def processReceivedUtxo(
transaction: Transaction,
index: Int,
state: TxoState): Future[SpendingInfoDb] = {
addUtxo(transaction = transaction, vout = UInt32(index), state = state)
.flatMap {
case AddUtxoSuccess(utxo) => Future.successful(utxo)
case err: AddUtxoError =>
logger.error(s"Could not add UTXO", err)
Future.failed(err)
}
state: ReceivedState): Future[SpendingInfoDb] = {
val addIncomingUtxoF =
addUtxo(transaction = transaction, vout = UInt32(index), state = state)
addIncomingUtxoF.flatMap {
case AddUtxoSuccess(utxo) => Future.successful(utxo)
case err: AddUtxoError =>
logger.error(s"Could not add UTXO", err)
Future.failed(err)
}
}
private case class OutputWithIndex(output: TransactionOutput, index: Int)
@ -428,12 +429,13 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
}
}
private def addUTXOsFut(
/** Adds utxos to the database that we are receiving */
private def addReceivedUTXOs(
outputsWithIndex: Seq[OutputWithIndex],
transaction: Transaction,
blockHashOpt: Option[DoubleSha256DigestBE]): Future[
Seq[SpendingInfoDb]] = {
val stateF: Future[TxoState] = blockHashOpt match {
val stateF: Future[ReceivedState] = blockHashOpt match {
case None =>
Future.successful(TxoState.BroadcastReceived)
case Some(blockHash) =>
@ -453,7 +455,7 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
stateF.flatMap { state =>
val outputsVec = outputsWithIndex.map { out =>
processUtxo(
processReceivedUtxo(
transaction,
out.index,
state = state
@ -504,17 +506,6 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
Future.successful(Vector.empty)
case outputsWithIndex =>
val count = outputsWithIndex.length
val outputStr = {
outputsWithIndex
.map { elem =>
s"${transaction.txIdBE.hex}:${elem.index}"
}
.mkString(", ")
}
logger.trace(
s"Found $count relevant output(s) in transaction=${transaction.txIdBE.hex}: $outputStr")
val totalIncoming = outputsWithIndex.map(_.output.value).sum
val spks = outputsWithIndex.map(_.output.scriptPubKey)
@ -556,7 +547,7 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
for {
(txDb, _) <- txDbF
ourOutputs <- ourOutputsF
utxos <- addUTXOsFut(ourOutputs, txDb.transaction, blockHashOpt)
utxos <- addReceivedUTXOs(ourOutputs, txDb.transaction, blockHashOpt)
_ <- newTagsF
} yield utxos
}

View file

@ -215,7 +215,7 @@ private[wallet] trait UtxoHandling extends WalletLogger {
/** Constructs a DB level representation of the given UTXO, and persist it to disk */
private def writeUtxo(
tx: Transaction,
state: TxoState,
state: ReceivedState,
output: TransactionOutput,
outPoint: TransactionOutPoint,
addressDb: AddressDb): Future[SpendingInfoDb] = {
@ -263,39 +263,27 @@ private[wallet] trait UtxoHandling extends WalletLogger {
}
}
private def getAddressDbEitherF(
output: TransactionOutput): Future[Either[AddUtxoError, AddressDb]] = {
findAddress(output.scriptPubKey)
}
/** Adds the provided UTXO to the wallet
*/
protected def addUtxo(
transaction: Transaction,
vout: UInt32,
state: TxoState): Future[AddUtxoResult] = {
state: ReceivedState): Future[AddUtxoResult] = {
logger.info(s"Adding UTXO to wallet: ${transaction.txId.hex}:${vout.toInt}")
// first check: does the provided vout exist in the tx?
val voutIndexOutOfBounds: Boolean = {
val voutLength = transaction.outputs.length
val outOfBunds = voutLength <= vout.toInt
if (outOfBunds)
logger.error(
s"TX with TXID ${transaction.txId.hex} only has $voutLength, got request to add vout ${vout.toInt}!")
outOfBunds
}
if (voutIndexOutOfBounds) {
if (vout.toInt >= transaction.outputs.length) {
//out of bounds output
Future.successful(VoutIndexOutOfBounds)
} else {
val output = transaction.outputs(vout.toInt)
val outPoint = TransactionOutPoint(transaction.txId, vout)
// second check: do we have an address associated with the provided
// output in our DB?
def addressDbEitherF: Future[Either[AddUtxoError, AddressDb]] = {
findAddress(output.scriptPubKey)
}
val addressDbEitherF = getAddressDbEitherF(output)
// insert the UTXO into the DB
addressDbEitherF
.map { addressDbE =>