From 86517c0a6d12f5e1ea5531e7c7b6901b09f56104 Mon Sep 17 00:00:00 2001 From: Torkel Rogstad Date: Wed, 26 Jun 2019 16:56:12 +0200 Subject: [PATCH] Replace incoming/outgoing TXs with TXOs Rework the wallet DB model so that instead of dealing with incoming and outgoing transactions we deal with incoming and outgoing transaction outputs. --- .../testkit/fixtures/WalletDAOFixture.scala | 14 +-- .../testkit/wallet/WalletTestUtil.scala | 70 ++++++++---- .../wallet/ProcessTransactionTest.scala | 52 +++++---- ...DAOTest.scala => IncomingTxoDAOTest.scala} | 19 ++-- .../wallet/models/SpendingInfoDbDAOTest.scala | 36 ++++++ .../bitcoins/wallet/api/AddUtxoResult.scala | 10 +- .../org/bitcoins/wallet/api/WalletApi.scala | 4 +- .../wallet/db/WalletDbManagement.scala | 10 +- .../models/IncomingTransactionDAO.scala | 51 --------- ...nsactionDAO.scala => OutgoingTxoDAO.scala} | 19 ++-- ...ingInfoDAO.scala => SpendingInfoDAO.scala} | 6 +- ...nfoTable.scala => SpendingInfoTable.scala} | 89 ++++++--------- .../wallet/models/TransactionTable.scala | 106 ------------------ 13 files changed, 198 insertions(+), 288 deletions(-) rename wallet-test/src/test/scala/org/bitcoins/wallet/models/{IncomingTransactionDAOTest.scala => IncomingTxoDAOTest.scala} (63%) create mode 100644 wallet-test/src/test/scala/org/bitcoins/wallet/models/SpendingInfoDbDAOTest.scala delete mode 100644 wallet/src/main/scala/org/bitcoins/wallet/models/IncomingTransactionDAO.scala rename wallet/src/main/scala/org/bitcoins/wallet/models/{OutgoingTransactionDAO.scala => OutgoingTxoDAO.scala} (58%) rename wallet/src/main/scala/org/bitcoins/wallet/models/{UTXOSpendingInfoDAO.scala => SpendingInfoDAO.scala} (70%) rename wallet/src/main/scala/org/bitcoins/wallet/models/{UTXOSpendingInfoTable.scala => SpendingInfoTable.scala} (69%) delete mode 100644 wallet/src/main/scala/org/bitcoins/wallet/models/TransactionTable.scala diff --git a/testkit/src/main/scala/org/bitcoins/testkit/fixtures/WalletDAOFixture.scala b/testkit/src/main/scala/org/bitcoins/testkit/fixtures/WalletDAOFixture.scala index c2973c4943..b5d8cc04fd 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/fixtures/WalletDAOFixture.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/fixtures/WalletDAOFixture.scala @@ -10,19 +10,19 @@ import slick.jdbc.SQLiteProfile case class WalletDAOs( accountDAO: AccountDAO, addressDAO: AddressDAO, - incomingTxDAO: IncomingTransactionDAO, - outgoingTxDAO: OutgoingTransactionDAO, - utxoDAO: UTXOSpendingInfoDAO) + incomingTxoDAO: IncomingTxoDAO, + outgoingTxoDAO: OutgoingTxoDAO, + utxoDAO: SpendingInfoDAO) trait WalletDAOFixture extends fixture.AsyncFlatSpec with BitcoinSWalletTest { private lazy val daos: WalletDAOs = { val account = AccountDAO() val address = AddressDAO() - val inTx = IncomingTransactionDAO(SQLiteProfile) - val outTx = OutgoingTransactionDAO(SQLiteProfile) - val utxo = UTXOSpendingInfoDAO() - WalletDAOs(account, address, inTx, outTx, utxo) + val inTxo = IncomingTxoDAO(SQLiteProfile) + val outTxo = OutgoingTxoDAO(SQLiteProfile) + val utxo = SpendingInfoDAO() + WalletDAOs(account, address, inTxo, outTxo, utxo) } final override type FixtureParam = WalletDAOs diff --git a/testkit/src/main/scala/org/bitcoins/testkit/wallet/WalletTestUtil.scala b/testkit/src/main/scala/org/bitcoins/testkit/wallet/WalletTestUtil.scala index e7fd5f4931..7b65c21f33 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/wallet/WalletTestUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/wallet/WalletTestUtil.scala @@ -2,6 +2,7 @@ package org.bitcoins.testkit.wallet import org.bitcoins.core.config.RegTest import org.bitcoins.core.crypto._ +import org.bitcoins.core.currency._ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.blockchain.{ ChainParams, @@ -17,11 +18,16 @@ import org.bitcoins.core.protocol.script.P2WPKHWitnessV0 import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.testkit.core.gen.TransactionGenerators import scala.concurrent.Future -import org.bitcoins.wallet.models.IncomingTransaction import org.bitcoins.wallet.models.AddressDb import org.bitcoins.wallet.models.AddressDbHelper import org.bitcoins.testkit.fixtures.WalletDAOs import scala.concurrent.ExecutionContext +import org.bitcoins.wallet.models.IncomingWalletTXO +import org.bitcoins.wallet.models.LegacySpendingInfo +import org.bitcoins.core.protocol.transaction.TransactionOutPoint +import org.bitcoins.core.protocol.transaction.TransactionOutput +import org.bitcoins.wallet.models.SegwitV0SpendingInfo +import org.bitcoins.wallet.models.SpendingInfoDb object WalletTestUtil { @@ -75,15 +81,40 @@ object WalletTestUtil { lazy val sampleScriptWitness: ScriptWitness = P2WPKHWitnessV0(freshXpub.key) + lazy val sampleSegwitUTXO: SegwitV0SpendingInfo = { + val outpoint = + TransactionOutPoint(WalletTestUtil.sampleTxid, WalletTestUtil.sampleVout) + val output = TransactionOutput(1.bitcoin, WalletTestUtil.sampleSPK) + val scriptWitness = WalletTestUtil.sampleScriptWitness + val privkeyPath = WalletTestUtil.sampleSegwitPath + SegwitV0SpendingInfo(outPoint = outpoint, + output = output, + privKeyPath = privkeyPath, + scriptWitness = scriptWitness) + } + + lazy val sampleLegacyUTXO: LegacySpendingInfo = { + val outpoint = + TransactionOutPoint(WalletTestUtil.sampleTxid, WalletTestUtil.sampleVout) + val output = TransactionOutput(1.bitcoin, WalletTestUtil.sampleSPK) + val privKeyPath = WalletTestUtil.sampleLegacyPath + LegacySpendingInfo(outPoint = outpoint, + output = output, + privKeyPath = privKeyPath) + } + /** - * Inserts a incoming TX, and returns it with the address it was sent to + * Inserts a incoming TXO, and returns it with the address it was sent to * * This method also does some asserts on the result, to make sure what * we're writing and reading matches up */ - def insertIncomingTx(daos: WalletDAOs)(implicit ec: ExecutionContext): Future[ - (IncomingTransaction, AddressDb)] = { - val WalletDAOs(accountDAO, addressDAO, txDAO, _, _) = daos + def insertIncomingTxo(daos: WalletDAOs, utxo: SpendingInfoDb)( + implicit ec: ExecutionContext): Future[(IncomingWalletTXO, AddressDb)] = { + + require(utxo.id.isDefined) + + val WalletDAOs(accountDAO, addressDAO, txoDAO, _, utxoDAO) = daos /** Get a TX with outputs */ def getTx: Transaction = @@ -106,30 +137,31 @@ object WalletTestUtil { } val tx = getTx - val txDb = IncomingTransaction(tx, - confirmations = 3, - scriptPubKey = address.scriptPubKey, - voutIndex = 0) + val txoDb = IncomingWalletTXO(confirmations = 3, + txid = tx.txIdBE, + spent = false, + scriptPubKey = address.scriptPubKey, + spendingInfoID = utxo.id.get) for { _ <- accountDAO.create(account) _ <- addressDAO.create(address) - createdTx <- txDAO.create(txDb) - txAndAddr <- txDAO.withAddress(createdTx.transaction) + _ <- utxoDAO.create(utxo) + createdTxo <- txoDAO.create(txoDb) + txAndAddrs <- txoDAO.withAddress(createdTxo.txid) } yield - txAndAddr match { - case None => + txAndAddrs match { + case Vector() => throw new org.scalatest.exceptions.TestFailedException( s"Couldn't read back TX with address from DB!", 0) - case Some((foundTx, foundAddr)) => - assert(foundTx.confirmations == txDb.confirmations) - assert(foundTx.scriptPubKey == txDb.scriptPubKey) - assert(foundTx.transaction == txDb.transaction) + case ((foundTxo, foundAddr)) +: _ => + assert(foundTxo.confirmations == txoDb.confirmations) + assert(foundTxo.scriptPubKey == txoDb.scriptPubKey) + assert(foundTxo.txid == txoDb.txid) assert(foundAddr == address) - (foundTx, foundAddr) + (foundTxo, foundAddr) } - } } diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessTransactionTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessTransactionTest.scala index dddd343233..32e278a1ee 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessTransactionTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/ProcessTransactionTest.scala @@ -7,20 +7,23 @@ import scala.concurrent.Future import org.scalatest.compatible.Assertion import org.bitcoins.wallet.api.UnlockedWalletApi import org.bitcoins.rpc.client.common.BitcoindRpcClient +import org.bitcoins.core.protocol.transaction.Transaction +import org.bitcoins.testkit.core.gen.TransactionGenerators +import org.bitcoins.core.protocol.script.ScriptPubKey +import scala.annotation.tailrec class ProcessTransactionTest extends BitcoinSWalletTest { - override type FixtureParam = WalletWithBitcoind + override type FixtureParam = UnlockedWalletApi def withFixture(test: OneArgAsyncTest): FutureOutcome = { - withNewWalletAndBitcoind(test) + withNewWallet(test) } behavior of "Wallet.processTransaction" /** Verifies that executing the given action doesn't change wallet state */ - private def checkUtxosAndBalance( - wallet: UnlockedWalletApi, - bitcoind: BitcoindRpcClient)(action: => Future[_]): Future[Assertion] = + private def checkUtxosAndBalance(wallet: UnlockedWalletApi)( + action: => Future[_]): Future[Assertion] = for { oldUtxos <- wallet.listUtxos() oldUnconfirmed <- wallet.getUnconfirmedBalance() @@ -36,49 +39,56 @@ class ProcessTransactionTest extends BitcoinSWalletTest { assert(oldUtxos == newUtxos) } - it must "not change state when processing the same transaction twice" in { - walletAndBitcoind => - val WalletWithBitcoind(wallet, bitcoind) = walletAndBitcoind + /** Gets a TX which pays to the given SPK */ + private def getTxFor(spk: ScriptPubKey): Transaction = + TransactionGenerators + .transactionTo(spk) + .sample + .getOrElse(getTxFor(spk)) + private def getUnrelatedTx(): Transaction = + TransactionGenerators.transaction.sample.getOrElse(getUnrelatedTx()) + + it must "not change state when processing the same transaction twice" in { + wallet => for { address <- wallet.getNewAddress() - tx <- bitcoind - .sendToAddress(address, 1.bitcoin) - .flatMap(bitcoind.getRawTransactionRaw(_)) + tx = getTxFor(address.scriptPubKey) + _ = logger.info(s"tx: $tx") _ <- wallet.processTransaction(tx, confirmations = 0) oldBalance <- wallet.getBalance() oldUnconfirmed <- wallet.getUnconfirmedBalance() // repeating the action should not make a difference - _ <- checkUtxosAndBalance(wallet, bitcoind) { + _ <- checkUtxosAndBalance(wallet) { wallet.processTransaction(tx, confirmations = 0) } _ <- wallet.processTransaction(tx, confirmations = 3) newBalance <- wallet.getBalance() newUnconfirmed <- wallet.getUnconfirmedBalance() + utxosPostAdd <- wallet.listUtxos() // repeating the action should not make a difference - _ <- checkUtxosAndBalance(wallet, bitcoind) { + _ <- checkUtxosAndBalance(wallet) { wallet.processTransaction(tx, confirmations = 3) } } yield { + val ourOutputs = + tx.outputs.filter(_.scriptPubKey == address.scriptPubKey) + + assert(utxosPostAdd.length == ourOutputs.length) assert(newBalance != oldBalance) assert(newUnconfirmed != oldUnconfirmed) } } it must "not change state when processing an unrelated transaction" in { - walletAndBitcoind => - val WalletWithBitcoind(wallet, bitcoind) = walletAndBitcoind - + wallet => + val unrelated = getUnrelatedTx() for { - unrelated <- bitcoind.getNewAddress - .flatMap(bitcoind.sendToAddress(_, 1.bitcoin)) - .flatMap(bitcoind.getRawTransactionRaw(_)) - - _ <- checkUtxosAndBalance(wallet, bitcoind) { + _ <- checkUtxosAndBalance(wallet) { wallet.processTransaction(unrelated, confirmations = 4) } diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/models/IncomingTransactionDAOTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/models/IncomingTxoDAOTest.scala similarity index 63% rename from wallet-test/src/test/scala/org/bitcoins/wallet/models/IncomingTransactionDAOTest.scala rename to wallet-test/src/test/scala/org/bitcoins/wallet/models/IncomingTxoDAOTest.scala index ddba15ed0b..cf089deefa 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/models/IncomingTransactionDAOTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/models/IncomingTxoDAOTest.scala @@ -12,19 +12,18 @@ import org.bouncycastle.crypto.tls.CertChainType import org.bitcoins.core.hd.HDChainType import org.bitcoins.core.hd.LegacyHDPath import scala.concurrent.Future +import org.bitcoins.testkit.fixtures.WalletDAOs -class IncomingTransactionDAOTest - extends BitcoinSWalletTest - with WalletDAOFixture { +class IncomingTxoDAOTest extends BitcoinSWalletTest with WalletDAOFixture { it must "insert a incoming transaction and read it back with its address" in { daos => - val txDAO = daos.incomingTxDAO - WalletTestUtil.insertIncomingTx(daos).flatMap { - case (tx, _) => - txDAO.findTx(tx.transaction).map { txOpt => - assert(txOpt.contains(tx)) - } - } + val WalletDAOs(_, _, txoDAO, _, utxoDAO) = daos + + for { + utxo <- utxoDAO.create(WalletTestUtil.sampleLegacyUTXO) + (txo, _) <- WalletTestUtil.insertIncomingTxo(daos, utxo) + foundTxos <- txoDAO.findTx(txo.txid) + } yield assert(foundTxos.contains(txo)) } } diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/models/SpendingInfoDbDAOTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/models/SpendingInfoDbDAOTest.scala new file mode 100644 index 0000000000..7298e20519 --- /dev/null +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/models/SpendingInfoDbDAOTest.scala @@ -0,0 +1,36 @@ +package org.bitcoins.wallet.models + +import org.bitcoins.core.currency._ +import org.bitcoins.core.protocol.transaction.{ + TransactionOutPoint, + TransactionOutput +} +import org.bitcoins.testkit.fixtures.WalletDAOFixture +import org.bitcoins.wallet.Wallet +import org.bitcoins.testkit.wallet.WalletTestUtil +import org.bitcoins.testkit.wallet.BitcoinSWalletTest + +class SpendingInfoDbDAOTest extends BitcoinSWalletTest with WalletDAOFixture { + behavior of "SpendingInfoDAO" + + it should "insert a segwit UTXO and read it" in { daos => + val utxoDAO = daos.utxoDAO + + for { + created <- utxoDAO.create(WalletTestUtil.sampleSegwitUTXO) + read <- utxoDAO.read(created.id.get) + } yield assert(read.contains(created)) + } + + it should "insert a legacy UTXO and read it" in { daos => + val utxoDAO = daos.utxoDAO + for { + created <- utxoDAO.create(WalletTestUtil.sampleLegacyUTXO) + read <- utxoDAO.read(created.id.get) + } yield assert(read.contains(created)) + } + + it should "insert a nested segwit UTXO and read it" ignore { _ => + ??? + } +} diff --git a/wallet/src/main/scala/org/bitcoins/wallet/api/AddUtxoResult.scala b/wallet/src/main/scala/org/bitcoins/wallet/api/AddUtxoResult.scala index 691206d039..2b0d943cf1 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/api/AddUtxoResult.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/api/AddUtxoResult.scala @@ -1,11 +1,11 @@ package org.bitcoins.wallet.api -sealed trait AddUtxoResult { - def flatMap(f: AddUtxoResult => AddUtxoResult) = ??? - def map(success: AddUtxoSuccess => AddUtxoResult) = ??? -} +import org.bitcoins.wallet.models._ -case class AddUtxoSuccess(walletApi: WalletApi) extends AddUtxoResult +sealed trait AddUtxoResult + +/** Contains the freshly added UTXO */ +case class AddUtxoSuccess(spendingInfo: SpendingInfoDb) extends AddUtxoResult /** Represents an error that might occur when adding an UTXO to the wallet */ sealed trait AddUtxoError extends Error with AddUtxoResult diff --git a/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala b/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala index fb5c2aa7e7..0eb903c0e1 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/api/WalletApi.scala @@ -9,7 +9,7 @@ import org.bitcoins.core.protocol.blockchain.ChainParams import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.wallet.fee.FeeUnit import org.bitcoins.wallet.HDUtil -import org.bitcoins.wallet.models.{AccountDb, AddressDb, UTXOSpendingInfoDb} +import org.bitcoins.wallet.models.{AccountDb, AddressDb, SpendingInfoDb} import scala.concurrent.Future import scala.concurrent.ExecutionContext @@ -66,7 +66,7 @@ trait LockedWalletApi extends WalletApi { */ // def updateUtxo: Future[WalletApi] - def listUtxos(): Future[Vector[UTXOSpendingInfoDb]] + def listUtxos(): Future[Vector[SpendingInfoDb]] def listAddresses(): Future[Vector[AddressDb]] diff --git a/wallet/src/main/scala/org/bitcoins/wallet/db/WalletDbManagement.scala b/wallet/src/main/scala/org/bitcoins/wallet/db/WalletDbManagement.scala index a4cb48c1be..c3dec879ea 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/db/WalletDbManagement.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/db/WalletDbManagement.scala @@ -7,16 +7,16 @@ import org.bitcoins.wallet.models._ sealed abstract class WalletDbManagement extends DbManagement { private val accountTable = TableQuery[AccountTable] private val addressTable = TableQuery[AddressTable] - private val utxoTable = TableQuery[UTXOSpendingInfoTable] - private val incomingTxTable = TableQuery[IncomingTransactionTable] - private val outgoingTxTable = TableQuery[OutgoingTransactionTable] + private val utxoTable = TableQuery[SpendingInfoTable] + private val incomingTxoTable = TableQuery[IncomingTXOTable] + private val outgoingTxoTable = TableQuery[OutgoingTXOTable] override val allTables: List[TableQuery[_ <: Table[_]]] = List(accountTable, addressTable, utxoTable, - incomingTxTable, - outgoingTxTable) + incomingTxoTable, + outgoingTxoTable) } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/IncomingTransactionDAO.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/IncomingTransactionDAO.scala deleted file mode 100644 index 21ea3ef9ae..0000000000 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/IncomingTransactionDAO.scala +++ /dev/null @@ -1,51 +0,0 @@ -package org.bitcoins.wallet.models - -import scala.concurrent.ExecutionContext -import org.bitcoins.wallet.config.WalletAppConfig -import org.bitcoins.db.CRUDAutoInc -import slick.jdbc.JdbcProfile -import scala.concurrent.Future -import org.bitcoins.core.protocol.transaction.Transaction - -final case class IncomingTransactionDAO(profile: JdbcProfile)( - implicit val ec: ExecutionContext, - val appConfig: WalletAppConfig) - extends CRUDAutoInc[IncomingTransaction] { - - import profile.api._ - import org.bitcoins.db.DbCommonsColumnMappers._ - - override val table = TableQuery[IncomingTransactionTable] - val addrTable = TableQuery[AddressTable] - - /** - * @param tx The transaction to look for - * @return If found, the DB representation of the given TX, - * along with the address it pays to - */ - def withAddress( - tx: Transaction): Future[Option[(IncomingTransaction, AddressDb)]] = { - withAddress(_ === tx) - } - - /** - * @param rep A predicate to filter our incoming TXs on - * @return The first TX that meets the predicate, along with - * the address the transaction pays to - */ - def withAddress(pred: Rep[Transaction] => Rep[Boolean]): Future[ - Option[(IncomingTransaction, AddressDb)]] = { - val query = { - val filtered = table.filter(dbTx => pred(dbTx.transaction)) - filtered join addrTable on (_.scriptPubKey === _.scriptPubKey) - } - - database.run(query.result.headOption) - } - - def findTx(tx: Transaction): Future[Option[IncomingTransaction]] = { - val filtered = table.filter(_.transaction === tx) - database.run(filtered.result.headOption) - } - -} diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/OutgoingTransactionDAO.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/OutgoingTxoDAO.scala similarity index 58% rename from wallet/src/main/scala/org/bitcoins/wallet/models/OutgoingTransactionDAO.scala rename to wallet/src/main/scala/org/bitcoins/wallet/models/OutgoingTxoDAO.scala index c02d8a22e3..5e50873f15 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/OutgoingTransactionDAO.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/models/OutgoingTxoDAO.scala @@ -1,14 +1,19 @@ package org.bitcoins.wallet.models -import scala.concurrent.ExecutionContext -import slick.jdbc.JdbcProfile -import org.bitcoins.wallet.config.WalletAppConfig -import org.bitcoins.db.CRUDAutoInc -final case class OutgoingTransactionDAO(profile: JdbcProfile)( +import org.bitcoins.db.CRUDAutoInc +import org.bitcoins.wallet.config.WalletAppConfig +import slick.jdbc.JdbcProfile + +import scala.concurrent.ExecutionContext + +/** + * DAO for outgoing transaction outputs + */ +final case class OutgoingTxoDAO(profile: JdbcProfile)( implicit val ec: ExecutionContext, val appConfig: WalletAppConfig) - extends CRUDAutoInc[OutgoingTransaction] { + extends CRUDAutoInc[OutgoingWalletTXO] { import profile.api._ - override val table = TableQuery[OutgoingTransactionTable] + override val table = TableQuery[OutgoingTXOTable] } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/UTXOSpendingInfoDAO.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoDAO.scala similarity index 70% rename from wallet/src/main/scala/org/bitcoins/wallet/models/UTXOSpendingInfoDAO.scala rename to wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoDAO.scala index 865337b929..2b716ce671 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/UTXOSpendingInfoDAO.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoDAO.scala @@ -6,11 +6,11 @@ import slick.jdbc.SQLiteProfile.api._ import scala.concurrent.ExecutionContext -case class UTXOSpendingInfoDAO()( +case class SpendingInfoDAO()( implicit val ec: ExecutionContext, val appConfig: WalletAppConfig) - extends CRUDAutoInc[UTXOSpendingInfoDb] { + extends CRUDAutoInc[SpendingInfoDb] { /** The table inside our database we are inserting into */ - override val table = TableQuery[UTXOSpendingInfoTable] + override val table = TableQuery[SpendingInfoTable] } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/UTXOSpendingInfoTable.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoTable.scala similarity index 69% rename from wallet/src/main/scala/org/bitcoins/wallet/models/UTXOSpendingInfoTable.scala rename to wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoTable.scala index 43d3289170..8f3e5caa8f 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/UTXOSpendingInfoTable.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoTable.scala @@ -23,36 +23,37 @@ import org.bitcoins.core.hd.LegacyHDPath * DB representation of a native V0 * SegWit UTXO */ -case class NativeV0UTXOSpendingInfoDb( +case class SegwitV0SpendingInfo( outPoint: TransactionOutPoint, output: TransactionOutput, privKeyPath: SegWitHDPath, scriptWitness: ScriptWitness, - incomingTxId: Long, id: Option[Long] = None -) extends UTXOSpendingInfoDb { +) extends SpendingInfoDb { override val redeemScriptOpt: Option[ScriptPubKey] = None override val scriptWitnessOpt: Option[ScriptWitness] = Some(scriptWitness) override type PathType = SegWitHDPath - override def copyWithId(id: Long): NativeV0UTXOSpendingInfoDb = + override def copyWithId(id: Long): SegwitV0SpendingInfo = copy(id = Some(id)) } -case class LegacyUTXOSpendingInfoDb( +/** + * DB representation of a legacy UTXO + */ +case class LegacySpendingInfo( outPoint: TransactionOutPoint, output: TransactionOutput, privKeyPath: LegacyHDPath, - incomingTxId: Long, id: Option[Long] = None -) extends UTXOSpendingInfoDb { +) extends SpendingInfoDb { override val redeemScriptOpt: Option[ScriptPubKey] = None override def scriptWitnessOpt: Option[ScriptWitness] = None override type PathType = LegacyHDPath - override def copyWithId(id: Long): LegacyUTXOSpendingInfoDb = + override def copyWithId(id: Long): LegacySpendingInfo = copy(id = Some(id)) } @@ -65,8 +66,8 @@ case class LegacyUTXOSpendingInfoDb( * we need to derive the private keys, given * the root wallet seed. */ -sealed trait UTXOSpendingInfoDb - extends DbRowAutoInc[UTXOSpendingInfoDb] +sealed trait SpendingInfoDb + extends DbRowAutoInc[SpendingInfoDb] with BitcoinSLogger { protected type PathType <: HDPath @@ -82,9 +83,6 @@ sealed trait UTXOSpendingInfoDb def value: CurrencyUnit = output.value - /** The ID of the transaction this UTXO was received in */ - def incomingTxId: Long - /** Converts a non-sensitive DB representation of a UTXO into * a signable (and sensitive) real-world UTXO */ @@ -116,8 +114,19 @@ sealed trait UTXOSpendingInfoDb } -case class UTXOSpendingInfoTable(tag: Tag) - extends TableAutoInc[UTXOSpendingInfoDb](tag, "utxos") { +/** + * This table stores the necessary information to spend + * a TXO at a later point in time. + * + * It does not contain informations about whether or not + * it is spent, how many (if any) confirmations it has + * or which block/transaction it was included in. + * + * That is rather handled by + * [[org.bitcoins.wallet.models.WalletTXOTable WalletTXOTable]]. + */ +case class SpendingInfoTable(tag: Tag) + extends TableAutoInc[SpendingInfoDb](tag, "txo_spending_info") { import org.bitcoins.db.DbCommonsColumnMappers._ def outPoint: Rep[TransactionOutPoint] = @@ -134,57 +143,40 @@ case class UTXOSpendingInfoTable(tag: Tag) def scriptWitnessOpt: Rep[Option[ScriptWitness]] = column[Option[ScriptWitness]]("script_witness") - /** The ID of the incoming transaction corresponding to this UTXO */ - def incomingTxId: Rep[Long] = column("incoming_tx_id") - - def fk_incomingTx = - foreignKey("fk_incoming_tx", - sourceColumns = incomingTxId, - targetTableQuery = TableQuery[IncomingTransactionTable]) { - _.id - } - private type UTXOTuple = ( Option[Long], TransactionOutPoint, TransactionOutput, HDPath, Option[ScriptPubKey], - Option[ScriptWitness], - Long // incoming TX ID + Option[ScriptWitness] ) - private val fromTuple: UTXOTuple => UTXOSpendingInfoDb = { + private val fromTuple: UTXOTuple => SpendingInfoDb = { case (id, outpoint, output, path: SegWitHDPath, None, // ReedemScript - Some(scriptWitness), - txId) => - NativeV0UTXOSpendingInfoDb(outpoint, - output, - path, - scriptWitness, - txId, - id) + Some(scriptWitness)) => + SegwitV0SpendingInfo(outpoint, output, path, scriptWitness, id) case (id, outpoint, output, path: LegacyHDPath, None, // RedeemScript - None, // ScriptWitness - txId) => - LegacyUTXOSpendingInfoDb(outpoint, output, path, txId, id) - case (id, outpoint, output, path, spkOpt, swOpt, txId) => + None // ScriptWitness + ) => + LegacySpendingInfo(outpoint, output, path, id) + case (id, outpoint, output, path, spkOpt, swOpt) => throw new IllegalArgumentException( "Could not construct UtxoSpendingInfoDb from bad tuple:" - + s" ($outpoint, $output, $path, $spkOpt, $swOpt, $txId, $id) . Note: Nested Segwit is not implemented") + + s" ($outpoint, $output, $path, $spkOpt, $swOpt, $id) . Note: Nested Segwit is not implemented") } - private val toTuple: UTXOSpendingInfoDb => Option[UTXOTuple] = + private val toTuple: SpendingInfoDb => Option[UTXOTuple] = utxo => Some( (utxo.id, @@ -192,15 +184,8 @@ case class UTXOSpendingInfoTable(tag: Tag) utxo.output, utxo.privKeyPath, utxo.redeemScriptOpt, - utxo.scriptWitnessOpt, - utxo.incomingTxId)) + utxo.scriptWitnessOpt)) - def * : ProvenShape[UTXOSpendingInfoDb] = - (id.?, - outPoint, - output, - privKeyPath, - redeemScriptOpt, - scriptWitnessOpt, - incomingTxId) <> (fromTuple, toTuple) + def * : ProvenShape[SpendingInfoDb] = + (id.?, outPoint, output, privKeyPath, redeemScriptOpt, scriptWitnessOpt) <> (fromTuple, toTuple) } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionTable.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionTable.scala deleted file mode 100644 index 323a91c661..0000000000 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionTable.scala +++ /dev/null @@ -1,106 +0,0 @@ -package org.bitcoins.wallet.models - -import org.bitcoins.core.protocol.transaction.Transaction -import slick.jdbc.SQLiteProfile.api._ -import slick.lifted.ProvenShape -import org.bitcoins.db.TableAutoInc -import org.bitcoins.core.crypto.DoubleSha256DigestBE -import org.bitcoins.db.DbRowAutoInc -import org.bitcoins.core.protocol.script.ScriptPubKey -import org.bitcoins.core.protocol.transaction.TransactionOutput - -/** - * Database representation of transactions - * relevant to our wallet. - */ -sealed trait TransactionDb[T <: TransactionDb[_]] extends DbRowAutoInc[T] { - val transaction: Transaction - lazy val txid: DoubleSha256DigestBE = transaction.txIdBE - val confirmations: Int - -} - -/** Transactions our wallet has received */ -final case class IncomingTransaction( - transaction: Transaction, - scriptPubKey: ScriptPubKey, - voutIndex: Int, - confirmations: Int, - id: Option[Long] = None -) extends TransactionDb[IncomingTransaction] { - require(voutIndex >= 0, s"voutIndex cannot be negative, got $voutIndex") - override def copyWithId(id: Long): IncomingTransaction = copy(id = Some(id)) - - /** The output we're interested in - * - * TODO: Concerns about TXs paying to multiple SPKs in our wallet, see note below - */ - lazy val output: TransactionOutput = transaction.outputs(voutIndex) -} - -/** Transactions our wallet has sent */ -final case class OutgoingTransaction( - transaction: Transaction, - confirmations: Int, - id: Option[Long] = None, - utxoId: Option[Long] = None -) extends TransactionDb[OutgoingTransaction] { - override def copyWithId(id: Long): OutgoingTransaction = copy(id = Some(id)) -} - -sealed abstract class TransactionTable[TxType <: TransactionDb[_]]( - tag: Tag, - tableName: String) - extends TableAutoInc[TxType](tag, tableName) { - - import org.bitcoins.db.DbCommonsColumnMappers._ - - def transaction: Rep[Transaction] = column("transaction") - - def confirmations: Rep[Int] = column("confirmations") - -} - -final case class IncomingTransactionTable(tag: Tag) - extends TransactionTable[IncomingTransaction](tag, "incoming_transactions") { - import org.bitcoins.db.DbCommonsColumnMappers._ - - // TODO: What happens if we get paid to multiple SPKs in the same - // transaction? Need to make a table of SPKs, and map IDs in that - // table to TXs in this table... - /** The SPK that's relevant to us in this transaction. Foreign key into address table */ - def scriptPubKey: Rep[ScriptPubKey] = column("our_script_pubkey") - - // TODO: The same concerns as above - /** The output of this TX that's ours */ - def voutIndex: Rep[Int] = column("vout_index") - - def fk_scriptPubKey = - foreignKey("fk_script_pubkey", - sourceColumns = scriptPubKey, - targetTableQuery = TableQuery[AddressTable]) { addressTable => - addressTable.scriptPubKey - } - - override def * : ProvenShape[IncomingTransaction] = - (transaction, scriptPubKey, confirmations, voutIndex, id.?) <> (IncomingTransaction.tupled, IncomingTransaction.unapply) - -} - -final case class OutgoingTransactionTable(tag: Tag) - extends TransactionTable[OutgoingTransaction](tag, "outgoing_transactions") { - import org.bitcoins.db.DbCommonsColumnMappers._ - - def utxoId: Rep[Long] = column("utxo_id", O.Unique) - - def fk_utxo = { - val utxoTable = TableQuery[UTXOSpendingInfoTable] - foreignKey("fk_utxo", sourceColumns = utxoId, targetTableQuery = utxoTable) { - _.id - } - } - - override def * : ProvenShape[OutgoingTransaction] = - (transaction, confirmations, id.?, utxoId.?) <> (OutgoingTransaction.tupled, OutgoingTransaction.unapply) - -}