2021 10 04 issue 3715 (#3724)

* WIP

* Setup DLC fixtures to work with bitcoind

* Implement test case for issue 3715

* turn logging back down, add another assertion for making sure funding tx is still unconfirmed

* Make sure we are broadcasting from acceptor

* Cleanup
This commit is contained in:
Chris Stewart 2021-10-10 07:51:00 -05:00 committed by GitHub
parent 99107b61ea
commit 9668358807
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 254 additions and 84 deletions

View file

@ -0,0 +1,85 @@
package org.bitcoins.dlc.wallet
import org.bitcoins.core.protocol.dlc.models.{DLCState, DLCStatus}
import org.bitcoins.testkit.rpc.CachedBitcoindNewest
import org.bitcoins.testkit.wallet.{BitcoinSDualWalletTest, DLCWalletUtil}
import org.bitcoins.testkit.wallet.DLCWalletUtil.InitializedDLCWallet
import org.scalatest.FutureOutcome
import scala.concurrent.Future
class DLCExecutionBitcoindBackendTest
extends BitcoinSDualWalletTest
with CachedBitcoindNewest {
override type FixtureParam = (InitializedDLCWallet, InitializedDLCWallet)
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
val outcomeF = for {
bitcoind <- cachedBitcoindWithFundsF
outcome = withDualDLCWallets(test = test,
contractOraclePair =
DLCWalletUtil.sampleContractOraclePair,
bitcoind = bitcoind)
fut <- outcome.toFuture
} yield fut
new FutureOutcome(outcomeF)
}
behavior of "DLCExecutionBitcoindBackendTest"
it must "be able to broadcast a CET when the funding tx is unconfirmed" in {
wallets =>
val dlcA = wallets._1.wallet
val dlcB = wallets._2.wallet
val broadcastBF: Future[DLCStatus.Broadcasted] = for {
dlcAs <- dlcA.listDLCs()
dlcBs <- dlcB.listDLCs()
} yield {
assert(dlcAs.length == 1)
assert(dlcAs.head.state == DLCState.Broadcasted)
assert(dlcBs.length == 1)
assert(dlcBs.head.state == DLCState.Broadcasted)
dlcBs.head.asInstanceOf[DLCStatus.Broadcasted]
}
val isFundingTxUnconfirmedF = for {
broadcastB <- broadcastBF
bitcoind <- cachedBitcoindWithFundsF
result <- bitcoind.getRawTransaction(broadcastB.fundingTxId)
} yield {
//make sure no confirmations on the funding tx
assert(result.confirmations.isEmpty)
}
val executedF = for {
broadcastB <- broadcastBF
bitcoind <- cachedBitcoindWithFundsF
_ <- isFundingTxUnconfirmedF
contractInfo = broadcastB.contractInfo
contractId = broadcastB.contractId
dlcId = broadcastB.dlcId
(oracleSigs, _) = DLCWalletUtil.getSigs(contractInfo)
closingTx <- dlcB.executeDLC(contractId, oracleSigs)
//broadcast the closing tx
_ <- dlcB.broadcastTransaction(closingTx)
dlcs <- dlcB
.listDLCs()
.map(_.filter(_.dlcId == dlcId))
_ = assert(dlcs.length == 1)
dlc = dlcs.head
_ = assert(dlc.state == DLCState.Claimed)
claimed = dlc.asInstanceOf[DLCStatus.Claimed]
//make sure funding tx still doesn't have confs
fundingTxResult <- bitcoind.getRawTransaction(claimed.fundingTxId)
//make sure bitcoind sees it
closingTxResult <- bitcoind.getRawTransaction(claimed.closingTxId)
} yield {
assert(fundingTxResult.confirmations.isEmpty)
assert(closingTxResult.confirmations.isEmpty)
}
executedF
}
}

View file

@ -3,21 +3,15 @@ package org.bitcoins.dlc.wallet
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.dlc.models.DLCMessage.DLCOffer
import org.bitcoins.core.protocol.dlc.models.DLCState
import org.bitcoins.core.protocol.dlc.models.DLCStatus.{
Claimed,
Refunded,
RemoteClaimed
}
import org.bitcoins.core.protocol.dlc.models.{
ContractInfo,
DLCState,
EnumContractDescriptor,
NumericContractDescriptor
}
import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.crypto.CryptoUtil
import org.bitcoins.testkit.wallet.DLCWalletUtil._
import org.bitcoins.testkit.wallet.{BitcoinSDualWalletTest, DLCWalletUtil}
import org.scalatest.FutureOutcome
@ -25,7 +19,7 @@ import org.scalatest.FutureOutcome
import scala.concurrent.Future
class DLCExecutionTest extends BitcoinSDualWalletTest {
type FixtureParam = (InitializedDLCWallet, InitializedDLCWallet)
override type FixtureParam = (InitializedDLCWallet, InitializedDLCWallet)
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
withDualDLCWallets(test, DLCWalletUtil.sampleContractOraclePair)
@ -33,51 +27,6 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
behavior of "DLCWallet"
def getSigs(contractInfo: ContractInfo): (
OracleAttestmentTLV,
OracleAttestmentTLV) = {
val desc: EnumContractDescriptor = contractInfo.contractDescriptor match {
case desc: EnumContractDescriptor => desc
case _: NumericContractDescriptor =>
throw new IllegalArgumentException("Unexpected Contract Info")
}
// Get a hash that the initiator wins for
val initiatorWinStr =
desc
.maxBy(_._2.toLong)
._1
.outcome
val initiatorWinSig = DLCWalletUtil.oraclePrivKey
.schnorrSignWithNonce(CryptoUtil
.sha256DLCAttestation(initiatorWinStr)
.bytes,
DLCWalletUtil.kValue)
// Get a hash that the recipient wins for
val recipientWinStr =
desc.find(_._2 == Satoshis.zero).get._1.outcome
val recipientWinSig = DLCWalletUtil.oraclePrivKey
.schnorrSignWithNonce(CryptoUtil
.sha256DLCAttestation(recipientWinStr)
.bytes,
DLCWalletUtil.kValue)
val publicKey = DLCWalletUtil.oraclePrivKey.schnorrPublicKey
val eventId = DLCWalletUtil.sampleOracleInfo.announcement.eventTLV match {
case v0: OracleEventV0TLV => v0.eventId
}
(OracleAttestmentV0TLV(eventId,
publicKey,
Vector(initiatorWinSig),
Vector(initiatorWinStr)),
OracleAttestmentV0TLV(eventId,
publicKey,
Vector(recipientWinSig),
Vector(recipientWinStr)))
}
it must "get the correct funding transaction" in { wallets =>
val dlcA = wallets._1.wallet
val dlcB = wallets._2.wallet

View file

@ -1,9 +1,12 @@
package org.bitcoins.testkit.wallet
import org.bitcoins.commons.config.AppConfig
import org.bitcoins.core.api.chain.ChainQueryApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.dlc.models.{ContractInfo, ContractOraclePair}
import org.bitcoins.dlc.wallet.DLCAppConfig
import org.bitcoins.dlc.wallet.{DLCAppConfig, DLCWallet}
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.server.BitcoinSAppConfig
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.wallet.DLCWalletUtil.InitializedDLCWallet
@ -11,6 +14,8 @@ import org.bitcoins.testkit.wallet.FundWalletUtil.FundedDLCWallet
import org.bitcoins.wallet.config.WalletAppConfig
import org.scalatest.FutureOutcome
import scala.concurrent.Future
trait BitcoinSDualWalletTest extends BitcoinSWalletTest {
import BitcoinSWalletTest._
@ -61,41 +66,125 @@ trait BitcoinSDualWalletTest extends BitcoinSWalletTest {
)(test)
}
/** Dual funded DLC wallets that are backed by a bitcoind node */
def withDualFundedDLCWallets(
test: OneArgAsyncTest,
bitcoind: BitcoindRpcClient): FutureOutcome = {
makeDependentFixture(
build = () => {
createDualFundedDLCWallet(nodeApi = bitcoind, chainQueryApi = bitcoind)
},
destroy = { fundedWallets: (FundedDLCWallet, FundedDLCWallet) =>
destroyDLCWallets(dlcWallet1 = fundedWallets._1.wallet,
dlcWallet2 = fundedWallets._2.wallet)
}
)(test)
}
private def createDualFundedDLCWallet(
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi): Future[
(FundedDLCWallet, FundedDLCWallet)] = {
val walletAF = FundWalletUtil.createFundedDLCWallet(
nodeApi = nodeApi,
chainQueryApi = chainQueryApi,
bip39PasswordOpt = getBIP39PasswordOpt(),
extraConfig = Some(segwitWalletConf))
val walletBF = FundWalletUtil.createFundedDLCWallet(
nodeApi,
chainQueryApi,
getBIP39PasswordOpt(),
Some(segwitWalletConf))(config2, system)
for {
walletA <- walletAF
walletB <- walletBF
} yield (walletA, walletB)
}
private def destroyDLCWallets(
dlcWallet1: DLCWallet,
dlcWallet2: DLCWallet): Future[Unit] = {
val destroy1F = destroyDLCWallet(dlcWallet1)
val destroy2F = destroyDLCWallet(dlcWallet2)
for {
_ <- destroy1F
_ <- destroy2F
} yield ()
}
/** Creates 2 funded segwit wallets that have a DLC initiated */
def withDualDLCWallets(
test: OneArgAsyncTest,
contractOraclePair: ContractOraclePair): FutureOutcome = {
makeDependentFixture(
build = () => {
val walletAF = {
FundWalletUtil.createFundedDLCWallet(nodeApi,
chainQueryApi,
getBIP39PasswordOpt(),
Some(segwitWalletConf))
}
val walletBF = {
FundWalletUtil.createFundedDLCWallet(
nodeApi,
chainQueryApi,
getBIP39PasswordOpt(),
Some(segwitWalletConf))(config2, system)
}
for {
walletA <- walletAF
walletB <- walletBF
amt = expectedDefaultAmt / Satoshis(2)
contractInfo = ContractInfo(amt.satoshis, contractOraclePair)
(dlcWalletA, dlcWalletB) <-
DLCWalletUtil.initDLC(walletA, walletB, contractInfo)
} yield (dlcWalletA, dlcWalletB)
createDualWalletsWithDLC(contractOraclePair = contractOraclePair,
nodeApi = nodeApi,
chainQueryApi = chainQueryApi)
},
destroy = { dlcWallets: (InitializedDLCWallet, InitializedDLCWallet) =>
for {
_ <- destroyDLCWallet(dlcWallets._1.wallet)
_ <- destroyDLCWallet(dlcWallets._2.wallet)
} yield ()
destroyDLCWallets(dlcWallet1 = dlcWallets._1.wallet,
dlcWallet2 = dlcWallets._2.wallet)
}
)(test)
}
def withDualDLCWallets(
test: OneArgAsyncTest,
contractOraclePair: ContractOraclePair,
bitcoind: BitcoindRpcClient): FutureOutcome = {
makeDependentFixture(
build = () => {
createDualWalletsWithDLC(contractOraclePair = contractOraclePair,
bitcoind = bitcoind)
},
destroy = { dlcWallets: (InitializedDLCWallet, InitializedDLCWallet) =>
destroyDLCWallets(dlcWallet1 = dlcWallets._1.wallet,
dlcWallet2 = dlcWallets._2.wallet)
}
)(test)
}
private def createDualWalletsWithDLC(
contractOraclePair: ContractOraclePair,
bitcoind: BitcoindRpcClient): Future[
(InitializedDLCWallet, InitializedDLCWallet)] = {
for {
walletA <- FundWalletUtil.createFundedDLCWalletWithBitcoind(
bitcoind,
getBIP39PasswordOpt(),
Some(segwitWalletConf))
walletB <- FundWalletUtil.createFundedDLCWalletWithBitcoind(
bitcoind = bitcoind,
bip39PasswordOpt = getBIP39PasswordOpt(),
extraConfig = Some(segwitWalletConf))(config2, system)
amt = expectedDefaultAmt / Satoshis(2)
contractInfo = ContractInfo(amt.satoshis, contractOraclePair)
(dlcWalletA, dlcWalletB) <-
DLCWalletUtil.initDLC(walletA, walletB, contractInfo)
} yield (dlcWalletA, dlcWalletB)
}
private def createDualWalletsWithDLC(
contractOraclePair: ContractOraclePair,
nodeApi: NodeApi,
chainQueryApi: ChainQueryApi): Future[
(InitializedDLCWallet, InitializedDLCWallet)] = {
for {
walletA <- FundWalletUtil.createFundedDLCWallet(
nodeApi = nodeApi,
chainQueryApi = chainQueryApi,
bip39PasswordOpt = getBIP39PasswordOpt(),
extraConfig = Some(segwitWalletConf))
walletB <- FundWalletUtil.createFundedDLCWallet(
nodeApi = nodeApi,
chainQueryApi = chainQueryApi,
bip39PasswordOpt = getBIP39PasswordOpt(),
extraConfig = Some(segwitWalletConf))(config2, system)
amt = expectedDefaultAmt / Satoshis(2)
contractInfo = ContractInfo(amt.satoshis, contractOraclePair)
(dlcWalletA, dlcWalletB) <-
DLCWalletUtil.initDLC(walletA, walletB, contractInfo)
} yield (dlcWalletA, dlcWalletB)
}
}

View file

@ -230,6 +230,7 @@ object DLCWalletUtil extends Logging {
totalCollateral = total
)
/** Creates a DLC between two wallets. */
def initDLC(
fundedWalletA: FundedDLCWallet,
fundedWalletB: FundedDLCWallet,
@ -353,4 +354,50 @@ object DLCWalletUtil extends Logging {
}
}
}
def getSigs(contractInfo: ContractInfo): (
OracleAttestmentTLV,
OracleAttestmentTLV) = {
val desc: EnumContractDescriptor = contractInfo.contractDescriptor match {
case desc: EnumContractDescriptor => desc
case _: NumericContractDescriptor =>
throw new IllegalArgumentException("Unexpected Contract Info")
}
// Get a hash that the initiator wins for
val initiatorWinStr =
desc
.maxBy(_._2.toLong)
._1
.outcome
val initiatorWinSig = DLCWalletUtil.oraclePrivKey
.schnorrSignWithNonce(CryptoUtil
.sha256DLCAttestation(initiatorWinStr)
.bytes,
DLCWalletUtil.kValue)
// Get a hash that the recipient wins for
val recipientWinStr =
desc.find(_._2 == Satoshis.zero).get._1.outcome
val recipientWinSig = DLCWalletUtil.oraclePrivKey
.schnorrSignWithNonce(CryptoUtil
.sha256DLCAttestation(recipientWinStr)
.bytes,
DLCWalletUtil.kValue)
val publicKey = DLCWalletUtil.oraclePrivKey.schnorrPublicKey
val eventId = DLCWalletUtil.sampleOracleInfo.announcement.eventTLV match {
case v0: OracleEventV0TLV => v0.eventId
}
(OracleAttestmentV0TLV(eventId,
publicKey,
Vector(initiatorWinSig),
Vector(initiatorWinStr)),
OracleAttestmentV0TLV(eventId,
publicKey,
Vector(recipientWinSig),
Vector(recipientWinStr)))
}
}

View file

@ -9,7 +9,6 @@ import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.HDAccount
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.dlc.wallet.DLCWallet
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.server.{BitcoinSAppConfig, BitcoindRpcBackendUtil}
@ -48,8 +47,7 @@ trait FundWalletUtil extends Logging {
}
val fundedWalletF =
txsF.flatMap(txs =>
wallet.processTransactions(txs, Some(DoubleSha256DigestBE.empty)))
txsF.flatMap(txs => wallet.processTransactions(txs, None))
fundedWalletF.map(_.asInstanceOf[Wallet])
}
@ -183,7 +181,9 @@ object FundWalletUtil extends FundWalletUtil {
bip39PasswordOpt = bip39PasswordOpt,
extraConfig = extraConfig)
funded <- FundWalletUtil.fundWallet(wallet)
} yield FundedDLCWallet(funded.wallet.asInstanceOf[DLCWallet])
} yield {
FundedDLCWallet(funded.wallet.asInstanceOf[DLCWallet])
}
}
def createFundedDLCWalletWithBitcoind(