mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-26 01:29:20 +01:00
Rework findDLC() (#3214)
* Rework findDLC(), break things out into separate objects to make things more testable and correct * Address ben's code review * Add caching of oracle outcomes we know are valid and broadcast
This commit is contained in:
parent
549d840d02
commit
2269a052b1
6 changed files with 427 additions and 223 deletions
|
@ -6,49 +6,57 @@ sealed abstract class DLCState
|
|||
|
||||
object DLCState extends StringFactory[DLCState] {
|
||||
|
||||
sealed abstract class InProgressState extends DLCState
|
||||
|
||||
/** Means that someone has attempted to claim the DLC */
|
||||
sealed abstract class ClosedState extends DLCState
|
||||
|
||||
/** A state that requires an oracle outcome to be valid */
|
||||
sealed trait ClosedViaOracleOutcomeState extends ClosedState
|
||||
|
||||
/** The state where an offer has been created but no
|
||||
* accept message has yet been created/received.
|
||||
*/
|
||||
final case object Offered extends DLCState
|
||||
final case object Offered extends InProgressState
|
||||
|
||||
/** The state where an offer has been accepted but
|
||||
* no sign message has yet been created/received.
|
||||
*/
|
||||
final case object Accepted extends DLCState
|
||||
final case object Accepted extends InProgressState
|
||||
|
||||
/** The state where the initiating party has created
|
||||
* a sign message in response to an accept message
|
||||
* but the DLC funding transaction has not yet been
|
||||
* broadcasted to the network.
|
||||
*/
|
||||
final case object Signed extends DLCState
|
||||
final case object Signed extends InProgressState
|
||||
|
||||
/** The state where the accepting (non-initiating)
|
||||
* party has broadcasted the DLC funding transaction
|
||||
* to the blockchain, and it has not yet been confirmed.
|
||||
*/
|
||||
final case object Broadcasted extends DLCState
|
||||
final case object Broadcasted extends InProgressState
|
||||
|
||||
/** The state where the DLC funding transaction has been
|
||||
* confirmed on-chain and no execution paths have yet been
|
||||
* initiated.
|
||||
*/
|
||||
final case object Confirmed extends DLCState
|
||||
final case object Confirmed extends InProgressState
|
||||
|
||||
/** The state where one of the CETs has been accepted by the network
|
||||
* and executed by ourselves.
|
||||
*/
|
||||
final case object Claimed extends DLCState
|
||||
final case object Claimed extends ClosedViaOracleOutcomeState
|
||||
|
||||
/** The state where one of the CETs has been accepted by the network
|
||||
* and executed by a remote party.
|
||||
*/
|
||||
final case object RemoteClaimed extends DLCState
|
||||
final case object RemoteClaimed extends ClosedViaOracleOutcomeState
|
||||
|
||||
/** The state where the DLC refund transaction has been
|
||||
* accepted by the network.
|
||||
*/
|
||||
final case object Refunded extends DLCState
|
||||
final case object Refunded extends ClosedState
|
||||
|
||||
val all: Vector[DLCState] = Vector(Offered,
|
||||
Accepted,
|
||||
|
|
|
@ -254,4 +254,5 @@ object DLCStatus {
|
|||
localAdaptorSigs,
|
||||
cet)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,14 +7,12 @@ import org.bitcoins.core.api.wallet.db._
|
|||
import org.bitcoins.core.config.BitcoinNetwork
|
||||
import org.bitcoins.core.crypto.ExtPublicKey
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.dlc.accounting.DLCAccounting
|
||||
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.models.DLCMessage.DLCAccept._
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCStatus._
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.dlc.sign._
|
||||
import org.bitcoins.core.protocol.script._
|
||||
|
@ -26,6 +24,7 @@ import org.bitcoins.core.wallet.utxo._
|
|||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.dlc.wallet.internal._
|
||||
import org.bitcoins.dlc.wallet.models._
|
||||
import org.bitcoins.dlc.wallet.util.DLCStatusBuilder
|
||||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.bitcoins.wallet.models.TransactionDAO
|
||||
|
@ -1235,228 +1234,105 @@ abstract class DLCWallet
|
|||
}
|
||||
|
||||
override def findDLC(dlcId: Sha256Digest): Future[Option[DLCStatus]] = {
|
||||
for {
|
||||
dlcDbOpt <- dlcDAO.read(dlcId)
|
||||
contractDataOpt <- contractDataDAO.read(dlcId)
|
||||
offerDbOpt <- dlcOfferDAO.read(dlcId)
|
||||
acceptDbOpt <- dlcAcceptDAO.read(dlcId)
|
||||
(announcements, announcementData, nonceDbs) <- getDLCAnnouncementDbs(
|
||||
dlcId)
|
||||
closingTxFOpt <- dlcDbOpt.map(dlcDb => getClosingTxOpt(dlcDb)) match {
|
||||
case None => Future.successful(None)
|
||||
case Some(closingTxIdOpt) => closingTxIdOpt
|
||||
val start = System.currentTimeMillis()
|
||||
val dlcDbOptF = dlcDAO.read(dlcId)
|
||||
val contractDataOptF = contractDataDAO.read(dlcId)
|
||||
val offerDbOptF = dlcOfferDAO.read(dlcId)
|
||||
val acceptDbOptF = dlcAcceptDAO.read(dlcId)
|
||||
val closingTxOptF: Future[Option[TransactionDb]] = for {
|
||||
dlcDbOpt <- dlcDbOptF
|
||||
closingTxFOpt <- {
|
||||
dlcDbOpt.map(dlcDb => getClosingTxOpt(dlcDb)) match {
|
||||
case None => Future.successful(None)
|
||||
case Some(closingTxIdOpt) => closingTxIdOpt
|
||||
}
|
||||
}
|
||||
} yield (dlcDbOpt, contractDataOpt, offerDbOpt) match {
|
||||
case (Some(dlcDb), Some(contractData), Some(offerDb)) =>
|
||||
val totalCollateral = contractData.totalCollateral
|
||||
} yield closingTxFOpt
|
||||
|
||||
val localCollateral = if (dlcDb.isInitiator) {
|
||||
offerDb.collateral
|
||||
} else {
|
||||
totalCollateral - offerDb.collateral
|
||||
}
|
||||
|
||||
val contractInfo =
|
||||
getContractInfo(contractData,
|
||||
announcements,
|
||||
announcementData,
|
||||
nonceDbs)
|
||||
|
||||
// Only called when safe
|
||||
lazy val (oracleOutcome, sigs) = {
|
||||
val aggSig = dlcDb.aggregateSignatureOpt.get
|
||||
val outcome =
|
||||
contractInfo.sigPointMap(aggSig.sig.toPrivateKey.publicKey)
|
||||
|
||||
val sigs = nonceDbs.flatMap(_.signatureOpt)
|
||||
|
||||
(outcome, sigs)
|
||||
}
|
||||
|
||||
val status = dlcDb.state match {
|
||||
case DLCState.Offered =>
|
||||
Offered(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral
|
||||
)
|
||||
case DLCState.Accepted =>
|
||||
Accepted(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral
|
||||
)
|
||||
case DLCState.Signed =>
|
||||
Signed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral
|
||||
)
|
||||
case DLCState.Broadcasted =>
|
||||
Broadcasted(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get
|
||||
)
|
||||
case DLCState.Confirmed =>
|
||||
Confirmed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get
|
||||
)
|
||||
case DLCState.Claimed =>
|
||||
require(acceptDbOpt.isDefined,
|
||||
s"Must have acceptDb to be in state=${DLCState.Claimed}")
|
||||
val closingTxDb = closingTxFOpt.get
|
||||
val accounting: DLCAccounting =
|
||||
calculatePnl(dlcDb,
|
||||
val dlcOptF: Future[Option[DLCStatus]] = for {
|
||||
dlcDbOpt <- dlcDbOptF
|
||||
contractDataOpt <- contractDataOptF
|
||||
offerDbOpt <- offerDbOptF
|
||||
acceptDbOpt <- acceptDbOptF
|
||||
closingTxOpt <- closingTxOptF
|
||||
result <- {
|
||||
(dlcDbOpt, contractDataOpt, offerDbOpt) match {
|
||||
case (Some(dlcDb), Some(contractData), Some(offerDb)) =>
|
||||
buildDLCStatus(dlcDb,
|
||||
contractData,
|
||||
offerDb,
|
||||
acceptDbOpt.get,
|
||||
closingTxDb.transaction)
|
||||
Claimed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get,
|
||||
dlcDb.closingTxIdOpt.get,
|
||||
sigs,
|
||||
oracleOutcome,
|
||||
myPayout = accounting.myPayout,
|
||||
counterPartyPayout = accounting.theirPayout
|
||||
)
|
||||
case DLCState.RemoteClaimed =>
|
||||
require(
|
||||
acceptDbOpt.isDefined,
|
||||
s"Must have acceptDb to be in state=${DLCState.RemoteClaimed}")
|
||||
val closingTxDb = closingTxFOpt.get
|
||||
val accounting: DLCAccounting =
|
||||
calculatePnl(dlcDb,
|
||||
offerDb,
|
||||
acceptDbOpt.get,
|
||||
closingTxDb.transaction)
|
||||
RemoteClaimed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get,
|
||||
dlcDb.closingTxIdOpt.get,
|
||||
dlcDb.aggregateSignatureOpt.get,
|
||||
oracleOutcome,
|
||||
myPayout = accounting.myPayout,
|
||||
counterPartyPayout = accounting.theirPayout
|
||||
)
|
||||
case DLCState.Refunded =>
|
||||
require(acceptDbOpt.isDefined,
|
||||
s"Must have acceptDb to be in state=${DLCState.Refunded}")
|
||||
|
||||
val closingTxDb = closingTxFOpt.get
|
||||
val accounting: DLCAccounting =
|
||||
calculatePnl(dlcDb,
|
||||
offerDb,
|
||||
acceptDbOpt.get,
|
||||
closingTxDb.transaction)
|
||||
Refunded(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get,
|
||||
dlcDb.closingTxIdOpt.get,
|
||||
myPayout = accounting.myPayout,
|
||||
counterPartyPayout = accounting.theirPayout
|
||||
)
|
||||
acceptDbOpt,
|
||||
closingTxOpt)
|
||||
case (_, _, _) => Future.successful(None)
|
||||
}
|
||||
}
|
||||
} yield result
|
||||
|
||||
Some(status)
|
||||
case (_, _, _) => None
|
||||
}
|
||||
dlcOptF.foreach(_ =>
|
||||
logger.debug(
|
||||
s"Done finding dlc=$dlcId, it took=${System.currentTimeMillis() - start}ms"))
|
||||
dlcOptF
|
||||
}
|
||||
|
||||
private def calculatePnl(
|
||||
/** Helper method to assemble a [[DLCStatus]] */
|
||||
private def buildDLCStatus(
|
||||
dlcDb: DLCDb,
|
||||
contractData: DLCContractDataDb,
|
||||
offerDb: DLCOfferDb,
|
||||
acceptDb: DLCAcceptDb,
|
||||
closingTx: Transaction): DLCAccounting = {
|
||||
val (myCollateral, theirCollateral, myPayoutAddress, theirPayoutAddress) = {
|
||||
if (dlcDb.isInitiator) {
|
||||
val myCollateral = offerDb.collateral
|
||||
val theirCollateral = acceptDb.collateral
|
||||
val myPayoutAddress = offerDb.payoutAddress
|
||||
val theirPayoutAddress = acceptDb.payoutAddress
|
||||
(myCollateral, theirCollateral, myPayoutAddress, theirPayoutAddress)
|
||||
acceptDbOpt: Option[DLCAcceptDb],
|
||||
closingTxOpt: Option[TransactionDb]): Future[Option[DLCStatus]] = {
|
||||
val dlcId = dlcDb.dlcId
|
||||
val aggregatedF: Future[(
|
||||
Vector[DLCAnnouncementDb],
|
||||
Vector[OracleAnnouncementDataDb],
|
||||
Vector[OracleNonceDb])] = getDLCAnnouncementDbs(dlcDb.dlcId)
|
||||
|
||||
} else {
|
||||
val myCollateral = acceptDb.collateral
|
||||
val theirCollateral = offerDb.collateral
|
||||
val myPayoutAddress = acceptDb.payoutAddress
|
||||
val theirPayoutAddress = offerDb.payoutAddress
|
||||
(myCollateral, theirCollateral, myPayoutAddress, theirPayoutAddress)
|
||||
val contractInfoF: Future[ContractInfo] = {
|
||||
aggregatedF.map { case (announcements, announcementData, nonceDbs) =>
|
||||
getContractInfo(contractData, announcements, announcementData, nonceDbs)
|
||||
}
|
||||
}
|
||||
|
||||
val myPayout = closingTx.outputs
|
||||
.filter(_.scriptPubKey == myPayoutAddress.scriptPubKey)
|
||||
.map(_.value)
|
||||
.sum
|
||||
val theirPayout = closingTx.outputs
|
||||
.filter(_.scriptPubKey == theirPayoutAddress.scriptPubKey)
|
||||
.map(_.value)
|
||||
.sum
|
||||
DLCAccounting(
|
||||
dlcId = dlcDb.dlcId,
|
||||
myCollateral = myCollateral,
|
||||
theirCollateral = theirCollateral,
|
||||
myPayout = myPayout,
|
||||
theirPayout = theirPayout
|
||||
)
|
||||
val statusF: Future[DLCStatus] = for {
|
||||
contractInfo <- contractInfoF
|
||||
(_, _, nonceDbs) <- aggregatedF
|
||||
status <- {
|
||||
dlcDb.state match {
|
||||
case _: DLCState.InProgressState =>
|
||||
val inProgress = DLCStatusBuilder.buildInProgressDLCStatus(
|
||||
dlcDb = dlcDb,
|
||||
contractInfo = contractInfo,
|
||||
contractData = contractData,
|
||||
offerDb = offerDb)
|
||||
Future.successful(inProgress)
|
||||
case _: DLCState.ClosedState =>
|
||||
(acceptDbOpt, closingTxOpt) match {
|
||||
case (Some(acceptDb), Some(closingTx)) =>
|
||||
val statusF = DLCStatusBuilder.buildClosedDLCStatus(
|
||||
dlcDb = dlcDb,
|
||||
contractInfo = contractInfo,
|
||||
contractData = contractData,
|
||||
nonceDbs = nonceDbs,
|
||||
offerDb = offerDb,
|
||||
acceptDb = acceptDb,
|
||||
closingTx = closingTx.transaction
|
||||
)
|
||||
statusF
|
||||
case (None, None) =>
|
||||
Future.failed(new RuntimeException(
|
||||
s"Could not find acceptDb or closingTx for closing state=${dlcDb.state} dlcId=$dlcId"))
|
||||
case (Some(_), None) =>
|
||||
Future.failed(new RuntimeException(
|
||||
s"Could not find closingTx for state=${dlcDb.state} dlcId=$dlcId"))
|
||||
case (None, Some(_)) =>
|
||||
Future.failed(new RuntimeException(
|
||||
s"Cannot find acceptDb for dlcId=$dlcId. This likely means we have data corruption"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} yield status
|
||||
|
||||
statusF.map(Some(_))
|
||||
}
|
||||
|
||||
/** @param newAnnouncements announcements we do not have in our db
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.bitcoins.dlc.wallet.accounting
|
||||
|
||||
import org.bitcoins.core.dlc.accounting.DLCAccounting
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.dlc.wallet.models.{DLCAcceptDb, DLCDb, DLCOfferDb}
|
||||
|
||||
object AccountingUtil {
|
||||
|
||||
/** Calculates the profit and loss for the given dlc */
|
||||
def calculatePnl(
|
||||
dlcDb: DLCDb,
|
||||
offerDb: DLCOfferDb,
|
||||
acceptDb: DLCAcceptDb,
|
||||
closingTx: Transaction): DLCAccounting = {
|
||||
val (myCollateral, theirCollateral, myPayoutAddress, theirPayoutAddress) = {
|
||||
if (dlcDb.isInitiator) {
|
||||
val myCollateral = offerDb.collateral
|
||||
val theirCollateral = acceptDb.collateral
|
||||
val myPayoutAddress = offerDb.payoutAddress
|
||||
val theirPayoutAddress = acceptDb.payoutAddress
|
||||
(myCollateral, theirCollateral, myPayoutAddress, theirPayoutAddress)
|
||||
|
||||
} else {
|
||||
val myCollateral = acceptDb.collateral
|
||||
val theirCollateral = offerDb.collateral
|
||||
val myPayoutAddress = acceptDb.payoutAddress
|
||||
val theirPayoutAddress = offerDb.payoutAddress
|
||||
(myCollateral, theirCollateral, myPayoutAddress, theirPayoutAddress)
|
||||
}
|
||||
}
|
||||
|
||||
val myPayout = closingTx.outputs
|
||||
.filter(_.scriptPubKey == myPayoutAddress.scriptPubKey)
|
||||
.map(_.value)
|
||||
.sum
|
||||
val theirPayout = closingTx.outputs
|
||||
.filter(_.scriptPubKey == theirPayoutAddress.scriptPubKey)
|
||||
.map(_.value)
|
||||
.sum
|
||||
DLCAccounting(
|
||||
dlcId = dlcDb.dlcId,
|
||||
myCollateral = myCollateral,
|
||||
theirCollateral = theirCollateral,
|
||||
myPayout = myPayout,
|
||||
theirPayout = theirPayout
|
||||
)
|
||||
}
|
||||
}
|
|
@ -26,11 +26,21 @@ private[bitcoins] trait DLCDataManagement { self: DLCWallet =>
|
|||
Vector[DLCAnnouncementDb],
|
||||
Vector[OracleAnnouncementDataDb],
|
||||
Vector[OracleNonceDb])] = {
|
||||
for {
|
||||
announcements <- dlcAnnouncementDAO.findByDLCId(dlcId)
|
||||
val announcementsF = dlcAnnouncementDAO.findByDLCId(dlcId)
|
||||
val announcementIdsF = for {
|
||||
announcements <- announcementsF
|
||||
announcementIds = announcements.map(_.announcementId)
|
||||
announcementData <- announcementDAO.findByIds(announcementIds)
|
||||
nonceDbs <- oracleNonceDAO.findByAnnouncementIds(announcementIds)
|
||||
} yield announcementIds
|
||||
|
||||
val announcementDataF =
|
||||
announcementIdsF.flatMap(ids => announcementDAO.findByIds(ids))
|
||||
val nonceDbsF =
|
||||
announcementIdsF.flatMap(ids => oracleNonceDAO.findByAnnouncementIds(ids))
|
||||
|
||||
for {
|
||||
announcements <- announcementsF
|
||||
announcementData <- announcementDataF
|
||||
nonceDbs <- nonceDbsF
|
||||
} yield (announcements, announcementData, nonceDbs)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
package org.bitcoins.dlc.wallet.util
|
||||
|
||||
import org.bitcoins.core.dlc.accounting.DLCAccounting
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
ClosedDLCStatus,
|
||||
ContractInfo,
|
||||
DLCState,
|
||||
DLCStatus,
|
||||
OracleOutcome
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCStatus.{
|
||||
Accepted,
|
||||
Broadcasted,
|
||||
Claimed,
|
||||
Confirmed,
|
||||
Offered,
|
||||
Refunded,
|
||||
RemoteClaimed,
|
||||
Signed
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.crypto.SchnorrDigitalSignature
|
||||
import org.bitcoins.dlc.wallet.accounting.AccountingUtil
|
||||
import org.bitcoins.dlc.wallet.models.{
|
||||
DLCAcceptDb,
|
||||
DLCContractDataDb,
|
||||
DLCDb,
|
||||
DLCOfferDb,
|
||||
OracleNonceDb
|
||||
}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
object DLCStatusBuilder {
|
||||
|
||||
/** Helper method to convert a bunch of indepdendent datastructures into a in progress dlc status */
|
||||
def buildInProgressDLCStatus(
|
||||
dlcDb: DLCDb,
|
||||
contractInfo: ContractInfo,
|
||||
contractData: DLCContractDataDb,
|
||||
offerDb: DLCOfferDb): DLCStatus = {
|
||||
require(
|
||||
dlcDb.state.isInstanceOf[DLCState.InProgressState],
|
||||
s"Cannot have divergent states beteween dlcDb and the parameter state, got= dlcDb.state=${dlcDb.state} state=${dlcDb.state}"
|
||||
)
|
||||
val dlcId = dlcDb.dlcId
|
||||
|
||||
val totalCollateral = contractData.totalCollateral
|
||||
|
||||
val localCollateral = if (dlcDb.isInitiator) {
|
||||
offerDb.collateral
|
||||
} else {
|
||||
totalCollateral - offerDb.collateral
|
||||
}
|
||||
|
||||
val status = dlcDb.state.asInstanceOf[DLCState.InProgressState] match {
|
||||
case DLCState.Offered =>
|
||||
Offered(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral
|
||||
)
|
||||
case DLCState.Accepted =>
|
||||
Accepted(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral
|
||||
)
|
||||
case DLCState.Signed =>
|
||||
Signed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral
|
||||
)
|
||||
case DLCState.Broadcasted =>
|
||||
Broadcasted(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get
|
||||
)
|
||||
case DLCState.Confirmed =>
|
||||
Confirmed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get
|
||||
)
|
||||
}
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
def buildClosedDLCStatus(
|
||||
dlcDb: DLCDb,
|
||||
contractInfo: ContractInfo,
|
||||
contractData: DLCContractDataDb,
|
||||
nonceDbs: Vector[OracleNonceDb],
|
||||
offerDb: DLCOfferDb,
|
||||
acceptDb: DLCAcceptDb,
|
||||
closingTx: Transaction)(implicit
|
||||
ec: ExecutionContext): Future[ClosedDLCStatus] = {
|
||||
require(
|
||||
dlcDb.state.isInstanceOf[DLCState.ClosedState],
|
||||
s"Cannot have divergent states beteween dlcDb and the parameter state, got= dlcDb.state=${dlcDb.state} state=${dlcDb.state}"
|
||||
)
|
||||
|
||||
val dlcId = dlcDb.dlcId
|
||||
val accounting: DLCAccounting =
|
||||
AccountingUtil.calculatePnl(dlcDb, offerDb, acceptDb, closingTx)
|
||||
|
||||
//start calculation up here in parallel as this is a bottleneck currently
|
||||
val outcomesOptF: Future[
|
||||
Option[(OracleOutcome, Vector[SchnorrDigitalSignature])]] =
|
||||
for {
|
||||
oracleOutcomeSigsOpt <- getOracleOutcomeAndSigs(dlcDb = dlcDb,
|
||||
contractInfo =
|
||||
contractInfo,
|
||||
nonceDbs = nonceDbs)
|
||||
} yield oracleOutcomeSigsOpt
|
||||
|
||||
val totalCollateral = contractData.totalCollateral
|
||||
|
||||
val localCollateral = if (dlcDb.isInitiator) {
|
||||
offerDb.collateral
|
||||
} else {
|
||||
totalCollateral - offerDb.collateral
|
||||
}
|
||||
val statusF = dlcDb.state.asInstanceOf[DLCState.ClosedState] match {
|
||||
case DLCState.Refunded =>
|
||||
//no oracle information in the refund case
|
||||
val refund = Refunded(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get,
|
||||
closingTx.txIdBE,
|
||||
myPayout = accounting.myPayout,
|
||||
counterPartyPayout = accounting.theirPayout
|
||||
)
|
||||
Future.successful(refund)
|
||||
case oracleOutcomeState: DLCState.ClosedViaOracleOutcomeState =>
|
||||
//a state that requires an oracle outcome
|
||||
//the .get below should always be valid
|
||||
for {
|
||||
outcomesOpt <- outcomesOptF
|
||||
(oracleOutcome, sigs) = outcomesOpt.get
|
||||
} yield {
|
||||
oracleOutcomeState match {
|
||||
case DLCState.Claimed =>
|
||||
Claimed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get,
|
||||
closingTx.txIdBE,
|
||||
sigs,
|
||||
oracleOutcome,
|
||||
myPayout = accounting.myPayout,
|
||||
counterPartyPayout = accounting.theirPayout
|
||||
)
|
||||
case DLCState.RemoteClaimed =>
|
||||
RemoteClaimed(
|
||||
dlcId,
|
||||
dlcDb.isInitiator,
|
||||
dlcDb.tempContractId,
|
||||
dlcDb.contractIdOpt.get,
|
||||
contractInfo,
|
||||
contractData.dlcTimeouts,
|
||||
dlcDb.feeRate,
|
||||
totalCollateral,
|
||||
localCollateral,
|
||||
dlcDb.fundingTxIdOpt.get,
|
||||
closingTx.txIdBE,
|
||||
dlcDb.aggregateSignatureOpt.get,
|
||||
oracleOutcome,
|
||||
myPayout = accounting.myPayout,
|
||||
counterPartyPayout = accounting.theirPayout
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
statusF
|
||||
}
|
||||
|
||||
/** Calculates oracle outcome and signatures. Returns none if the dlc is not in a valid state to
|
||||
* calculate the outcome
|
||||
*/
|
||||
def getOracleOutcomeAndSigs(
|
||||
dlcDb: DLCDb,
|
||||
contractInfo: ContractInfo,
|
||||
nonceDbs: Vector[OracleNonceDb])(implicit ec: ExecutionContext): Future[
|
||||
Option[(OracleOutcome, Vector[SchnorrDigitalSignature])]] = {
|
||||
Future {
|
||||
dlcDb.aggregateSignatureOpt match {
|
||||
case Some(aggSig) =>
|
||||
val oracleOutcome = sigPointCache.get(aggSig) match {
|
||||
case Some(outcome) => outcome //return cached outcome
|
||||
case None =>
|
||||
val o =
|
||||
contractInfo.sigPointMap(aggSig.sig.toPrivateKey.publicKey)
|
||||
sigPointCache.+=((aggSig, o))
|
||||
o
|
||||
}
|
||||
val sigs = nonceDbs.flatMap(_.signatureOpt)
|
||||
Some((oracleOutcome, sigs))
|
||||
case None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A performance optimization to cache sigpoints we know map to oracle outcomes.
|
||||
* This is needed as a workaround for issue 3213
|
||||
* @see https://github.com/bitcoin-s/bitcoin-s/issues/3213
|
||||
*/
|
||||
private val sigPointCache: mutable.Map[
|
||||
SchnorrDigitalSignature,
|
||||
OracleOutcome] = mutable.Map.empty[SchnorrDigitalSignature, OracleOutcome]
|
||||
}
|
Loading…
Add table
Reference in a new issue