Get UTXO DB unit tests passing

This commit is contained in:
Torkel Rogstad 2019-06-19 11:48:48 +02:00
parent 88e318d485
commit e2278f7606
4 changed files with 94 additions and 82 deletions

View file

@ -14,11 +14,14 @@ import scodec.bits.HexStringSyntax
import org.bitcoins.core.hd._ import org.bitcoins.core.hd._
import org.bitcoins.core.protocol.script.ScriptWitness import org.bitcoins.core.protocol.script.ScriptWitness
import org.bitcoins.core.protocol.script.P2WPKHWitnessV0 import org.bitcoins.core.protocol.script.P2WPKHWitnessV0
import org.bitcoins.core.protocol.transaction.TransactionOutput import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.protocol.transaction.TransactionOutPoint import org.bitcoins.testkit.core.gen.TransactionGenerators
import org.bitcoins.wallet.models.NativeV0UTXOSpendingInfoDb import scala.concurrent.Future
import org.bitcoins.core.currency._ import org.bitcoins.wallet.models.IncomingTransaction
import org.bitcoins.wallet.models.LegacyUTXOSpendingInfoDb import org.bitcoins.wallet.models.AddressDb
import org.bitcoins.wallet.models.AddressDbHelper
import org.bitcoins.testkit.fixtures.WalletDAOs
import scala.concurrent.ExecutionContext
object WalletTestUtil { object WalletTestUtil {
@ -70,30 +73,62 @@ object WalletTestUtil {
lazy val sampleSPK: ScriptPubKey = lazy val sampleSPK: ScriptPubKey =
ScriptPubKey.fromAsmBytes(hex"001401b2ac67587e4b603bb3ad709a8102c30113892d") ScriptPubKey.fromAsmBytes(hex"001401b2ac67587e4b603bb3ad709a8102c30113892d")
lazy val sampleSegwitUtxo: NativeV0UTXOSpendingInfoDb = {
val outpoint =
TransactionOutPoint(WalletTestUtil.sampleTxid, WalletTestUtil.sampleVout)
val output = TransactionOutput(1.bitcoin, WalletTestUtil.sampleSPK)
val scriptWitness = WalletTestUtil.sampleScriptWitness
val privkeyPath = WalletTestUtil.sampleSegwitPath
NativeV0UTXOSpendingInfoDb(id = None,
outPoint = outpoint,
output = output,
privKeyPath = privkeyPath,
scriptWitness = scriptWitness,
incomingTxId = None)
}
lazy val sampleLegacyUtxo = {
val outpoint =
TransactionOutPoint(WalletTestUtil.sampleTxid, WalletTestUtil.sampleVout)
val output = TransactionOutput(1.bitcoin, WalletTestUtil.sampleSPK)
val privKeyPath = WalletTestUtil.sampleLegacyPath
LegacyUTXOSpendingInfoDb(id = None,
outPoint = outpoint,
output = output,
privKeyPath = privKeyPath,
incomingTxId = None)
}
lazy val sampleScriptWitness: ScriptWitness = P2WPKHWitnessV0(freshXpub.key) lazy val sampleScriptWitness: ScriptWitness = P2WPKHWitnessV0(freshXpub.key)
/**
* Inserts a incoming TX, 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
/** Get a TX with outputs */
def getTx: Transaction =
TransactionGenerators.transaction
.suchThat(_.outputs.nonEmpty)
.sample
.getOrElse(getTx)
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)
for {
_ <- accountDAO.create(account)
_ <- addressDAO.create(address)
createdTx <- txDAO.create(txDb)
txAndAddr <- txDAO.withAddress(createdTx.transaction)
} yield
txAndAddr match {
case None =>
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)
assert(foundAddr == address)
(foundTx, foundAddr)
}
}
} }

View file

@ -11,57 +11,14 @@ import org.bitcoins.wallet.config.WalletAppConfig
import org.bouncycastle.crypto.tls.CertChainType import org.bouncycastle.crypto.tls.CertChainType
import org.bitcoins.core.hd.HDChainType import org.bitcoins.core.hd.HDChainType
import org.bitcoins.core.hd.LegacyHDPath import org.bitcoins.core.hd.LegacyHDPath
import scala.concurrent.Future
class IncomingTransactionDAOTest class IncomingTransactionDAOTest
extends BitcoinSWalletTest extends BitcoinSWalletTest
with WalletDAOFixture { with WalletDAOFixture {
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 { it must "insert a incoming transaction and read it back with its address" in {
daos => daos =>
val txDao = daos.incomingTxDAO WalletTestUtil.insertIncomingTx(daos).map(_ => succeed)
val addrDao = daos.addressDAO
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

@ -19,6 +19,26 @@ This is meant to be a stand alone project that can be used as a cold storage wal
[BIP44/BIP49/BIP84 paths](../core/src/main/scala/org/bitcoins/core/hd/HDPath.scala) [BIP44/BIP49/BIP84 paths](../core/src/main/scala/org/bitcoins/core/hd/HDPath.scala)
and script types, so that everything we need for spending the money sent to an address and script types, so that everything we need for spending the money sent to an address
is derivable. is derivable.
- **The wallet is a "dumb" wallet that acts mostly as a database of UTXOs, transactions and
addresses, with associated operations on these.**
The wallet module does very little verification of incoming data about transactions,
UTXOs and reorgs. We're aiming to write small, self contained modules, that can be
composed together into more fully fledged systems. That means the `chain` and `node`
modules does the actual verification of data we receive, and `wallet` just blindly
acts on this. This results in a design where you can swap out `node` for a Bitcoin Core
full node, use it with hardware wallets, or something else entirely. However, that also
means that users of `wallet` that doesn't want to use the other modules we provide have
to make sure that the data they are feeding the wallet is correct.
#### Database structure
We store information in the following tables:
- UTXOs - must reference the incoming transaction it was received in
- Addresses - must reference the account it belongs to
- Accounts
- Incoming transactions - must reference the SPK (in our address table) that a TX spends to
- Outgoing transactions - must reference the UTXO(s) it spends
#### Mnemonic encryption #### Mnemonic encryption

View file

@ -24,12 +24,12 @@ import org.bitcoins.core.hd.LegacyHDPath
* SegWit UTXO * SegWit UTXO
*/ */
case class NativeV0UTXOSpendingInfoDb( case class NativeV0UTXOSpendingInfoDb(
id: Option[Long], id: Option[Long] = None,
outPoint: TransactionOutPoint, outPoint: TransactionOutPoint,
output: TransactionOutput, output: TransactionOutput,
privKeyPath: SegWitHDPath, privKeyPath: SegWitHDPath,
scriptWitness: ScriptWitness, scriptWitness: ScriptWitness,
incomingTxId: Option[Long] incomingTxId: Long
) extends UTXOSpendingInfoDb { ) extends UTXOSpendingInfoDb {
override val redeemScriptOpt: Option[ScriptPubKey] = None override val redeemScriptOpt: Option[ScriptPubKey] = None
override val scriptWitnessOpt: Option[ScriptWitness] = Some(scriptWitness) override val scriptWitnessOpt: Option[ScriptWitness] = Some(scriptWitness)
@ -41,11 +41,11 @@ case class NativeV0UTXOSpendingInfoDb(
} }
case class LegacyUTXOSpendingInfoDb( case class LegacyUTXOSpendingInfoDb(
id: Option[Long], id: Option[Long] = None,
outPoint: TransactionOutPoint, outPoint: TransactionOutPoint,
output: TransactionOutput, output: TransactionOutput,
privKeyPath: LegacyHDPath, privKeyPath: LegacyHDPath,
incomingTxId: Option[Long] incomingTxId: Long
) extends UTXOSpendingInfoDb { ) extends UTXOSpendingInfoDb {
override val redeemScriptOpt: Option[ScriptPubKey] = None override val redeemScriptOpt: Option[ScriptPubKey] = None
override def scriptWitnessOpt: Option[ScriptWitness] = None override def scriptWitnessOpt: Option[ScriptWitness] = None
@ -83,7 +83,7 @@ sealed trait UTXOSpendingInfoDb
def value: CurrencyUnit = output.value def value: CurrencyUnit = output.value
/** The ID of the transaction this UTXO was received in */ /** The ID of the transaction this UTXO was received in */
def incomingTxId: Option[Long] def incomingTxId: Long
/** Converts a non-sensitive DB representation of a UTXO into /** Converts a non-sensitive DB representation of a UTXO into
* a signable (and sensitive) real-world UTXO * a signable (and sensitive) real-world UTXO
@ -151,7 +151,7 @@ case class UTXOSpendingInfoTable(tag: Tag)
HDPath, HDPath,
Option[ScriptPubKey], Option[ScriptPubKey],
Option[ScriptWitness], Option[ScriptWitness],
Option[Long] // incoming TX ID Long // incoming TX ID
) )
private val fromTuple: UTXOTuple => UTXOSpendingInfoDb = { private val fromTuple: UTXOTuple => UTXOSpendingInfoDb = {
@ -202,5 +202,5 @@ case class UTXOSpendingInfoTable(tag: Tag)
privKeyPath, privKeyPath,
redeemScriptOpt, redeemScriptOpt,
scriptWitnessOpt, scriptWitnessOpt,
incomingTxId.?) <> (fromTuple, toTuple) incomingTxId) <> (fromTuple, toTuple)
} }