diff --git a/core/src/main/scala/org/bitcoins/core/currency/CurrencyUnits.scala b/core/src/main/scala/org/bitcoins/core/currency/CurrencyUnits.scala index ab8827a255..9b1ac99588 100644 --- a/core/src/main/scala/org/bitcoins/core/currency/CurrencyUnits.scala +++ b/core/src/main/scala/org/bitcoins/core/currency/CurrencyUnits.scala @@ -128,6 +128,7 @@ object Satoshis val max = Satoshis(Int64.max) val zero = Satoshis(Int64.zero) val one = Satoshis(Int64.one) + val two = Satoshis(2) override def fromBytes(bytes: ByteVector): Satoshis = RawSatoshisSerializer.read(bytes) diff --git a/dlc-wallet-test/src/test/scala/org/bitcoins/dlc/wallet/WalletDLCSetupTest.scala b/dlc-wallet-test/src/test/scala/org/bitcoins/dlc/wallet/WalletDLCSetupTest.scala index 4f7eb94159..8ef8f1d476 100644 --- a/dlc-wallet-test/src/test/scala/org/bitcoins/dlc/wallet/WalletDLCSetupTest.scala +++ b/dlc-wallet-test/src/test/scala/org/bitcoins/dlc/wallet/WalletDLCSetupTest.scala @@ -153,7 +153,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest { fundedDLCWallets: (FundedDLCWallet, FundedDLCWallet) => // construct a contract info that uses many inputs val totalCol = Bitcoins(11).satoshis - val col = totalCol / Satoshis(2) + val col = totalCol / Satoshis.two val outcomes: Vector[(EnumOutcome, Satoshis)] = Vector(EnumOutcome(winStr) -> totalCol, @@ -897,7 +897,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest { def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = { walletA.createDLCOffer( contractInfoTLV = contractInfo, - collateral = totalCollateral, + collateral = (totalCollateral / Satoshis.two).satoshis, feeRateOpt = feeRateOpt, locktime = UInt32.zero, refundLT = UInt32.one, @@ -928,7 +928,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest { def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = { walletA.createDLCOffer( contractInfoTLV = contractInfo, - collateral = totalCollateral, + collateral = (totalCollateral / Satoshis.two).satoshis, feeRateOpt = feeRateOpt, locktime = UInt32.zero, refundLT = UInt32.one, @@ -1032,7 +1032,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest { val feeRateOpt = Some(SatoshisPerVirtualByte(Satoshis.one)) val totalCollateral = Satoshis(5000) - val feeRateOpt1 = Some(SatoshisPerVirtualByte(Satoshis(2))) + val feeRateOpt1 = Some(SatoshisPerVirtualByte(Satoshis.two)) val totalCollateral1 = Satoshis(10000) // random testnet addresses @@ -1080,4 +1080,41 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest { } } + it must "setup a DLC and allow re-use of inputs on the accept side" in { + FundedDLCWallets: (FundedDLCWallet, FundedDLCWallet) => + val walletA = FundedDLCWallets._1.wallet + val walletB = FundedDLCWallets._2.wallet + val offerData: DLCOffer = + DLCWalletUtil.sampleDLCOffer.copy(contractInfo = + DLCWalletUtil.sampleContractInfo2, + totalCollateral = DLCWalletUtil.amt2) + val offerData2 = DLCWalletUtil.sampleDLCOffer + + for { + offer1 <- walletA.createDLCOffer( + offerData.contractInfo, + offerData.totalCollateral, + Some(offerData.feeRate), + offerData.timeouts.contractMaturity.toUInt32, + offerData.timeouts.contractTimeout.toUInt32, + None, + None + ) + //accept it for the first time using the inputs + _ <- walletB.acceptDLCOffer(offer1.toTLV, None, None) + //cancel the offer + _ <- walletA.cancelDLC(dlcId = offer1.dlcId) + amt = DLCWalletUtil.half + offer2 <- walletA.createDLCOffer( + offerData2.contractInfo, + amt, + Some(offerData2.feeRate), + offerData2.timeouts.contractMaturity.toUInt32, + offerData2.timeouts.contractTimeout.toUInt32, + None, + None + ) + _ <- walletB.acceptDLCOffer(offer2.toTLV, None, None) + } yield succeed + } } diff --git a/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/DLCWallet.scala b/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/DLCWallet.scala index 74a48e4df9..7b9b64cfc9 100644 --- a/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/DLCWallet.scala +++ b/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/DLCWallet.scala @@ -652,7 +652,6 @@ abstract class DLCWallet val dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) val collateral = offer.contractInfo.max - offer.totalCollateral - logger.debug(s"Checking if Accept (${dlcId.hex}) has already been made") for { dlcAcceptOpt <- DLCAcceptUtil.findDLCAccept(dlcId = dlcId, @@ -721,7 +720,6 @@ abstract class DLCWallet externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept] = Future { DLCWallet.AcceptingOffersLatch.startAccepting(offer.tempContractId) - logger.info( s"Creating DLC Accept for tempContractId ${offer.tempContractId.hex}") val result = for { @@ -740,7 +738,10 @@ abstract class DLCWallet externalPayoutAddressOpt = externalPayoutAddressOpt, externalChangeAddressOpt = externalChangeAddressOpt ) - + _ = require( + initializedAccept.acceptWithoutSigs.tempContractId == offer.tempContractId, + s"Offer and Accept have differing tempContractIds! offer=${offer.tempContractId} accept=${initializedAccept.acceptWithoutSigs.tempContractId}" + ) offerPrevTxs = offer.fundingInputs.map(funding => TransactionDbHelper.fromTransaction(funding.prevTx, blockHashOpt = None)) @@ -817,9 +818,6 @@ abstract class DLCWallet refundSig = refundSig) .copy(isExternalAddress = status.payoutAddress.forall(_.isExternal)) - _ = require(accept.tempContractId == offer.tempContractId, - "Offer and Accept have differing tempContractIds!") - actions = actionBuilder.buildCreateAcceptAction( dlcDb = dlc.updateState(DLCState.Accepted), offerInputs = offerInputs, diff --git a/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/util/DLCActionBuilder.scala b/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/util/DLCActionBuilder.scala index cecbcaaf32..6497d3a4a5 100644 --- a/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/util/DLCActionBuilder.scala +++ b/dlc-wallet/src/main/scala/org/bitcoins/dlc/wallet/util/DLCActionBuilder.scala @@ -61,9 +61,9 @@ case class DLCActionBuilder(dlcWalletDAOs: DLCWalletDAOs) { refundSigsDb: DLCRefundSigsDb)(implicit ec: ExecutionContext): DBIOAction[ Unit, NoStream, - Effect.Write with Effect.Transactional] = { + Effect.Write with Effect.Read with Effect.Transactional] = { val dlcDbAction = dlcDAO.updateAction(dlcDb) - val inputAction = dlcInputsDAO.createAllAction(offerInputs) + val inputAction = dlcInputsDAO.upsertAllAction(offerInputs) val sigsAction = dlcSigsDAO.createAllAction(cetSigsDb) val refundSigAction = dlcRefundSigDAO.createAction(refundSigsDb) val actions = Vector(dlcDbAction, inputAction, sigsAction, refundSigAction) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/wallet/DLCWalletUtil.scala b/testkit/src/main/scala/org/bitcoins/testkit/wallet/DLCWalletUtil.scala index 53295ce854..dfd8cbe3a1 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/wallet/DLCWalletUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/wallet/DLCWalletUtil.scala @@ -86,6 +86,11 @@ object DLCWalletUtil extends Logging { lazy val sampleContractInfo: ContractInfo = SingleContractInfo(half, sampleContractOraclePair) + val amt2: Satoshis = Satoshis(100000) + + lazy val sampleContractInfo2: ContractInfo = + SingleContractInfo(amt2, sampleContractOraclePair) + lazy val invalidContractInfo: ContractInfo = SingleContractInfo(half, invalidContractOraclePair) @@ -180,6 +185,20 @@ object DLCWalletUtil extends Logging { timeouts = dummyTimeouts ) + lazy val sampleDLCOffer2 = DLCOffer( + protocolVersionOpt = DLCOfferTLV.currentVersionOpt, + contractInfo = sampleContractInfo2, + pubKeys = dummyDLCKeys, + totalCollateral = sampleContractInfo2.totalCollateral, + fundingInputs = Vector(dummyFundingInputs.head), + changeAddress = dummyAddress, + payoutSerialId = sampleOfferPayoutSerialId, + changeSerialId = sampleOfferChangeSerialId, + fundOutputSerialId = sampleFundOutputSerialId, + feeRate = SatoshisPerVirtualByte(Satoshis(3)), + timeouts = dummyTimeouts + ) + lazy val invalidDLCOffer: DLCOffer = DLCOffer( protocolVersionOpt = DLCOfferTLV.currentVersionOpt, contractInfo = invalidContractInfo, diff --git a/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala b/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala index 9de29b238a..866c8b0194 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala @@ -57,7 +57,11 @@ trait FundTransactionHandling extends WalletLogger { self: Wallet => markAsReserved: Boolean): Future[( RawTxBuilderWithFinalizer[ShufflingNonInteractiveFinalizer], Vector[ScriptSignatureParams[InputInfo]])] = { - val amt = destinations.map(_.value).sum + val amts = destinations.map(_.value) + //need to allow 0 for OP_RETURN outputs + require(amts.forall(_.satoshis.toBigInt >= 0), + s"Cannot fund a transaction for a negative amount, got=$amts") + val amt = amts.sum logger.info(s"Attempting to fund a tx for amt=${amt} with feeRate=$feeRate") val utxosF: Future[Vector[(SpendingInfoDb, Transaction)]] = for {