Add tables for incoming and outgoing transactions

This commit is contained in:
Torkel Rogstad 2019-06-18 17:56:33 +02:00
parent e4e9ec9db9
commit bdd0468383
5 changed files with 223 additions and 0 deletions

View file

@ -19,6 +19,8 @@ import org.bitcoins.core.hd.HDPurpose
import org.bitcoins.core.hd.HDPurposes
import org.bitcoins.core.hd.SegWitHDPath
import slick.jdbc.GetResult
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.currency.Satoshis
abstract class DbCommonsColumnMappers {
@ -159,6 +161,9 @@ abstract class DbCommonsColumnMappers {
MappedColumnType
.base[ScriptType, String](_.toString, ScriptType.fromStringExn)
implicit val txMapper: BaseColumnType[Transaction] =
MappedColumnType.base[Transaction, String](_.hex, Transaction.fromHex)
}
object DbCommonsColumnMappers extends DbCommonsColumnMappers

View file

@ -0,0 +1,66 @@
package org.bitcoins.wallet.models
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.testkit.core.gen.TransactionGenerators
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.wallet.fixtures._
import org.bitcoins.testkit.wallet.WalletTestUtil
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto.ECPublicKey
import org.bitcoins.wallet.config.WalletAppConfig
import org.bouncycastle.crypto.tls.CertChainType
import org.bitcoins.core.hd.HDChainType
import org.bitcoins.core.hd.LegacyHDPath
class IncomingTransactionDAOTest
extends BitcoinSWalletTest
with IncomingTransactionDAOFixture {
private def getTx: Transaction =
TransactionGenerators.transaction
.suchThat(_.outputs.nonEmpty)
.sample
.getOrElse(getTx)
it must "insert a incoming transaction and read it back with its address" in {
daos =>
val (txDao, addrDao) = daos
implicit val walletconf: WalletAppConfig = config
val accountDAO = AccountDAO()
val account = WalletTestUtil.firstAccountDb
val address = {
val pub = ECPublicKey()
val path =
account.hdAccount
.toChain(HDChainType.External)
.toAddress(0)
.toPath
AddressDbHelper.getAddress(pub, path, RegTest)
}
val tx = getTx
val txDb = IncomingTransaction(tx,
confirmations = 3,
scriptPubKey = address.scriptPubKey)
import org.bitcoins.db.DbCommonsColumnMappers._
for {
_ <- accountDAO.create(account)
createdAddress <- addrDao.create(address)
createdTx <- txDao.create(txDb)
txAndAddr <- txDao.withAddress(createdTx.transaction)
} yield {
txAndAddr match {
case None => fail(s"Couldn't read back TX with address from DB!")
case Some((foundTx, foundAddr)) =>
// can't do just foundTx == txDb, ID's are different (None/Some(_))
assert(foundTx.confirmations == txDb.confirmations)
assert(foundTx.scriptPubKey == txDb.scriptPubKey)
assert(foundTx.transaction == txDb.transaction)
assert(foundAddr == address)
}
}
}
}

View file

@ -0,0 +1,46 @@
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)
}
}

View file

@ -0,0 +1,14 @@
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)(
implicit val ec: ExecutionContext,
val appConfig: WalletAppConfig)
extends CRUDAutoInc[OutgoingTransaction] {
import profile.api._
override val table = TableQuery[OutgoingTransactionTable]
}

View file

@ -0,0 +1,92 @@
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
/**
* 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,
confirmations: Int,
id: Option[Long] = None
) extends TransactionDb[IncomingTransaction] {
override def copyWithId(id: Long): IncomingTransaction = copy(id = Some(id))
}
/** 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...
def scriptPubKey: Rep[ScriptPubKey] = column("our_script_pubkey")
def fk_scriptPubKey =
foreignKey("fk_script_pubkey",
sourceColumns = scriptPubKey,
targetTableQuery = TableQuery[AddressTable]) { addressTable =>
addressTable.scriptPubKey
}
override def * : ProvenShape[IncomingTransaction] =
(transaction, scriptPubKey, confirmations, 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)
}