mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +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 = {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 ()
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.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
|
||||||
|
|
|
@ -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,14 +1426,29 @@ 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 <- {
|
||||||
|
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 |
|
case state @ (Offered | AcceptComputingAdaptorSigs | Accepted |
|
||||||
SignComputingAdaptorSigs | Signed) =>
|
SignComputingAdaptorSigs | Signed) =>
|
||||||
sys.error(
|
sys.error(
|
||||||
|
@ -1438,6 +1456,7 @@ abstract class DLCWallet
|
||||||
case Broadcasted | Confirmed | _: ClosedState =>
|
case Broadcasted | Confirmed | _: ClosedState =>
|
||||||
//can continue executing, do nothing
|
//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,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(
|
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)
|
||||||
_ = require(setupStateOpt.isDefined,
|
setupStateOptF.flatMap {
|
||||||
s"Must have setup state defined to create execution tx")
|
case None => Future.successful(None)
|
||||||
_ = require(
|
case Some(setupDbState) =>
|
||||||
setupStateOpt.get.isInstanceOf[AcceptDbState],
|
setupDbState match {
|
||||||
s"Setup state must be accept to create dlc execution tx, got=${setupStateOpt.get.state}")
|
case o: OfferedDbState =>
|
||||||
setupState = setupStateOpt.get.asInstanceOf[AcceptDbState]
|
logger.info(
|
||||||
dlcDb = setupState.dlcDb
|
s"Cannot create execution tx for dlc in state=${o.state}")
|
||||||
fundingInputs = setupState.allFundingInputs
|
Future.successful(None)
|
||||||
scriptSigParams <- getScriptSigParams(dlcDb, fundingInputs)
|
case c: SetupCompleteDLCDbState =>
|
||||||
executorWithSetupOpt <- dlcDataManagement.executorAndSetupFromDb(
|
val dlcDb = c.dlcDb
|
||||||
|
val fundingInputs = c.allFundingInputs
|
||||||
|
val scriptSigParamsF = getScriptSigParams(dlcDb, fundingInputs)
|
||||||
|
val executorWithSetupOptF = scriptSigParamsF.flatMap {
|
||||||
|
scriptSigParams =>
|
||||||
|
dlcDataManagement.executorAndSetupFromDb(
|
||||||
contractId = contractId,
|
contractId = contractId,
|
||||||
txDAO = transactionDAO,
|
txDAO = transactionDAO,
|
||||||
fundingUtxoScriptSigParams = scriptSigParams,
|
fundingUtxoScriptSigParams = scriptSigParams,
|
||||||
keyManager = keyManager)
|
keyManager = keyManager)
|
||||||
tx <- {
|
}
|
||||||
executorWithSetupOpt match {
|
executorWithSetupOptF.flatMap {
|
||||||
case Some(executorWithSetup) =>
|
case Some(executorWithSetup) =>
|
||||||
buildExecutionTxWithExecutor(executorWithSetup,
|
buildExecutionTxWithExecutor(executorWithSetup,
|
||||||
oracleSigs,
|
oracleSigs,
|
||||||
contractId)
|
contractId).map(Some(_))
|
||||||
case None =>
|
case None =>
|
||||||
//means we don't have cet sigs in the db anymore
|
//means we don't have cet sigs in the db anymore
|
||||||
//can we retrieve the CET some other way?
|
//can we retrieve the CET some other way?
|
||||||
|
@ -1518,18 +1545,19 @@ abstract class DLCWallet
|
||||||
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
|
closingTxId = dlcDb.closingTxIdOpt.get
|
||||||
closingTxOpt <- transactionDAO.findByTxId(closingTxId)
|
closingTxOpt <- transactionDAO.findByTxId(closingTxId)
|
||||||
} yield {
|
} yield {
|
||||||
require(
|
require(
|
||||||
closingTxOpt.isDefined,
|
closingTxOpt.isDefined,
|
||||||
s"Could not find closing tx for DLC in db, contactId=${contractId.toHex} closingTxId=${closingTxId.hex}")
|
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(
|
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(
|
private[wallet] def getDLCFundingData(
|
||||||
dlcId: Sha256Digest,
|
dlcId: Sha256Digest,
|
||||||
txDAO: TransactionDAO): Future[Option[DLCSetupDbState]] = {
|
txDAO: TransactionDAO): Future[Option[DLCSetupDbState]] = {
|
||||||
|
@ -282,41 +313,55 @@ case class DLCDataManagement(dlcWalletDAOs: DLCWalletDAOs)(implicit
|
||||||
for {
|
for {
|
||||||
offerDbState <- offerDbStateOpt
|
offerDbState <- offerDbStateOpt
|
||||||
} yield {
|
} 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
|
//if the accept message is defined we must have refund sigs
|
||||||
dlcAcceptOpt.zip(refundSigsOpt).headOption match {
|
dlcAcceptOpt.zip(refundSigsOpt).headOption match {
|
||||||
case Some((dlcAccept, refundSigDb)) =>
|
case Some((dlcAccept, refundSigDb)) =>
|
||||||
require(
|
require(
|
||||||
refundSigsOpt.isDefined,
|
refundSigsOpt.isDefined,
|
||||||
s"Cannot have accept in the database if we do not have refund signatures, dlcId=${dlcId.hex}")
|
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 =>
|
buildAcceptDbState(offerDbState = offerDbState,
|
||||||
offerDbState.toAcceptDb(
|
dlcAccept = dlcAccept,
|
||||||
acceptDb = dlcAccept,
|
acceptInputs = acceptInputs,
|
||||||
acceptFundingInputsDb = acceptInputs,
|
cetSignatures = cetSignatures,
|
||||||
acceptPrevTxsDb = acceptPrevTxs,
|
refundSigDb = refundSigDb,
|
||||||
cetSignaturesOpt = signaturesOpt,
|
txDAO = txDAO)
|
||||||
refundSigDb = refundSigDb
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case None =>
|
case None =>
|
||||||
//just return the offerDbState if we don't have an accept
|
//just return the offerDbState if we don't have an accept
|
||||||
Future.successful(offerDbState)
|
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 =>
|
||||||
|
acceptState.state match {
|
||||||
|
case _: DLCState.ClosedState =>
|
||||||
val closedState =
|
val closedState =
|
||||||
DLCClosedDbState.fromSetupState(acceptState, sigsOpt)
|
DLCClosedDbState.fromCompleteSetupState(acceptState, sigsOpt)
|
||||||
Some(closedState)
|
Some(closedState)
|
||||||
case _: OfferedDbState =>
|
case _: DLCState.InProgressState =>
|
||||||
//cannot return a closed state because we haven't seen the accept message
|
Some(acceptState)
|
||||||
None
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 =>
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue