2022 01 18 issue 3969 Add serialization_version to global_dlc_data (#3992)

* Get refactor working where we decouple CETSignatures and the partial refund signature

* WIP

* Implement migration to specify serialization_version on global_dlc_data table

* Fix migrations test

* WIP

* implement contractid computation

* Get migration working for wallet on testnet

* Add check to make sure we only try to migrate old wallets

* Refactor DLCDataManagement.getOfferAndAcceptWithoutSigs to not use DLCTxBuilder

* WIP

* Bubble up Option[] into DLCWallet, trivially assert on it and then use .get. We will have to do the refactor of DLCWallet sometime in the future

* Change global_dlc_data serializationVersion at database level to not have an Option

* Add DLCDbState, propogate it through the codebase

* Remove log

* Remove invariant for making sure all DLCs are defined on migraiton, add log for the case of issue 4001

* Add filter on ALPHA serialization DLCs. We dont need to migrate DLCs that are using the beta serialization

* Rebase onto 4004

* Refactor to use the refund signature in the accept/sign message

* Add output index to the return of DLCTxBuilder.buildFundingTransaction()

* Fix compile

* Remove log
This commit is contained in:
Chris Stewart 2022-01-26 14:57:45 -06:00 committed by GitHub
parent 98c5d816ac
commit bd5bcfef3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1277 additions and 667 deletions

View file

@ -4,6 +4,7 @@ import org.bitcoins.core.api.db.LastUpdatedDb
import org.bitcoins.core.hd.{HDAccount, HDChainType}
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.dlc.models.DLCState
import org.bitcoins.core.protocol.tlv.DLCSerializationVersion
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import org.bitcoins.core.util.TimeUtil
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
@ -35,7 +36,8 @@ case class DLCDb(
fundingOutPointOpt: Option[TransactionOutPoint],
fundingTxIdOpt: Option[DoubleSha256DigestBE],
closingTxIdOpt: Option[DoubleSha256DigestBE],
aggregateSignatureOpt: Option[SchnorrDigitalSignature]
aggregateSignatureOpt: Option[SchnorrDigitalSignature],
serializationVersion: DLCSerializationVersion
) extends LastUpdatedDb {
def updateState(newState: DLCState): DLCDb = {

View file

@ -142,19 +142,21 @@ case class DLCTxBuilder(offer: DLCOffer, accept: DLCAcceptWithoutSigs) {
/** Constructs the unsigned funding transaction */
lazy val buildFundingTx: Transaction = {
DLCTxBuilder.buildFundingTransaction(
offerInput = offerTotalCollateral,
acceptInput = acceptTotalCollateral,
offerFundingInputs = offerFundingInputs,
acceptFundingInputs = acceptFundingInputs,
offerChangeSPK = offerChangeAddress.scriptPubKey,
offerChangeSerialId = offerChangeSerialId,
acceptChangeSPK = acceptChangeAddress.scriptPubKey,
acceptChangeSerialId = acceptChangeSerialId,
fundingSPK = fundingSPK,
fundOutputSerialId = fundOutputSerialId,
finalizer = fundingTxFinalizer
)
DLCTxBuilder
.buildFundingTransaction(
offerInput = offerTotalCollateral,
acceptInput = acceptTotalCollateral,
offerFundingInputs = offerFundingInputs,
acceptFundingInputs = acceptFundingInputs,
offerChangeSPK = offerChangeAddress.scriptPubKey,
offerChangeSerialId = offerChangeSerialId,
acceptChangeSPK = acceptChangeAddress.scriptPubKey,
acceptChangeSerialId = acceptChangeSerialId,
fundingSPK = fundingSPK,
fundOutputSerialId = fundOutputSerialId,
finalizer = fundingTxFinalizer
)
._1
}
// Need to build funding tx so it takes into account the dust threshold
@ -262,6 +264,9 @@ object DLCTxBuilder {
)
}
/** Builds the funding transaction for a DLC
* @return the transaction and the output index of the funding output
*/
def buildFundingTransaction(
offerInput: CurrencyUnit,
acceptInput: CurrencyUnit,
@ -273,7 +278,7 @@ object DLCTxBuilder {
acceptChangeSerialId: UInt64,
fundingSPK: P2WSHWitnessSPKV0,
fundOutputSerialId: UInt64,
finalizer: DualFundingTxFinalizer): Transaction = {
finalizer: DualFundingTxFinalizer): (Transaction, Int) = {
// The total collateral of both parties combined
val totalInput: CurrencyUnit = offerInput + acceptInput
@ -313,20 +318,34 @@ object DLCTxBuilder {
val acceptChangeValue =
acceptTotalFunding - acceptInput - finalizer.acceptFees
val fundingOutput = TransactionOutput(fundingValue, fundingSPK)
val offererChangeOutput =
TransactionOutput(offerChangeValue, offerChangeSPK)
val acceptorChangeOutput =
TransactionOutput(acceptChangeValue, acceptChangeSPK)
val outputsWithSerialId = Vector(
(TransactionOutput(fundingValue, fundingSPK), fundOutputSerialId),
(TransactionOutput(offerChangeValue, offerChangeSPK),
offerChangeSerialId),
(TransactionOutput(acceptChangeValue, acceptChangeSPK),
acceptChangeSerialId)
(fundingOutput, fundOutputSerialId),
(offererChangeOutput, offerChangeSerialId),
(acceptorChangeOutput, acceptChangeSerialId)
)
val outputs = sortAndFilterOutputs(outputsWithSerialId)
BaseTransaction(TransactionConstants.validLockVersion,
inputs,
outputs,
UInt32.zero)
val btx = BaseTransaction(TransactionConstants.validLockVersion,
inputs,
outputs,
UInt32.zero)
val changeSPKs = Vector(offererChangeOutput.scriptPubKey,
acceptorChangeOutput.scriptPubKey)
val outputIdx = btx.outputs.zipWithIndex
.filterNot { case (output, _) =>
changeSPKs.contains(output.scriptPubKey)
}
.map(_._2)
require(outputIdx.length == 1,
s"Can only have one funding output idx, got=$outputIdx")
(btx, outputIdx.head)
}
def buildCET(

View file

@ -2,6 +2,12 @@ package org.bitcoins.core.protocol.dlc.compute
import org.bitcoins.core.number.UInt16
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
DLCAccept,
DLCAcceptWithoutSigs,
DLCOffer
}
import org.bitcoins.core.protocol.dlc.models.{ContractInfo, OracleOutcome}
import org.bitcoins.core.protocol.script.P2WSHWitnessV0
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
@ -131,4 +137,54 @@ object DLCUtil {
(SchnorrDigitalSignature(outcome.aggregateNonce, s), outcome)
}
}
def calcContractId(offer: DLCOffer, accept: DLCAccept): ByteVector = {
calcContractId(offer, accept.withoutSigs)
}
def calcContractId(
offer: DLCOffer,
acceptWithoutSigs: DLCAcceptWithoutSigs): ByteVector = {
val fundingKeys =
Vector(offer.pubKeys.fundingKey, acceptWithoutSigs.pubKeys.fundingKey)
val fundOutputSerialId = offer.fundOutputSerialId
val offerFundingInputs = offer.fundingInputs
val acceptFundingInputs = acceptWithoutSigs.fundingInputs
val offerChangeSPK = offer.changeAddress.scriptPubKey
val acceptChangeSPK = acceptWithoutSigs.changeAddress.scriptPubKey
val offerFinalAddressSPK = offer.pubKeys.payoutAddress.scriptPubKey
val acceptFinalAddressSPK =
acceptWithoutSigs.pubKeys.payoutAddress.scriptPubKey
val feeRate = offer.feeRate
val (_, fundingSPK) = DLCTxBuilder.buildFundingSPKs(fundingKeys)
val fundingTxFinalizer = DLCTxBuilder.buildFundingTxFinalizer(
offerFundingInputs = offerFundingInputs,
acceptFundingInputs = acceptFundingInputs,
offerChangeSPK = offerChangeSPK,
acceptChangeSPK = acceptChangeSPK,
offerPayoutSPK = offerFinalAddressSPK,
acceptPayoutSPK = acceptFinalAddressSPK,
feeRate = feeRate,
fundingSPK = fundingSPK
)
val (fundingTx, fundingOutputIdx) = DLCTxBuilder.buildFundingTransaction(
offerInput = offer.totalCollateral,
acceptInput = acceptWithoutSigs.totalCollateral,
offerFundingInputs = offerFundingInputs,
acceptFundingInputs = acceptFundingInputs,
offerChangeSPK = offerChangeSPK,
offerChangeSerialId = offer.changeSerialId,
acceptChangeSPK = acceptChangeSPK,
acceptChangeSerialId = acceptWithoutSigs.changeSerialId,
fundingSPK = fundingSPK,
fundOutputSerialId = fundOutputSerialId,
finalizer = fundingTxFinalizer
)
computeContractId(fundingTx = fundingTx,
outputIdx = fundingOutputIdx,
tempContractId = offer.tempContractId)
}
}

View file

@ -18,6 +18,7 @@ import org.bitcoins.core.protocol.tlv.{
ContractInfoTLV,
ContractInfoV0TLV,
ContractInfoV1TLV,
DLCSerializationVersion,
OracleAnnouncementTLV,
TLVDeserializable,
TLVSerializable,
@ -148,6 +149,17 @@ sealed trait ContractInfo extends TLVSerializable[ContractInfoTLV] {
def updateOnAccept(
newTotalCollateral: Satoshis,
negotiationFields: DLCAccept.NegotiationFields): ContractInfo
def serializationVersion: DLCSerializationVersion = {
contractDescriptors.head match {
case _: EnumContractDescriptor =>
//enum contracts weren't broken by
//https://github.com/bitcoin-s/bitcoin-s/pull/3854
DLCSerializationVersion.Beta
case n: NumericContractDescriptor =>
n.outcomeValueFunc.serializationVersion
}
}
}
object ContractInfo

View file

@ -11,6 +11,8 @@ sealed trait DLCSerializationVersion
object DLCSerializationVersion extends StringFactory[DLCSerializationVersion] {
val current: DLCSerializationVersion = Beta
/** This format existed in our wallet before we merged support for this PR
* on the DLC spec repo. See the diff below
* @see [[https://github.com/discreetlogcontracts/dlcspecs/pull/144]]

View file

@ -83,13 +83,13 @@ class DbManagementTest extends BitcoinSAsyncTest with EmbeddedPg {
val result = dlcDbManagement.migrate()
dlcAppConfig.driver match {
case SQLite =>
val expected = 4
val expected = 5
assert(result == expected)
val flywayInfo = dlcAppConfig.info()
assert(flywayInfo.applied().length == expected)
assert(flywayInfo.pending().length == 0)
case PostgreSQL =>
val expected = 4
val expected = 5
assert(result == expected)
val flywayInfo = dlcAppConfig.info()

View file

@ -140,7 +140,7 @@ abstract class CRUD[T, PrimaryKeyType](implicit
/** Finds all elements in the table */
def findAll(): Future[Vector[T]] =
safeDatabase.run(table.result).map(_.toVector)
safeDatabase.run(findAllAction())
/** Returns number of rows in the table */
def count(): Future[Int] = safeDatabase.run(table.length.result)

View file

@ -56,6 +56,13 @@ abstract class CRUDAction[T, PrimaryKeyType](implicit
protected def findAll(ts: Vector[T]): Query[Table[_], T, Seq]
def findAllAction(): DBIOAction[
Vector[T],
profile.api.NoStream,
profile.api.Effect.Read] = {
table.result.map(_.toVector)
}
/** Updates all of the given ts.
* Returns all ts that actually existed in the database and got updated
* This method discards things that did not exist in the database,

View file

@ -798,7 +798,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
asInitiator = true,
func = func,
expectedOutputs = 1)
} yield assert(result)
} yield {
assert(result)
}
}
it must "accept 2 offers with the same oracle info" in { wallets =>

View file

@ -0,0 +1,2 @@
ALTER TABLE "global_dlc_data"
ADD COLUMN "serialization_version" TEXT NOT NULL DEFAULT 'ALPHA';

View file

@ -0,0 +1,2 @@
ALTER TABLE "global_dlc_data"
ADD COLUMN "serialization_version" VARCHAR(254) NOT NULL DEFAULT "ALPHA";

View file

@ -3,12 +3,22 @@ package org.bitcoins.dlc.wallet
import com.typesafe.config.Config
import org.bitcoins.commons.config.{AppConfigFactory, ConfigOps}
import org.bitcoins.core.api.chain.ChainQueryApi
import org.bitcoins.core.api.dlc.wallet.db.DLCDb
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.util.{FutureUtil, Mutable}
import org.bitcoins.core.protocol.dlc.compute.DLCUtil
import org.bitcoins.core.protocol.tlv.DLCSerializationVersion
import org.bitcoins.core.util.Mutable
import org.bitcoins.db.DatabaseDriver._
import org.bitcoins.db._
import org.bitcoins.dlc.wallet.internal.DLCDataManagement
import org.bitcoins.dlc.wallet.models.{
AcceptDbState,
DLCSetupDbState,
OfferedDbState
}
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.TransactionDAO
import org.bitcoins.wallet.{Wallet, WalletLogger}
import java.nio.file._
@ -40,13 +50,26 @@ case class DLCAppConfig(baseDatadir: Path, configOverrides: Vector[Config])(
Files.createDirectories(datadir)
}
//get migrations applied the last time the wallet was started
val initMigrations = migrationsApplied()
val numMigrations = {
migrate()
}
val f = if (initMigrations != 0 && initMigrations <= 5) {
//means we have an old wallet that we need to migrate
logger.info(s"Running serialization version migration code")
serializationVersionMigration()
} else {
//the wallet is new enough where we cannot have any old
//DLCs in the database with a broken contractId
Future.unit
}
logger.info(s"Applied $numMigrations to the dlc project")
FutureUtil.unit
f
}
lazy val walletConf: WalletAppConfig =
@ -96,6 +119,86 @@ case class DLCAppConfig(baseDatadir: Path, configOverrides: Vector[Config])(
def addCallbacks(newCallbacks: DLCWalletCallbacks): DLCWalletCallbacks = {
callbacks.atomicUpdate(newCallbacks)(_ + _)
}
/** Correctly populates the serialization version for existing DLCs
* in our wallet database
*/
private def serializationVersionMigration(): Future[Unit] = {
val dlcManagement = DLCDataManagement.fromDbAppConfig()(this, ec)
val dlcDAO = dlcManagement.dlcDAO
//read all existing DLCs
val allDlcsF = dlcDAO.findAll()
//ugh, this is kinda nasty, idk how to make better though
val walletAppConfig =
WalletAppConfig(baseDatadir, configOverrides)
val txDAO: TransactionDAO =
TransactionDAO()(ec = ec, appConfig = walletAppConfig)
//get the offers so we can figure out what the serialization version is
val dlcDbContractInfoOfferF: Future[Vector[DLCSetupDbState]] = {
for {
allDlcs <- allDlcsF
//only DLC with the alpha version need to be migrated
alphaVersionDLCs = allDlcs.filter(
_.serializationVersion == DLCSerializationVersion.Alpha)
nestedOfferAndAccept = alphaVersionDLCs.map { a =>
val setupDbOptF =
dlcManagement.getDLCFundingData(a.dlcId, txDAO = txDAO)
setupDbOptF.foreach {
case Some(_) => //happy path, do nothing
case None =>
logger.warn(s"Corrupted dlcId=${a.dlcId.hex} state=${a.state}, " +
s"this is likely because of issue 4001 https://github.com/bitcoin-s/bitcoin-s/issues/4001 . " +
s"This DLC will not have its contractId migrated to DLSerializationVersion.Beta")
}
setupDbOptF
}
offerAndAccepts <- Future.sequence(nestedOfferAndAccept)
} yield {
offerAndAccepts.flatten
}
}
//now we need to insert the serialization type
//into global_dlc_data
val updatedDLCDbsF = for {
dlcDbContractInfoOffer <- dlcDbContractInfoOfferF
} yield setSerializationVersions(dlcDbContractInfoOffer)
val updatedInDbF = updatedDLCDbsF.flatMap(dlcDAO.updateAll)
updatedInDbF.map(_ => ())
}
/** Sets serialization versions on [[DLCDb]] based on the corresponding [[ContractInfo]] */
private def setSerializationVersions(
vec: Vector[DLCSetupDbState]): Vector[DLCDb] = {
vec.map { case state: DLCSetupDbState =>
val updatedDlcDb: DLCDb = state match {
case acceptDbState: AcceptDbState =>
val offer = acceptDbState.offer
val acceptWithoutSigs = acceptDbState.acceptWithoutSigs
val dlcDb = acceptDbState.dlcDb
val contractId = DLCUtil.calcContractId(offer, acceptWithoutSigs)
logger.info(
s"Updating contractId for dlcId=${dlcDb.dlcId.hex} old contractId=${dlcDb.contractIdOpt
.map(_.toHex)} new contractId=${contractId.toHex}")
dlcDb.copy(tempContractId = offer.tempContractId,
contractIdOpt = Some(contractId),
serializationVersion = DLCSerializationVersion.Beta)
case offerDbState: OfferedDbState =>
//if we don't have an accept message, we can only calculate tempContractId
val dlcDb = offerDbState.dlcDb
val offer = offerDbState.offer
logger.info(
s"Updating tempContractId for dlcId=${dlcDb.dlcId.hex} old tempContractId=${dlcDb.tempContractId.hex} new contractId=${offer.tempContractId.hex}")
dlcDb.copy(tempContractId = offer.tempContractId,
serializationVersion = DLCSerializationVersion.Beta)
}
updatedDlcDb
}
}
}
object DLCAppConfig extends AppConfigFactory[DLCAppConfig] with WalletLogger {

View file

@ -14,6 +14,7 @@ import org.bitcoins.core.hd._
import org.bitcoins.core.number._
import org.bitcoins.core.protocol._
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
import org.bitcoins.core.protocol.dlc.compute.DLCUtil
import org.bitcoins.core.protocol.dlc.models.DLCMessage.DLCAccept._
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
import org.bitcoins.core.protocol.dlc.models.DLCState._
@ -76,7 +77,8 @@ abstract class DLCWallet
dlcSigsDAO,
dlcRefundSigDAO,
oracleNonceDAO,
announcementDAO
announcementDAO,
remoteTxDAO
)
private val dlcDataManagement = DLCDataManagement(dlcWalletDAOs)
@ -87,11 +89,6 @@ abstract class DLCWallet
private lazy val safeDatabase: SafeDatabase = dlcDAO.safeDatabase
private def calcContractId(offer: DLCOffer, accept: DLCAccept): ByteVector = {
val builder = DLCTxBuilder(offer, accept.withoutSigs)
builder.calcContractId
}
/** Updates the contract Id in the wallet database for the given offer and accept */
private def updateDLCContractIds(
offer: DLCOffer,
@ -108,7 +105,7 @@ abstract class DLCWallet
new IllegalArgumentException(
s"No DLCDb found with dlcId ${dlcId.hex}"))
}
contractId = calcContractId(offer, accept)
contractId = DLCUtil.calcContractId(offer, accept)
newDLCDb = dlcDb.updateContractId(contractId)
_ = logger.debug(s"Updating DLC contract Ids")
@ -408,7 +405,8 @@ abstract class DLCWallet
fundingOutPointOpt = None,
fundingTxIdOpt = None,
closingTxIdOpt = None,
aggregateSignatureOpt = None
aggregateSignatureOpt = None,
serializationVersion = contractInfo.serializationVersion
)
contractDataDb = DLCContractDataDb(
@ -497,7 +495,8 @@ abstract class DLCWallet
fundingOutPointOpt = None,
fundingTxIdOpt = None,
closingTxIdOpt = None,
aggregateSignatureOpt = None
aggregateSignatureOpt = None,
serializationVersion = contractInfo.serializationVersion
)
contractDataDb = {
@ -832,12 +831,12 @@ abstract class DLCWallet
logger.info(
s"Verifying ${accept.cetSigs.outcomeSigs.size} CET Signatures")
for {
isCETSigsValid <- verifyCETSigs(accept)
_ = if (!isCETSigsValid)
isCETSigsValidOpt <- verifyCETSigs(accept)
_ = if (!(isCETSigsValidOpt.getOrElse(false)))
throw new IllegalArgumentException(
s"CET sigs provided are not valid! got ${accept.cetSigs.outcomeSigs}")
isRefundSigValid <- verifyRefundSig(accept)
_ = if (!isRefundSigValid)
_ = if (!(isRefundSigValid.getOrElse(false)))
throw new IllegalArgumentException(
s"Refund sig provided is not valid! got ${accept.refundSig}")
@ -934,57 +933,55 @@ abstract class DLCWallet
dlcId = dlc.dlcId
fundingInputs <- dlcInputsDAO.findByDLCId(dlcId)
scriptSigParams <- getScriptSigParams(dlc, fundingInputs)
signer <- dlcDataManagement.signerFromDb(dlcId = dlc.dlcId,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
fundingUtxoScriptSigParams =
scriptSigParams,
keyManager = keyManager)
signerOpt <- dlcDataManagement.signerFromDb(
dlcId = dlc.dlcId,
transactionDAO = transactionDAO,
fundingUtxoScriptSigParams = scriptSigParams,
keyManager = keyManager)
mySigs <- dlcSigsDAO.findByDLCId(dlc.dlcId)
refundSigsDb <- dlcRefundSigDAO.findByDLCId(dlc.dlcId).map(_.head)
refundSig = {
refundSigsDb.initiatorSig match {
case Some(sig) => sig
case None => signer.signRefundTx
cetSigsOpt <- {
signerOpt match {
case Some(signer) =>
val cetSigsF = getCetSigs(signer = signer,
contractId = contractId,
cetSigsDbs = cetSigsDbs,
mySigs = mySigs)
cetSigsF.map(Some(_))
case None =>
Future.successful(None)
}
}
cetSigs <-
if (mySigs.forall(_.initiatorSig.isEmpty)) {
logger.info(s"Creating CET Sigs for contract ${contractId.toHex}")
for {
sigs <- signer.createCETSigsAsync()
sigsDbs: Vector[DLCCETSignaturesDb] = sigs.outcomeSigs
.zip(cetSigsDbs.sortBy(_.index))
.map { case (sig, db) =>
db.copy(initiatorSig = Some(sig._2))
}
_ <- dlcSigsDAO.updateAll(sigsDbs)
} yield sigs
} else {
logger.debug(s"CET Sigs already created for ${contractId.toHex}")
val outcomeSigs = mySigs.map { dbSig =>
dbSig.sigPoint -> dbSig.initiatorSig.get
}
val signatures = CETSignatures(outcomeSigs)
Future.successful(signatures)
}
_ = logger.info(s"Creating funding sigs for ${contractId.toHex}")
fundingSigs <- Future.fromTry(signer.signFundingTx())
fundingSigsOpt <- {
signerOpt match {
case Some(signer) =>
val fundingSignaturesT = signer.signFundingTx()
Future
.fromTry(fundingSignaturesT)
.map(Some(_))
case None =>
Future.successful(None)
}
}
// order fundingSigs by index
inputDbs <- dlcInputsDAO.findByDLCId(dlc.dlcId, isInitiator = true)
_ = require(
fundingSigsOpt.isDefined,
s"Cannot sign a DLC message when we cannot generate funding signatures, dlcId=${dlcId}")
fundingSigs = fundingSigsOpt.get
cetSigs = cetSigsOpt.get
sortedSigVec = inputDbs.sortBy(_.index).map { db =>
val sig = fundingSigs(db.outPoint)
(db.outPoint, sig)
}
updatedRefundSigsDb = refundSigsDb.copy(initiatorSig = Some(refundSig))
updatedRefundSigsDb = refundSigsDb.copy(initiatorSig =
signerOpt.map(_.signRefundTx))
_ <- dlcRefundSigDAO.update(updatedRefundSigsDb)
_ <- updateDLCState(dlc.contractIdOpt.get, DLCState.Signed)
@ -993,53 +990,114 @@ abstract class DLCWallet
_ <- dlcConfig.walletCallbacks.executeOnDLCStateChange(logger, status.get)
} yield {
//?? is signer.signRefundTx persisted anywhere ??
DLCSign(cetSigs, refundSig, FundingSignatures(sortedSigVec), contractId)
DLCSign(cetSigs,
signerOpt.map(_.signRefundTx).get,
FundingSignatures(sortedSigVec),
contractId)
}
}
def verifyCETSigs(accept: DLCAccept): Future[Boolean] = {
val verifierAcceptF =
private def getCetSigs(
signer: DLCTxSigner,
contractId: ByteVector,
cetSigsDbs: Vector[DLCCETSignaturesDb],
mySigs: Vector[DLCCETSignaturesDb]): Future[CETSignatures] = {
if (mySigs.forall(_.initiatorSig.isEmpty)) {
logger.info(s"Creating CET Sigs for contract ${contractId.toHex}")
for {
sigs <- signer.createCETSigsAsync()
sigsDbs: Vector[DLCCETSignaturesDb] = sigs.outcomeSigs
.zip(cetSigsDbs.sortBy(_.index))
.map { case (sig, db) =>
db.copy(initiatorSig = Some(sig._2))
}
_ <- dlcSigsDAO.updateAll(sigsDbs)
} yield sigs
} else {
logger.debug(s"CET Sigs already created for ${contractId.toHex}")
val outcomeSigs = mySigs.map { dbSig =>
dbSig.sigPoint -> dbSig.initiatorSig.get
}
val signatures = CETSignatures(outcomeSigs)
Future.successful(signatures)
}
}
/** Verify CET sigs for the given accept message if it exists
* If it doesnt not exist, return None
*/
def verifyCETSigs(accept: DLCAccept): Future[Option[Boolean]] = {
val verifierAcceptOptF =
dlcDataManagement.verifierFromAccept(accept, transactionDAO)
verifierAcceptF.flatMap(_.verifyCETSigs(accept.cetSigs.indexedOutcomeSigs))
verifierAcceptOptF.flatMap {
case Some(verifier) =>
verifier
.verifyCETSigs(accept.cetSigs.indexedOutcomeSigs)
.map(Some(_))
case None => Future.successful(None)
}
}
def verifyCETSigs(sign: DLCSign): Future[Boolean] = {
val verifierF = dlcDataManagement.verifierFromDb(sign.contractId,
transactionDAO,
remoteTxDAO)
verifierF.flatMap(_.verifyCETSigs(sign.cetSigs.indexedOutcomeSigs))
/** Verify CET sigs for the given sign message if it exists
* If it doesnt not exist, return None
*/
def verifyCETSigs(sign: DLCSign): Future[Option[Boolean]] = {
val verifierF =
dlcDataManagement.verifierFromDb(sign.contractId, transactionDAO)
verifierF.flatMap {
case Some(verifier) =>
val boolF = verifier.verifyCETSigs(sign.cetSigs.indexedOutcomeSigs)
boolF.map(Some(_))
case None =>
Future.successful(None)
}
}
def verifyRefundSig(accept: DLCAccept): Future[Boolean] = {
val verifierF = dlcDataManagement.verifierFromAccept(accept, transactionDAO)
verifierF.map(_.verifyRefundSig(accept.refundSig))
def verifyRefundSig(accept: DLCAccept): Future[Option[Boolean]] = {
val verifierOptF =
dlcDataManagement.verifierFromAccept(accept, transactionDAO)
verifierOptF.map {
case Some(verifier) =>
val bool = verifier.verifyRefundSig(accept.refundSig)
Some(bool)
case None => None
}
}
def verifyRefundSig(sign: DLCSign): Future[Boolean] = {
val verifierF = dlcDataManagement.verifierFromDb(
def verifyRefundSig(sign: DLCSign): Future[Option[Boolean]] = {
val verifierOptF = dlcDataManagement.verifierFromDb(
contractId = sign.contractId,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO)
verifierF.map(_.verifyRefundSig(sign.refundSig))
transactionDAO = transactionDAO)
verifierOptF.map {
case Some(verifier) =>
val bool = verifier.verifyRefundSig(sign.refundSig)
Some(bool)
case None =>
None
}
}
def verifyFundingSigs(
inputs: Vector[DLCFundingInputDb],
sign: DLCSign): Future[Boolean] = {
sign: DLCSign): Future[Option[Boolean]] = {
if (inputs.count(_.isInitiator) == sign.fundingSigs.length) {
val verifierF = dlcDataManagement.verifierFromDb(
val verifierOptF = dlcDataManagement.verifierFromDb(
contractId = sign.contractId,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO)
verifierF.map { verifier =>
verifier.verifyRemoteFundingSigs(sign.fundingSigs)
transactionDAO = transactionDAO)
verifierOptF.map {
case Some(verifier) =>
val bool = verifier.verifyRemoteFundingSigs(sign.fundingSigs)
Some(bool)
case None =>
None
}
} else {
logger.info(
"Funding Signatures provided did not have the correct amount of inputs")
Future.successful(false)
Future.successful(Some(false))
}
}
@ -1053,9 +1111,9 @@ abstract class DLCWallet
_ = logger.info(
s"Verifying ${sign.fundingSigs.length} funding sigs for contract ${sign.contractId.toHex}")
isValid <- verifyFundingSigs(inputs = inputs, sign = sign)
isValidOpt <- verifyFundingSigs(inputs = inputs, sign = sign)
_ <- {
if (!isValid)
if (!(isValidOpt.getOrElse(false)))
Future.failed(new IllegalArgumentException(
s"Funding Signatures provided are not valid! got ${sign.fundingSigs}"))
else FutureUtil.unit
@ -1087,19 +1145,15 @@ abstract class DLCWallet
Future.failed(new RuntimeException(
s"No DLC found with corresponding contractId ${contractId.toHex}"))
}
(_, contractData, dlcOffer, dlcAccept, fundingInputs, contractInfo) <-
dlcDataManagement.getDLCFundingData(dlcDb.dlcId)
builder <- dlcDataManagement.builderFromDbData(
builderOpt <- dlcDataManagement.builderFromDbData(
dlcDb = dlcDb,
contractDataDb = contractData,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputs,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO
transactionDAO = transactionDAO
)
_ = require(
builderOpt.isDefined,
s"Cannot add DLC sigs when the builder is not defined, dlcId=${dlcDb.dlcId.hex}")
builder = builderOpt.get
sign = DLCSign.fromTLV(signTLV, builder.offer)
result <- addDLCSigs(sign)
} yield result
@ -1123,12 +1177,12 @@ abstract class DLCWallet
s"Verifying CET Signatures for contract ${sign.contractId.toHex}")
for {
isRefundSigValid <- verifyRefundSig(sign)
_ = if (!isRefundSigValid)
_ = if (!(isRefundSigValid.getOrElse(false)))
throw new IllegalArgumentException(
s"Refund sig provided is not valid! got ${sign.refundSig}")
isCETSigsValid <- verifyCETSigs(sign)
_ = if (!isCETSigsValid)
_ = if (!(isCETSigsValid.getOrElse(false)))
throw new IllegalArgumentException(
s"CET sigs provided are not valid! got ${sign.cetSigs.outcomeSigs}")
@ -1187,22 +1241,31 @@ abstract class DLCWallet
override def getDLCFundingTx(contractId: ByteVector): Future[Transaction] = {
for {
(dlcDb, contractData, dlcOffer, dlcAccept, fundingInputs, contractInfo) <-
dlcDataManagement.getDLCFundingData(contractId)
setupStateOpt <- dlcDataManagement.getDLCFundingData(contractId,
txDAO =
transactionDAO)
acceptState = {
setupStateOpt.map {
case _: OfferedDbState =>
sys.error(
s"Cannot retrieve funding transaction when DLC is in offered state")
case accept: AcceptDbState => accept
}.get //bad but going to have to save this refactor for future
}
dlcDb = acceptState.dlcDb
//is this right? We don't have counterpart scriptSigParams
fundingInputs = acceptState.allFundingInputs
scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs)
signer <- dlcDataManagement.signerFromDb(
signerOpt <- dlcDataManagement.signerFromDb(
dlcDb = dlcDb,
contractDataDb = contractData,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputs,
fundingUtxoScriptSigParams = scriptSigParams,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
keyManager = keyManager
)
_ = require(
signerOpt.isDefined,
s"Cannot get dlc funding tx when signerOpt isn't defined, dlcId=${dlcDb.dlcId.hex}")
signer = signerOpt.get
fundingTx <-
if (dlcDb.isInitiator) {
transactionDAO.findByTxId(signer.builder.buildFundingTx.txIdBE).map {
@ -1339,13 +1402,20 @@ abstract class DLCWallet
oracleSigs: Vector[OracleSignatures]): Future[Transaction] = {
require(oracleSigs.nonEmpty, "Must provide at least one oracle signature")
for {
(dlcDb, _, _, _, fundingInputs, _) <-
dlcDataManagement.getDLCFundingData(contractId)
setupStateOpt <-
dlcDataManagement.getDLCFundingData(contractId, txDAO = transactionDAO)
_ = require(setupStateOpt.isDefined,
s"Must have setup state defined to create execution tx")
_ = require(
setupStateOpt.get.isInstanceOf[AcceptDbState],
s"Setup state must be accept to create dlc execution tx, got=${setupStateOpt.get.state}")
setupState = setupStateOpt.get.asInstanceOf[AcceptDbState]
dlcDb = setupState.dlcDb
fundingInputs = setupState.allFundingInputs
scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs)
executorWithSetupOpt <- dlcDataManagement.executorAndSetupFromDb(
contractId = contractId,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
txDAO = transactionDAO,
fundingUtxoScriptSigParams = scriptSigParams,
keyManager = keyManager)
tx <- {
@ -1437,11 +1507,14 @@ abstract class DLCWallet
fundingInputs <- dlcInputsDAO.findByDLCId(dlcDb.dlcId)
scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs)
executor <- dlcDataManagement.executorFromDb(dlcDb.dlcId,
transactionDAO,
remoteTxDAO,
scriptSigParams,
keyManager)
executorOpt <- dlcDataManagement.executorFromDb(dlcDb.dlcId,
transactionDAO,
scriptSigParams,
keyManager)
_ = require(
executorOpt.isDefined,
s"Cannot execute refund transaction when the executor isn't defined, dlcId=${dlcDb.dlcId.hex}")
executor = executorOpt.get
refundSigsDbOpt <- dlcRefundSigDAO.findByDLCId(dlcDb.dlcId)
refundSig =

View file

@ -1,6 +1,8 @@
package org.bitcoins.dlc.wallet.internal
import grizzled.slf4j.Logging
import org.bitcoins.core.api.dlc.wallet.db.DLCDb
import org.bitcoins.core.api.wallet.db.TransactionDb
import org.bitcoins.core.hd._
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
import org.bitcoins.core.protocol.dlc.execution._
@ -13,33 +15,38 @@ import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.util.sorted.{OrderedAnnouncements, OrderedNonces}
import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto.Sha256Digest
import org.bitcoins.db.SafeDatabase
import org.bitcoins.dlc.wallet.DLCAppConfig
import org.bitcoins.dlc.wallet.models._
import org.bitcoins.dlc.wallet.util.{DLCActionBuilder, DLCTxUtil}
import org.bitcoins.dlc.wallet.util.DLCActionBuilder
import org.bitcoins.keymanager.bip39.BIP39KeyManager
import org.bitcoins.wallet.models.TransactionDAO
import scodec.bits._
import slick.dbio.{DBIOAction, Effect, NoStream}
import scala.concurrent._
import scala.util.Try
/** Handles fetching and constructing different DLC datastructures from the database */
case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
ec: ExecutionContext) {
private val dlcDAO = dlcWalletDAOs.dlcDAO
ec: ExecutionContext)
extends Logging {
val dlcDAO = dlcWalletDAOs.dlcDAO
private val dlcAnnouncementDAO = dlcWalletDAOs.dlcAnnouncementDAO
//private val dlcInputsDAO = dlcWalletDAOs.dlcInputsDAO
// private val dlcOfferDAO = dlcWalletDAOs.dlcOfferDAO
private val dlcInputsDAO = dlcWalletDAOs.dlcInputsDAO
//private val dlcOfferDAO = dlcWalletDAOs.dlcOfferDAO
private val contractDataDAO = dlcWalletDAOs.contractDataDAO
private val dlcAcceptDAO = dlcWalletDAOs.dlcAcceptDAO
private val dlcSigsDAO = dlcWalletDAOs.dlcSigsDAO
private val dlcRefundSigDAO = dlcWalletDAOs.dlcRefundSigDAO
private val dlcSigsDAO: DLCCETSignaturesDAO = dlcWalletDAOs.dlcSigsDAO
private val dlcRefundSigDAO: DLCRefundSigsDAO = dlcWalletDAOs.dlcRefundSigDAO
private val announcementDAO = dlcWalletDAOs.oracleAnnouncementDAO
private val oracleNonceDAO = dlcWalletDAOs.oracleNonceDAO
private val remoteTxDAO = dlcWalletDAOs.dlcRemoteTxDAO
private val actionBuilder: DLCActionBuilder = {
DLCActionBuilder(dlcWalletDAOs)
}
private lazy val safeDatabase = dlcDAO.safeDatabase
private val safeDatabase: SafeDatabase = dlcDAO.safeDatabase
private[wallet] def getDLCAnnouncementDbs(dlcId: Sha256Digest): Future[(
Vector[DLCAnnouncementDb],
@ -195,385 +202,346 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
}
}
private[wallet] def getDLCFundingData(contractId: ByteVector): Future[
(
DLCDb,
DLCContractDataDb,
DLCOfferDb,
DLCAcceptDb,
Vector[DLCFundingInputDb],
ContractInfo)] = {
for {
dlcDbOpt <- dlcDAO.findByContractId(contractId)
dlcDb = dlcDbOpt.get
(_, contractData, dlcOffer, dlcAccept, fundingInputs, contractInfo) <-
getDLCFundingData(dlcDb.dlcId)
} yield (dlcDb,
contractData,
dlcOffer,
dlcAccept,
fundingInputs,
contractInfo)
}
private[wallet] def getDLCOfferData(dlcId: Sha256Digest): Future[
(
DLCDb,
DLCContractDataDb,
DLCOfferDb,
Vector[DLCFundingInputDb],
ContractInfo)] = {
private def getDLCOfferData(
dlcId: Sha256Digest,
transactionDAO: TransactionDAO): Future[Option[OfferedDbState]] = {
val combined = actionBuilder.getDLCOfferDataAction(dlcId)
val combinedF = safeDatabase.run(combined)
for {
(dlcDbs, contractDataDbs, offerDbs, fundingInputDbs) <- combinedF
dlcDb = dlcDbs.head
contractData = contractDataDbs.head
dlcOffer = offerDbs.head
(announcements, announcementData, nonceDbs) <- getDLCAnnouncementDbs(
dlcId)
contractInfo = getContractInfo(contractData,
announcements,
announcementData,
nonceDbs)
val announcementDataF = getDLCAnnouncementDbs(dlcId)
val result: Future[Option[Future[OfferedDbState]]] = for {
(dlcDbOpt, contractDataDbOpt, offerDbOpt, fundingInputDbs) <- combinedF
(announcements, announcementData, nonceDbs) <- announcementDataF
contractInfoOpt = {
contractDataDbOpt.map { case contractData =>
getContractInfo(contractData,
announcements,
announcementData,
nonceDbs)
}
}
sortedInputs = fundingInputDbs.sortBy(_.index)
} yield {
(dlcDb, contractData, dlcOffer, sortedInputs, contractInfo)
for {
dlcDb <- dlcDbOpt
offerPrevTxsF = getOfferPrevTxs(dlcDb, fundingInputDbs, transactionDAO)
contractDataDb <- contractDataDbOpt
offerDb <- offerDbOpt
contractInfo <- contractInfoOpt
} yield {
offerPrevTxsF.map { offerPrevTxs =>
OfferedDbState(dlcDb = dlcDb,
contractDataDb = contractDataDb,
contractInfo = contractInfo,
offerDb = offerDb,
offerFundingInputsDb = sortedInputs,
offerPrevTxs = offerPrevTxs)
}
}
}
result.flatMap {
case Some(f) => f.map(Some(_))
case None => Future.successful(None)
}
}
private[wallet] def getDLCFundingData(dlcId: Sha256Digest): Future[
(
DLCDb,
DLCContractDataDb,
DLCOfferDb,
DLCAcceptDb,
Vector[DLCFundingInputDb],
ContractInfo)] = {
for {
(dlcDb, contractData, dlcOffer, fundingInputs, contractInfo) <-
getDLCOfferData(dlcId)
private[wallet] def getDLCFundingData(
contractId: ByteVector,
txDAO: TransactionDAO
): Future[Option[DLCSetupDbState]] = {
val dlcDbOpt = dlcDAO.findByContractId(contractId)
dlcDbOpt.flatMap {
case Some(d) =>
getDLCFundingData(d.dlcId, txDAO)
case None =>
Future.successful(None)
}
}
private[wallet] def getDLCFundingData(
dlcId: Sha256Digest,
txDAO: TransactionDAO): Future[Option[DLCSetupDbState]] = {
val nestedF: Future[Option[Future[DLCSetupDbState]]] = for {
offerDbStateOpt <- getDLCOfferData(dlcId, txDAO)
dlcAcceptOpt <- dlcAcceptDAO.findByDLCId(dlcId)
dlcAccept = dlcAcceptOpt.head
} yield (dlcDb,
contractData,
dlcOffer,
dlcAccept,
fundingInputs,
contractInfo)
}
private[wallet] def getAllDLCData(contractId: ByteVector): Future[
(
DLCDb,
DLCContractDataDb,
DLCOfferDb,
DLCAcceptDb,
DLCRefundSigsDb,
ContractInfo,
Vector[DLCFundingInputDb],
Option[Vector[DLCCETSignaturesDb]])] = {
for {
dlcDbOpt <- dlcDAO.findByContractId(contractId)
dlcDb = dlcDbOpt.get
(_,
contractData,
dlcOffer,
dlcAccept,
refundSig,
contractInfo,
fundingInputs,
outcomeSigs) <-
getAllDLCData(dlcDb.dlcId)
} yield (dlcDb,
contractData,
dlcOffer,
dlcAccept,
refundSig,
contractInfo,
fundingInputs,
outcomeSigs)
}
private[wallet] def getAllDLCData(dlcId: Sha256Digest): Future[
(
DLCDb,
DLCContractDataDb,
DLCOfferDb,
DLCAcceptDb,
DLCRefundSigsDb,
ContractInfo,
Vector[DLCFundingInputDb],
Option[Vector[DLCCETSignaturesDb]])] = {
val safeDatabase = dlcRefundSigDAO.safeDatabase
val refundSigDLCs = dlcRefundSigDAO.findByDLCIdAction(dlcId)
val sigDLCs = dlcSigsDAO.findByDLCIdAction(dlcId)
val refundAndOutcomeSigsAction =
refundSigDLCs.flatMap(r => sigDLCs.map(s => (r, s)))
val refundAndOutcomeSigsF = safeDatabase.run(refundAndOutcomeSigsAction)
for {
(dlcDb, contractData, dlcOffer, dlcAccept, fundingInputs, contractInfo) <-
getDLCFundingData(dlcId)
(refundSigs, outcomeSigs) <- refundAndOutcomeSigsF
dlcFundingInputs <- dlcInputsDAO.findByDLCId(dlcId)
(cetSignatures, refundSigsOpt) <- getCetAndRefundSigs(dlcId)
acceptInputs = dlcFundingInputs.filterNot(_.isInitiator)
} yield {
for {
offerDbState <- offerDbStateOpt
} yield {
//if the accept message is defined we must have refund sigs
dlcAcceptOpt.zip(refundSigsOpt).headOption match {
case Some((dlcAccept, refundSigDb)) =>
require(
refundSigsOpt.isDefined,
s"Cannot have accept in the database if we do not have refund signatures, dlcId=${dlcId.hex}")
val outcomeSigs = cetSignatures.map { dbSig =>
dbSig.sigPoint -> dbSig.accepterSig
}
val signaturesOpt = {
if (cetSignatures.isEmpty) {
//means we have pruned signatures from the database
//we have to return None
None
} else {
val sigs = CETSignatures(outcomeSigs)
Some(sigs)
}
}
val acceptPrevTxsDbF =
getAcceptPrevTxs(offerDbState.dlcDb, acceptInputs, txDAO)
val sigsOpt = if (outcomeSigs.isEmpty) None else Some(outcomeSigs)
acceptPrevTxsDbF.map { case acceptPrevTxs =>
offerDbState.toAcceptDb(
acceptDb = dlcAccept,
acceptFundingInputsDb = acceptInputs,
acceptPrevTxsDb = acceptPrevTxs,
cetSignaturesOpt = signaturesOpt,
refundSigDb = refundSigDb
)
}
case None =>
//just return the offerDbState if we don't have an accept
Future.successful(offerDbState)
}
}
}
(dlcDb,
contractData,
dlcOffer,
dlcAccept,
refundSigs.head,
contractInfo,
fundingInputs,
sigsOpt)
val resultF = nestedF.flatMap {
case Some(f) =>
f.map(Some(_))
case None => Future.successful(None)
}
resultF
}
private[wallet] def getAllDLCData(
contractId: ByteVector,
txDAO: TransactionDAO): Future[Option[DLCClosedDbState]] = {
val resultF = for {
dlcDbOpt <- dlcDAO.findByContractId(contractId)
closedDbStateOptNested = dlcDbOpt.map(d => getAllDLCData(d.dlcId, txDAO))
} yield {
closedDbStateOptNested match {
case Some(stateF) => stateF
case None => Future.successful(None)
}
}
resultF.flatten
}
private[wallet] def getAllDLCData(
dlcId: Sha256Digest,
txDAO: TransactionDAO): Future[Option[DLCClosedDbState]] = {
val sigDLCsF = dlcSigsDAO.findByDLCId(dlcId)
for {
setupStateOpt <- getDLCFundingData(dlcId, txDAO)
sigs <- sigDLCsF
} yield {
//check if we have pruned signatures
val sigsOpt = if (sigs.isEmpty) None else Some(sigs)
val closedState = setupStateOpt.flatMap {
case acceptState: AcceptDbState =>
val closedState =
DLCClosedDbState.fromSetupState(acceptState, sigsOpt)
Some(closedState)
case _: OfferedDbState =>
//cannot return a closed state because we haven't seen the accept message
None
}
closedState
}
}
/** Build a verifier from an accept message.
* Returns None if there is no DLC in the database associated with this accept message
*/
private[wallet] def verifierFromAccept(
accept: DLCAccept,
transactionDAO: TransactionDAO): Future[DLCSignatureVerifier] = {
txDAO: TransactionDAO): Future[Option[DLCSignatureVerifier]] = {
for {
dlcDbOpt <- dlcDAO.findByTempContractId(accept.tempContractId)
dlcDb = dlcDbOpt.get
(_, contractData, dlcOffer, fundingInputsDb, contractInfo) <-
getDLCOfferData(dlcDb.dlcId)
localFundingInputs = fundingInputsDb.filter(_.isInitiator)
prevTxs <-
transactionDAO.findByTxIdBEs(localFundingInputs.map(_.outPoint.txIdBE))
offerDataOpt <- {
dlcDbOpt.map(d => getDLCOfferData(d.dlcId, txDAO)) match {
case Some(value) => value
case None => Future.successful(None)
}
}
} yield {
val offerFundingInputs =
DLCTxUtil.matchPrevTxsWithInputs(localFundingInputs, prevTxs)
val offer = dlcOffer.toDLCOffer(contractInfo,
offerFundingInputs,
dlcDb,
contractData)
val builder = DLCTxBuilder(offer, accept.withoutSigs)
DLCSignatureVerifier(builder, dlcDb.isInitiator)
offerDataOpt match {
case Some(offeredDataState) =>
val offer = offeredDataState.offer
val builder = DLCTxBuilder(offer, accept.withoutSigs)
val verifier =
DLCSignatureVerifier(builder, offeredDataState.dlcDb.isInitiator)
Some(verifier)
case None => None
}
}
}
private[wallet] def verifierFromDb(
contractId: ByteVector,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO): Future[DLCSignatureVerifier] = {
getDLCFundingData(contractId).flatMap {
case (dlcDb,
contractData,
dlcOffer,
dlcAccept,
fundingInputsDb,
contractInfo) =>
verifierFromDbData(
dlcDb = dlcDb,
contractData = contractData,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputsDb,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO
)
transactionDAO: TransactionDAO): Future[Option[DLCSignatureVerifier]] = {
dlcDAO.findByContractId(contractId).flatMap { case dlcDbOpt =>
val optF = dlcDbOpt.map(verifierFromDbData(_, transactionDAO))
optF match {
case Some(sigVerifierFOpt) => sigVerifierFOpt
case None => Future.successful(None)
}
}
}
def getOfferAndAcceptWithoutSigs(
dlcId: Sha256Digest,
txDAO: TransactionDAO): Future[Option[AcceptDbState]] = {
val dataF: Future[Option[DLCSetupDbState]] = getDLCFundingData(dlcId, txDAO)
dataF.map {
case Some(setupDbState) =>
setupDbState match {
case a: AcceptDbState => Some(a)
case _: OfferedDbState => None
}
case None => None
}
}
private[wallet] def builderFromDbData(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
dlcOffer: DLCOfferDb,
dlcAccept: DLCAcceptDb,
fundingInputsDb: Vector[DLCFundingInputDb],
contractInfo: ContractInfo,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO): Future[DLCTxBuilder] = {
val (localDbFundingInputs, remoteDbFundingInputs) = if (dlcDb.isInitiator) {
(fundingInputsDb.filter(_.isInitiator),
fundingInputsDb.filterNot(_.isInitiator))
} else {
(fundingInputsDb.filterNot(_.isInitiator),
fundingInputsDb.filter(_.isInitiator))
}
transactionDAO: TransactionDAO): Future[Option[DLCTxBuilder]] = {
for {
localPrevTxs <- transactionDAO.findByTxIdBEs(
localDbFundingInputs.map(_.outPoint.txIdBE))
remotePrevTxs <-
remoteTxDAO.findByTxIdBEs(remoteDbFundingInputs.map(_.outPoint.txIdBE))
setupStateOpt <- getOfferAndAcceptWithoutSigs(dlcDb.dlcId, transactionDAO)
} yield {
val localFundingInputs = DLCTxUtil.matchPrevTxsWithInputs(
inputs = localDbFundingInputs,
prevTxs = localPrevTxs)
val remoteFundingInputs = DLCTxUtil.matchPrevTxsWithInputs(
inputs = remoteDbFundingInputs,
prevTxs = remotePrevTxs)
val (offerFundingInputs, acceptFundingInputs) = if (dlcDb.isInitiator) {
(localFundingInputs, remoteFundingInputs)
} else {
(remoteFundingInputs, localFundingInputs)
setupStateOpt.map { acceptDbState =>
val txBuilder = DLCTxBuilder(offer = acceptDbState.offer,
accept = acceptDbState.acceptWithoutSigs)
txBuilder
}
val offer = dlcOffer.toDLCOffer(contractInfo,
offerFundingInputs,
dlcDb.fundOutputSerialId,
dlcDb.feeRate,
contractDataDb.dlcTimeouts)
val accept = dlcAccept.toDLCAcceptWithoutSigs(dlcDb.tempContractId,
acceptFundingInputs)
DLCTxBuilder(offer, accept)
}
}
private[wallet] def verifierFromDbData(
dlcDb: DLCDb,
contractData: DLCContractDataDb,
dlcOffer: DLCOfferDb,
dlcAccept: DLCAcceptDb,
fundingInputsDb: Vector[DLCFundingInputDb],
contractInfo: ContractInfo,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO): Future[DLCSignatureVerifier] = {
val builderF =
builderFromDbData(
dlcDb = dlcDb,
contractDataDb = contractData,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputsDb,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO
)
transactionDAO: TransactionDAO): Future[Option[DLCSignatureVerifier]] = {
val builderOptF =
builderFromDbData(dlcDb = dlcDb, transactionDAO = transactionDAO)
builderF.map(DLCSignatureVerifier(_, dlcDb.isInitiator))
builderOptF.map {
case Some(builder) =>
val verifier = DLCSignatureVerifier(builder, dlcDb.isInitiator)
Some(verifier)
case None => None
}
}
private[wallet] def signerFromDb(
dlcId: Sha256Digest,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO,
fundingUtxoScriptSigParams: Vector[ScriptSignatureParams[InputInfo]],
keyManager: BIP39KeyManager): Future[DLCTxSigner] = {
keyManager: BIP39KeyManager): Future[Option[DLCTxSigner]] = {
for {
(dlcDb,
contractData,
dlcOffer,
dlcAccept,
fundingInputsDb,
contractInfo) <-
getDLCFundingData(dlcId)
signer <- signerFromDb(
dlcDb = dlcDb,
contractDataDb = contractData,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputsDb,
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
keyManager = keyManager
)
} yield signer
dlcDbOpt <- dlcDAO.findByDLCId(dlcId)
signerOpt <- {
dlcDbOpt match {
case Some(dlcDb) =>
val signerOptF = signerFromDb(
dlcDb = dlcDb,
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
transactionDAO = transactionDAO,
keyManager = keyManager
)
signerOptF
case None =>
Future.successful(None)
}
}
} yield signerOpt
}
private[wallet] def signerFromDb(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
dlcOffer: DLCOfferDb,
dlcAccept: DLCAcceptDb,
fundingInputsDb: Vector[DLCFundingInputDb],
fundingUtxoScriptSigParams: Vector[ScriptSignatureParams[InputInfo]],
contractInfo: ContractInfo,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO,
keyManager: BIP39KeyManager): Future[DLCTxSigner] = {
keyManager: BIP39KeyManager): Future[Option[DLCTxSigner]] = {
for {
builder <- builderFromDbData(
builderOpt <- builderFromDbData(
dlcDb = dlcDb,
contractDataDb = contractDataDb,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputsDb,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO
transactionDAO = transactionDAO
)
} yield {
val (fundingKey, payoutAddress) = if (dlcDb.isInitiator) {
(dlcOffer.fundingKey, dlcOffer.payoutAddress)
} else {
(dlcAccept.fundingKey, dlcAccept.payoutAddress)
builderOpt match {
case Some(builder) =>
val dlcOffer = builder.offer
val dlcAccept = builder.accept
val (fundingKey, payoutAddress) = if (dlcDb.isInitiator) {
(dlcOffer.pubKeys.fundingKey, dlcOffer.pubKeys.payoutAddress)
} else {
(dlcAccept.pubKeys.fundingKey, dlcAccept.pubKeys.payoutAddress)
}
val bip32Path = BIP32Path(
dlcDb.account.path ++ Vector(
BIP32Node(dlcDb.changeIndex.index, hardened = false),
BIP32Node(dlcDb.keyIndex, hardened = false)))
val privKeyPath = HDPath.fromString(bip32Path.toString)
val fundingPrivKey = keyManager.toSign(privKeyPath)
require(fundingKey == fundingPrivKey.publicKey)
val signer = DLCTxSigner(builder = builder,
isInitiator = dlcDb.isInitiator,
fundingKey = fundingPrivKey,
finalAddress = payoutAddress,
fundingUtxos = fundingUtxoScriptSigParams)
Some(signer)
case None => None
}
val bip32Path = BIP32Path(
dlcDb.account.path ++ Vector(
BIP32Node(dlcDb.changeIndex.index, hardened = false),
BIP32Node(dlcDb.keyIndex, hardened = false)))
val privKeyPath = HDPath.fromString(bip32Path.toString)
val fundingPrivKey = keyManager.toSign(privKeyPath)
require(fundingKey == fundingPrivKey.publicKey)
DLCTxSigner(builder = builder,
isInitiator = dlcDb.isInitiator,
fundingKey = fundingPrivKey,
finalAddress = payoutAddress,
fundingUtxos = fundingUtxoScriptSigParams)
}
}
private[wallet] def executorFromDb(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
dlcOffer: DLCOfferDb,
dlcAccept: DLCAcceptDb,
fundingInputsDb: Vector[DLCFundingInputDb],
fundingUtxoScriptSigParams: Vector[ScriptSignatureParams[InputInfo]],
contractInfo: ContractInfo,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO,
keyManager: BIP39KeyManager): Future[DLCExecutor] = {
signerFromDb(
keyManager: BIP39KeyManager): Future[Option[DLCExecutor]] = {
val signerOptF = signerFromDb(
dlcDb = dlcDb,
contractDataDb = contractDataDb,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputsDb,
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
keyManager = keyManager
).map(DLCExecutor.apply)
)
signerOptF.map {
case Some(dlcTxSigner) =>
val e = DLCExecutor(dlcTxSigner)
Some(e)
case None => None
}
}
private[wallet] def executorFromDb(
dlcId: Sha256Digest,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO,
fundingUtxoScriptSigParams: Vector[ScriptSignatureParams[InputInfo]],
keyManager: BIP39KeyManager): Future[DLCExecutor] = {
signerFromDb(dlcId = dlcId,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
keyManager = keyManager).map(DLCExecutor.apply)
keyManager: BIP39KeyManager): Future[Option[DLCExecutor]] = {
val signerOptF = signerFromDb(dlcId = dlcId,
transactionDAO = transactionDAO,
fundingUtxoScriptSigParams =
fundingUtxoScriptSigParams,
keyManager = keyManager)
signerOptF.map {
case Some(dlcTxSigner) =>
val e = DLCExecutor(dlcTxSigner)
Some(e)
case None => None
}
}
/** Builds an [[DLCExecutor]] and [[SetupDLC]] for a given contract id
@ -581,120 +549,102 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
*/
private[wallet] def executorAndSetupFromDb(
contractId: ByteVector,
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO,
txDAO: TransactionDAO,
fundingUtxoScriptSigParams: Vector[ScriptSignatureParams[InputInfo]],
keyManager: BIP39KeyManager): Future[Option[DLCExecutorWithSetup]] = {
getAllDLCData(contractId).flatMap {
case (dlcDb,
contractData,
dlcOffer,
dlcAccept,
refundSigs,
contractInfo,
fundingInputsDb,
outcomeSigsDbsOpt) =>
outcomeSigsDbsOpt match {
case Some(outcomeSigsDbs) =>
getAllDLCData(contractId, txDAO).flatMap {
case Some(closedDbState) =>
closedDbState match {
case withCETSigs: ClosedDbStateWithCETSigs =>
executorAndSetupFromDb(
dlcDb = dlcDb,
contractDataDb = contractData,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
refundSigsDb = refundSigs,
contractInfo = contractInfo,
fundingInputs = fundingInputsDb,
outcomeSigsDbs = outcomeSigsDbs,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
dlcDb = withCETSigs.dlcDb,
refundSigsDb = withCETSigs.refundSigsDb,
fundingInputs = withCETSigs.allFundingInputs,
outcomeSigsDbs = withCETSigs.cetSigs,
transactionDAO = txDAO,
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
keyManager = keyManager
).map(Some(_))
case None =>
)
case _: ClosedDbStateNoCETSigs =>
//means we cannot re-create messages because
//we don't have the cets in the database anymore
Future.successful(None)
}
case None => Future.successful(None)
}
}
private[wallet] def executorAndSetupFromDb(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
dlcOffer: DLCOfferDb,
dlcAccept: DLCAcceptDb,
refundSigsDb: DLCRefundSigsDb,
contractInfo: ContractInfo,
fundingInputs: Vector[DLCFundingInputDb],
outcomeSigsDbs: Vector[DLCCETSignaturesDb],
transactionDAO: TransactionDAO,
remoteTxDAO: DLCRemoteTxDAO,
fundingUtxoScriptSigParams: Vector[ScriptSignatureParams[InputInfo]],
keyManager: BIP39KeyManager): Future[DLCExecutorWithSetup] = {
keyManager: BIP39KeyManager): Future[Option[DLCExecutorWithSetup]] = {
val dlcExecutorF = executorFromDb(
val dlcExecutorOptF = executorFromDb(
dlcDb = dlcDb,
contractDataDb = contractDataDb,
dlcOffer = dlcOffer,
dlcAccept = dlcAccept,
fundingInputsDb = fundingInputs,
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
contractInfo = contractInfo,
transactionDAO = transactionDAO,
remoteTxDAO = remoteTxDAO,
keyManager = keyManager
)
dlcExecutorF.flatMap { executor =>
// Filter for only counterparty's outcome sigs
val outcomeSigs = if (dlcDb.isInitiator) {
outcomeSigsDbs
.map { dbSig =>
dbSig.sigPoint -> dbSig.accepterSig
}
} else {
outcomeSigsDbs
.map { dbSig =>
dbSig.sigPoint -> dbSig.initiatorSig.get
}
}
val refundSig = if (dlcDb.isInitiator) {
refundSigsDb.accepterSig
} else refundSigsDb.initiatorSig.get
//sometimes we do not have cet signatures, for instance
//if we have settled a DLC, we prune the cet signatures
//from the database
val cetSigs = CETSignatures(outcomeSigs)
val setupF = if (dlcDb.isInitiator) {
// Note that the funding tx in this setup is not signed
executor.setupDLCOffer(cetSigs, refundSig)
} else {
val fundingSigs =
fundingInputs
.filter(_.isInitiator)
.map { input =>
input.witnessScriptOpt match {
case Some(witnessScript) =>
witnessScript match {
case EmptyScriptWitness =>
throw new RuntimeException(
"Script witness cannot be empty")
case witness: ScriptWitnessV0 => (input.outPoint, witness)
}
case None => throw new RuntimeException("")
}
dlcExecutorOptF.flatMap {
case Some(executor) =>
// Filter for only counterparty's outcome sigs
val outcomeSigs = if (dlcDb.isInitiator) {
outcomeSigsDbs
.map { dbSig =>
dbSig.sigPoint -> dbSig.accepterSig
}
executor.setupDLCAccept(cetSigs,
refundSig,
FundingSignatures(fundingSigs),
None)
}
} else {
outcomeSigsDbs
.map { dbSig =>
dbSig.sigPoint -> dbSig.initiatorSig.get
}
}
val refundSig = if (dlcDb.isInitiator) {
refundSigsDb.accepterSig
} else refundSigsDb.initiatorSig.get
Future.fromTry(setupF.map(DLCExecutorWithSetup(executor, _)))
//sometimes we do not have cet signatures, for instance
//if we have settled a DLC, we prune the cet signatures
//from the database
val cetSigs = CETSignatures(outcomeSigs)
val setupF: Try[SetupDLC] = if (dlcDb.isInitiator) {
// Note that the funding tx in this setup is not signed
executor.setupDLCOffer(cetSigs, refundSig)
} else {
val fundingSigs =
fundingInputs
.filter(_.isInitiator)
.map { input =>
input.witnessScriptOpt match {
case Some(witnessScript) =>
witnessScript match {
case EmptyScriptWitness =>
throw new RuntimeException(
"Script witness cannot be empty")
case witness: ScriptWitnessV0 => (input.outPoint, witness)
}
case None => throw new RuntimeException("")
}
}
executor.setupDLCAccept(cetSigs,
refundSig,
FundingSignatures(fundingSigs),
None)
}
val x: Try[DLCExecutorWithSetup] =
setupF.map(DLCExecutorWithSetup(executor, _))
Future
.fromTry(x)
.map(Some(_))
case None =>
Future.successful(None)
}
}
@ -715,4 +665,74 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
val action = getCetAndRefundSigsAction(dlcId)
safeDatabase.run(action)
}
/** Retrieves the transaction(s) used to fund the offer message */
private def getOfferPrevTxs(
dlcDb: DLCDb,
fundingInputs: Vector[DLCFundingInputDb],
txDAO: TransactionDAO): Future[Vector[TransactionDb]] = {
val txIds = fundingInputs.map(_.outPoint.txIdBE)
if (dlcDb.isInitiator) {
//query txDAO as we created the offer
txDAO.findByTxIdBEs(txIds)
} else {
//query remote tx dao as we didn't create the offers
remoteTxDAO.findByTxIdBEs(txIds)
}
}
/** Retreives the transaction(s) used to fund the accept message */
private def getAcceptPrevTxs(
dlcDb: DLCDb,
fundingInputs: Vector[DLCFundingInputDb],
txDAO: TransactionDAO): Future[Vector[TransactionDb]] = {
val txIds = fundingInputs.map(_.outPoint.txIdBE)
if (dlcDb.isInitiator) {
//if we are the initiator we need to query the remote tx dao
remoteTxDAO.findByTxIdBEs(txIds)
} else {
//else they are in our local tx dao
txDAO.findByTxIdBEs(txIds)
}
}
}
object DLCDataManagement {
def fromDbAppConfig()(implicit
dbAppConfig: DLCAppConfig,
ec: ExecutionContext): DLCDataManagement = {
val announcementDAO: OracleAnnouncementDataDAO =
OracleAnnouncementDataDAO()
val oracleNonceDAO: OracleNonceDAO = OracleNonceDAO()
val dlcAnnouncementDAO: DLCAnnouncementDAO =
DLCAnnouncementDAO()
val dlcOfferDAO: DLCOfferDAO = DLCOfferDAO()
val dlcAcceptDAO: DLCAcceptDAO = DLCAcceptDAO()
val dlcDAO: DLCDAO = DLCDAO()
val contractDataDAO: DLCContractDataDAO =
DLCContractDataDAO()
val dlcInputsDAO: DLCFundingInputDAO = DLCFundingInputDAO()
val dlcSigsDAO: DLCCETSignaturesDAO = DLCCETSignaturesDAO()
val dlcRefundSigDAO: DLCRefundSigsDAO = DLCRefundSigsDAO()
val dlcRemoteTxDAO: DLCRemoteTxDAO = DLCRemoteTxDAO()
val dlcWalletDAOs = DLCWalletDAOs(
dlcDAO,
contractDataDAO,
dlcAnnouncementDAO,
dlcInputsDAO,
dlcOfferDAO,
dlcAcceptDAO,
dlcSigsDAO,
dlcRefundSigDAO,
oracleNonceDAO,
announcementDAO,
dlcRemoteTxDAO
)
DLCDataManagement(dlcWalletDAOs)
}
}

View file

@ -8,11 +8,18 @@ import org.bitcoins.core.protocol.dlc.models._
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.core.wallet.utxo.AddressTag
import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.db.SafeDatabase
import org.bitcoins.dlc.wallet.DLCWallet
import org.bitcoins.dlc.wallet.models.{
AcceptDbState,
DLCCETSignaturesDb,
DLCFundingInputDb,
OfferedDbState
}
import org.bitcoins.wallet.internal.TransactionProcessing
import scala.concurrent._
@ -31,8 +38,9 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
/** Calculates the new state of the DLCDb based on the closing transaction,
* will delete old CET sigs that are no longer needed after execution
* @return a DLCDb if we can calculate the state, else None if we cannot calculate the state
*/
def calculateAndSetState(dlcDb: DLCDb): Future[DLCDb] = {
def calculateAndSetState(dlcDb: DLCDb): Future[Option[DLCDb]] = {
(dlcDb.contractIdOpt, dlcDb.closingTxIdOpt) match {
case (Some(id), Some(txId)) =>
val fundingInputsF = dlcInputsDAO.findByDLCId(dlcDb.dlcId)
@ -44,7 +52,6 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
executorWithSetupOpt <- dlcDataManagement.executorAndSetupFromDb(
id,
transactionDAO,
remoteTxDAO,
scriptSigParams,
keyManager)
updatedDlcDb <- executorWithSetupOpt match {
@ -55,11 +62,13 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
case None =>
//this means we have already deleted the cet sigs
//just return the dlcdb given to us
Future.successful(dlcDb)
Future.successful(Some(dlcDb))
}
} yield updatedDlcDb
} yield {
updatedDlcDb
}
case (None, None) | (None, Some(_)) | (Some(_), None) =>
Future.successful(dlcDb)
Future.successful(Some(dlcDb))
}
}
@ -69,55 +78,80 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
private def calculateAndSetStateWithSetupDLC(
setup: SetupDLC,
dlcDb: DLCDb,
closingTxId: DoubleSha256DigestBE): Future[DLCDb] = {
val updatedF = if (closingTxId == setup.refundTx.txIdBE) {
Future.successful(dlcDb.copy(state = DLCState.Refunded))
} else if (dlcDb.state == DLCState.Claimed) {
Future.successful(dlcDb.copy(state = DLCState.Claimed))
} else {
val withState = dlcDb.updateState(DLCState.RemoteClaimed)
if (dlcDb.state != DLCState.RemoteClaimed) {
for {
// update so we can calculate correct DLCStatus
_ <- dlcDAO.update(withState)
withOutcome <- calculateAndSetOutcome(withState)
dlc <- findDLC(dlcDb.dlcId)
_ = dlcConfig.walletCallbacks.executeOnDLCStateChange(logger, dlc.get)
} yield withOutcome
} else Future.successful(withState)
closingTxId: DoubleSha256DigestBE): Future[Option[DLCDb]] = {
val updatedOptF: Future[Option[DLCDb]] = {
if (closingTxId == setup.refundTx.txIdBE) {
val updatedOpt = Some(dlcDb.copy(state = DLCState.Refunded))
Future.successful(updatedOpt)
} else if (dlcDb.state == DLCState.Claimed) {
val updatedOpt = Some(dlcDb.copy(state = DLCState.Claimed))
Future.successful(updatedOpt)
} else {
val withState = dlcDb.updateState(DLCState.RemoteClaimed)
if (dlcDb.state != DLCState.RemoteClaimed) {
for {
// update so we can calculate correct DLCStatus
_ <- dlcDAO.update(withState)
withOutcomeOpt <- calculateAndSetOutcome(withState)
dlc <- findDLC(dlcDb.dlcId)
_ = dlcConfig.walletCallbacks.executeOnDLCStateChange(logger,
dlc.get)
} yield withOutcomeOpt
} else Future.successful(Some(withState))
}
}
for {
updated <- updatedF
updatedOpt <- updatedOptF
_ <- {
updated.state match {
case DLCState.Claimed | DLCState.RemoteClaimed | DLCState.Refunded =>
val contractId = updated.contractIdOpt.get.toHex
logger.info(
s"Deleting unneeded DLC signatures for contract $contractId")
val resultFOpt: Option[Future[Unit]] = {
updatedOpt.map { updated =>
updated.state match {
case DLCState.Claimed | DLCState.RemoteClaimed |
DLCState.Refunded =>
val contractId = updated.contractIdOpt.get.toHex
logger.info(
s"Deleting unneeded DLC signatures for contract $contractId")
dlcSigsDAO.deleteByDLCId(updated.dlcId)
case DLCState.Offered | DLCState.Accepted | DLCState.Signed |
DLCState.Broadcasted | DLCState.Confirmed =>
FutureUtil.unit
dlcSigsDAO
.deleteByDLCId(updated.dlcId)
.map(_ => ())
case DLCState.Offered | DLCState.Accepted | DLCState.Signed |
DLCState.Broadcasted | DLCState.Confirmed =>
FutureUtil.unit
}
}
}
resultFOpt match {
case Some(resultF) => resultF
case None => Future.unit
}
}
} yield updated
} yield updatedOpt
}
/** Calculates the outcome used for execution
* based on the closing transaction
* based on the closing transaction. Returns None if we cannot calculate
* the outcome because we have pruned the cet signatures from the database
*/
def calculateAndSetOutcome(dlcDb: DLCDb): Future[DLCDb] = {
def calculateAndSetOutcome(dlcDb: DLCDb): Future[Option[DLCDb]] = {
if (dlcDb.state == DLCState.RemoteClaimed) {
val dlcId = dlcDb.dlcId
for {
(_, contractData, offerDb, acceptDb, fundingInputDbs, _) <-
dlcDataManagement.getDLCFundingData(dlcId)
txIds = fundingInputDbs.map(_.outPoint.txIdBE)
remotePrevTxs <- remoteTxDAO.findByTxIdBEs(txIds)
localPrevTxs <- transactionDAO.findByTxIdBEs(txIds)
setupStateOpt <- dlcDataManagement.getDLCFundingData(dlcId,
txDAO =
transactionDAO)
acceptDbState = {
setupStateOpt.get match {
case accept: AcceptDbState => accept
case offered: OfferedDbState =>
sys.error(
s"Cannot calculate and set outcome of dlc that is only offered, id=${offered.dlcDb.dlcId.hex}")
}
}
offer = acceptDbState.offer
acceptOpt = acceptDbState.acceptOpt
(sigDbs, refundSigsDb) <- dlcDataManagement.getCetAndRefundSigs(dlcId)
(announcements, announcementData, nonceDbs) <- dlcDataManagement
.getDLCAnnouncementDbs(dlcDb.dlcId)
@ -127,66 +161,41 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
.read(dlcDb.closingTxIdOpt.get)
.map(_.get.transaction.asInstanceOf[WitnessTransaction])
(sig, outcome) = {
val prevTxs = (remotePrevTxs ++ localPrevTxs).map(_.transaction)
val txs = prevTxs.groupBy(_.txIdBE)
sigAndOutcomeOpt = {
val isInit = dlcDb.isInitiator
val fundingInputs = fundingInputDbs.map(input =>
input.toFundingInput(txs(input.outPoint.txIdBE).head))
val fundingInputDbs = acceptDbState.allFundingInputs
val offerRefundSigOpt = refundSigsDb.flatMap(_.initiatorSig)
val acceptRefundSigOpt = refundSigsDb.map(_.accepterSig)
val contractInfo =
dlcDataManagement.getContractInfo(contractData,
announcements,
announcementData,
nonceDbs)
val offer =
offerDb.toDLCOffer(contractInfo, fundingInputs, dlcDb, contractData)
val accept =
acceptDb.toDLCAccept(
dlcDb.tempContractId,
fundingInputs,
sigDbs.map(dbSig => (dbSig.sigPoint, dbSig.accepterSig)),
acceptRefundSigOpt.head)
val sign: DLCSign = {
val cetSigs: CETSignatures =
CETSignatures(
sigDbs.map(dbSig => (dbSig.sigPoint, dbSig.initiatorSig.get)))
val contractId = dlcDb.contractIdOpt.get
val fundingSigs =
fundingInputDbs
.filter(_.isInitiator)
.map { input =>
input.witnessScriptOpt match {
case Some(witnessScript) =>
witnessScript match {
case EmptyScriptWitness =>
throw new RuntimeException(
"Script witness cannot be empty")
case witness: ScriptWitnessV0 =>
(input.outPoint, witness)
}
case None =>
throw new RuntimeException("Must be segwit")
}
}
DLCSign(cetSigs,
offerRefundSigOpt.get,
FundingSignatures(fundingSigs),
contractId)
val signOpt: Option[DLCSign] = offerRefundSigOpt.map { refundSig =>
buildSignMessage(
dlcDb = dlcDb,
sigDbs = sigDbs,
offerRefundSig = refundSig,
fundingInputDbs = fundingInputDbs
)
}
for {
accept <- acceptOpt
sign <- signOpt
sigsAndOutcomeOpt <- DLCStatus.calculateOutcomeAndSig(isInit,
offer,
accept,
sign,
cet)
} yield {
sigsAndOutcomeOpt
}
DLCStatus.calculateOutcomeAndSig(isInit, offer, accept, sign, cet).get
}
(_, oracleInfos) = getOutcomeDbInfo(outcome)
sigOpt = sigAndOutcomeOpt.map(_._1)
outcomeOpt = sigAndOutcomeOpt.map(_._2)
oracleInfosOpt = {
outcomeOpt
.map { case outcome =>
getOutcomeDbInfo(outcome)
}
.map(_._2)
}
noncesByAnnouncement = nonceDbs
.groupBy(_.announcementId)
@ -196,17 +205,23 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
announcementData,
nonceDbs)
usedIds = announcementsWithId
.filter(t => oracleInfos.exists(_.announcement == t._1))
.map(_._2)
usedIds = {
oracleInfosOpt match {
case Some(oracleInfos) =>
announcementsWithId
.filter(t => oracleInfos.exists(_.announcement == t._1))
.map(_._2)
case None =>
Vector.empty
}
}
updatedAnnouncements = announcements
.filter(t => usedIds.contains(t.announcementId))
.map(_.copy(used = true))
updatedNonces = {
usedIds.flatMap { id =>
outcome match {
outcomeOpt.map {
case enum: EnumOracleOutcome =>
val nonces = noncesByAnnouncement(id).sortBy(_.index)
nonces.map(_.copy(outcomeOpt = Some(enum.outcome.outcome)))
@ -222,11 +237,10 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
}
}
}
}
}.flatten
}
updatedDlcDbA = dlcDAO.updateAction(
dlcDb.copy(aggregateSignatureOpt = Some(sig)))
dlcDb.copy(aggregateSignatureOpt = sigOpt))
updateNonceA = oracleNonceDAO.updateAllAction(updatedNonces)
updateAnnouncementA = dlcAnnouncementDAO.updateAllAction(
updatedAnnouncements)
@ -235,10 +249,12 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
.transactionally
_ <- safeDatabase.run(actions)
} yield {
dlcDb.copy(aggregateSignatureOpt = Some(sig))
//err, this probably needs to be contingent on if sigOpt is defined?
val updatedSig = dlcDb.copy(aggregateSignatureOpt = sigOpt)
Some(updatedSig)
}
} else {
Future.successful(dlcDb)
Future.successful(Some(dlcDb))
}
}
@ -256,7 +272,7 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
_ <-
if (dlcDbs.nonEmpty) {
logger.info(
s"Processing tx ${tx.txIdBE.hex} for ${dlcDbs.size} DLC(s)")
s"Processing received utxos in tx ${tx.txIdBE.hex} for ${dlcDbs.size} DLC(s)")
insertTransaction(tx, blockHashOpt)
} else FutureUtil.unit
@ -281,7 +297,9 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
updatedDlcDbs.map(u =>
dlcConfig.walletCallbacks.executeOnDLCStateChange(logger, u.get))
}
} yield res
} yield {
res
}
}
}
@ -300,15 +318,17 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
_ <-
if (dlcDbs.nonEmpty) {
logger.info(
s"Processing tx ${transaction.txIdBE.hex} for ${dlcDbs.size} DLC(s)")
s"Processing spent utxos in tx ${transaction.txIdBE.hex} for ${dlcDbs.size} DLC(s)")
insertTransaction(transaction, blockHashOpt)
} else FutureUtil.unit
withTx = dlcDbs.map(_.updateClosingTxId(transaction.txIdBE))
updatedFs = withTx.map(calculateAndSetState)
updated <- Future.sequence(updatedFs)
_ <- dlcDAO.updateAll(updated)
} yield res
_ <- dlcDAO.updateAll(updated.flatten)
} yield {
res
}
}
}
@ -322,4 +342,41 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
(numeric.outcomes, numeric.oracles)
}
}
private def buildSignMessage(
dlcDb: DLCDb,
sigDbs: Vector[DLCCETSignaturesDb],
offerRefundSig: PartialSignature,
fundingInputDbs: Vector[DLCFundingInputDb]): DLCSign = {
{
//if we don't have an acceptOpt because we don't have CET sigs
//how are we getting them here?
val cetSigs: CETSignatures =
CETSignatures(
sigDbs.map(dbSig => (dbSig.sigPoint, dbSig.initiatorSig.get)))
val contractId = dlcDb.contractIdOpt.get
val fundingSigs =
fundingInputDbs
.filter(_.isInitiator)
.map { input =>
input.witnessScriptOpt match {
case Some(witnessScript) =>
witnessScript match {
case EmptyScriptWitness =>
throw new RuntimeException("Script witness cannot be empty")
case witness: ScriptWitnessV0 =>
(input.outPoint, witness)
}
case None =>
throw new RuntimeException("Must be segwit")
}
}
DLCSign(cetSigs,
offerRefundSig,
FundingSignatures(fundingSigs),
contractId)
}
}
}

View file

@ -45,12 +45,12 @@ case class DLCAcceptDAO()(implicit
dlcs: Vector[DLCAcceptDb]): Query[DLCAcceptTable, DLCAcceptDb, Seq] =
findByPrimaryKeys(dlcs.map(_.dlcId))
override def findByDLCIdAction(dlcId: Sha256Digest): DBIOAction[
Option[DLCAcceptDb],
override def findByDLCIdsAction(dlcIds: Vector[Sha256Digest]): DBIOAction[
Vector[DLCAcceptDb],
profile.api.NoStream,
profile.api.Effect.Read] = {
val q = table.filter(_.dlcId === dlcId)
q.result.map(_.headOption)
val q = table.filter(_.dlcId.inSet(dlcIds))
q.result.map(_.toVector)
}
override def deleteByDLCIdAction(dlcId: Sha256Digest): DBIOAction[

View file

@ -49,12 +49,12 @@ case class DLCContractDataDAO()(implicit
Seq] =
findByPrimaryKeys(dlcs.map(_.dlcId))
override def findByDLCIdAction(dlcId: Sha256Digest): DBIOAction[
Option[DLCContractDataDb],
override def findByDLCIdsAction(dlcIds: Vector[Sha256Digest]): DBIOAction[
Vector[DLCContractDataDb],
profile.api.NoStream,
profile.api.Effect.Read] = {
val q = table.filter(_.dlcId === dlcId)
q.result.map(_.headOption)
val q = table.filter(_.dlcId.inSet(dlcIds))
q.result.map(_.toVector)
}
override def deleteByDLCIdAction(dlcId: Sha256Digest): DBIOAction[

View file

@ -4,6 +4,7 @@ import org.bitcoins.core.api.dlc.wallet.db.DLCDb
import org.bitcoins.core.hd.{HDAccount, HDChainType}
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.dlc.models.DLCState
import org.bitcoins.core.protocol.tlv.DLCSerializationVersion
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.crypto._
@ -43,12 +44,12 @@ case class DLCDAO()(implicit
override def findAll(dlcs: Vector[DLCDb]): Query[DLCTable, DLCDb, Seq] =
findByPrimaryKeys(dlcs.map(_.dlcId))
override def findByDLCIdAction(dlcId: Sha256Digest): DBIOAction[
Option[DLCDb],
override def findByDLCIdsAction(dlcIds: Vector[Sha256Digest]): DBIOAction[
Vector[DLCDb],
profile.api.NoStream,
profile.api.Effect.Read] = {
val q = table.filter(_.dlcId === dlcId)
q.result.map(_.headOption)
val q = table.filter(_.dlcId.inSet(dlcIds))
q.result.map(_.toVector)
}
override def deleteByDLCIdAction(dlcId: Sha256Digest): DBIOAction[
@ -158,6 +159,9 @@ case class DLCDAO()(implicit
def aggregateSignatureOpt: Rep[Option[SchnorrDigitalSignature]] = column(
"aggregate_signature")
def serializationVersion: Rep[DLCSerializationVersion] = column(
"serialization_version")
override def * : ProvenShape[DLCDb] =
(dlcId,
tempContractId,
@ -174,6 +178,7 @@ case class DLCDAO()(implicit
fundingOutPointOpt,
fundingTxIdOpt,
closingTxIdOpt,
aggregateSignatureOpt).<>(DLCDb.tupled, DLCDb.unapply)
aggregateSignatureOpt,
serializationVersion).<>(DLCDb.tupled, DLCDb.unapply)
}
}

View file

@ -0,0 +1,236 @@
package org.bitcoins.dlc.wallet.models
import org.bitcoins.core.api.dlc.wallet.db.DLCDb
import org.bitcoins.core.api.wallet.db.TransactionDb
import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
DLCAccept,
DLCAcceptWithoutSigs,
DLCOffer
}
import org.bitcoins.core.protocol.dlc.models.DLCState.{
ClosedState,
InProgressState
}
import org.bitcoins.core.protocol.dlc.models.{
CETSignatures,
ContractInfo,
DLCFundingInput,
DLCState
}
import org.bitcoins.dlc.wallet.util.DLCTxUtil
/** Super trait to represent the state of a DLC in the database */
sealed trait DLCDbState {
def dlcDb: DLCDb
def contractDataDb: DLCContractDataDb
def contractInfo: ContractInfo
def offerFundingInputsDb: Vector[DLCFundingInputDb]
def offerDb: DLCOfferDb
def offerPrevTxs: Vector[TransactionDb]
def offerFundingInputs: Vector[DLCFundingInput] = {
DLCTxUtil.matchPrevTxsWithInputs(offerFundingInputsDb, offerPrevTxs)
}
def offer: DLCOffer = {
offerDb.toDLCOffer(contractInfo = contractInfo,
fundingInputs = offerFundingInputs,
dlcDb = dlcDb,
contractDataDb = contractDataDb)
}
def state: DLCState
}
/** Represents a DLC in the database that
* has not had its funding transaction published.
* This means we are still setting up the DLC
*/
sealed trait DLCSetupDbState extends DLCDbState {
override def state: InProgressState
}
/** Represents a DLC in the database that has
* been fully setup and is in progress
*/
sealed trait DLCClosedDbState extends DLCDbState
case class OfferedDbState(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
contractInfo: ContractInfo,
offerDb: DLCOfferDb,
offerFundingInputsDb: Vector[DLCFundingInputDb],
offerPrevTxs: Vector[TransactionDb])
extends DLCSetupDbState {
//require(dlcDb.state == DLCState.Offered,
// s"OfferedDbState requires state offered, got=${dlcDb.state}")
override val state: DLCState.Offered.type = DLCState.Offered
/** Converts a [[OfferedDbState]] to an [[AcceptDbState]]
* @param acceptDb
* @param acceptFundingInputsDb
* @param acceptPrevTxsDb
* @param cetSignaturesOpt the cet signatures, if we haven't pruned them from the database
*/
def toAcceptDb(
acceptDb: DLCAcceptDb,
acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxsDb: Vector[TransactionDb],
cetSignaturesOpt: Option[CETSignatures],
refundSigDb: DLCRefundSigsDb): AcceptDbState = {
AcceptDbState(
dlcDb = dlcDb,
contractDataDb = contractDataDb,
contractInfo = contractInfo,
offerDb = offerDb,
acceptDb = acceptDb,
offerFundingInputsDb = offerFundingInputsDb,
offerPrevTxs = offerPrevTxs,
acceptFundingInputsDb = acceptFundingInputsDb,
acceptPrevTxs = acceptPrevTxsDb,
cetSignaturesOpt = cetSignaturesOpt,
refundSigDb = refundSigDb
)
}
}
case class AcceptDbState(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
contractInfo: ContractInfo,
offerDb: DLCOfferDb,
acceptDb: DLCAcceptDb,
offerFundingInputsDb: Vector[DLCFundingInputDb],
offerPrevTxs: Vector[TransactionDb],
acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxs: Vector[TransactionDb],
cetSignaturesOpt: Option[CETSignatures],
refundSigDb: DLCRefundSigsDb)
extends DLCSetupDbState {
//require(dlcDb.state == DLCState.Accepted,
// s"OfferedDbState requires state accepted, got=${dlcDb.state}")
override val state: DLCState.Accepted.type = DLCState.Accepted
val acceptFundingInputs: Vector[DLCFundingInput] = {
DLCTxUtil.matchPrevTxsWithInputs(acceptFundingInputsDb, acceptPrevTxs)
}
val acceptWithoutSigs: DLCAcceptWithoutSigs = {
acceptDb.toDLCAcceptWithoutSigs(
tempContractId = dlcDb.tempContractId,
fundingInputs = acceptFundingInputs
)
}
val allFundingInputs: Vector[DLCFundingInputDb] =
offerFundingInputsDb ++ acceptFundingInputsDb
val remotePrevTxs: Vector[TransactionDb] = {
if (dlcDb.isInitiator) acceptPrevTxs
else offerPrevTxs
}
val localPrevTxs: Vector[TransactionDb] = {
if (dlcDb.isInitiator) offerPrevTxs
else acceptPrevTxs
}
/** Reconstructs the [[DLCAccept]] message if we have [[CETSignatures]]
* in the database. If we don't have the signatures because we have pruned
* them we return None as we can't reconstruct the message
*/
def acceptOpt: Option[DLCAccept] = {
cetSignaturesOpt.map { cetSignatures =>
acceptDb.toDLCAccept(dlcDb.tempContractId,
acceptFundingInputs,
outcomeSigs = cetSignatures.outcomeSigs,
refundSig = refundSigDb.accepterSig)
}
}
}
case class ClosedDbStateWithCETSigs(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
contractInfo: ContractInfo,
offerDb: DLCOfferDb,
acceptDb: DLCAcceptDb,
offerFundingInputsDb: Vector[DLCFundingInputDb],
offerPrevTxs: Vector[TransactionDb],
acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxs: Vector[TransactionDb],
refundSigsDb: DLCRefundSigsDb,
cetSigs: Vector[DLCCETSignaturesDb]
) extends DLCClosedDbState {
//require(
// dlcDb.state.isInstanceOf[DLCState.ClosedState],
// s"ClosedDbStateWithCETSigs dlc state must be closed, got=${dlcDb.state}")
override val state: DLCState = dlcDb.state
val allFundingInputs: Vector[DLCFundingInputDb] =
offerFundingInputsDb ++ acceptFundingInputsDb
}
/** Sometimes we prune CET sigs from the database to save on disk space.
* We need to handle this different than [[ClosedDbStateWithCETSigs]]
*/
case class ClosedDbStateNoCETSigs(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
contractInfo: ContractInfo,
offerDb: DLCOfferDb,
acceptDb: DLCAcceptDb,
offerFundingInputsDb: Vector[DLCFundingInputDb],
offerPrevTxs: Vector[TransactionDb],
acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxs: Vector[TransactionDb],
refundSigsDb: DLCRefundSigsDb)
extends DLCClosedDbState {
//require(
// dlcDb.state.isInstanceOf[DLCState.ClosedState],
// s"ClosedDbStateWithCETSigs dlc state must be closed, got=${dlcDb.state}")
override val state: ClosedState =
dlcDb.state.asInstanceOf[DLCState.ClosedState]
}
object DLCClosedDbState {
def fromSetupState(
acceptState: AcceptDbState,
cetSigsOpt: Option[Vector[DLCCETSignaturesDb]]): DLCClosedDbState = {
cetSigsOpt match {
case Some(cetSigs) =>
ClosedDbStateWithCETSigs(
acceptState.dlcDb,
acceptState.contractDataDb,
acceptState.contractInfo,
acceptState.offerDb,
acceptState.acceptDb,
acceptState.offerFundingInputsDb,
acceptState.offerPrevTxs,
acceptState.acceptFundingInputsDb,
acceptState.acceptPrevTxs,
acceptState.refundSigDb,
cetSigs
)
case None =>
ClosedDbStateNoCETSigs(
acceptState.dlcDb,
acceptState.contractDataDb,
acceptState.contractInfo,
acceptState.offerDb,
acceptState.acceptDb,
acceptState.offerFundingInputsDb,
acceptState.offerPrevTxs,
acceptState.acceptFundingInputsDb,
acceptState.acceptPrevTxs,
acceptState.refundSigDb
)
}
}
}

View file

@ -12,6 +12,14 @@ trait DLCIdDaoUtil[T, PrimaryKeyType] { _: CRUD[T, PrimaryKeyType] =>
def findByDLCIdAction(dlcId: Sha256Digest): profile.api.DBIOAction[
Option[T],
profile.api.NoStream,
profile.api.Effect.Read] = {
findByDLCIdsAction(Vector(dlcId))
.map(_.headOption)
}
def findByDLCIdsAction(dlcId: Vector[Sha256Digest]): profile.api.DBIOAction[
Vector[T],
profile.api.NoStream,
profile.api.Effect.Read]
def findByDLCId(dlcId: Sha256Digest): Future[Option[T]] = {

View file

@ -44,12 +44,12 @@ case class DLCOfferDAO()(implicit
dlcs: Vector[DLCOfferDb]): Query[DLCOfferTable, DLCOfferDb, Seq] =
findByPrimaryKeys(dlcs.map(_.dlcId))
override def findByDLCIdAction(dlcId: Sha256Digest): DBIOAction[
Option[DLCOfferDb],
override def findByDLCIdsAction(dlcIds: Vector[Sha256Digest]): DBIOAction[
Vector[DLCOfferDb],
profile.api.NoStream,
profile.api.Effect.Read] = {
val q = table.filter(_.dlcId === dlcId)
q.result.map(_.headOption)
val q = table.filter(_.dlcId.inSet(dlcIds))
q.result.map(_.toVector)
}
override def deleteByDLCIdAction(dlcId: Sha256Digest): DBIOAction[

View file

@ -48,12 +48,12 @@ case class DLCRefundSigsDAO()(implicit
Seq] =
findByPrimaryKeys(dlcs.map(_.dlcId))
override def findByDLCIdAction(dlcId: Sha256Digest): DBIOAction[
Option[DLCRefundSigsDb],
override def findByDLCIdsAction(dlcIds: Vector[Sha256Digest]): DBIOAction[
Vector[DLCRefundSigsDb],
profile.api.NoStream,
profile.api.Effect.Read] = {
val q = table.filter(_.dlcId === dlcId)
q.result.map(_.headOption)
val q = table.filter(_.dlcId.inSet(dlcIds))
q.result.map(_.toVector)
}
override def deleteByDLCIdAction(dlcId: Sha256Digest): DBIOAction[

View file

@ -10,4 +10,5 @@ case class DLCWalletDAOs(
dlcSigsDAO: DLCCETSignaturesDAO,
dlcRefundSigDAO: DLCRefundSigsDAO,
oracleNonceDAO: OracleNonceDAO,
oracleAnnouncementDAO: OracleAnnouncementDataDAO)
oracleAnnouncementDAO: OracleAnnouncementDataDAO,
dlcRemoteTxDAO: DLCRemoteTxDAO)

View file

@ -135,7 +135,9 @@ case class DLCActionBuilder(dlcWalletDAOs: DLCWalletDAOs) {
contractData <- contractDataAction
offer <- dlcOfferAction
inputs <- fundingInputsAction
} yield (dlcDb, contractData, offer, inputs)
//only want offerer inputs
offerInputs = inputs.filter(_.isInitiator)
} yield (dlcDb, contractData, offer, offerInputs)
combined
}

View file

@ -226,7 +226,8 @@ object DLCWalletUtil extends Logging {
fundingOutPointOpt = None,
fundingTxIdOpt = None,
closingTxIdOpt = None,
aggregateSignatureOpt = None
aggregateSignatureOpt = None,
serializationVersion = DLCSerializationVersion.current
)
lazy val sampleContractDataDb: DLCContractDataDb = DLCContractDataDb(
@ -332,8 +333,8 @@ object DLCWalletUtil extends Logging {
else dlcA.processTransaction(tx, None)
}
_ <- dlcA.broadcastTransaction(tx)
dlcDb <- dlcA.dlcDAO.findByContractId(contractId)
_ <- verifyProperlySetTxIds(dlcA)
_ <- verifyProperlySetTxIds(dlcB)
} yield {