Add SignDbState to fix rescan exception (#4246)

* Add SignDbState to fix rescan exception

* Cleanup

* Add test, doesn't pass

* Add unit tests, add invariants

* Add unit test to appServerTest

* Fix handling so an exception is not thrown internally when a contractId does not exist
This commit is contained in:
Chris Stewart 2022-04-11 08:06:53 -05:00 committed by GitHub
parent 27cb4a3c20
commit 37da24b94b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 550 additions and 236 deletions

View file

@ -211,12 +211,7 @@ object Server {
) )
} }
def httpBadRequest(ex: Throwable): HttpResponse = { def httpError(msg: String): HttpEntity.Strict = {
httpBadRequest(ex.getMessage)
}
def httpBadRequest(msg: String): HttpResponse = {
val entity = { val entity = {
val response = Response(error = Some(msg)) val response = Response(error = Some(msg))
HttpEntity( HttpEntity(
@ -224,6 +219,15 @@ object Server {
up.write(response.toJsonMap) up.write(response.toJsonMap)
) )
} }
entity
}
def httpBadRequest(ex: Throwable): HttpResponse = {
httpBadRequest(ex.getMessage)
}
def httpBadRequest(msg: String): HttpResponse = {
val entity = httpError(msg)
HttpResponse(status = StatusCodes.BadRequest, entity = entity) HttpResponse(status = StatusCodes.BadRequest, entity = entity)
} }

View file

@ -1175,7 +1175,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
(mockWalletApi (mockWalletApi
.executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV])) .executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV]))
.expects(contractId, Vector(dummyOracleAttestment)) .expects(contractId, Vector(dummyOracleAttestment))
.returning(Future.successful(EmptyTransaction)) .returning(Future.successful(Some(EmptyTransaction)))
val route = walletRoutes.handleCommand( val route = walletRoutes.handleCommand(
ServerCommand("executedlc", ServerCommand("executedlc",
@ -1191,12 +1191,31 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
} }
} }
"execute a dlc that is not in the wallet and handle gracefully" in {
(mockWalletApi
.executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV]))
.expects(contractId, Vector(dummyOracleAttestment))
.returning(Future.successful(None))
val route = walletRoutes.handleCommand(
ServerCommand("executedlc",
Arr(Str(contractId.toHex),
Arr(Str(dummyOracleAttestment.hex)),
Bool(true))))
Post() ~> route ~> check {
assert(contentType == `application/json`)
assert(
responseAs[String] == s"""{"result":null,"error":"Cannot execute DLC with contractId=${contractId.toHex}"}""")
}
}
"execute a dlc with multiple sigs" in { "execute a dlc with multiple sigs" in {
(mockWalletApi (mockWalletApi
.executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV])) .executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV]))
.expects(contractId, .expects(contractId,
Vector(dummyOracleAttestment, dummyOracleAttestment)) Vector(dummyOracleAttestment, dummyOracleAttestment))
.returning(Future.successful(EmptyTransaction)) .returning(Future.successful(Some(EmptyTransaction)))
val route = walletRoutes.handleCommand( val route = walletRoutes.handleCommand(
ServerCommand("executedlc", ServerCommand("executedlc",
@ -1217,7 +1236,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
(mockWalletApi (mockWalletApi
.executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV])) .executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV]))
.expects(contractId, Vector(dummyOracleAttestment)) .expects(contractId, Vector(dummyOracleAttestment))
.returning(Future.successful(EmptyTransaction)) .returning(Future.successful(Some(EmptyTransaction)))
(mockWalletApi.broadcastTransaction _) (mockWalletApi.broadcastTransaction _)
.expects(EmptyTransaction) .expects(EmptyTransaction)

View file

@ -537,10 +537,25 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
case Success(ExecuteDLC(contractId, sigs, noBroadcast)) => case Success(ExecuteDLC(contractId, sigs, noBroadcast)) =>
complete { complete {
for { for {
tx <- wallet.executeDLC(contractId, sigs) txOpt <- wallet.executeDLC(contractId, sigs)
ret <- handleBroadcastable(tx, noBroadcast) retOpt <- {
txOpt match {
case Some(tx) =>
handleBroadcastable(tx, noBroadcast)
.map(_.hex)
.map(Some(_))
case None =>
Future.successful(None)
}
}
} yield { } yield {
Server.httpSuccess(ret.hex) retOpt match {
case Some(ret) =>
Server.httpSuccess(ret)
case None =>
Server.httpError(
s"Cannot execute DLC with contractId=${contractId.toHex}")
}
} }
} }
} }

View file

@ -86,24 +86,24 @@ trait DLCWalletApi { self: WalletApi =>
/** Creates the CET for the given contractId and oracle signature, does not broadcast it */ /** Creates the CET for the given contractId and oracle signature, does not broadcast it */
def executeDLC( def executeDLC(
contractId: ByteVector, contractId: ByteVector,
oracleSig: OracleAttestmentTLV): Future[Transaction] = oracleSig: OracleAttestmentTLV): Future[Option[Transaction]] =
executeDLC(contractId, Vector(oracleSig)) executeDLC(contractId, Vector(oracleSig))
/** Creates the CET for the given contractId and oracle signature, does not broadcast it */ /** Creates the CET for the given contractId and oracle signature, does not broadcast it */
def executeDLC( def executeDLC(
contractId: ByteVector, contractId: ByteVector,
oracleSigs: Seq[OracleAttestmentTLV]): Future[Transaction] oracleSigs: Seq[OracleAttestmentTLV]): Future[Option[Transaction]]
/** Creates the CET for the given contractId and oracle signature, does not broadcast it */ /** Creates the CET for the given contractId and oracle signature, does not broadcast it */
def executeDLC( def executeDLC(
contractId: ByteVector, contractId: ByteVector,
oracleSig: OracleSignatures): Future[Transaction] = oracleSig: OracleSignatures): Future[Option[Transaction]] =
executeDLC(contractId, Vector(oracleSig)) executeDLC(contractId, Vector(oracleSig))
/** Creates the CET for the given contractId and oracle signature, does not broadcast it */ /** Creates the CET for the given contractId and oracle signature, does not broadcast it */
def executeDLC( def executeDLC(
contractId: ByteVector, contractId: ByteVector,
oracleSigs: Vector[OracleSignatures]): Future[Transaction] oracleSigs: Vector[OracleSignatures]): Future[Option[Transaction]]
/** Creates the refund transaction for the given contractId, does not broadcast it */ /** Creates the refund transaction for the given contractId, does not broadcast it */
def executeDLCRefund(contractId: ByteVector): Future[Transaction] def executeDLCRefund(contractId: ByteVector): Future[Transaction]

View file

@ -74,7 +74,7 @@ class DLCExecutionBitcoindBackendTest
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint") s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
} }
} }
closingTx <- dlcB.executeDLC(contractId, oracleSigs) closingTx <- dlcB.executeDLC(contractId, oracleSigs).map(_.get)
//broadcast the closing tx //broadcast the closing tx
_ <- dlcB.broadcastTransaction(closingTx) _ <- dlcB.broadcastTransaction(closingTx)
dlcs <- dlcB dlcs <- dlcB

View file

@ -108,7 +108,8 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint") s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
} }
} }
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,
@ -153,7 +154,8 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint") s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
} }
} }
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = false, asInitiator = false,
@ -225,7 +227,8 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
} }
} }
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,
@ -406,7 +409,8 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint") s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
} }
} }
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,
@ -452,7 +456,7 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
sigs = badSigs, sigs = badSigs,
outcomes = badOutcomes) outcomes = badOutcomes)
func = (wallet: DLCWallet) => func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, badAttestment) wallet.executeDLC(contractId, badAttestment).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,

View file

@ -112,7 +112,8 @@ class DLCMultiOracleEnumExecutionTest extends BitcoinSDualWalletTest {
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
(sig, _) = getSigs (sig, _) = getSigs
status <- getDLCStatus(wallets._2.wallet) status <- getDLCStatus(wallets._2.wallet)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,
@ -151,7 +152,8 @@ class DLCMultiOracleEnumExecutionTest extends BitcoinSDualWalletTest {
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
(_, sig) = getSigs (_, sig) = getSigs
status <- getDLCStatus(wallets._2.wallet) status <- getDLCStatus(wallets._2.wallet)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = false, asInitiator = false,

View file

@ -105,7 +105,8 @@ class DLCMultiOracleExactNumericExecutionTest extends BitcoinSDualWalletTest {
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
status <- getDLCStatus(wallets._1.wallet) status <- getDLCStatus(wallets._1.wallet)
(sigs, _) = getSigs(status.contractInfo) (sigs, _) = getSigs(status.contractInfo)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sigs).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,
@ -144,7 +145,8 @@ class DLCMultiOracleExactNumericExecutionTest extends BitcoinSDualWalletTest {
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
status <- getDLCStatus(wallets._2.wallet) status <- getDLCStatus(wallets._2.wallet)
(_, sigs) = getSigs(status.contractInfo) (_, sigs) = getSigs(status.contractInfo)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sigs).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = false, asInitiator = false,

View file

@ -114,7 +114,8 @@ class DLCMultiOracleNumericExecutionTest
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
status <- getDLCStatus(wallets._1.wallet) status <- getDLCStatus(wallets._1.wallet)
(sigs, _) = getSigs(status.contractInfo) (sigs, _) = getSigs(status.contractInfo)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sigs).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,
@ -153,7 +154,8 @@ class DLCMultiOracleNumericExecutionTest
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
status <- getDLCStatus(wallets._2.wallet) status <- getDLCStatus(wallets._2.wallet)
(_, sigs) = getSigs(status.contractInfo) (_, sigs) = getSigs(status.contractInfo)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sigs).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = false, asInitiator = false,

View file

@ -91,7 +91,8 @@ class DLCNumericExecutionTest extends BitcoinSDualWalletTest {
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
status <- getDLCStatus(wallets._1.wallet) status <- getDLCStatus(wallets._1.wallet)
(sigs, _) = getSigs(status.contractInfo) (sigs, _) = getSigs(status.contractInfo)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sigs).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = true, asInitiator = true,
@ -130,7 +131,8 @@ class DLCNumericExecutionTest extends BitcoinSDualWalletTest {
contractId <- getContractId(wallets._1.wallet) contractId <- getContractId(wallets._1.wallet)
status <- getDLCStatus(wallets._2.wallet) status <- getDLCStatus(wallets._2.wallet)
(_, sigs) = getSigs(status.contractInfo) (_, sigs) = getSigs(status.contractInfo)
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sigs).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = false, asInitiator = false,
@ -181,7 +183,7 @@ class DLCNumericExecutionTest extends BitcoinSDualWalletTest {
sigs = badSigs, sigs = badSigs,
outcomes = badOutcomes) outcomes = badOutcomes)
func = (wallet: DLCWallet) => func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, badAttestment) wallet.executeDLC(contractId, badAttestment).map(_.get)
result <- dlcExecutionTest(wallets = wallets, result <- dlcExecutionTest(wallets = wallets,
asInitiator = false, asInitiator = false,

View file

@ -106,7 +106,7 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint") s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
} }
} }
transaction <- walletA.executeDLC(contractId, sigs._1) transaction <- walletA.executeDLC(contractId, sigs._1).map(_.get)
_ <- walletB.processTransaction(transaction, None) _ <- walletB.processTransaction(transaction, None)
} yield () } yield ()

View file

@ -40,7 +40,8 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint") s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
} }
} }
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = (walletA, walletB), result <- dlcExecutionTest(wallets = (walletA, walletB),
asInitiator = true, asInitiator = true,
@ -79,7 +80,8 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint") s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
} }
} }
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(contractId, sig).map(_.get)
result <- dlcExecutionTest(wallets = (walletA, walletB), result <- dlcExecutionTest(wallets = (walletA, walletB),
asInitiator = true, asInitiator = true,

View file

@ -816,7 +816,8 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
tx <- walletB.broadcastDLCFundingTx(sign.contractId) tx <- walletB.broadcastDLCFundingTx(sign.contractId)
_ <- walletA.processTransaction(tx, None) _ <- walletA.processTransaction(tx, None)
func = (wallet: DLCWallet) => wallet.executeDLC(sign.contractId, sig) func = (wallet: DLCWallet) =>
wallet.executeDLC(sign.contractId, sig).map(_.get)
result <- dlcExecutionTest(dlcA = walletA, result <- dlcExecutionTest(dlcA = walletA,
dlcB = walletB, dlcB = walletB,
asInitiator = true, asInitiator = true,

View file

@ -0,0 +1,79 @@
package org.bitcoins.dlc.wallet.internal
import org.bitcoins.core.protocol.dlc.compute.DLCUtil
import org.bitcoins.core.protocol.dlc.models.DLCMessage.DLCOffer
import org.bitcoins.core.protocol.dlc.models.DLCState
import org.bitcoins.dlc.wallet.models.{AcceptDbState, SignDbState}
import org.bitcoins.testkit.wallet.{BitcoinSDualWalletTest, DLCWalletUtil}
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedDLCWallet
import org.scalatest.FutureOutcome
class DLCDataManagementTest extends BitcoinSDualWalletTest {
type FixtureParam = (FundedDLCWallet, FundedDLCWallet)
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
withDualFundedDLCWallets(test)
}
behavior of "DLCDataManagement"
it must "retrieve a acceptdb state from getDLCFundingData" in {
fundedDLCWallets: (FundedDLCWallet, FundedDLCWallet) =>
val walletA = fundedDLCWallets._1.wallet
val walletB = fundedDLCWallets._2.wallet
val offerData: DLCOffer =
DLCWalletUtil.sampleDLCOffer
for {
offer1 <- walletA.createDLCOffer(
offerData.contractInfo,
offerData.collateral,
Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32,
None,
None
)
accept <- walletB.acceptDLCOffer(offer1, None, None)
contractId = DLCUtil.calcContractId(offer1, accept)
acceptDbStateOpt <- walletB.dlcDataManagement.getDLCFundingData(
contractId,
walletA.transactionDAO)
} yield {
assert(acceptDbStateOpt.isDefined)
assert(acceptDbStateOpt.get.isInstanceOf[AcceptDbState])
assert(acceptDbStateOpt.get.state == DLCState.Accepted)
}
}
it must "retrieve a signdb state from getDLCFundingData" in {
fundedDLCWallets: (FundedDLCWallet, FundedDLCWallet) =>
val walletA = fundedDLCWallets._1.wallet
val walletB = fundedDLCWallets._2.wallet
val offerData: DLCOffer =
DLCWalletUtil.sampleDLCOffer
for {
offer1 <- walletA.createDLCOffer(
offerData.contractInfo,
offerData.collateral,
Some(offerData.feeRate),
offerData.timeouts.contractMaturity.toUInt32,
offerData.timeouts.contractTimeout.toUInt32,
None,
None
)
accept <- walletB.acceptDLCOffer(offer1, None, None)
sign <- walletA.signDLC(accept)
signDbStateOpt <- walletA.dlcDataManagement.getDLCFundingData(
sign.contractId,
walletA.transactionDAO)
} yield {
assert(signDbStateOpt.isDefined)
assert(signDbStateOpt.get.isInstanceOf[SignDbState])
assert(signDbStateOpt.get.state == DLCState.Signed)
}
}
}

View file

@ -13,9 +13,9 @@ import org.bitcoins.db.DatabaseDriver._
import org.bitcoins.db._ import org.bitcoins.db._
import org.bitcoins.dlc.wallet.internal.DLCDataManagement import org.bitcoins.dlc.wallet.internal.DLCDataManagement
import org.bitcoins.dlc.wallet.models.{ import org.bitcoins.dlc.wallet.models.{
AcceptDbState,
DLCSetupDbState, DLCSetupDbState,
OfferedDbState OfferedDbState,
SetupCompleteDLCDbState
} }
import org.bitcoins.wallet.config.WalletAppConfig import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.TransactionDAO import org.bitcoins.wallet.models.TransactionDAO
@ -176,7 +176,7 @@ case class DLCAppConfig(baseDatadir: Path, configOverrides: Vector[Config])(
vec: Vector[DLCSetupDbState]): Vector[DLCDb] = { vec: Vector[DLCSetupDbState]): Vector[DLCDb] = {
vec.map { case state: DLCSetupDbState => vec.map { case state: DLCSetupDbState =>
val updatedDlcDb: DLCDb = state match { val updatedDlcDb: DLCDb = state match {
case acceptDbState: AcceptDbState => case acceptDbState: SetupCompleteDLCDbState =>
val offer = acceptDbState.offer val offer = acceptDbState.offer
val acceptWithoutSigs = acceptDbState.acceptWithoutSigs val acceptWithoutSigs = acceptDbState.acceptWithoutSigs
val dlcDb = acceptDbState.dlcDb val dlcDb = acceptDbState.dlcDb

View file

@ -89,7 +89,7 @@ abstract class DLCWallet
incomingOfferDAO incomingOfferDAO
) )
private val dlcDataManagement = DLCDataManagement(dlcWalletDAOs) private[wallet] val dlcDataManagement = DLCDataManagement(dlcWalletDAOs)
protected lazy val actionBuilder: DLCActionBuilder = { protected lazy val actionBuilder: DLCActionBuilder = {
DLCActionBuilder(dlcWalletDAOs) DLCActionBuilder(dlcWalletDAOs)
@ -482,6 +482,9 @@ abstract class DLCWallet
Future.failed( Future.failed(
new RuntimeException( new RuntimeException(
s"We cannot accept a DLC we offered, dlcId=${dlcId.hex}")) s"We cannot accept a DLC we offered, dlcId=${dlcId.hex}"))
case _: SignDbState =>
Future.failed(new RuntimeException(
s"We cannot accept a DLC we have already signed, dlcId=${dlcId.hex}"))
case a: AcceptDbState => case a: AcceptDbState =>
val dlcPubKeys = DLCUtil.calcDLCPubKeys( val dlcPubKeys = DLCUtil.calcDLCPubKeys(
xpub = account.xpub, xpub = account.xpub,
@ -1327,17 +1330,17 @@ abstract class DLCWallet
setupStateOpt <- dlcDataManagement.getDLCFundingData(contractId, setupStateOpt <- dlcDataManagement.getDLCFundingData(contractId,
txDAO = txDAO =
transactionDAO) transactionDAO)
acceptState = { complete = {
setupStateOpt.map { setupStateOpt.map {
case _: OfferedDbState => case _: OfferedDbState =>
sys.error( sys.error(
s"Cannot retrieve funding transaction when DLC is in offered state") s"Cannot retrieve funding transaction when DLC is in offered state")
case accept: AcceptDbState => accept case complete: SetupCompleteDLCDbState => complete
}.get //bad but going to have to save this refactor for future }.get //bad but going to have to save this refactor for future
} }
dlcDb = acceptState.dlcDb dlcDb = complete.dlcDb
//is this right? We don't have counterpart scriptSigParams //is this right? We don't have counterpart scriptSigParams
fundingInputs = acceptState.allFundingInputs fundingInputs = complete.allFundingInputs
scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs) scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs)
signerOpt <- dlcDataManagement.signerFromDb( signerOpt <- dlcDataManagement.signerFromDb(
dlcDb = dlcDb, dlcDb = dlcDb,
@ -1423,21 +1426,37 @@ abstract class DLCWallet
override def executeDLC( override def executeDLC(
contractId: ByteVector, contractId: ByteVector,
sigs: Seq[OracleAttestmentTLV]): Future[Transaction] = { sigs: Seq[OracleAttestmentTLV]): Future[Option[Transaction]] = {
logger.info( logger.info(
s"Executing dlc with contractId=${contractId.toHex} sigs=${sigs.map(_.hex)}") s"Executing dlc with contractId=${contractId.toHex} sigs=${sigs.map(_.hex)}")
require(sigs.nonEmpty, "Must provide at least one oracle signature") require(sigs.nonEmpty, "Must provide at least one oracle signature")
val dlcDbOpt = dlcDAO.findByContractId(contractId)
for { for {
dlcDb <- dlcDAO.findByContractId(contractId).map(_.get) dlcDbOpt <- dlcDbOpt
_ = dlcDb.state match { txOpt <- {
case state @ (Offered | AcceptComputingAdaptorSigs | Accepted | dlcDbOpt match {
SignComputingAdaptorSigs | Signed) => case Some(dlcDb) =>
sys.error( executeDLC(dlcDb, sigs)
s"Cannot execute DLC before the DLC is broadcast to the blockchain, state=$state") case None =>
case Broadcasted | Confirmed | _: ClosedState => Future.successful(None)
//can continue executing, do nothing }
} }
} yield txOpt
}
def executeDLC(
dlcDb: DLCDb,
sigs: Seq[OracleAttestmentTLV]): Future[Option[Transaction]] = {
val _ = dlcDb.state match {
case state @ (Offered | AcceptComputingAdaptorSigs | Accepted |
SignComputingAdaptorSigs | Signed) =>
sys.error(
s"Cannot execute DLC before the DLC is broadcast to the blockchain, state=$state")
case Broadcasted | Confirmed | _: ClosedState =>
//can continue executing, do nothing
}
for {
(announcements, announcementData, nonceDbs) <- dlcDataManagement (announcements, announcementData, nonceDbs) <- dlcDataManagement
.getDLCAnnouncementDbs(dlcDb.dlcId) .getDLCAnnouncementDbs(dlcDb.dlcId)
@ -1450,13 +1469,13 @@ abstract class DLCWallet
announcements = announcementTLVs, announcements = announcementTLVs,
attestments = sigs.toVector) attestments = sigs.toVector)
tx <- executeDLC(contractId, oracleSigs) tx <- executeDLC(dlcDb.contractIdOpt.get, oracleSigs)
} yield tx } yield tx
} }
override def executeDLC( override def executeDLC(
contractId: ByteVector, contractId: ByteVector,
oracleSigs: Vector[OracleSignatures]): Future[Transaction] = { oracleSigs: Vector[OracleSignatures]): Future[Option[Transaction]] = {
require(oracleSigs.nonEmpty, "Must provide at least one oracle signature") require(oracleSigs.nonEmpty, "Must provide at least one oracle signature")
dlcDAO.findByContractId(contractId).flatMap { dlcDAO.findByContractId(contractId).flatMap {
case None => case None =>
@ -1467,7 +1486,7 @@ abstract class DLCWallet
db.closingTxIdOpt match { db.closingTxIdOpt match {
case Some(txId) => case Some(txId) =>
transactionDAO.findByTxId(txId).flatMap { transactionDAO.findByTxId(txId).flatMap {
case Some(tx) => Future.successful(tx.transaction) case Some(tx) => Future.successful(Some(tx.transaction))
case None => createDLCExecutionTx(contractId, oracleSigs) case None => createDLCExecutionTx(contractId, oracleSigs)
} }
case None => case None =>
@ -1476,60 +1495,69 @@ abstract class DLCWallet
} }
} }
/** Returns a execution transaction if we can build one. Returns None
* if the dlc db state is incorrect, or we have pruned CET signatures
* from the database
*/
private def createDLCExecutionTx( private def createDLCExecutionTx(
contractId: ByteVector, contractId: ByteVector,
oracleSigs: Vector[OracleSignatures]): Future[Transaction] = { oracleSigs: Vector[OracleSignatures]): Future[Option[Transaction]] = {
require(oracleSigs.nonEmpty, "Must provide at least one oracle signature") require(oracleSigs.nonEmpty, "Must provide at least one oracle signature")
for { val setupStateOptF =
setupStateOpt <- dlcDataManagement.getDLCFundingData(contractId, txDAO = transactionDAO)
dlcDataManagement.getDLCFundingData(contractId, txDAO = transactionDAO) setupStateOptF.flatMap {
_ = require(setupStateOpt.isDefined, case None => Future.successful(None)
s"Must have setup state defined to create execution tx") case Some(setupDbState) =>
_ = require( setupDbState match {
setupStateOpt.get.isInstanceOf[AcceptDbState], case o: OfferedDbState =>
s"Setup state must be accept to create dlc execution tx, got=${setupStateOpt.get.state}") logger.info(
setupState = setupStateOpt.get.asInstanceOf[AcceptDbState] s"Cannot create execution tx for dlc in state=${o.state}")
dlcDb = setupState.dlcDb Future.successful(None)
fundingInputs = setupState.allFundingInputs case c: SetupCompleteDLCDbState =>
scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs) val dlcDb = c.dlcDb
executorWithSetupOpt <- dlcDataManagement.executorAndSetupFromDb( val fundingInputs = c.allFundingInputs
contractId = contractId, val scriptSigParamsF = getScriptSigParams(dlcDb, fundingInputs)
txDAO = transactionDAO, val executorWithSetupOptF = scriptSigParamsF.flatMap {
fundingUtxoScriptSigParams = scriptSigParams, scriptSigParams =>
keyManager = keyManager) dlcDataManagement.executorAndSetupFromDb(
tx <- { contractId = contractId,
executorWithSetupOpt match { txDAO = transactionDAO,
case Some(executorWithSetup) => fundingUtxoScriptSigParams = scriptSigParams,
buildExecutionTxWithExecutor(executorWithSetup, keyManager = keyManager)
oracleSigs, }
contractId) executorWithSetupOptF.flatMap {
case None => case Some(executorWithSetup) =>
//means we don't have cet sigs in the db anymore buildExecutionTxWithExecutor(executorWithSetup,
//can we retrieve the CET some other way? oracleSigs,
contractId).map(Some(_))
case None =>
//means we don't have cet sigs in the db anymore
//can we retrieve the CET some other way?
//lets try to retrieve it from our transactionDAO //lets try to retrieve it from our transactionDAO
val dlcDbOptF = dlcDAO.findByContractId(contractId) val dlcDbOptF = dlcDAO.findByContractId(contractId)
for { for {
dlcDbOpt <- dlcDbOptF dlcDbOpt <- dlcDbOptF
_ = require( _ = require(
dlcDbOpt.isDefined, dlcDbOpt.isDefined,
s"Could not find dlc associated with this contractId=${contractId.toHex}") s"Could not find dlc associated with this contractId=${contractId.toHex}")
dlcDb = dlcDbOpt.get dlcDb = dlcDbOpt.get
_ = require( _ = require(
dlcDb.closingTxIdOpt.isDefined, dlcDb.closingTxIdOpt.isDefined,
s"If we don't have CET signatures, the closing tx must be defined, contractId=${contractId.toHex}") s"If we don't have CET signatures, the closing tx must be defined, contractId=${contractId.toHex}, state=${dlcDb.state}"
closingTxId = dlcDb.closingTxIdOpt.get )
closingTxOpt <- transactionDAO.findByTxId(closingTxId) closingTxId = dlcDb.closingTxIdOpt.get
} yield { closingTxOpt <- transactionDAO.findByTxId(closingTxId)
require( } yield {
closingTxOpt.isDefined, require(
s"Could not find closing tx for DLC in db, contactId=${contractId.toHex} closingTxId=${closingTxId.hex}") closingTxOpt.isDefined,
closingTxOpt.get.transaction s"Could not find closing tx for DLC in db, contactId=${contractId.toHex} closingTxId=${closingTxId.hex}")
Some(closingTxOpt.get.transaction)
}
} }
} }
} }
} yield tx
} }
private def buildExecutionTxWithExecutor( private def buildExecutionTxWithExecutor(

View file

@ -269,6 +269,37 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
} }
} }
private def buildAcceptDbState(
offerDbState: OfferedDbState,
dlcAccept: DLCAcceptDb,
acceptInputs: Vector[DLCFundingInputDb],
cetSignatures: Vector[DLCCETSignaturesDb],
refundSigDb: DLCRefundSigsDb,
txDAO: TransactionDAO): Future[AcceptDbState] = {
val signaturesOpt = {
if (cetSignatures.isEmpty) {
//means we have pruned signatures from the database
//we have to return None
None
} else {
Some(cetSignatures)
}
}
val acceptPrevTxsDbF =
getAcceptPrevTxs(offerDbState.dlcDb, acceptInputs, txDAO)
acceptPrevTxsDbF.map { case acceptPrevTxs =>
offerDbState.toAcceptDb(
acceptDb = dlcAccept,
acceptFundingInputsDb = acceptInputs,
acceptPrevTxsDb = acceptPrevTxs,
cetSigsOpt = signaturesOpt,
refundSigDb = refundSigDb
)
}
}
private[wallet] def getDLCFundingData( private[wallet] def getDLCFundingData(
dlcId: Sha256Digest, dlcId: Sha256Digest,
txDAO: TransactionDAO): Future[Option[DLCSetupDbState]] = { txDAO: TransactionDAO): Future[Option[DLCSetupDbState]] = {
@ -282,40 +313,54 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
for { for {
offerDbState <- offerDbStateOpt offerDbState <- offerDbStateOpt
} yield { } yield {
//if the accept message is defined we must have refund sigs offerDbState.dlcDb.state match {
dlcAcceptOpt.zip(refundSigsOpt).headOption match { case DLCState.Offered | DLCState.AcceptComputingAdaptorSigs =>
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)
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) Future.successful(offerDbState)
case DLCState.Accepted | DLCState.SignComputingAdaptorSigs =>
//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}")
buildAcceptDbState(offerDbState = offerDbState,
dlcAccept = dlcAccept,
acceptInputs = acceptInputs,
cetSignatures = cetSignatures,
refundSigDb = refundSigDb,
txDAO = txDAO)
case None =>
//just return the offerDbState if we don't have an accept
Future.successful(offerDbState)
}
case DLCState.Signed | DLCState.Confirmed | DLCState.Broadcasted |
_: DLCState.ClosedState =>
//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 acceptDbStateF = buildAcceptDbState(
offerDbState = offerDbState,
dlcAccept = dlcAccept,
acceptInputs = acceptInputs,
cetSignatures = cetSignatures,
refundSigDb = refundSigDb,
txDAO = txDAO)
val signDbF = for {
acceptDbState <- acceptDbStateF
} yield {
acceptDbState.toSignDbOpt.get
}
signDbF
case None =>
//just return the offerDbState if we don't have an accept
Future.successful(offerDbState)
}
} }
} }
} }
@ -330,7 +375,7 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
private[wallet] def getAllDLCData( private[wallet] def getAllDLCData(
contractId: ByteVector, contractId: ByteVector,
txDAO: TransactionDAO): Future[Option[DLCClosedDbState]] = { txDAO: TransactionDAO): Future[Option[DLCDbState]] = {
val resultF = for { val resultF = for {
dlcDbOpt <- dlcDAO.findByContractId(contractId) dlcDbOpt <- dlcDAO.findByContractId(contractId)
closedDbStateOptNested = dlcDbOpt.map(d => getAllDLCData(d.dlcId, txDAO)) closedDbStateOptNested = dlcDbOpt.map(d => getAllDLCData(d.dlcId, txDAO))
@ -345,7 +390,7 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
private[wallet] def getAllDLCData( private[wallet] def getAllDLCData(
dlcId: Sha256Digest, dlcId: Sha256Digest,
txDAO: TransactionDAO): Future[Option[DLCClosedDbState]] = { txDAO: TransactionDAO): Future[Option[DLCDbState]] = {
val sigDLCsF = dlcSigsDAO.findByDLCId(dlcId) val sigDLCsF = dlcSigsDAO.findByDLCId(dlcId)
for { for {
@ -356,12 +401,27 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
val sigsOpt = if (sigs.isEmpty) None else Some(sigs) val sigsOpt = if (sigs.isEmpty) None else Some(sigs)
val closedState = setupStateOpt.flatMap { val closedState = setupStateOpt.flatMap {
case acceptState: AcceptDbState => case acceptState: AcceptDbState =>
val closedState = acceptState.state match {
DLCClosedDbState.fromSetupState(acceptState, sigsOpt) case _: DLCState.ClosedState =>
Some(closedState) val closedState =
case _: OfferedDbState => DLCClosedDbState.fromCompleteSetupState(acceptState, sigsOpt)
//cannot return a closed state because we haven't seen the accept message Some(closedState)
None case _: DLCState.InProgressState =>
Some(acceptState)
}
case signState: SignDbState =>
signState.state match {
case _: DLCState.ClosedState =>
val closedState =
DLCClosedDbState.fromCompleteSetupState(signState, sigsOpt)
Some(closedState)
case _: DLCState.InProgressState =>
Some(signState)
}
case o: OfferedDbState =>
Some(o)
} }
closedState closedState
} }
@ -409,12 +469,13 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
def getOfferAndAcceptWithoutSigs( def getOfferAndAcceptWithoutSigs(
dlcId: Sha256Digest, dlcId: Sha256Digest,
txDAO: TransactionDAO): Future[Option[AcceptDbState]] = { txDAO: TransactionDAO): Future[Option[SetupCompleteDLCDbState]] = {
val dataF: Future[Option[DLCSetupDbState]] = getDLCFundingData(dlcId, txDAO) val dataF: Future[Option[DLCSetupDbState]] = getDLCFundingData(dlcId, txDAO)
dataF.map { dataF.map {
case Some(setupDbState) => case Some(setupDbState) =>
setupDbState match { setupDbState match {
case a: AcceptDbState => Some(a) case a: AcceptDbState => Some(a)
case s: SignDbState => Some(s)
case _: OfferedDbState => None case _: OfferedDbState => None
} }
case None => None case None => None
@ -427,9 +488,10 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
for { for {
setupStateOpt <- getOfferAndAcceptWithoutSigs(dlcDb.dlcId, transactionDAO) setupStateOpt <- getOfferAndAcceptWithoutSigs(dlcDb.dlcId, transactionDAO)
} yield { } yield {
setupStateOpt.map { acceptDbState => setupStateOpt.map { completeSetupDLCDbState =>
val txBuilder = DLCTxBuilder(offer = acceptDbState.offer, val txBuilder =
accept = acceptDbState.acceptWithoutSigs) DLCTxBuilder(offer = completeSetupDLCDbState.offer,
accept = completeSetupDLCDbState.acceptWithoutSigs)
txBuilder txBuilder
} }
} }
@ -573,10 +635,32 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams, fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
keyManager = keyManager keyManager = keyManager
) )
case c: SetupCompleteDLCDbState =>
c.cetSigsOpt match {
case Some(cetSigs) =>
executorAndSetupFromDb(
dlcDb = c.dlcDb,
refundSigsDb = c.refundSigDb,
fundingInputs = c.allFundingInputs,
outcomeSigsDbs = cetSigs,
transactionDAO = txDAO,
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
keyManager = keyManager
)
case None =>
//we don't have cet signatures for the
//sign message any more
Future.successful(None)
}
case _: ClosedDbStateNoCETSigs => case _: ClosedDbStateNoCETSigs =>
//means we cannot re-create messages because //means we cannot re-create messages because
//we don't have the cets in the database anymore //we don't have the cets in the database anymore
Future.successful(None) Future.successful(None)
case _: OfferedDbState =>
//means we cannot recreate messages because
//we don't have an accept or sign message in the database
Future.successful(None)
} }
case None => Future.successful(None) case None => Future.successful(None)
} }

View file

@ -30,9 +30,6 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
self: DLCWallet => self: DLCWallet =>
private lazy val safeDatabase: SafeDatabase = dlcDAO.safeDatabase private lazy val safeDatabase: SafeDatabase = dlcDAO.safeDatabase
private lazy val dlcDataManagement: DLCDataManagement = DLCDataManagement(
dlcWalletDAOs)
/** Calculates the new state of the DLCDb based on the closing transaction, /** Calculates the new state of the DLCDb based on the closing transaction,
* will delete old CET sigs that are no longer needed after execution * 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 * @return a DLCDb if we can calculate the state, else None if we cannot calculate the state
@ -147,9 +144,9 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
setupStateOpt <- dlcDataManagement.getDLCFundingData(dlcId, setupStateOpt <- dlcDataManagement.getDLCFundingData(dlcId,
txDAO = txDAO =
transactionDAO) transactionDAO)
acceptDbState = { completeDbState = {
setupStateOpt.get match { setupStateOpt.get match {
case accept: AcceptDbState => accept case c: SetupCompleteDLCDbState => c
case offered: OfferedDbState => case offered: OfferedDbState =>
sys.error( sys.error(
s"Cannot calculate and set outcome of dlc that is only offered, id=${offered.dlcDb.dlcId.hex}") s"Cannot calculate and set outcome of dlc that is only offered, id=${offered.dlcDb.dlcId.hex}")
@ -165,7 +162,7 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
.map(_.get.transaction.asInstanceOf[WitnessTransaction]) .map(_.get.transaction.asInstanceOf[WitnessTransaction])
sigAndOutcome = recoverSigAndOutcomeForRemoteClaimed( sigAndOutcome = recoverSigAndOutcomeForRemoteClaimed(
acceptDbState = acceptDbState, completeDbState = completeDbState,
cet = cet, cet = cet,
sigDbs = sigDbs, sigDbs = sigDbs,
refundSigsDbOpt = refundSigOpt) refundSigsDbOpt = refundSigOpt)
@ -384,25 +381,25 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
* so we do not necessarily have access to what the [[OracleAttestment]] is * so we do not necessarily have access to what the [[OracleAttestment]] is
*/ */
private def recoverSigAndOutcomeForRemoteClaimed( private def recoverSigAndOutcomeForRemoteClaimed(
acceptDbState: AcceptDbState, completeDbState: SetupCompleteDLCDbState,
cet: WitnessTransaction, cet: WitnessTransaction,
sigDbs: Vector[DLCCETSignaturesDb], sigDbs: Vector[DLCCETSignaturesDb],
refundSigsDbOpt: Option[DLCRefundSigsDb]): ( refundSigsDbOpt: Option[DLCRefundSigsDb]): (
SchnorrDigitalSignature, SchnorrDigitalSignature,
OracleOutcome) = { OracleOutcome) = {
val dlcDb = acceptDbState.dlcDb val dlcDb = completeDbState.dlcDb
val dlcId = dlcDb.dlcId val dlcId = dlcDb.dlcId
val isInit = dlcDb.isInitiator val isInit = dlcDb.isInitiator
val offer = acceptDbState.offer val offer = completeDbState.offer
val acceptOpt = acceptDbState.acceptOpt val acceptOpt = completeDbState.acceptOpt
require( require(
acceptOpt.isDefined, acceptOpt.isDefined,
s"Accept message must still have CET signatures to recover an outcome on chain, dlcId=${dlcId.hex}") s"Accept message must still have CET signatures to recover an outcome on chain, dlcId=${dlcId.hex}")
val accept = acceptOpt.get val accept = acceptOpt.get
val fundingInputDbs = acceptDbState.allFundingInputs val fundingInputDbs = completeDbState.allFundingInputs
val offerRefundSigOpt = refundSigsDbOpt.flatMap(_.initiatorSig) val offerRefundSigOpt = refundSigsDbOpt.flatMap(_.initiatorSig)
val signOpt: Option[DLCSign] = offerRefundSigOpt.map { refundSig => val signOpt: Option[DLCSign] = offerRefundSigOpt.map { refundSig =>

View file

@ -7,10 +7,6 @@ import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
DLCAcceptWithoutSigs, DLCAcceptWithoutSigs,
DLCOffer DLCOffer
} }
import org.bitcoins.core.protocol.dlc.models.DLCState.{
ClosedState,
InProgressState
}
import org.bitcoins.core.protocol.dlc.models.{ import org.bitcoins.core.protocol.dlc.models.{
CETSignatures, CETSignatures,
ContractInfo, ContractInfo,
@ -39,19 +35,17 @@ sealed trait DLCDbState {
contractDataDb = contractDataDb) contractDataDb = contractDataDb)
} }
def state: DLCState final def state: DLCState = dlcDb.state
} }
/** Represents a DLC in the database that /** Represents a DLC in the database that
* has not had its funding transaction published. * has not had its funding transaction published.
* This means we are still setting up the DLC * This means we are still setting up the DLC
*/ */
sealed trait DLCSetupDbState extends DLCDbState { sealed trait DLCSetupDbState extends DLCDbState
override def state: InProgressState
}
/** Represents a DLC in the database that has /** Represents a DLC in the database that has
* been fully setup and is in progress * been fully setup and settled
*/ */
sealed trait DLCClosedDbState extends DLCDbState sealed trait DLCClosedDbState extends DLCDbState
@ -63,10 +57,6 @@ case class OfferedDbState(
offerFundingInputsDb: Vector[DLCFundingInputDb], offerFundingInputsDb: Vector[DLCFundingInputDb],
offerPrevTxs: Vector[TransactionDb]) offerPrevTxs: Vector[TransactionDb])
extends DLCSetupDbState { 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]] /** Converts a [[OfferedDbState]] to an [[AcceptDbState]]
* @param acceptDb * @param acceptDb
@ -78,7 +68,7 @@ case class OfferedDbState(
acceptDb: DLCAcceptDb, acceptDb: DLCAcceptDb,
acceptFundingInputsDb: Vector[DLCFundingInputDb], acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxsDb: Vector[TransactionDb], acceptPrevTxsDb: Vector[TransactionDb],
cetSignaturesOpt: Option[CETSignatures], cetSigsOpt: Option[Vector[DLCCETSignaturesDb]],
refundSigDb: DLCRefundSigsDb): AcceptDbState = { refundSigDb: DLCRefundSigsDb): AcceptDbState = {
AcceptDbState( AcceptDbState(
dlcDb = dlcDb, dlcDb = dlcDb,
@ -90,12 +80,78 @@ case class OfferedDbState(
offerPrevTxs = offerPrevTxs, offerPrevTxs = offerPrevTxs,
acceptFundingInputsDb = acceptFundingInputsDb, acceptFundingInputsDb = acceptFundingInputsDb,
acceptPrevTxs = acceptPrevTxsDb, acceptPrevTxs = acceptPrevTxsDb,
cetSignaturesOpt = cetSignaturesOpt, cetSigsOpt = cetSigsOpt,
refundSigDb = refundSigDb refundSigDb = refundSigDb
) )
} }
} }
/** Shared data structured when we have all information to build a funding
* transaction for a discreet log contract
*/
sealed trait SetupCompleteDLCDbState extends DLCSetupDbState {
def dlcDb: DLCDb
def contractDataDb: DLCContractDataDb
def contractInfo: ContractInfo
def offerDb: DLCOfferDb
def acceptDb: DLCAcceptDb
def offerFundingInputsDb: Vector[DLCFundingInputDb]
def offerPrevTxs: Vector[TransactionDb]
def acceptFundingInputsDb: Vector[DLCFundingInputDb]
def acceptPrevTxs: Vector[TransactionDb]
def refundSigDb: DLCRefundSigsDb
def cetSigsOpt: Option[Vector[DLCCETSignaturesDb]]
def allFundingInputs: Vector[DLCFundingInputDb]
def acceptFundingInputs: Vector[DLCFundingInput] = {
DLCTxUtil.matchPrevTxsWithInputs(acceptFundingInputsDb, acceptPrevTxs)
}
def acceptWithoutSigs: DLCAcceptWithoutSigs = {
acceptDb.toDLCAcceptWithoutSigs(
tempContractId = dlcDb.tempContractId,
fundingInputs = acceptFundingInputs
)
}
def cetSignaturesOpt: Option[CETSignatures] = {
cetSigsOpt.map { cetSigs =>
this match {
case _: AcceptDbState =>
acceptCETSigsOpt.get
case _: SignDbState =>
CETSignatures(cetSigs.map(c => (c.sigPoint, c.initiatorSig.get)))
}
}
}
def acceptCETSigsOpt: Option[CETSignatures] = {
cetSigsOpt.map { cetSigs =>
CETSignatures(cetSigs.map(c => (c.sigPoint, c.accepterSig)))
}
}
def offererCETSigsOpt: Option[CETSignatures] = {
cetSigsOpt.map { cetSigs =>
CETSignatures(cetSigs.map(c => (c.sigPoint, c.initiatorSig.get)))
}
}
/** 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] = {
acceptCETSigsOpt.map { cetSignatures =>
acceptDb.toDLCAccept(dlcDb.tempContractId,
acceptFundingInputs,
outcomeSigs = cetSignatures.outcomeSigs,
refundSig = refundSigDb.accepterSig)
}
}
}
case class AcceptDbState( case class AcceptDbState(
dlcDb: DLCDb, dlcDb: DLCDb,
contractDataDb: DLCContractDataDb, contractDataDb: DLCContractDataDb,
@ -106,26 +162,11 @@ case class AcceptDbState(
offerPrevTxs: Vector[TransactionDb], offerPrevTxs: Vector[TransactionDb],
acceptFundingInputsDb: Vector[DLCFundingInputDb], acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxs: Vector[TransactionDb], acceptPrevTxs: Vector[TransactionDb],
cetSignaturesOpt: Option[CETSignatures], cetSigsOpt: Option[Vector[DLCCETSignaturesDb]],
refundSigDb: DLCRefundSigsDb) refundSigDb: DLCRefundSigsDb)
extends DLCSetupDbState { extends SetupCompleteDLCDbState {
//require(dlcDb.state == DLCState.Accepted,
// s"OfferedDbState requires state accepted, got=${dlcDb.state}")
override val state: DLCState.Accepted.type = DLCState.Accepted override val allFundingInputs: Vector[DLCFundingInputDb] =
val acceptFundingInputs: Vector[DLCFundingInput] = {
DLCTxUtil.matchPrevTxsWithInputs(acceptFundingInputsDb, acceptPrevTxs)
}
val acceptWithoutSigs: DLCAcceptWithoutSigs = {
acceptDb.toDLCAcceptWithoutSigs(
tempContractId = dlcDb.tempContractId,
fundingInputs = acceptFundingInputs
)
}
val allFundingInputs: Vector[DLCFundingInputDb] =
offerFundingInputsDb ++ acceptFundingInputsDb offerFundingInputsDb ++ acceptFundingInputsDb
val remotePrevTxs: Vector[TransactionDb] = { val remotePrevTxs: Vector[TransactionDb] = {
@ -138,20 +179,64 @@ case class AcceptDbState(
else acceptPrevTxs else acceptPrevTxs
} }
/** Reconstructs the [[DLCAccept]] message if we have [[CETSignatures]] /** Converts the AcceptDbState -> SignDbState if we have
* in the database. If we don't have the signatures because we have pruned * all parties CET signatures and refund signatures
* them we return None as we can't reconstruct the message
*/ */
def acceptOpt: Option[DLCAccept] = { def toSignDbOpt: Option[SignDbState] = {
cetSignaturesOpt.map { cetSignatures =>
acceptDb.toDLCAccept(dlcDb.tempContractId, //if we haven't pruned CET signatures from the db
acceptFundingInputs, //they must have the offerer's CET signatures defined
outcomeSigs = cetSignatures.outcomeSigs, cetSigsOpt.map { cetSigs =>
refundSig = refundSigDb.accepterSig) require(cetSigs.forall(_.initiatorSig.isDefined),
s"CET signatures must be defined for the offerer")
}
//if we don't have a refund signature from the offerer
//yet we haven't completed the sign message
refundSigDb.initiatorSig.map { _ =>
val sign = SignDbState(
dlcDb,
contractDataDb,
contractInfo,
offerDb,
acceptDb = acceptDb,
offerFundingInputsDb = offerFundingInputsDb,
offerPrevTxs = offerPrevTxs,
acceptFundingInputsDb = acceptFundingInputsDb,
acceptPrevTxs = acceptPrevTxs,
refundSigDb = refundSigDb,
cetSigsOpt = cetSigsOpt
)
sign
} }
} }
} }
case class SignDbState(
dlcDb: DLCDb,
contractDataDb: DLCContractDataDb,
contractInfo: ContractInfo,
offerDb: DLCOfferDb,
acceptDb: DLCAcceptDb,
offerFundingInputsDb: Vector[DLCFundingInputDb],
offerPrevTxs: Vector[TransactionDb],
acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxs: Vector[TransactionDb],
refundSigDb: DLCRefundSigsDb,
cetSigsOpt: Option[Vector[DLCCETSignaturesDb]]
) extends SetupCompleteDLCDbState {
require(refundSigDb.initiatorSig.isDefined,
s"Refund signature for offerer must be defined")
//If we have not prune CET signatures, the offerer CET signatures must be defined
cetSigsOpt.map(cetSigs =>
require(cetSigs.forall(_.initiatorSig.isDefined),
s"Offerer CET signatures must be defined when in SignDbState"))
override val allFundingInputs: Vector[DLCFundingInputDb] =
offerFundingInputsDb ++ acceptFundingInputsDb
}
case class ClosedDbStateWithCETSigs( case class ClosedDbStateWithCETSigs(
dlcDb: DLCDb, dlcDb: DLCDb,
contractDataDb: DLCContractDataDb, contractDataDb: DLCContractDataDb,
@ -165,11 +250,6 @@ case class ClosedDbStateWithCETSigs(
refundSigsDb: DLCRefundSigsDb, refundSigsDb: DLCRefundSigsDb,
cetSigs: Vector[DLCCETSignaturesDb] cetSigs: Vector[DLCCETSignaturesDb]
) extends DLCClosedDbState { ) 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] = val allFundingInputs: Vector[DLCFundingInputDb] =
offerFundingInputsDb ++ acceptFundingInputsDb offerFundingInputsDb ++ acceptFundingInputsDb
@ -189,47 +269,40 @@ case class ClosedDbStateNoCETSigs(
acceptFundingInputsDb: Vector[DLCFundingInputDb], acceptFundingInputsDb: Vector[DLCFundingInputDb],
acceptPrevTxs: Vector[TransactionDb], acceptPrevTxs: Vector[TransactionDb],
refundSigsDb: DLCRefundSigsDb) refundSigsDb: DLCRefundSigsDb)
extends DLCClosedDbState { 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 { object DLCClosedDbState {
def fromSetupState( def fromCompleteSetupState(
acceptState: AcceptDbState, completeState: SetupCompleteDLCDbState,
cetSigsOpt: Option[Vector[DLCCETSignaturesDb]]): DLCClosedDbState = { cetSigsOpt: Option[Vector[DLCCETSignaturesDb]]): DLCClosedDbState = {
cetSigsOpt match { cetSigsOpt match {
case Some(cetSigs) => case Some(cetSigs) =>
ClosedDbStateWithCETSigs( ClosedDbStateWithCETSigs(
acceptState.dlcDb, completeState.dlcDb,
acceptState.contractDataDb, completeState.contractDataDb,
acceptState.contractInfo, completeState.contractInfo,
acceptState.offerDb, completeState.offerDb,
acceptState.acceptDb, completeState.acceptDb,
acceptState.offerFundingInputsDb, completeState.offerFundingInputsDb,
acceptState.offerPrevTxs, completeState.offerPrevTxs,
acceptState.acceptFundingInputsDb, completeState.acceptFundingInputsDb,
acceptState.acceptPrevTxs, completeState.acceptPrevTxs,
acceptState.refundSigDb, completeState.refundSigDb,
cetSigs cetSigs
) )
case None => case None =>
ClosedDbStateNoCETSigs( ClosedDbStateNoCETSigs(
acceptState.dlcDb, completeState.dlcDb,
acceptState.contractDataDb, completeState.contractDataDb,
acceptState.contractInfo, completeState.contractInfo,
acceptState.offerDb, completeState.offerDb,
acceptState.acceptDb, completeState.acceptDb,
acceptState.offerFundingInputsDb, completeState.offerFundingInputsDb,
acceptState.offerPrevTxs, completeState.offerPrevTxs,
acceptState.acceptFundingInputsDb, completeState.acceptFundingInputsDb,
acceptState.acceptPrevTxs, completeState.acceptPrevTxs,
acceptState.refundSigDb completeState.refundSigDb
) )
} }
} }