mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
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:
parent
98c5d816ac
commit
bd5bcfef3a
25 changed files with 1277 additions and 667 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "global_dlc_data"
|
||||
ADD COLUMN "serialization_version" TEXT NOT NULL DEFAULT 'ALPHA';
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "global_dlc_data"
|
||||
ADD COLUMN "serialization_version" VARCHAR(254) NOT NULL DEFAULT "ALPHA";
|
|
@ -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 {
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[
|
||||
|
|
|
@ -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[
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]] = {
|
||||
|
|
|
@ -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[
|
||||
|
|
|
@ -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[
|
||||
|
|
|
@ -10,4 +10,5 @@ case class DLCWalletDAOs(
|
|||
dlcSigsDAO: DLCCETSignaturesDAO,
|
||||
dlcRefundSigDAO: DLCRefundSigsDAO,
|
||||
oracleNonceDAO: OracleNonceDAO,
|
||||
oracleAnnouncementDAO: OracleAnnouncementDataDAO)
|
||||
oracleAnnouncementDAO: OracleAnnouncementDataDAO,
|
||||
dlcRemoteTxDAO: DLCRemoteTxDAO)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue