mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-26 21:42:48 +01:00
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:
parent
99107b61ea
commit
9668358807
5 changed files with 254 additions and 84 deletions
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Reference in a new issue