mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 22:36:34 +01:00
Use nonceDb's outcome to calc oracle outcome (#3217)
This commit is contained in:
parent
0f9024b7ae
commit
a56086b751
3 changed files with 139 additions and 143 deletions
|
@ -1287,15 +1287,23 @@ abstract class DLCWallet
|
|||
Vector[OracleAnnouncementDataDb],
|
||||
Vector[OracleNonceDb])] = getDLCAnnouncementDbs(dlcDb.dlcId)
|
||||
|
||||
val contractInfoF: Future[ContractInfo] = {
|
||||
val contractInfoAndAnnouncementsF: Future[
|
||||
(ContractInfo, Vector[(OracleAnnouncementV0TLV, Long)])] = {
|
||||
aggregatedF.map { case (announcements, announcementData, nonceDbs) =>
|
||||
getContractInfo(contractData, announcements, announcementData, nonceDbs)
|
||||
val contractInfo = getContractInfo(contractData,
|
||||
announcements,
|
||||
announcementData,
|
||||
nonceDbs)
|
||||
val announcementsWithId = getOracleAnnouncementsWithId(announcements,
|
||||
announcementData,
|
||||
nonceDbs)
|
||||
(contractInfo, announcementsWithId)
|
||||
}
|
||||
}
|
||||
|
||||
val statusF: Future[DLCStatus] = for {
|
||||
contractInfo <- contractInfoF
|
||||
(_, _, nonceDbs) <- aggregatedF
|
||||
(contractInfo, announcementsWithId) <- contractInfoAndAnnouncementsF
|
||||
(announcementIds, _, nonceDbs) <- aggregatedF
|
||||
status <- {
|
||||
dlcDb.state match {
|
||||
case _: DLCState.InProgressState =>
|
||||
|
@ -1308,16 +1316,18 @@ abstract class DLCWallet
|
|||
case _: DLCState.ClosedState =>
|
||||
(acceptDbOpt, closingTxOpt) match {
|
||||
case (Some(acceptDb), Some(closingTx)) =>
|
||||
val statusF = DLCStatusBuilder.buildClosedDLCStatus(
|
||||
val status = DLCStatusBuilder.buildClosedDLCStatus(
|
||||
dlcDb = dlcDb,
|
||||
contractInfo = contractInfo,
|
||||
contractData = contractData,
|
||||
announcementsWithId = announcementsWithId,
|
||||
announcementIds = announcementIds,
|
||||
nonceDbs = nonceDbs,
|
||||
offerDb = offerDb,
|
||||
acceptDb = acceptDb,
|
||||
closingTx = closingTx.transaction
|
||||
)
|
||||
statusF
|
||||
Future.successful(status)
|
||||
case (None, None) =>
|
||||
Future.failed(new RuntimeException(
|
||||
s"Could not find acceptDb or closingTx for closing state=${dlcDb.state} dlcId=$dlcId"))
|
||||
|
|
|
@ -14,7 +14,6 @@ import org.bitcoins.dlc.wallet.models._
|
|||
import org.bitcoins.wallet.internal.TransactionProcessing
|
||||
|
||||
import scala.concurrent._
|
||||
import scala.util.Try
|
||||
|
||||
/** Overrides TransactionProcessing from Wallet to add extra logic to
|
||||
* process transactions that could from our own DLC.
|
||||
|
@ -157,32 +156,14 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
|
|||
}
|
||||
(outcomes, oracleInfos) = getOutcomeDbInfo(outcome)
|
||||
|
||||
sortedNonces = nonceDbs
|
||||
noncesByAnnouncement = nonceDbs
|
||||
.groupBy(_.announcementId)
|
||||
.values
|
||||
.map(_.sortBy(_.index))
|
||||
.toVector
|
||||
|
||||
updatedNonces =
|
||||
sortedNonces.flatMap(_.zip(outcomes).zipWithIndex.map {
|
||||
case ((db, outcome), idx) =>
|
||||
outcome match {
|
||||
case EnumOutcome(outcome) =>
|
||||
db.copy(outcomeOpt = Some(outcome))
|
||||
case numeric: UnsignedNumericOutcome =>
|
||||
// Use try because it can be truncated
|
||||
db.copy(outcomeOpt =
|
||||
Try(numeric.digits(idx).toString).toOption)
|
||||
case _: SignedNumericOutcome =>
|
||||
throw new RuntimeException("Not supported")
|
||||
}
|
||||
})
|
||||
_ <- oracleNonceDAO.updateAll(updatedNonces)
|
||||
|
||||
usedIds =
|
||||
getOracleAnnouncementsWithId(announcements,
|
||||
announcementsWithId = getOracleAnnouncementsWithId(announcements,
|
||||
announcementData,
|
||||
nonceDbs)
|
||||
|
||||
usedIds = announcementsWithId
|
||||
.filter(t => oracleInfos.exists(_.announcement == t._1))
|
||||
.map(_._2)
|
||||
|
||||
|
@ -190,6 +171,29 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
|
|||
.filter(t => usedIds.contains(t.announcementId))
|
||||
.map(_.copy(used = true))
|
||||
|
||||
updatedNonces = {
|
||||
usedIds.flatMap { id =>
|
||||
outcome match {
|
||||
case enum: EnumOracleOutcome =>
|
||||
val nonces = noncesByAnnouncement(id).sortBy(_.index)
|
||||
nonces.map(_.copy(outcomeOpt = Some(enum.outcome.outcome)))
|
||||
case numeric: NumericOracleOutcome =>
|
||||
numeric.oraclesAndOutcomes.flatMap { case (oracle, outcome) =>
|
||||
val id = announcementsWithId
|
||||
.find(_._1 == oracle.announcement)
|
||||
.map(_._2)
|
||||
.get
|
||||
val nonces = noncesByAnnouncement(id).sortBy(_.index)
|
||||
outcome.digits.zip(nonces).map { case (digit, nonceDb) =>
|
||||
nonceDb.copy(outcomeOpt = Some(digit.toString))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
_ <- oracleNonceDAO.updateAll(updatedNonces)
|
||||
|
||||
_ <- dlcAnnouncementDAO.updateAll(updatedAnnouncements)
|
||||
} yield dlcDb.copy(aggregateSignatureOpt = Some(sig))
|
||||
} else {
|
||||
|
|
|
@ -1,36 +1,13 @@
|
|||
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.dlc.models.DLCStatus._
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.tlv._
|
||||
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}
|
||||
import org.bitcoins.dlc.wallet.models._
|
||||
|
||||
object DLCStatusBuilder {
|
||||
|
||||
|
@ -126,10 +103,11 @@ object DLCStatusBuilder {
|
|||
contractInfo: ContractInfo,
|
||||
contractData: DLCContractDataDb,
|
||||
nonceDbs: Vector[OracleNonceDb],
|
||||
announcementsWithId: Vector[(OracleAnnouncementV0TLV, Long)],
|
||||
announcementIds: Vector[DLCAnnouncementDb],
|
||||
offerDb: DLCOfferDb,
|
||||
acceptDb: DLCAcceptDb,
|
||||
closingTx: Transaction)(implicit
|
||||
ec: ExecutionContext): Future[ClosedDLCStatus] = {
|
||||
closingTx: Transaction): 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}"
|
||||
|
@ -139,16 +117,6 @@ object DLCStatusBuilder {
|
|||
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) {
|
||||
|
@ -156,7 +124,7 @@ object DLCStatusBuilder {
|
|||
} else {
|
||||
totalCollateral - offerDb.collateral
|
||||
}
|
||||
val statusF = dlcDb.state.asInstanceOf[DLCState.ClosedState] match {
|
||||
val status = dlcDb.state.asInstanceOf[DLCState.ClosedState] match {
|
||||
case DLCState.Refunded =>
|
||||
//no oracle information in the refund case
|
||||
val refund = Refunded(
|
||||
|
@ -174,14 +142,13 @@ object DLCStatusBuilder {
|
|||
myPayout = accounting.myPayout,
|
||||
counterPartyPayout = accounting.theirPayout
|
||||
)
|
||||
Future.successful(refund)
|
||||
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 {
|
||||
val (oracleOutcome, sigs) = getOracleOutcomeAndSigs(
|
||||
announcementIds = announcementIds,
|
||||
announcementsWithId = announcementsWithId,
|
||||
nonceDbs = nonceDbs)
|
||||
|
||||
oracleOutcomeState match {
|
||||
case DLCState.Claimed =>
|
||||
Claimed(
|
||||
|
@ -221,41 +188,56 @@ object DLCStatusBuilder {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
statusF
|
||||
status
|
||||
}
|
||||
|
||||
/** 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
|
||||
announcementIds: Vector[DLCAnnouncementDb],
|
||||
announcementsWithId: Vector[(OracleAnnouncementV0TLV, Long)],
|
||||
nonceDbs: Vector[OracleNonceDb]): (
|
||||
OracleOutcome,
|
||||
Vector[SchnorrDigitalSignature]) = {
|
||||
val noncesByAnnouncement =
|
||||
nonceDbs.sortBy(_.index).groupBy(_.announcementId)
|
||||
val oracleOutcome = {
|
||||
val usedOracleIds = announcementIds.filter(_.used)
|
||||
val usedOracles = usedOracleIds.sortBy(_.index).map { used =>
|
||||
announcementsWithId.find(_._2 == used.announcementId).get
|
||||
}
|
||||
val sigs = nonceDbs.flatMap(_.signatureOpt)
|
||||
Some((oracleOutcome, sigs))
|
||||
case None => None
|
||||
require(usedOracles.nonEmpty, "Error, no oracles used")
|
||||
announcementsWithId.head._1.eventTLV.eventDescriptor match {
|
||||
case _: EnumEventDescriptorV0TLV =>
|
||||
val oracleInfos = usedOracles.map(t => EnumSingleOracleInfo(t._1))
|
||||
val outcomes = usedOracles.map { case (_, id) =>
|
||||
val nonces = noncesByAnnouncement(id)
|
||||
require(nonces.size == 1,
|
||||
s"Only 1 outcome for enum, got ${nonces.size}")
|
||||
EnumOutcome(nonces.head.outcomeOpt.get)
|
||||
}
|
||||
require(outcomes.distinct.size == 1,
|
||||
s"Should only be one outcome for enum, got $outcomes")
|
||||
EnumOracleOutcome(oracleInfos, outcomes.head)
|
||||
case _: UnsignedDigitDecompositionEventDescriptor =>
|
||||
val oraclesAndOutcomes = usedOracles.map { case (announcement, id) =>
|
||||
val oracleInfo = NumericSingleOracleInfo(announcement)
|
||||
val nonces = noncesByAnnouncement(id).sortBy(_.index)
|
||||
// need to allow for some Nones because we don't always get
|
||||
// all the digits because of prefixing
|
||||
val digits = nonces.flatMap(_.outcomeOpt.map(_.toInt))
|
||||
require(digits.nonEmpty, s"Got no digits for announcement id $id")
|
||||
val outcome = UnsignedNumericOutcome(digits)
|
||||
(oracleInfo, outcome)
|
||||
}
|
||||
NumericOracleOutcome(oraclesAndOutcomes)
|
||||
case _: SignedDigitDecompositionEventDescriptor =>
|
||||
throw new RuntimeException(s"SignedNumericOutcome not yet supported")
|
||||
}
|
||||
}
|
||||
|
||||
/** 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]
|
||||
val sigs = nonceDbs.flatMap(_.signatureOpt)
|
||||
(oracleOutcome, sigs)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue