mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
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:
parent
27cb4a3c20
commit
37da24b94b
19 changed files with 550 additions and 236 deletions
|
@ -211,12 +211,7 @@ object Server {
|
|||
)
|
||||
}
|
||||
|
||||
def httpBadRequest(ex: Throwable): HttpResponse = {
|
||||
httpBadRequest(ex.getMessage)
|
||||
}
|
||||
|
||||
def httpBadRequest(msg: String): HttpResponse = {
|
||||
|
||||
def httpError(msg: String): HttpEntity.Strict = {
|
||||
val entity = {
|
||||
val response = Response(error = Some(msg))
|
||||
HttpEntity(
|
||||
|
@ -224,6 +219,15 @@ object Server {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1175,7 +1175,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
(mockWalletApi
|
||||
.executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV]))
|
||||
.expects(contractId, Vector(dummyOracleAttestment))
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
.returning(Future.successful(Some(EmptyTransaction)))
|
||||
|
||||
val route = walletRoutes.handleCommand(
|
||||
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 {
|
||||
(mockWalletApi
|
||||
.executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV]))
|
||||
.expects(contractId,
|
||||
Vector(dummyOracleAttestment, dummyOracleAttestment))
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
.returning(Future.successful(Some(EmptyTransaction)))
|
||||
|
||||
val route = walletRoutes.handleCommand(
|
||||
ServerCommand("executedlc",
|
||||
|
@ -1217,7 +1236,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
(mockWalletApi
|
||||
.executeDLC(_: ByteVector, _: Seq[OracleAttestmentTLV]))
|
||||
.expects(contractId, Vector(dummyOracleAttestment))
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
.returning(Future.successful(Some(EmptyTransaction)))
|
||||
|
||||
(mockWalletApi.broadcastTransaction _)
|
||||
.expects(EmptyTransaction)
|
||||
|
|
|
@ -537,10 +537,25 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
|||
case Success(ExecuteDLC(contractId, sigs, noBroadcast)) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.executeDLC(contractId, sigs)
|
||||
ret <- handleBroadcastable(tx, noBroadcast)
|
||||
txOpt <- wallet.executeDLC(contractId, sigs)
|
||||
retOpt <- {
|
||||
txOpt match {
|
||||
case Some(tx) =>
|
||||
handleBroadcastable(tx, noBroadcast)
|
||||
.map(_.hex)
|
||||
.map(Some(_))
|
||||
case None =>
|
||||
Future.successful(None)
|
||||
}
|
||||
}
|
||||
} 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,24 +86,24 @@ trait DLCWalletApi { self: WalletApi =>
|
|||
/** Creates the CET for the given contractId and oracle signature, does not broadcast it */
|
||||
def executeDLC(
|
||||
contractId: ByteVector,
|
||||
oracleSig: OracleAttestmentTLV): Future[Transaction] =
|
||||
oracleSig: OracleAttestmentTLV): Future[Option[Transaction]] =
|
||||
executeDLC(contractId, Vector(oracleSig))
|
||||
|
||||
/** Creates the CET for the given contractId and oracle signature, does not broadcast it */
|
||||
def executeDLC(
|
||||
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 */
|
||||
def executeDLC(
|
||||
contractId: ByteVector,
|
||||
oracleSig: OracleSignatures): Future[Transaction] =
|
||||
oracleSig: OracleSignatures): Future[Option[Transaction]] =
|
||||
executeDLC(contractId, Vector(oracleSig))
|
||||
|
||||
/** Creates the CET for the given contractId and oracle signature, does not broadcast it */
|
||||
def executeDLC(
|
||||
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 */
|
||||
def executeDLCRefund(contractId: ByteVector): Future[Transaction]
|
||||
|
|
|
@ -74,7 +74,7 @@ class DLCExecutionBitcoindBackendTest
|
|||
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
|
||||
_ <- dlcB.broadcastTransaction(closingTx)
|
||||
dlcs <- dlcB
|
||||
|
|
|
@ -108,7 +108,8 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
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,
|
||||
asInitiator = true,
|
||||
|
@ -153,7 +154,8 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
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,
|
||||
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,
|
||||
asInitiator = true,
|
||||
|
@ -406,7 +409,8 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
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,
|
||||
asInitiator = true,
|
||||
|
@ -452,7 +456,7 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
sigs = badSigs,
|
||||
outcomes = badOutcomes)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, badAttestment)
|
||||
wallet.executeDLC(contractId, badAttestment).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = true,
|
||||
|
|
|
@ -112,7 +112,8 @@ class DLCMultiOracleEnumExecutionTest extends BitcoinSDualWalletTest {
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
(sig, _) = getSigs
|
||||
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,
|
||||
asInitiator = true,
|
||||
|
@ -151,7 +152,8 @@ class DLCMultiOracleEnumExecutionTest extends BitcoinSDualWalletTest {
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
(_, sig) = getSigs
|
||||
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,
|
||||
asInitiator = false,
|
||||
|
|
|
@ -105,7 +105,8 @@ class DLCMultiOracleExactNumericExecutionTest extends BitcoinSDualWalletTest {
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._1.wallet)
|
||||
(sigs, _) = getSigs(status.contractInfo)
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, sigs).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = true,
|
||||
|
@ -144,7 +145,8 @@ class DLCMultiOracleExactNumericExecutionTest extends BitcoinSDualWalletTest {
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._2.wallet)
|
||||
(_, sigs) = getSigs(status.contractInfo)
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, sigs).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = false,
|
||||
|
|
|
@ -114,7 +114,8 @@ class DLCMultiOracleNumericExecutionTest
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._1.wallet)
|
||||
(sigs, _) = getSigs(status.contractInfo)
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, sigs).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = true,
|
||||
|
@ -153,7 +154,8 @@ class DLCMultiOracleNumericExecutionTest
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._2.wallet)
|
||||
(_, sigs) = getSigs(status.contractInfo)
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, sigs).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = false,
|
||||
|
|
|
@ -91,7 +91,8 @@ class DLCNumericExecutionTest extends BitcoinSDualWalletTest {
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._1.wallet)
|
||||
(sigs, _) = getSigs(status.contractInfo)
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, sigs).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = true,
|
||||
|
@ -130,7 +131,8 @@ class DLCNumericExecutionTest extends BitcoinSDualWalletTest {
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._2.wallet)
|
||||
(_, sigs) = getSigs(status.contractInfo)
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sigs)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, sigs).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = false,
|
||||
|
@ -181,7 +183,7 @@ class DLCNumericExecutionTest extends BitcoinSDualWalletTest {
|
|||
sigs = badSigs,
|
||||
outcomes = badOutcomes)
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(contractId, badAttestment)
|
||||
wallet.executeDLC(contractId, badAttestment).map(_.get)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
asInitiator = false,
|
||||
|
|
|
@ -106,7 +106,7 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
|
|||
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)
|
||||
} yield ()
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
|
|||
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),
|
||||
asInitiator = true,
|
||||
|
@ -79,7 +80,8 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
|
|||
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),
|
||||
asInitiator = true,
|
||||
|
|
|
@ -816,7 +816,8 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
tx <- walletB.broadcastDLCFundingTx(sign.contractId)
|
||||
_ <- 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,
|
||||
dlcB = walletB,
|
||||
asInitiator = true,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,9 +13,9 @@ 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
|
||||
OfferedDbState,
|
||||
SetupCompleteDLCDbState
|
||||
}
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.bitcoins.wallet.models.TransactionDAO
|
||||
|
@ -176,7 +176,7 @@ case class DLCAppConfig(baseDatadir: Path, configOverrides: Vector[Config])(
|
|||
vec: Vector[DLCSetupDbState]): Vector[DLCDb] = {
|
||||
vec.map { case state: DLCSetupDbState =>
|
||||
val updatedDlcDb: DLCDb = state match {
|
||||
case acceptDbState: AcceptDbState =>
|
||||
case acceptDbState: SetupCompleteDLCDbState =>
|
||||
val offer = acceptDbState.offer
|
||||
val acceptWithoutSigs = acceptDbState.acceptWithoutSigs
|
||||
val dlcDb = acceptDbState.dlcDb
|
||||
|
|
|
@ -89,7 +89,7 @@ abstract class DLCWallet
|
|||
incomingOfferDAO
|
||||
)
|
||||
|
||||
private val dlcDataManagement = DLCDataManagement(dlcWalletDAOs)
|
||||
private[wallet] val dlcDataManagement = DLCDataManagement(dlcWalletDAOs)
|
||||
|
||||
protected lazy val actionBuilder: DLCActionBuilder = {
|
||||
DLCActionBuilder(dlcWalletDAOs)
|
||||
|
@ -482,6 +482,9 @@ abstract class DLCWallet
|
|||
Future.failed(
|
||||
new RuntimeException(
|
||||
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 =>
|
||||
val dlcPubKeys = DLCUtil.calcDLCPubKeys(
|
||||
xpub = account.xpub,
|
||||
|
@ -1327,17 +1330,17 @@ abstract class DLCWallet
|
|||
setupStateOpt <- dlcDataManagement.getDLCFundingData(contractId,
|
||||
txDAO =
|
||||
transactionDAO)
|
||||
acceptState = {
|
||||
complete = {
|
||||
setupStateOpt.map {
|
||||
case _: OfferedDbState =>
|
||||
sys.error(
|
||||
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
|
||||
}
|
||||
dlcDb = acceptState.dlcDb
|
||||
dlcDb = complete.dlcDb
|
||||
//is this right? We don't have counterpart scriptSigParams
|
||||
fundingInputs = acceptState.allFundingInputs
|
||||
fundingInputs = complete.allFundingInputs
|
||||
scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs)
|
||||
signerOpt <- dlcDataManagement.signerFromDb(
|
||||
dlcDb = dlcDb,
|
||||
|
@ -1423,14 +1426,29 @@ abstract class DLCWallet
|
|||
|
||||
override def executeDLC(
|
||||
contractId: ByteVector,
|
||||
sigs: Seq[OracleAttestmentTLV]): Future[Transaction] = {
|
||||
sigs: Seq[OracleAttestmentTLV]): Future[Option[Transaction]] = {
|
||||
logger.info(
|
||||
s"Executing dlc with contractId=${contractId.toHex} sigs=${sigs.map(_.hex)}")
|
||||
require(sigs.nonEmpty, "Must provide at least one oracle signature")
|
||||
|
||||
val dlcDbOpt = dlcDAO.findByContractId(contractId)
|
||||
for {
|
||||
dlcDb <- dlcDAO.findByContractId(contractId).map(_.get)
|
||||
_ = dlcDb.state match {
|
||||
dlcDbOpt <- dlcDbOpt
|
||||
txOpt <- {
|
||||
dlcDbOpt match {
|
||||
case Some(dlcDb) =>
|
||||
executeDLC(dlcDb, sigs)
|
||||
case None =>
|
||||
Future.successful(None)
|
||||
}
|
||||
|
||||
}
|
||||
} 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(
|
||||
|
@ -1438,6 +1456,7 @@ abstract class DLCWallet
|
|||
case Broadcasted | Confirmed | _: ClosedState =>
|
||||
//can continue executing, do nothing
|
||||
}
|
||||
for {
|
||||
(announcements, announcementData, nonceDbs) <- dlcDataManagement
|
||||
.getDLCAnnouncementDbs(dlcDb.dlcId)
|
||||
|
||||
|
@ -1450,13 +1469,13 @@ abstract class DLCWallet
|
|||
announcements = announcementTLVs,
|
||||
attestments = sigs.toVector)
|
||||
|
||||
tx <- executeDLC(contractId, oracleSigs)
|
||||
tx <- executeDLC(dlcDb.contractIdOpt.get, oracleSigs)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def executeDLC(
|
||||
contractId: ByteVector,
|
||||
oracleSigs: Vector[OracleSignatures]): Future[Transaction] = {
|
||||
oracleSigs: Vector[OracleSignatures]): Future[Option[Transaction]] = {
|
||||
require(oracleSigs.nonEmpty, "Must provide at least one oracle signature")
|
||||
dlcDAO.findByContractId(contractId).flatMap {
|
||||
case None =>
|
||||
|
@ -1467,7 +1486,7 @@ abstract class DLCWallet
|
|||
db.closingTxIdOpt match {
|
||||
case Some(txId) =>
|
||||
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 =>
|
||||
|
@ -1476,33 +1495,41 @@ 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(
|
||||
contractId: ByteVector,
|
||||
oracleSigs: Vector[OracleSignatures]): Future[Transaction] = {
|
||||
oracleSigs: Vector[OracleSignatures]): Future[Option[Transaction]] = {
|
||||
require(oracleSigs.nonEmpty, "Must provide at least one oracle signature")
|
||||
for {
|
||||
setupStateOpt <-
|
||||
val setupStateOptF =
|
||||
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(
|
||||
setupStateOptF.flatMap {
|
||||
case None => Future.successful(None)
|
||||
case Some(setupDbState) =>
|
||||
setupDbState match {
|
||||
case o: OfferedDbState =>
|
||||
logger.info(
|
||||
s"Cannot create execution tx for dlc in state=${o.state}")
|
||||
Future.successful(None)
|
||||
case c: SetupCompleteDLCDbState =>
|
||||
val dlcDb = c.dlcDb
|
||||
val fundingInputs = c.allFundingInputs
|
||||
val scriptSigParamsF = getScriptSigParams(dlcDb, fundingInputs)
|
||||
val executorWithSetupOptF = scriptSigParamsF.flatMap {
|
||||
scriptSigParams =>
|
||||
dlcDataManagement.executorAndSetupFromDb(
|
||||
contractId = contractId,
|
||||
txDAO = transactionDAO,
|
||||
fundingUtxoScriptSigParams = scriptSigParams,
|
||||
keyManager = keyManager)
|
||||
tx <- {
|
||||
executorWithSetupOpt match {
|
||||
}
|
||||
executorWithSetupOptF.flatMap {
|
||||
case Some(executorWithSetup) =>
|
||||
buildExecutionTxWithExecutor(executorWithSetup,
|
||||
oracleSigs,
|
||||
contractId)
|
||||
contractId).map(Some(_))
|
||||
case None =>
|
||||
//means we don't have cet sigs in the db anymore
|
||||
//can we retrieve the CET some other way?
|
||||
|
@ -1518,18 +1545,19 @@ abstract class DLCWallet
|
|||
dlcDb = dlcDbOpt.get
|
||||
_ = require(
|
||||
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)
|
||||
} yield {
|
||||
require(
|
||||
closingTxOpt.isDefined,
|
||||
s"Could not find closing tx for DLC in db, contactId=${contractId.toHex} closingTxId=${closingTxId.hex}")
|
||||
closingTxOpt.get.transaction
|
||||
Some(closingTxOpt.get.transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} yield tx
|
||||
}
|
||||
|
||||
private def buildExecutionTxWithExecutor(
|
||||
|
|
|
@ -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(
|
||||
dlcId: Sha256Digest,
|
||||
txDAO: TransactionDAO): Future[Option[DLCSetupDbState]] = {
|
||||
|
@ -282,41 +313,55 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
|
|||
for {
|
||||
offerDbState <- offerDbStateOpt
|
||||
} yield {
|
||||
offerDbState.dlcDb.state match {
|
||||
case DLCState.Offered | DLCState.AcceptComputingAdaptorSigs =>
|
||||
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}")
|
||||
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
|
||||
)
|
||||
}
|
||||
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(
|
||||
contractId: ByteVector,
|
||||
txDAO: TransactionDAO): Future[Option[DLCClosedDbState]] = {
|
||||
txDAO: TransactionDAO): Future[Option[DLCDbState]] = {
|
||||
val resultF = for {
|
||||
dlcDbOpt <- dlcDAO.findByContractId(contractId)
|
||||
closedDbStateOptNested = dlcDbOpt.map(d => getAllDLCData(d.dlcId, txDAO))
|
||||
|
@ -345,7 +390,7 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
|
|||
|
||||
private[wallet] def getAllDLCData(
|
||||
dlcId: Sha256Digest,
|
||||
txDAO: TransactionDAO): Future[Option[DLCClosedDbState]] = {
|
||||
txDAO: TransactionDAO): Future[Option[DLCDbState]] = {
|
||||
val sigDLCsF = dlcSigsDAO.findByDLCId(dlcId)
|
||||
|
||||
for {
|
||||
|
@ -356,12 +401,27 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
|
|||
val sigsOpt = if (sigs.isEmpty) None else Some(sigs)
|
||||
val closedState = setupStateOpt.flatMap {
|
||||
case acceptState: AcceptDbState =>
|
||||
acceptState.state match {
|
||||
case _: DLCState.ClosedState =>
|
||||
val closedState =
|
||||
DLCClosedDbState.fromSetupState(acceptState, sigsOpt)
|
||||
DLCClosedDbState.fromCompleteSetupState(acceptState, sigsOpt)
|
||||
Some(closedState)
|
||||
case _: OfferedDbState =>
|
||||
//cannot return a closed state because we haven't seen the accept message
|
||||
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
|
||||
}
|
||||
|
@ -409,12 +469,13 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
|
|||
|
||||
def getOfferAndAcceptWithoutSigs(
|
||||
dlcId: Sha256Digest,
|
||||
txDAO: TransactionDAO): Future[Option[AcceptDbState]] = {
|
||||
txDAO: TransactionDAO): Future[Option[SetupCompleteDLCDbState]] = {
|
||||
val dataF: Future[Option[DLCSetupDbState]] = getDLCFundingData(dlcId, txDAO)
|
||||
dataF.map {
|
||||
case Some(setupDbState) =>
|
||||
setupDbState match {
|
||||
case a: AcceptDbState => Some(a)
|
||||
case s: SignDbState => Some(s)
|
||||
case _: OfferedDbState => None
|
||||
}
|
||||
case None => None
|
||||
|
@ -427,9 +488,10 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
|
|||
for {
|
||||
setupStateOpt <- getOfferAndAcceptWithoutSigs(dlcDb.dlcId, transactionDAO)
|
||||
} yield {
|
||||
setupStateOpt.map { acceptDbState =>
|
||||
val txBuilder = DLCTxBuilder(offer = acceptDbState.offer,
|
||||
accept = acceptDbState.acceptWithoutSigs)
|
||||
setupStateOpt.map { completeSetupDLCDbState =>
|
||||
val txBuilder =
|
||||
DLCTxBuilder(offer = completeSetupDLCDbState.offer,
|
||||
accept = completeSetupDLCDbState.acceptWithoutSigs)
|
||||
txBuilder
|
||||
}
|
||||
}
|
||||
|
@ -573,10 +635,32 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
|
|||
fundingUtxoScriptSigParams = fundingUtxoScriptSigParams,
|
||||
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 =>
|
||||
//means we cannot re-create messages because
|
||||
//we don't have the cets in the database anymore
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -30,9 +30,6 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
|
|||
self: DLCWallet =>
|
||||
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,
|
||||
* 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
|
||||
|
@ -147,9 +144,9 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
|
|||
setupStateOpt <- dlcDataManagement.getDLCFundingData(dlcId,
|
||||
txDAO =
|
||||
transactionDAO)
|
||||
acceptDbState = {
|
||||
completeDbState = {
|
||||
setupStateOpt.get match {
|
||||
case accept: AcceptDbState => accept
|
||||
case c: SetupCompleteDLCDbState => c
|
||||
case offered: OfferedDbState =>
|
||||
sys.error(
|
||||
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])
|
||||
|
||||
sigAndOutcome = recoverSigAndOutcomeForRemoteClaimed(
|
||||
acceptDbState = acceptDbState,
|
||||
completeDbState = completeDbState,
|
||||
cet = cet,
|
||||
sigDbs = sigDbs,
|
||||
refundSigsDbOpt = refundSigOpt)
|
||||
|
@ -384,25 +381,25 @@ private[bitcoins] trait DLCTransactionProcessing extends TransactionProcessing {
|
|||
* so we do not necessarily have access to what the [[OracleAttestment]] is
|
||||
*/
|
||||
private def recoverSigAndOutcomeForRemoteClaimed(
|
||||
acceptDbState: AcceptDbState,
|
||||
completeDbState: SetupCompleteDLCDbState,
|
||||
cet: WitnessTransaction,
|
||||
sigDbs: Vector[DLCCETSignaturesDb],
|
||||
refundSigsDbOpt: Option[DLCRefundSigsDb]): (
|
||||
SchnorrDigitalSignature,
|
||||
OracleOutcome) = {
|
||||
val dlcDb = acceptDbState.dlcDb
|
||||
val dlcDb = completeDbState.dlcDb
|
||||
val dlcId = dlcDb.dlcId
|
||||
val isInit = dlcDb.isInitiator
|
||||
|
||||
val offer = acceptDbState.offer
|
||||
val offer = completeDbState.offer
|
||||
|
||||
val acceptOpt = acceptDbState.acceptOpt
|
||||
val acceptOpt = completeDbState.acceptOpt
|
||||
require(
|
||||
acceptOpt.isDefined,
|
||||
s"Accept message must still have CET signatures to recover an outcome on chain, dlcId=${dlcId.hex}")
|
||||
val accept = acceptOpt.get
|
||||
|
||||
val fundingInputDbs = acceptDbState.allFundingInputs
|
||||
val fundingInputDbs = completeDbState.allFundingInputs
|
||||
val offerRefundSigOpt = refundSigsDbOpt.flatMap(_.initiatorSig)
|
||||
|
||||
val signOpt: Option[DLCSign] = offerRefundSigOpt.map { refundSig =>
|
||||
|
|
|
@ -7,10 +7,6 @@ import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
|
|||
DLCAcceptWithoutSigs,
|
||||
DLCOffer
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCState.{
|
||||
ClosedState,
|
||||
InProgressState
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
CETSignatures,
|
||||
ContractInfo,
|
||||
|
@ -39,19 +35,17 @@ sealed trait DLCDbState {
|
|||
contractDataDb = contractDataDb)
|
||||
}
|
||||
|
||||
def state: DLCState
|
||||
final def state: DLCState = dlcDb.state
|
||||
}
|
||||
|
||||
/** 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
|
||||
}
|
||||
sealed trait DLCSetupDbState extends DLCDbState
|
||||
|
||||
/** 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
|
||||
|
||||
|
@ -63,10 +57,6 @@ case class OfferedDbState(
|
|||
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
|
||||
|
@ -78,7 +68,7 @@ case class OfferedDbState(
|
|||
acceptDb: DLCAcceptDb,
|
||||
acceptFundingInputsDb: Vector[DLCFundingInputDb],
|
||||
acceptPrevTxsDb: Vector[TransactionDb],
|
||||
cetSignaturesOpt: Option[CETSignatures],
|
||||
cetSigsOpt: Option[Vector[DLCCETSignaturesDb]],
|
||||
refundSigDb: DLCRefundSigsDb): AcceptDbState = {
|
||||
AcceptDbState(
|
||||
dlcDb = dlcDb,
|
||||
|
@ -90,12 +80,78 @@ case class OfferedDbState(
|
|||
offerPrevTxs = offerPrevTxs,
|
||||
acceptFundingInputsDb = acceptFundingInputsDb,
|
||||
acceptPrevTxs = acceptPrevTxsDb,
|
||||
cetSignaturesOpt = cetSignaturesOpt,
|
||||
cetSigsOpt = cetSigsOpt,
|
||||
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(
|
||||
dlcDb: DLCDb,
|
||||
contractDataDb: DLCContractDataDb,
|
||||
|
@ -106,26 +162,11 @@ case class AcceptDbState(
|
|||
offerPrevTxs: Vector[TransactionDb],
|
||||
acceptFundingInputsDb: Vector[DLCFundingInputDb],
|
||||
acceptPrevTxs: Vector[TransactionDb],
|
||||
cetSignaturesOpt: Option[CETSignatures],
|
||||
cetSigsOpt: Option[Vector[DLCCETSignaturesDb]],
|
||||
refundSigDb: DLCRefundSigsDb)
|
||||
extends DLCSetupDbState {
|
||||
//require(dlcDb.state == DLCState.Accepted,
|
||||
// s"OfferedDbState requires state accepted, got=${dlcDb.state}")
|
||||
extends SetupCompleteDLCDbState {
|
||||
|
||||
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] =
|
||||
override val allFundingInputs: Vector[DLCFundingInputDb] =
|
||||
offerFundingInputsDb ++ acceptFundingInputsDb
|
||||
|
||||
val remotePrevTxs: Vector[TransactionDb] = {
|
||||
|
@ -138,20 +179,64 @@ case class AcceptDbState(
|
|||
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
|
||||
/** Converts the AcceptDbState -> SignDbState if we have
|
||||
* all parties CET signatures and refund signatures
|
||||
*/
|
||||
def acceptOpt: Option[DLCAccept] = {
|
||||
cetSignaturesOpt.map { cetSignatures =>
|
||||
acceptDb.toDLCAccept(dlcDb.tempContractId,
|
||||
acceptFundingInputs,
|
||||
outcomeSigs = cetSignatures.outcomeSigs,
|
||||
refundSig = refundSigDb.accepterSig)
|
||||
def toSignDbOpt: Option[SignDbState] = {
|
||||
|
||||
//if we haven't pruned CET signatures from the db
|
||||
//they must have the offerer's CET signatures defined
|
||||
cetSigsOpt.map { cetSigs =>
|
||||
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(
|
||||
dlcDb: DLCDb,
|
||||
contractDataDb: DLCContractDataDb,
|
||||
|
@ -165,11 +250,6 @@ case class ClosedDbStateWithCETSigs(
|
|||
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
|
||||
|
@ -189,47 +269,40 @@ case class ClosedDbStateNoCETSigs(
|
|||
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]
|
||||
}
|
||||
extends DLCClosedDbState
|
||||
|
||||
object DLCClosedDbState {
|
||||
|
||||
def fromSetupState(
|
||||
acceptState: AcceptDbState,
|
||||
def fromCompleteSetupState(
|
||||
completeState: SetupCompleteDLCDbState,
|
||||
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,
|
||||
completeState.dlcDb,
|
||||
completeState.contractDataDb,
|
||||
completeState.contractInfo,
|
||||
completeState.offerDb,
|
||||
completeState.acceptDb,
|
||||
completeState.offerFundingInputsDb,
|
||||
completeState.offerPrevTxs,
|
||||
completeState.acceptFundingInputsDb,
|
||||
completeState.acceptPrevTxs,
|
||||
completeState.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
|
||||
completeState.dlcDb,
|
||||
completeState.contractDataDb,
|
||||
completeState.contractInfo,
|
||||
completeState.offerDb,
|
||||
completeState.acceptDb,
|
||||
completeState.offerFundingInputsDb,
|
||||
completeState.offerPrevTxs,
|
||||
completeState.acceptFundingInputsDb,
|
||||
completeState.acceptPrevTxs,
|
||||
completeState.refundSigDb
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue