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.protocol.script.ScriptWitness
import org.bitcoins.core.protocol.script.P2WPKHWitnessV0
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import org.bitcoins.wallet.models.NativeV0UTXOSpendingInfoDb
import org.bitcoins.core.currency._
import org.bitcoins.wallet.models.LegacyUTXOSpendingInfoDb
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
object WalletTestUtil {
@ -70,30 +73,62 @@ object WalletTestUtil {
lazy val sampleSPK: ScriptPubKey =
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)
/**
* 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.bitcoins.core.hd.HDChainType
import org.bitcoins.core.hd.LegacyHDPath
import scala.concurrent.Future
class IncomingTransactionDAOTest
extends BitcoinSWalletTest
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 {
daos =>
val txDao = daos.incomingTxDAO
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)
}
}
WalletTestUtil.insertIncomingTx(daos).map(_ => succeed)
}
}

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)
and script types, so that everything we need for spending the money sent to an address
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

View file

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