Add an ability to set custom payout and change addresses (#4101)

* Add an ability to set custom payout and change addresses

* config changes

* formatting

* respond to the comments
This commit is contained in:
rorp 2022-02-18 07:29:00 -08:00 committed by GitHub
parent 5b1b1ee149
commit 5777ec1c31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 596 additions and 297 deletions

View file

@ -952,18 +952,26 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
) )
"create a dlc offer" in { "create a dlc offer" in {
(mockWalletApi (
.createDLCOffer(_: ContractInfoTLV, mockWalletApi
.createDLCOffer(
_: ContractInfoTLV,
_: Satoshis, _: Satoshis,
_: Option[SatoshisPerVirtualByte], _: Option[SatoshisPerVirtualByte],
_: UInt32, _: UInt32,
_: UInt32)) _: UInt32,
_: Option[BitcoinAddress],
_: Option[BitcoinAddress]
)
)
.expects( .expects(
contractInfoTLV, contractInfoTLV,
Satoshis(2500), Satoshis(2500),
Some(SatoshisPerVirtualByte(Satoshis.one)), Some(SatoshisPerVirtualByte(Satoshis.one)),
UInt32(contractMaturity), UInt32(contractMaturity),
UInt32(contractTimeout) UInt32(contractTimeout),
None,
None
) )
.returning(Future.successful(offer)) .returning(Future.successful(offer))
@ -1021,8 +1029,10 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
"accept a dlc offer" in { "accept a dlc offer" in {
(mockWalletApi (mockWalletApi
.acceptDLCOffer(_: DLCOfferTLV)) .acceptDLCOffer(_: DLCOfferTLV,
.expects(offer.toTLV) _: Option[BitcoinAddress],
_: Option[BitcoinAddress]))
.expects(offer.toTLV, None, None)
.returning(Future.successful(accept)) .returning(Future.successful(accept))
val route = walletRoutes.handleCommand( val route = walletRoutes.handleCommand(

View file

@ -2,7 +2,7 @@ package org.bitcoins.server
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LockUnspentOutputParameter import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LockUnspentOutputParameter
import org.bitcoins.commons.jsonmodels.cli.ContractDescriptorParser import org.bitcoins.commons.jsonmodels.cli.ContractDescriptorParser
import org.bitcoins.commons.serializers.{JsonReaders} import org.bitcoins.commons.serializers.JsonReaders
import org.bitcoins.core.api.wallet.CoinSelectionAlgo import org.bitcoins.core.api.wallet.CoinSelectionAlgo
import org.bitcoins.core.crypto._ import org.bitcoins.core.crypto._
import org.bitcoins.core.currency.{Bitcoins, Satoshis} import org.bitcoins.core.currency.{Bitcoins, Satoshis}
@ -655,26 +655,67 @@ case class CreateDLCOffer(
collateral: Satoshis, collateral: Satoshis,
feeRateOpt: Option[SatoshisPerVirtualByte], feeRateOpt: Option[SatoshisPerVirtualByte],
locktime: UInt32, locktime: UInt32,
refundLocktime: UInt32) refundLocktime: UInt32,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress])
object CreateDLCOffer extends ServerJsonModels { object CreateDLCOffer extends ServerJsonModels {
def fromJsArr(jsArr: ujson.Arr): Try[CreateDLCOffer] = { def fromJsArr(jsArr: ujson.Arr): Try[CreateDLCOffer] = {
jsArr.arr.toList match { def parseParameters(
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: Nil => contractInfoJs: Value,
Try { collateralJs: Value,
feeRateOptJs: Value,
locktimeJs: Value,
refundLTJs: Value,
payoutAddressJs: Value,
changeAddressJs: Value) = Try {
val contractInfoTLV = jsToContractInfoTLV(contractInfoJs) val contractInfoTLV = jsToContractInfoTLV(contractInfoJs)
val collateral = jsToSatoshis(collateralJs) val collateral = jsToSatoshis(collateralJs)
val feeRate = jsToSatoshisPerVirtualByteOpt(feeRateOptJs) val feeRate = jsToSatoshisPerVirtualByteOpt(feeRateOptJs)
val locktime = jsToUInt32(locktimeJs) val locktime = jsToUInt32(locktimeJs)
val refundLT = jsToUInt32(refundLTJs) val refundLT = jsToUInt32(refundLTJs)
val payoutAddressJsOpt = nullToOpt(payoutAddressJs)
val payoutAddressOpt =
payoutAddressJsOpt.map(js => jsToBitcoinAddress(js))
val changeAddressJsOpt = nullToOpt(changeAddressJs)
val changeAddressOpt =
changeAddressJsOpt.map(js => jsToBitcoinAddress(js))
CreateDLCOffer(contractInfoTLV, CreateDLCOffer(contractInfoTLV,
collateral, collateral,
feeRate, feeRate,
locktime, locktime,
refundLT) refundLT,
payoutAddressOpt,
changeAddressOpt)
} }
jsArr.arr.toList match {
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: Nil =>
parseParameters(contractInfoJs,
collateralJs,
feeRateOptJs,
locktimeJs,
refundLTJs,
Null,
Null)
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: payoutAddressJs :: Nil =>
parseParameters(contractInfoJs,
collateralJs,
feeRateOptJs,
locktimeJs,
refundLTJs,
payoutAddressJs,
Null)
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: payoutAddressJs :: changeAddressJs :: Nil =>
parseParameters(contractInfoJs,
collateralJs,
feeRateOptJs,
locktimeJs,
refundLTJs,
payoutAddressJs,
changeAddressJs)
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
@ -839,17 +880,35 @@ object DecodeAttestations extends ServerJsonModels {
} }
} }
case class AcceptDLCOffer(offer: LnMessage[DLCOfferTLV]) case class AcceptDLCOffer(
offer: LnMessage[DLCOfferTLV],
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress])
object AcceptDLCOffer extends ServerJsonModels { object AcceptDLCOffer extends ServerJsonModels {
def fromJsArr(jsArr: ujson.Arr): Try[AcceptDLCOffer] = { def fromJsArr(jsArr: ujson.Arr): Try[AcceptDLCOffer] = {
def parseParameters(
offerJs: Value,
payoutAddressJs: Value,
changeAddressJs: Value) = Try {
val offer = LnMessageFactory(DLCOfferTLV).fromHex(offerJs.str)
val payoutAddressJsOpt = nullToOpt(payoutAddressJs)
val payoutAddressOpt =
payoutAddressJsOpt.map(js => jsToBitcoinAddress(js))
val changeAddressJsOpt = nullToOpt(changeAddressJs)
val changeAddressOpt =
changeAddressJsOpt.map(js => jsToBitcoinAddress(js))
AcceptDLCOffer(offer, payoutAddressOpt, changeAddressOpt)
}
jsArr.arr.toList match { jsArr.arr.toList match {
case offerJs :: Nil => case offerJs :: Nil =>
Try { parseParameters(offerJs, Null, Null)
val offer = LnMessageFactory(DLCOfferTLV).fromHex(offerJs.str) case offerJs :: payoutAddressJs :: Nil =>
AcceptDLCOffer(offer) parseParameters(offerJs, payoutAddressJs, Null)
} case offerJs :: payoutAddressJs :: changeAddressJs :: Nil =>
parseParameters(offerJs, payoutAddressJs, changeAddressJs)
case Nil => case Nil =>
Failure(new IllegalArgumentException("Missing offer argument")) Failure(new IllegalArgumentException("Missing offer argument"))
@ -930,24 +989,42 @@ object AddDLCSigs extends ServerJsonModels {
} }
} }
case class DLCDataFromFile(path: Path, destinationOpt: Option[Path]) case class DLCDataFromFile(
path: Path,
destinationOpt: Option[Path],
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress])
object DLCDataFromFile extends ServerJsonModels { object DLCDataFromFile extends ServerJsonModels {
def fromJsArr(jsArr: ujson.Arr): Try[DLCDataFromFile] = { def fromJsArr(jsArr: ujson.Arr): Try[DLCDataFromFile] = {
jsArr.arr.toList match { def parseParameters(
case pathJs :: Nil => pathJs: Value,
Try { destJs: Value,
val path = new File(pathJs.str).toPath payoutAddressJs: Value,
DLCDataFromFile(path, None) changeAddressJs: Value) = Try {
}
case pathJs :: destJs :: Nil =>
Try {
val path = new File(pathJs.str).toPath val path = new File(pathJs.str).toPath
val destJsOpt = nullToOpt(destJs) val destJsOpt = nullToOpt(destJs)
val destOpt = destJsOpt.map(js => new File(js.str).toPath) val destOpt = destJsOpt.map(js => new File(js.str).toPath)
DLCDataFromFile(path, destOpt) val payoutAddressJsOpt = nullToOpt(payoutAddressJs)
val payoutAddressOpt =
payoutAddressJsOpt.map(js => jsToBitcoinAddress(js))
val changeAddressJsOpt = nullToOpt(changeAddressJs)
val changeAddressOpt =
changeAddressJsOpt.map(js => jsToBitcoinAddress(js))
DLCDataFromFile(path, destOpt, payoutAddressOpt, changeAddressOpt)
} }
jsArr.arr.toList match {
case pathJs :: Nil =>
parseParameters(pathJs, Null, Null, Null)
case pathJs :: destJs :: Nil =>
parseParameters(pathJs, destJs, Null, Null)
case pathJs :: destJs :: payoutAddressJs :: Nil =>
parseParameters(pathJs, destJs, payoutAddressJs, Null)
case pathJs :: destJs :: payoutAddressJs :: changeAddressJs :: Nil =>
parseParameters(pathJs, destJs, payoutAddressJs, changeAddressJs)
case Nil => case Nil =>
Failure(new IllegalArgumentException("Missing path argument")) Failure(new IllegalArgumentException("Missing path argument"))
case other => case other =>

View file

@ -6,14 +6,14 @@ import akka.http.scaladsl.server._
import akka.stream.Materializer import akka.stream.Materializer
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import org.bitcoins.commons.serializers.Picklers._ import org.bitcoins.commons.serializers.Picklers._
import org.bitcoins.core.api.dlc.wallet.AnyDLCHDWalletApi
import org.bitcoins.core.api.wallet.db.SpendingInfoDb import org.bitcoins.core.api.wallet.db.SpendingInfoDb
import org.bitcoins.core.currency._ import org.bitcoins.core.currency._
import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.{AddressLabelTagType, TxoState} import org.bitcoins.core.wallet.utxo.{AddressLabelTagType, TxoState}
import org.bitcoins.crypto.NetworkElement import org.bitcoins.crypto.NetworkElement
import org.bitcoins.core.api.dlc.wallet.AnyDLCHDWalletApi
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.keymanager._ import org.bitcoins.keymanager._
import org.bitcoins.keymanager.config.KeyManagerAppConfig import org.bitcoins.keymanager.config.KeyManagerAppConfig
import org.bitcoins.server.routes.{Server, ServerCommand, ServerRoute} import org.bitcoins.server.routes.{Server, ServerCommand, ServerRoute}
@ -298,7 +298,9 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
collateral, collateral,
feeRateOpt, feeRateOpt,
locktime, locktime,
refundLT)) => refundLT,
payoutAddressOpt,
changeAddressOpt)) =>
complete { complete {
val announcements = contractInfo.oracleInfo match { val announcements = contractInfo.oracleInfo match {
case OracleInfoV0TLV(announcement) => Vector(announcement) case OracleInfoV0TLV(announcement) => Vector(announcement)
@ -316,7 +318,9 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
collateral, collateral,
feeRateOpt, feeRateOpt,
locktime, locktime,
refundLT) refundLT,
payoutAddressOpt,
changeAddressOpt)
.map { offer => .map { offer =>
Server.httpSuccess(offer.toMessage.hex) Server.httpSuccess(offer.toMessage.hex)
} }
@ -327,10 +331,11 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
AcceptDLCOffer.fromJsArr(arr) match { AcceptDLCOffer.fromJsArr(arr) match {
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success(AcceptDLCOffer(offer)) => case Success(
AcceptDLCOffer(offer, payoutAddressOpt, changeAddressOpt)) =>
complete { complete {
wallet wallet
.acceptDLCOffer(offer.tlv) .acceptDLCOffer(offer.tlv, payoutAddressOpt, changeAddressOpt)
.map { accept => .map { accept =>
Server.httpSuccess(accept.toMessage.hex) Server.httpSuccess(accept.toMessage.hex)
} }
@ -341,7 +346,11 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
DLCDataFromFile.fromJsArr(arr) match { DLCDataFromFile.fromJsArr(arr) match {
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success(DLCDataFromFile(path, destOpt)) => case Success(
DLCDataFromFile(path,
destOpt,
payoutAddressOpt,
changeAddressOpt)) =>
complete { complete {
val hex = Files.readAllLines(path).get(0) val hex = Files.readAllLines(path).get(0)
@ -349,7 +358,9 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
val offerMessage = LnMessageFactory(DLCOfferTLV).fromHex(hex) val offerMessage = LnMessageFactory(DLCOfferTLV).fromHex(hex)
wallet wallet
.acceptDLCOffer(offerMessage.tlv) .acceptDLCOffer(offerMessage.tlv,
payoutAddressOpt,
changeAddressOpt)
.map { accept => .map { accept =>
val ret = handleDestinationOpt(accept.toMessage.hex, destOpt) val ret = handleDestinationOpt(accept.toMessage.hex, destOpt)
Server.httpSuccess(ret) Server.httpSuccess(ret)
@ -375,7 +386,7 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
DLCDataFromFile.fromJsArr(arr) match { DLCDataFromFile.fromJsArr(arr) match {
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success(DLCDataFromFile(path, destOpt)) => case Success(DLCDataFromFile(path, destOpt, _, _)) =>
complete { complete {
val hex = Files.readAllLines(path).get(0) val hex = Files.readAllLines(path).get(0)
@ -408,7 +419,7 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
DLCDataFromFile.fromJsArr(arr) match { DLCDataFromFile.fromJsArr(arr) match {
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success(DLCDataFromFile(path, _)) => case Success(DLCDataFromFile(path, _, _, _)) =>
complete { complete {
val hex = Files.readAllLines(path).get(0) val hex = Files.readAllLines(path).get(0)
@ -439,7 +450,7 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
DLCDataFromFile.fromJsArr(arr) match { DLCDataFromFile.fromJsArr(arr) match {
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success(DLCDataFromFile(path, _)) => case Success(DLCDataFromFile(path, _, _, _)) =>
val hex = Files.readAllLines(path).get(0) val hex = Files.readAllLines(path).get(0)
val signMessage = LnMessageFactory(DLCSignTLV).fromHex(hex) val signMessage = LnMessageFactory(DLCSignTLV).fromHex(hex)

View file

@ -32,5 +32,8 @@ bitcoin-s.dlcnode.proxy.socks5 = ${?BITCOIN_S_DLCNODE_PROXY_SOCKS5}
bitcoin-s.dlcnode.tor.enabled = ${?BITCOIN_S_DLCNODE_TOR_ENABLED} bitcoin-s.dlcnode.tor.enabled = ${?BITCOIN_S_DLCNODE_TOR_ENABLED}
bitcoin-s.dlcnode.external-ip = ${?BITCOIN_S_DLCNODE_EXTERNAL_IP} bitcoin-s.dlcnode.external-ip = ${?BITCOIN_S_DLCNODE_EXTERNAL_IP}
bitcoin-s.wallet.allowExternalDLCAddresses = false
bitcoin-s.wallet.allowExternalDLCAddresses = ${?BITCOIN_S_ALLOW_EXT_DLC_ADDRESSES}
bitcoin-s.tor.enabled = ${?BITCOIN_S_TOR_ENABLED} bitcoin-s.tor.enabled = ${?BITCOIN_S_TOR_ENABLED}
bitcoin-s.tor.provided = ${?BITCOIN_S_TOR_PROVIDED} bitcoin-s.tor.provided = ${?BITCOIN_S_TOR_PROVIDED}

View file

@ -5,6 +5,7 @@ import org.bitcoins.core.api.wallet._
import org.bitcoins.core.currency.Satoshis import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.dlc.accounting._ import org.bitcoins.core.dlc.accounting._
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.dlc.models.DLCMessage._ import org.bitcoins.core.protocol.dlc.models.DLCMessage._
import org.bitcoins.core.protocol.dlc.models._ import org.bitcoins.core.protocol.dlc.models._
import org.bitcoins.core.protocol.tlv._ import org.bitcoins.core.protocol.tlv._
@ -22,9 +23,17 @@ trait DLCWalletApi { self: WalletApi =>
collateral: Satoshis, collateral: Satoshis,
feeRateOpt: Option[SatoshisPerVirtualByte], feeRateOpt: Option[SatoshisPerVirtualByte],
locktime: UInt32, locktime: UInt32,
refundLT: UInt32): Future[DLCOffer] = { refundLT: UInt32,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCOffer] = {
val contractInfo = ContractInfo.fromTLV(contractInfoTLV) val contractInfo = ContractInfo.fromTLV(contractInfoTLV)
createDLCOffer(contractInfo, collateral, feeRateOpt, locktime, refundLT) createDLCOffer(contractInfo,
collateral,
feeRateOpt,
locktime,
refundLT,
externalPayoutAddressOpt,
externalChangeAddressOpt)
} }
def createDLCOffer( def createDLCOffer(
@ -32,7 +41,9 @@ trait DLCWalletApi { self: WalletApi =>
collateral: Satoshis, collateral: Satoshis,
feeRateOpt: Option[SatoshisPerVirtualByte], feeRateOpt: Option[SatoshisPerVirtualByte],
locktime: UInt32, locktime: UInt32,
refundLT: UInt32): Future[DLCOffer] refundLT: UInt32,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCOffer]
def registerDLCOffer(dlcOffer: DLCOffer): Future[DLCOffer] = { def registerDLCOffer(dlcOffer: DLCOffer): Future[DLCOffer] = {
createDLCOffer( createDLCOffer(
@ -40,15 +51,25 @@ trait DLCWalletApi { self: WalletApi =>
dlcOffer.totalCollateral, dlcOffer.totalCollateral,
Some(dlcOffer.feeRate), Some(dlcOffer.feeRate),
dlcOffer.timeouts.contractMaturity.toUInt32, dlcOffer.timeouts.contractMaturity.toUInt32,
dlcOffer.timeouts.contractTimeout.toUInt32 dlcOffer.timeouts.contractTimeout.toUInt32,
None,
None
) )
} }
def acceptDLCOffer(dlcOfferTLV: DLCOfferTLV): Future[DLCAccept] = { def acceptDLCOffer(
acceptDLCOffer(DLCOffer.fromTLV(dlcOfferTLV)) dlcOfferTLV: DLCOfferTLV,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept] = {
acceptDLCOffer(DLCOffer.fromTLV(dlcOfferTLV),
externalPayoutAddressOpt,
externalChangeAddressOpt)
} }
def acceptDLCOffer(dlcOffer: DLCOffer): Future[DLCAccept] def acceptDLCOffer(
dlcOffer: DLCOffer,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept]
def signDLC(acceptTLV: DLCAcceptTLV): Future[DLCSign] def signDLC(acceptTLV: DLCAcceptTLV): Future[DLCSign]

View file

@ -5,6 +5,7 @@ import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.hd.{BIP32Path, HDChainType} import org.bitcoins.core.hd.{BIP32Path, HDChainType}
import org.bitcoins.core.number.UInt16 import org.bitcoins.core.number.UInt16
import org.bitcoins.core.policy.Policy import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
import org.bitcoins.core.protocol.dlc.models.DLCMessage.{ import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
DLCAccept, DLCAccept,
@ -324,7 +325,8 @@ object DLCUtil {
xpub: ExtPublicKey, xpub: ExtPublicKey,
chainType: HDChainType, chainType: HDChainType,
keyIndex: Int, keyIndex: Int,
networkParameters: NetworkParameters): DLCPublicKeys = { networkParameters: NetworkParameters,
externalPayoutAddressOpt: Option[BitcoinAddress]): DLCPublicKeys = {
val chainIndex = chainType.index val chainIndex = chainType.index
val fundingKey = val fundingKey =
xpub xpub
@ -332,16 +334,20 @@ object DLCUtil {
.get .get
.key .key
networkParameters match {
case bitcoinNetwork: BitcoinNetwork =>
externalPayoutAddressOpt match {
case None =>
val payoutKey = val payoutKey =
xpub xpub
.deriveChildPubKey( .deriveChildPubKey(
BIP32Path.fromString(s"m/$chainIndex/${keyIndex + 1}")) BIP32Path.fromString(s"m/$chainIndex/${keyIndex + 1}"))
.get .get
.key .key
networkParameters match {
case bitcoinNetwork: BitcoinNetwork =>
DLCPublicKeys.fromPubKeys(fundingKey, payoutKey, bitcoinNetwork) DLCPublicKeys.fromPubKeys(fundingKey, payoutKey, bitcoinNetwork)
case Some(externalPayoutAddress) =>
DLCPublicKeys(fundingKey, externalPayoutAddress)
}
} }
} }
} }

View file

@ -134,6 +134,10 @@ bitcoin-s {
# before we timeout # before we timeout
addressQueueTimeout = 5 seconds addressQueueTimeout = 5 seconds
# Allow external payout and change addresses in DLCs
# By default all DLC addresses are generated by the wallet itself
allowExternalDLCAddresses = false
# this config key is read by Slick # this config key is read by Slick
db { db {
name = walletdb name = walletdb

View file

@ -57,8 +57,10 @@ class DLCNegotiationTest extends BitcoinSDualWalletTest {
half, half,
Some(SatoshisPerVirtualByte.one), Some(SatoshisPerVirtualByte.one),
UInt32.zero, UInt32.zero,
UInt32.one) UInt32.one,
accept <- walletA.acceptDLCOffer(offer) None,
None)
accept <- walletA.acceptDLCOffer(offer, None, None)
// Send accept message to begin p2p // Send accept message to begin p2p
_ = handler ! accept.toMessage _ = handler ! accept.toMessage

View file

@ -35,7 +35,9 @@ class DLCNodeTest extends BitcoinSDLCNodeTest {
half, half,
Some(SatoshisPerVirtualByte.one), Some(SatoshisPerVirtualByte.one),
UInt32.zero, UInt32.zero,
UInt32.one) UInt32.one,
None,
None)
_ <- nodeB.acceptDLCOffer(addrA, offer.toMessage) _ <- nodeB.acceptDLCOffer(addrA, offer.toMessage)

View file

@ -47,7 +47,7 @@ class DLCDataHandler(dlcWalletApi: DLCWalletApi, connectionHandler: ActorRef)
Future.unit Future.unit
case dlcOffer: DLCOfferTLV => case dlcOffer: DLCOfferTLV =>
val f = for { val f = for {
accept <- dlcWalletApi.acceptDLCOffer(dlcOffer) accept <- dlcWalletApi.acceptDLCOffer(dlcOffer, None, None)
_ = connectionHandler ! accept.toMessage _ = connectionHandler ! accept.toMessage
} yield () } yield ()
f f

View file

@ -362,11 +362,15 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
//helper method to make an offer //helper method to make an offer
def makeOffer(): Future[DLCOffer] = { def makeOffer(): Future[DLCOffer] = {
walletA.createDLCOffer(contractInfoTLV = contractInfo, walletA.createDLCOffer(
contractInfoTLV = contractInfo,
collateral = totalCollateral, collateral = totalCollateral,
feeRateOpt = feeRateOpt, feeRateOpt = feeRateOpt,
locktime = UInt32.zero, locktime = UInt32.zero,
refundLT = UInt32.one) refundLT = UInt32.one,
externalPayoutAddressOpt = None,
externalChangeAddressOpt = None
)
} }
//simply try to make 2 offers with the same contract info //simply try to make 2 offers with the same contract info
@ -414,7 +418,9 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
status.localCollateral.satoshis, status.localCollateral.satoshis,
None, None,
UInt32.zero, UInt32.zero,
UInt32.one) UInt32.one,
None,
None)
_ <- walletA.listDLCs() _ <- walletA.listDLCs()
} yield succeed } yield succeed

View file

@ -51,7 +51,9 @@ class MultiWalletDLCTest extends BitcoinSWalletTest {
half, half,
Some(SatoshisPerVirtualByte.one), Some(SatoshisPerVirtualByte.one),
UInt32.zero, UInt32.zero,
UInt32.one) UInt32.one,
None,
None)
dlcsA <- walletA.listDLCs() dlcsA <- walletA.listDLCs()
dlcsB <- walletB.listDLCs() dlcsB <- walletB.listDLCs()
@ -68,12 +70,15 @@ class MultiWalletDLCTest extends BitcoinSWalletTest {
fundedWallet: FundedDLCWallet => fundedWallet: FundedDLCWallet =>
//see: https://github.com/bitcoin-s/bitcoin-s/issues/3813#issue-1051117559 //see: https://github.com/bitcoin-s/bitcoin-s/issues/3813#issue-1051117559
val wallet = fundedWallet.wallet val wallet = fundedWallet.wallet
val offerF = wallet.createDLCOffer(contractInfo = sampleContractInfo, val offerF = wallet.createDLCOffer(
contractInfo = sampleContractInfo,
collateral = half, collateral = half,
feeRateOpt = feeRateOpt = Some(SatoshisPerVirtualByte.one),
Some(SatoshisPerVirtualByte.one),
locktime = UInt32.zero, locktime = UInt32.zero,
refundLocktime = UInt32.one) refundLocktime = UInt32.one,
externalPayoutAddressOpt = None,
externalChangeAddressOpt = None
)
//now unreserve the utxo //now unreserve the utxo
val reservedUtxoF = for { val reservedUtxoF = for {

View file

@ -2,6 +2,7 @@ package org.bitcoins.dlc.wallet
import org.bitcoins.core.currency._ import org.bitcoins.core.currency._
import org.bitcoins.core.number.{UInt32, UInt64} import org.bitcoins.core.number.{UInt32, UInt64}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.dlc.models.DLCMessage._ import org.bitcoins.core.protocol.dlc.models.DLCMessage._
import org.bitcoins.core.protocol.dlc.models._ import org.bitcoins.core.protocol.dlc.models._
import org.bitcoins.core.protocol.script.P2WPKHWitnessV0 import org.bitcoins.core.protocol.script.P2WPKHWitnessV0
@ -44,7 +45,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
dlcA1Opt <- walletA.dlcDAO.read(dlcId) dlcA1Opt <- walletA.dlcDAO.read(dlcId)
@ -61,7 +64,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
assert(offer.changeAddress.value.nonEmpty) assert(offer.changeAddress.value.nonEmpty)
} }
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
dlcB1Opt <- walletB.dlcDAO.read(dlcId) dlcB1Opt <- walletB.dlcDAO.read(dlcId)
_ = { _ = {
assert(dlcB1Opt.isDefined) assert(dlcB1Opt.isDefined)
@ -185,11 +188,13 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
// reorder dlc inputs in wallets // reorder dlc inputs in wallets
_ <- reorderInputDbs(walletA, dlcId) _ <- reorderInputDbs(walletA, dlcId)
@ -242,11 +247,13 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
accept <- walletB.acceptDLCOffer(offer.toTLV) accept <- walletB.acceptDLCOffer(offer.toTLV, None, None)
// reorder dlc inputs in wallets // reorder dlc inputs in wallets
_ <- reorderInputDbs(walletA, dlcId) _ <- reorderInputDbs(walletA, dlcId)
@ -271,7 +278,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
dlcA1Opt <- walletA.dlcDAO.read(dlcId) dlcA1Opt <- walletA.dlcDAO.read(dlcId)
@ -287,7 +296,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
assert(offer.changeAddress.value.nonEmpty) assert(offer.changeAddress.value.nonEmpty)
} }
accept <- walletB.acceptDLCOffer(offer.toTLV) accept <- walletB.acceptDLCOffer(offer.toTLV, None, None)
dlcB1Opt <- walletB.dlcDAO.read(dlcId) dlcB1Opt <- walletB.dlcDAO.read(dlcId)
_ = { _ = {
assert(dlcB1Opt.isDefined) assert(dlcB1Opt.isDefined)
@ -365,10 +374,12 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
} yield accept } yield accept
} }
@ -509,7 +520,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
@ -552,9 +565,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
_ <- walletB.acceptDLCOffer(offer) _ <- walletB.acceptDLCOffer(offer, None, None)
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
@ -593,9 +608,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
sign <- walletA.signDLC(accept) sign <- walletA.signDLC(accept)
_ <- walletB.addDLCSigs(sign) _ <- walletB.addDLCSigs(sign)
@ -635,9 +652,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
sign <- walletA.signDLC(accept) sign <- walletA.signDLC(accept)
_ <- walletB.addDLCSigs(sign) _ <- walletB.addDLCSigs(sign)
@ -668,9 +687,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
UInt32.max UInt32.max,
None,
None
) )
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
sign <- walletA.signDLC(accept) sign <- walletA.signDLC(accept)
_ <- walletB.addDLCSigs(sign) _ <- walletB.addDLCSigs(sign)
@ -733,7 +754,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32 offerData.timeouts.contractTimeout.toUInt32,
None,
None
) )
_ = { _ = {
assert(offer.oracleInfos == offerData.oracleInfos) assert(offer.oracleInfos == offerData.oracleInfos)
@ -747,7 +770,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint)) dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
_ = { _ = {
assert(accept.fundingInputs.nonEmpty) assert(accept.fundingInputs.nonEmpty)
assert( assert(
@ -824,19 +847,23 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
val totalCollateral = Satoshis(5000) val totalCollateral = Satoshis(5000)
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = { def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
walletA.createDLCOffer(contractInfoTLV = contractInfo, walletA.createDLCOffer(
contractInfoTLV = contractInfo,
collateral = totalCollateral, collateral = totalCollateral,
feeRateOpt = feeRateOpt, feeRateOpt = feeRateOpt,
locktime = UInt32.zero, locktime = UInt32.zero,
refundLT = UInt32.one) refundLT = UInt32.one,
externalPayoutAddressOpt = None,
externalChangeAddressOpt = None
)
} }
for { for {
offerA <- makeOffer(contractInfoA) offerA <- makeOffer(contractInfoA)
offerB <- makeOffer(contractInfoB) offerB <- makeOffer(contractInfoB)
_ <- walletB.acceptDLCOffer(offerA) _ <- walletB.acceptDLCOffer(offerA, None, None)
_ <- walletB.acceptDLCOffer(offerB) _ <- walletB.acceptDLCOffer(offerB, None, None)
} yield succeed } yield succeed
} }
@ -868,17 +895,21 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
val totalCollateral = Satoshis(100000) val totalCollateral = Satoshis(100000)
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = { def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
walletA.createDLCOffer(contractInfoTLV = contractInfo, walletA.createDLCOffer(
contractInfoTLV = contractInfo,
collateral = totalCollateral, collateral = totalCollateral,
feeRateOpt = feeRateOpt, feeRateOpt = feeRateOpt,
locktime = UInt32.zero, locktime = UInt32.zero,
refundLT = UInt32.one) refundLT = UInt32.one,
externalPayoutAddressOpt = None,
externalChangeAddressOpt = None
)
} }
for { for {
offer <- makeOffer(contractInfoA) offer <- makeOffer(contractInfoA)
accept1F = walletB.acceptDLCOffer(offer) accept1F = walletB.acceptDLCOffer(offer, None, None)
accept2F = walletB.acceptDLCOffer(offer) accept2F = walletB.acceptDLCOffer(offer, None, None)
_ <- recoverToSucceededIf[DuplicateOfferException]( _ <- recoverToSucceededIf[DuplicateOfferException](
Future.sequence(Seq(accept1F, accept2F))) Future.sequence(Seq(accept1F, accept2F)))
} yield { } yield {
@ -895,17 +926,21 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
val totalCollateral = Satoshis(100000) val totalCollateral = Satoshis(100000)
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = { def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
walletA.createDLCOffer(contractInfoTLV = contractInfo, walletA.createDLCOffer(
contractInfoTLV = contractInfo,
collateral = totalCollateral, collateral = totalCollateral,
feeRateOpt = feeRateOpt, feeRateOpt = feeRateOpt,
locktime = UInt32.zero, locktime = UInt32.zero,
refundLT = UInt32.one) refundLT = UInt32.one,
externalPayoutAddressOpt = None,
externalChangeAddressOpt = None
)
} }
for { for {
offer <- makeOffer(contractInfoA) offer <- makeOffer(contractInfoA)
accept1 <- walletB.acceptDLCOffer(offer) accept1 <- walletB.acceptDLCOffer(offer, None, None)
accept2 <- walletB.acceptDLCOffer(offer) accept2 <- walletB.acceptDLCOffer(offer, None, None)
} yield { } yield {
assert(accept1 == accept2) assert(accept1 == accept2)
} }
@ -923,9 +958,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
UInt32.max UInt32.max,
None,
None
) )
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer, None, None)
res <- recoverToSucceededIf[IllegalArgumentException]( res <- recoverToSucceededIf[IllegalArgumentException](
walletB.signDLC(accept)) walletB.signDLC(accept))
} yield res } yield res
@ -944,7 +981,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
offerData.totalCollateral, offerData.totalCollateral,
Some(offerData.feeRate), Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32, offerData.timeouts.contractMaturity.toUInt32,
UInt32.max UInt32.max,
None,
None
)) ))
} yield { } yield {
res res
@ -963,18 +1002,82 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
val totalCollateral = Satoshis(5000) val totalCollateral = Satoshis(5000)
for { for {
offer <- walletA.createDLCOffer(contractInfoTLV = contractInfo, offer <- walletA.createDLCOffer(
contractInfoTLV = contractInfo,
collateral = totalCollateral, collateral = totalCollateral,
feeRateOpt = feeRateOpt, feeRateOpt = feeRateOpt,
locktime = UInt32.zero, locktime = UInt32.zero,
refundLT = UInt32.one) refundLT = UInt32.one,
externalPayoutAddressOpt = None,
externalChangeAddressOpt = None
)
invalidOffer = offer.copy(contractInfo = invalidContractInfo) invalidOffer = offer.copy(contractInfo = invalidContractInfo)
res <- recoverToSucceededIf[InvalidAnnouncementSignature]( res <- recoverToSucceededIf[InvalidAnnouncementSignature](
walletB.acceptDLCOffer(invalidOffer)) walletB.acceptDLCOffer(invalidOffer, None, None))
} yield { } yield {
res res
} }
} }
it must "use external payout and change addresses when they are provided" in {
wallets =>
val walletA = wallets._1.wallet
val walletB = wallets._2.wallet
//https://test.oracle.suredbits.com/contract/enum/75b08299654dca23b80cf359db6afb6cfd6e55bc898b5397d3c0fe796dfc13f0/12fb3e5f091086329ed0d2a12c3fcfa80111a36ef3fc1ac9c2567076a57d6a73
val contractInfo = ContractInfoV0TLV.fromHex(
"fdd82eeb00000000000186a0fda71026030359455300000000000186a0024e4f0000000000000000056f746865720000000000000000fda712b5fdd824b1596ec40d0dae3fdf54d9795ad51ec069970c6863a02d244663d39fd6bedadc0070349e1ba2e17583ee2d1cb3ae6fffaaa1c45039b61c5c4f1d0d864221c461745d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9fdd8224d0001fa5b84283852400b21a840d5d5ca1cc31867c37326ad521aa50bebf3df4eea1a60b03280fdd8060f000303594553024e4f056f74686572135465746865722d52657365727665732d363342")
val contractInfo1 = DLCWalletUtil.sampleDLCOffer.contractInfo.toTLV
val feeRateOpt = Some(SatoshisPerVirtualByte(Satoshis.one))
val totalCollateral = Satoshis(5000)
val feeRateOpt1 = Some(SatoshisPerVirtualByte(Satoshis(2)))
val totalCollateral1 = Satoshis(10000)
// random testnet addresses
val payoutAddressAOpt = Some(
BitcoinAddress.fromString("tb1qw98mrsxpqtz25xe332khnvlapvl09ejnzk7c3f"))
val changeAddressAOpt = Some(
BitcoinAddress.fromString("tb1qkfaglsvpcwe5pm9ktqs80u9d9jd0qzgqjqd240"))
val payoutAddressBOpt =
Some(BitcoinAddress.fromString("2MsM67NLa71fHvTUBqNENW15P68nHB2vVXb"))
val changeAddressBOpt =
Some(BitcoinAddress.fromString("2N4YXTxKEso3yeYXNn5h42Vqu3FzTTQ8Lq5"))
for {
offer <- walletA.createDLCOffer(
contractInfoTLV = contractInfo,
collateral = totalCollateral,
feeRateOpt = feeRateOpt,
locktime = UInt32.zero,
refundLT = UInt32.one,
externalPayoutAddressOpt = payoutAddressAOpt,
externalChangeAddressOpt = changeAddressAOpt
)
accept <- walletB.acceptDLCOffer(offer,
payoutAddressBOpt,
changeAddressBOpt)
offer1 <- walletA.createDLCOffer(
contractInfoTLV = contractInfo1,
collateral = totalCollateral1,
feeRateOpt = feeRateOpt1,
locktime = UInt32.zero,
refundLT = UInt32.one,
externalPayoutAddressOpt = None,
externalChangeAddressOpt = None
)
accept1 <- walletB.acceptDLCOffer(offer1, None, None)
} yield {
assert(offer.pubKeys.payoutAddress == payoutAddressAOpt.get)
assert(offer.changeAddress == changeAddressAOpt.get)
assert(accept.pubKeys.payoutAddress == payoutAddressBOpt.get)
assert(accept.changeAddress == changeAddressBOpt.get)
assert(offer1.pubKeys.payoutAddress != payoutAddressAOpt.get)
assert(offer1.changeAddress != changeAddressAOpt.get)
assert(accept1.pubKeys.payoutAddress != payoutAddressBOpt.get)
assert(accept1.changeAddress != changeAddressBOpt.get)
}
}
} }

View file

@ -268,8 +268,16 @@ abstract class DLCWallet
collateral: Satoshis, collateral: Satoshis,
feeRateOpt: Option[SatoshisPerVirtualByte], feeRateOpt: Option[SatoshisPerVirtualByte],
locktime: UInt32, locktime: UInt32,
refundLocktime: UInt32): Future[DLCOffer] = { refundLocktime: UInt32,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCOffer] = {
logger.info("Creating DLC Offer") logger.info("Creating DLC Offer")
if (
!walletConfig.allowExternalDLCAddresses && (externalPayoutAddressOpt.nonEmpty || externalChangeAddressOpt.nonEmpty)
) {
return Future.failed(
new IllegalArgumentException("External DLC addresses are not allowed"))
}
if (!validateAnnouncementSignatures(contractInfo.oracleInfos)) { if (!validateAnnouncementSignatures(contractInfo.oracleInfos)) {
return Future.failed( return Future.failed(
InvalidAnnouncementSignature( InvalidAnnouncementSignature(
@ -279,19 +287,15 @@ abstract class DLCWallet
val announcements = val announcements =
contractInfo.oracleInfos.head.singleOracleInfos.map(_.announcement) contractInfo.oracleInfos.head.singleOracleInfos.map(_.announcement)
//hack for now to get around https://github.com/bitcoin-s/bitcoin-s/issues/3127
//filter announcements that we already have in the db
val groupedAnnouncementsF: Future[AnnouncementGrouping] = {
groupByExistingAnnouncements(announcements)
}
val feeRateF = determineFeeRate(feeRateOpt).map { fee => val feeRateF = determineFeeRate(feeRateOpt).map { fee =>
SatoshisPerVirtualByte(fee.currencyUnit) SatoshisPerVirtualByte(fee.currencyUnit)
} }
for { for {
feeRate <- feeRateF feeRate <- feeRateF
groupedAnnouncements <- groupedAnnouncementsF //hack for now to get around https://github.com/bitcoin-s/bitcoin-s/issues/3127
//filter announcements that we already have in the db
groupedAnnouncements <- groupByExistingAnnouncements(announcements)
announcementDataDbs <- announcementDAO.createAll( announcementDataDbs <- announcementDAO.createAll(
groupedAnnouncements.newAnnouncements) groupedAnnouncements.newAnnouncements)
allAnnouncementDbs = allAnnouncementDbs =
@ -341,14 +345,17 @@ abstract class DLCWallet
used = false) used = false)
} }
changeSPK = changeAddr = externalChangeAddressOpt.getOrElse {
txBuilder.finalizer.changeSPK val changeSPK = txBuilder.finalizer.changeSPK
changeAddr = BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters) BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters)
}
dlcPubKeys = DLCUtil.calcDLCPubKeys(xpub = account.xpub, dlcPubKeys = DLCUtil.calcDLCPubKeys(xpub = account.xpub,
chainType = chainType, chainType = chainType,
keyIndex = nextIndex, keyIndex = nextIndex,
networkParameters = networkParameters) networkParameters = networkParameters,
externalPayoutAddressOpt =
externalPayoutAddressOpt)
_ = logger.debug( _ = logger.debug(
s"DLC Offer data collected, creating database entry, ${dlcId.hex}") s"DLC Offer data collected, creating database entry, ${dlcId.hex}")
@ -552,8 +559,17 @@ abstract class DLCWallet
* *
* This is the first step of the recipient * This is the first step of the recipient
*/ */
override def acceptDLCOffer(offer: DLCOffer): Future[DLCAccept] = { override def acceptDLCOffer(
offer: DLCOffer,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept] = {
logger.debug("Calculating relevant wallet data for DLC Accept") logger.debug("Calculating relevant wallet data for DLC Accept")
if (
!walletConfig.allowExternalDLCAddresses && (externalPayoutAddressOpt.nonEmpty || externalChangeAddressOpt.nonEmpty)
) {
return Future.failed(
new IllegalArgumentException("External DLC addresses are not allowed"))
}
if (!validateAnnouncementSignatures(offer.oracleInfos)) { if (!validateAnnouncementSignatures(offer.oracleInfos)) {
return Future.failed(InvalidAnnouncementSignature( return Future.failed(InvalidAnnouncementSignature(
s"Offer ${offer.tempContractId.hex} contains invalid announcement signature(s)")) s"Offer ${offer.tempContractId.hex} contains invalid announcement signature(s)"))
@ -573,7 +589,11 @@ abstract class DLCWallet
dlcAccept <- { dlcAccept <- {
dlcAcceptOpt match { dlcAcceptOpt match {
case Some(accept) => Future.successful(accept) case Some(accept) => Future.successful(accept)
case None => createNewDLCAccept(collateral, offer) case None =>
createNewDLCAccept(collateral,
offer,
externalPayoutAddressOpt,
externalChangeAddressOpt)
} }
} }
status <- findDLC(dlcId) status <- findDLC(dlcId)
@ -612,7 +632,10 @@ abstract class DLCWallet
private def createNewDLCAccept( private def createNewDLCAccept(
collateral: CurrencyUnit, collateral: CurrencyUnit,
offer: DLCOffer): Future[DLCAccept] = Future { offer: DLCOffer,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept] =
Future {
DLCWallet.AcceptingOffersLatch.startAccepting(offer.tempContractId) DLCWallet.AcceptingOffersLatch.startAccepting(offer.tempContractId)
logger.info( logger.info(
@ -642,7 +665,9 @@ abstract class DLCWallet
account = account, account = account,
fundingPrivKey = fundingPrivKey, fundingPrivKey = fundingPrivKey,
collateral = collateral, collateral = collateral,
networkParameters = networkParameters networkParameters = networkParameters,
externalPayoutAddressOpt = externalPayoutAddressOpt,
externalChangeAddressOpt = externalChangeAddressOpt
) )
builder = DLCTxBuilder(offer, acceptWithoutSigs) builder = DLCTxBuilder(offer, acceptWithoutSigs)
@ -731,7 +756,8 @@ abstract class DLCWallet
accept = accept =
dlcAcceptDb.toDLCAccept(tempContractId = dlc.tempContractId, dlcAcceptDb.toDLCAccept(tempContractId = dlc.tempContractId,
fundingInputs = acceptWithoutSigs.fundingInputs, fundingInputs =
acceptWithoutSigs.fundingInputs,
outcomeSigs = cetSigs.outcomeSigs, outcomeSigs = cetSigs.outcomeSigs,
refundSig = refundSig) refundSig = refundSig)

View file

@ -38,7 +38,9 @@ object DLCAcceptUtil extends Logging {
account: AccountDb, account: AccountDb,
fundingPrivKey: AdaptorSign, fundingPrivKey: AdaptorSign,
collateral: CurrencyUnit, collateral: CurrencyUnit,
networkParameters: NetworkParameters): ( networkParameters: NetworkParameters,
externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]): (
DLCAcceptWithoutSigs, DLCAcceptWithoutSigs,
DLCPublicKeys) = { DLCPublicKeys) = {
val serialIds = DLCMessage.genSerialIds( val serialIds = DLCMessage.genSerialIds(
@ -49,15 +51,18 @@ object DLCAcceptUtil extends Logging {
.fromInputSigningInfo(utxo, id, TransactionConstants.enableRBFSequence) .fromInputSigningInfo(utxo, id, TransactionConstants.enableRBFSequence)
} }
val changeAddr = externalChangeAddressOpt.getOrElse {
val changeSPK = txBuilder.finalizer.changeSPK val changeSPK = txBuilder.finalizer.changeSPK
val changeAddr =
BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters) BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters)
}
val dlcPubKeys = DLCUtil.calcDLCPubKeys(xpub = account.xpub, val dlcPubKeys = DLCUtil.calcDLCPubKeys(
xpub = account.xpub,
chainType = dlc.changeIndex, chainType = dlc.changeIndex,
keyIndex = dlc.keyIndex, keyIndex = dlc.keyIndex,
networkParameters = networkParameters = networkParameters,
networkParameters) externalPayoutAddressOpt = externalPayoutAddressOpt
)
require(dlcPubKeys.fundingKey == fundingPrivKey.publicKey, require(dlcPubKeys.fundingKey == fundingPrivKey.publicKey,
"Did not derive the same funding private and public key") "Did not derive the same funding private and public key")

View file

@ -242,6 +242,10 @@ bitcoin-s {
# before we timeout # before we timeout
addressQueueTimeout = 5 seconds addressQueueTimeout = 5 seconds
# Allow external payout and change addresses in DLCs
# By default all DLC addresses are generated by the wallet itself
allowExternalDLCAddresses = false
# How often the wallet will rebroadcast unconfirmed transactions # How often the wallet will rebroadcast unconfirmed transactions
rebroadcastFrequency = 4 hours rebroadcastFrequency = 4 hours

View file

@ -46,7 +46,9 @@ object BitcoinSTestAppConfig {
| node { | node {
| mode = spv | mode = spv
| } | }
| | wallet {
| allowExternalDLCAddresses = true
| }
| proxy.enabled = $torEnabled | proxy.enabled = $torEnabled
| tor.enabled = $torEnabled | tor.enabled = $torEnabled
| tor.use-random-ports = false | tor.use-random-ports = false

View file

@ -274,7 +274,12 @@ object DLCWalletUtil extends Logging {
def initDLC( def initDLC(
fundedWalletA: FundedDLCWallet, fundedWalletA: FundedDLCWallet,
fundedWalletB: FundedDLCWallet, fundedWalletB: FundedDLCWallet,
contractInfo: ContractInfo)(implicit ec: ExecutionContext): Future[ contractInfo: ContractInfo,
payoutAddressAOpt: Option[BitcoinAddress] = None,
changeAddressAOpt: Option[BitcoinAddress] = None,
payoutAddressBOpt: Option[BitcoinAddress] = None,
changeAddressBOpt: Option[BitcoinAddress] = None)(implicit
ec: ExecutionContext): Future[
(InitializedDLCWallet, InitializedDLCWallet)] = { (InitializedDLCWallet, InitializedDLCWallet)] = {
val walletA = fundedWalletA.wallet val walletA = fundedWalletA.wallet
val walletB = fundedWalletB.wallet val walletB = fundedWalletB.wallet
@ -285,9 +290,13 @@ object DLCWalletUtil extends Logging {
collateral = half, collateral = half,
feeRateOpt = Some(SatoshisPerVirtualByte.fromLong(10)), feeRateOpt = Some(SatoshisPerVirtualByte.fromLong(10)),
locktime = dummyTimeouts.contractMaturity.toUInt32, locktime = dummyTimeouts.contractMaturity.toUInt32,
refundLocktime = dummyTimeouts.contractTimeout.toUInt32 refundLocktime = dummyTimeouts.contractTimeout.toUInt32,
externalPayoutAddressOpt = payoutAddressAOpt,
externalChangeAddressOpt = changeAddressAOpt
) )
accept <- walletB.acceptDLCOffer(offer) accept <- walletB.acceptDLCOffer(offer,
payoutAddressBOpt,
changeAddressBOpt)
sigs <- walletA.signDLC(accept) sigs <- walletA.signDLC(accept)
_ <- walletB.addDLCSigs(sigs) _ <- walletB.addDLCSigs(sigs)
tx <- walletB.broadcastDLCFundingTx(sigs.contractId) tx <- walletB.broadcastDLCFundingTx(sigs.contractId)

View file

@ -145,6 +145,9 @@ case class WalletAppConfig(baseDatadir: Path, configOverrides: Vector[Config])(
lazy val feeProviderTargetOpt: Option[Int] = lazy val feeProviderTargetOpt: Option[Int] =
config.getIntOpt("bitcoin-s.fee-provider.target") config.getIntOpt("bitcoin-s.fee-provider.target")
lazy val allowExternalDLCAddresses: Boolean =
config.getBoolean("bitcoin-s.wallet.allowExternalDLCAddresses")
lazy val bip39PasswordOpt: Option[String] = kmConf.bip39PasswordOpt lazy val bip39PasswordOpt: Option[String] = kmConf.bip39PasswordOpt
lazy val aesPasswordOpt: Option[AesPassword] = kmConf.aesPasswordOpt lazy val aesPasswordOpt: Option[AesPassword] = kmConf.aesPasswordOpt