mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 22:36:34 +01:00
Get UTXO DB unit tests passing
This commit is contained in:
parent
88e318d485
commit
e2278f7606
4 changed files with 94 additions and 82 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue