mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-19 05:43:51 +01:00
Prevent DB state corruption while accepting the same offer multiple times (#4067)
* Prevent db state corruption while accepting the same offer multiple times * cleanup
This commit is contained in:
parent
2166faf61d
commit
6cfbf67812
@ -9,7 +9,10 @@ import org.bitcoins.core.protocol.tlv._
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.TxoState
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.dlc.wallet.DLCWallet.InvalidAnnouncementSignature
|
||||
import org.bitcoins.dlc.wallet.DLCWallet.{
|
||||
DuplicateOfferException,
|
||||
InvalidAnnouncementSignature
|
||||
}
|
||||
import org.bitcoins.dlc.wallet.internal.DLCDataManagement
|
||||
import org.bitcoins.testkit.wallet.DLCWalletUtil._
|
||||
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedDLCWallet
|
||||
@ -837,6 +840,77 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
//https://test.oracle.suredbits.com/contract/numeric/022428196c6a60673d1f2b90e7be14ac615986dce5571246e8bcc9d2d689d57b
|
||||
private val numericContractInfo = ContractInfoV0TLV.fromHex(
|
||||
"""fdd82efd033f00000000000186a0fda720540012fda72648000501000000000000000000000001fd88b80000000000000000000001
|
||||
|fdafc8000000000000c350000001fdd6d800000000000186a0000001fe0003ffff00000000000186a00000fda724020000fda712fd
|
||||
|02d9fdd824fd02d391177fd623a72d56e7bc12e3903f8d6bce7f07a25226d54009cd7e670f5e7a7320b0704286580d8b6a7f31ab7b
|
||||
|f71356a13c28aa609a021111b2e3d2b2db26bc120bd29248895b81f76b07d85a21b86021f22352d6376d19bbf5c93f918828f1fdd8
|
||||
|22fd026d0012fad0cde50a2258efa25cbba60ef0b6677cd29654802937c112097edb64bd205beea02263d6461e60a9ca8e08209c8b
|
||||
|d5552863156014d5418cad91ac590bbf13a847f105db9899d560e5040f9565c043c9e7fdf3782ad2708c5b99646c722b4118547472
|
||||
|48fb52e6486cce3eca5ddf9d64ecbe0864501a446efd378863f9a4055fab50d2112320ff14d8747a72467589822103f197063b49e7
|
||||
|7b90d82d3a8d49b63c3ceb9bd3328398a53989d4237216a24a1d12364efa2d2aec59cdc87909b115dca5b07106b70032ff78072f82
|
||||
|ceeaf2e20db55086e9a2e5e5cac864992d747fd40f4b26bc3d7de958ee02460d1199ff81438c9b76b3934cbc4566d10f242563b95e
|
||||
|7df79c28d52c9c46b617676a4ee84a549ee1f0f53865c9ef4d0ff825e2f438384c5f6238d0734beb570a1a49d035d9f86ee31c23f1
|
||||
|e97bd34fba3f586c0fdf29997530e528b3200a0d7e34f865dc4ca7bfd722bf77c0478ddd25bfa2dc6a4ab973d0c1b7a9ff38283b7c
|
||||
|19bbe02677a9b628f3ee3d5198d66c1623c9608293093c126d4124a445bb6412f720493b6ffa411db53923895fd50c9563d50a97a8
|
||||
|6188084fe4c0f15ce50438ff0b00e1a9470185fd7c96296ae2be12056f61ceaeee7ced48314a3b6855bc9aa3b446b9dfad68553f53
|
||||
|02c60670a95fb9bdc5e72db7f1e9d42e4f4baca1bcbb22612db6417b45cc3d78b1ef33fc362a68db56df00ab1ee0700bd900200f6a
|
||||
|24882101e71de7e18a7fb0d7da27b340de52f97d96239f359cfe31afcaf69cc9ddfcbfbdb2267e673ad728a29dd22d31d1a1187162
|
||||
|037480fdd80a100002000642544355534400000000001212446572696269742d4254432d394645423232""".stripMargin)
|
||||
|
||||
it must "fail accepting an offer twice simultaneously" in { wallets =>
|
||||
val walletA = wallets._1.wallet
|
||||
val walletB = wallets._2.wallet
|
||||
|
||||
val contractInfoA = numericContractInfo
|
||||
val feeRateOpt = Some(SatoshisPerVirtualByte(Satoshis.one))
|
||||
val totalCollateral = Satoshis(100000)
|
||||
|
||||
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
||||
walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
||||
collateral = totalCollateral,
|
||||
feeRateOpt = feeRateOpt,
|
||||
locktime = UInt32.zero,
|
||||
refundLT = UInt32.one)
|
||||
}
|
||||
|
||||
for {
|
||||
offer <- makeOffer(contractInfoA)
|
||||
accept1F = walletB.acceptDLCOffer(offer)
|
||||
accept2F = walletB.acceptDLCOffer(offer)
|
||||
_ <- recoverToSucceededIf[DuplicateOfferException](
|
||||
Future.sequence(Seq(accept1F, accept2F)))
|
||||
} yield {
|
||||
succeed
|
||||
}
|
||||
}
|
||||
|
||||
it must "accept an offer twice sequentially" in { wallets =>
|
||||
val walletA = wallets._1.wallet
|
||||
val walletB = wallets._2.wallet
|
||||
|
||||
val contractInfoA = numericContractInfo
|
||||
val feeRateOpt = Some(SatoshisPerVirtualByte(Satoshis.one))
|
||||
val totalCollateral = Satoshis(100000)
|
||||
|
||||
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
||||
walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
||||
collateral = totalCollateral,
|
||||
feeRateOpt = feeRateOpt,
|
||||
locktime = UInt32.zero,
|
||||
refundLT = UInt32.one)
|
||||
}
|
||||
|
||||
for {
|
||||
offer <- makeOffer(contractInfoA)
|
||||
accept1 <- walletB.acceptDLCOffer(offer)
|
||||
accept2 <- walletB.acceptDLCOffer(offer)
|
||||
} yield {
|
||||
assert(accept1 == accept2)
|
||||
}
|
||||
}
|
||||
|
||||
it must "not be able to sign its own accept" in { wallets =>
|
||||
val walletA = wallets._1.wallet
|
||||
val walletB = wallets._2.wallet
|
||||
|
@ -480,11 +480,6 @@ abstract class DLCWallet
|
||||
|
||||
val chainType = HDChainType.External
|
||||
|
||||
//filter announcements that we already have in the db
|
||||
val groupedAnnouncementsF: Future[AnnouncementGrouping] = {
|
||||
groupByExistingAnnouncements(announcements)
|
||||
}
|
||||
|
||||
getDlcDbOfferDbContractDataDb(offer.tempContractId).flatMap {
|
||||
case Some((dlcDb, dlcOffer, contractDataDb)) =>
|
||||
Future.successful((dlcDb, dlcOffer, contractDataDb))
|
||||
@ -502,7 +497,7 @@ abstract class DLCWallet
|
||||
dlcId,
|
||||
offer)
|
||||
_ <- writeDLCKeysToAddressDb(account, chainType, nextIndex)
|
||||
groupedAnnouncements <- groupedAnnouncementsF
|
||||
groupedAnnouncements <- groupByExistingAnnouncements(announcements)
|
||||
dlcDbAction = dlcDAO.createAction(dlc)
|
||||
dlcOfferAction = dlcOfferDAO.createAction(dlcOfferDb)
|
||||
contractAction = contractDataDAO.createAction(contractDataDb)
|
||||
@ -617,38 +612,28 @@ abstract class DLCWallet
|
||||
|
||||
private def createNewDLCAccept(
|
||||
collateral: CurrencyUnit,
|
||||
offer: DLCOffer): Future[DLCAccept] = {
|
||||
offer: DLCOffer): Future[DLCAccept] = Future {
|
||||
DLCWallet.AcceptingOffersLatch.startAccepting(offer.tempContractId)
|
||||
|
||||
logger.info(
|
||||
s"Creating DLC Accept for tempContractId ${offer.tempContractId.hex}")
|
||||
|
||||
val accountF = getDefaultAccountForType(AddressType.SegWit)
|
||||
|
||||
val dlcDbOfferDbF: Future[(DLCDb, DLCOfferDb, DLCContractDataDb)] = {
|
||||
for {
|
||||
accountDb <- accountF
|
||||
(dlcDb, offerDb, contractDataDb) <- initDLCForAccept(offer, accountDb)
|
||||
} yield (dlcDb, offerDb, contractDataDb)
|
||||
def getFundingPrivKey(account: AccountDb, dlc: DLCDb): AdaptorSign = {
|
||||
val bip32Path = BIP32Path(
|
||||
account.hdAccount.path ++ Vector(BIP32Node(0, hardened = false),
|
||||
BIP32Node(dlc.keyIndex,
|
||||
hardened = false)))
|
||||
val privKeyPath = HDPath.fromString(bip32Path.toString)
|
||||
keyManager.toSign(privKeyPath)
|
||||
}
|
||||
|
||||
val fundingPrivKeyF: Future[AdaptorSign] = {
|
||||
for {
|
||||
(dlc, _, _) <- dlcDbOfferDbF
|
||||
account <- accountF
|
||||
bip32Path = BIP32Path(
|
||||
account.hdAccount.path ++ Vector(BIP32Node(0, hardened = false),
|
||||
BIP32Node(dlc.keyIndex,
|
||||
hardened = false)))
|
||||
privKeyPath = HDPath.fromString(bip32Path.toString)
|
||||
} yield keyManager.toSign(privKeyPath)
|
||||
}
|
||||
|
||||
for {
|
||||
(dlc, offerDb, contractDataDb) <- dlcDbOfferDbF
|
||||
account <- accountF
|
||||
val result = for {
|
||||
account <- getDefaultAccountForType(AddressType.SegWit)
|
||||
(dlc, offerDb, contractDataDb) <- initDLCForAccept(offer, account)
|
||||
(txBuilder, spendingInfos) <- fundDLCAcceptMsg(offer = offer,
|
||||
collateral = collateral,
|
||||
account = account)
|
||||
fundingPrivKey <- fundingPrivKeyF
|
||||
fundingPrivKey = getFundingPrivKey(account, dlc)
|
||||
(acceptWithoutSigs, dlcPubKeys) = DLCAcceptUtil.buildAcceptWithoutSigs(
|
||||
dlc = dlc,
|
||||
offer = offer,
|
||||
@ -673,11 +658,8 @@ abstract class DLCWallet
|
||||
|
||||
spkDb = ScriptPubKeyDb(builder.fundingSPK)
|
||||
// only update spk db if we don't have it
|
||||
spkDbOpt <- scriptPubKeyDAO.findScriptPubKey(spkDb.scriptPubKey)
|
||||
_ <- spkDbOpt match {
|
||||
case Some(_) => Future.unit
|
||||
case None => scriptPubKeyDAO.create(spkDb)
|
||||
}
|
||||
_ <- scriptPubKeyDAO.createIfNotExists(spkDb)
|
||||
|
||||
_ = logger.info(s"Creating CET Sigs for ${contractId.toHex}")
|
||||
//emit websocket event that we are now computing adaptor signatures
|
||||
status = DLCStatusBuilder.buildInProgressDLCStatus(
|
||||
@ -775,7 +757,10 @@ abstract class DLCWallet
|
||||
UInt32(builder.fundOutputIndex))
|
||||
_ <- updateFundingOutPoint(dlcDb.contractIdOpt.get, outPoint)
|
||||
} yield accept
|
||||
}
|
||||
result.onComplete(_ =>
|
||||
DLCWallet.AcceptingOffersLatch.doneAccepting(offer.tempContractId))
|
||||
result
|
||||
}.flatten
|
||||
|
||||
def registerDLCAccept(
|
||||
accept: DLCAccept): Future[(DLCDb, Vector[DLCCETSignaturesDb])] = {
|
||||
@ -1743,6 +1728,9 @@ abstract class DLCWallet
|
||||
|
||||
object DLCWallet extends WalletLogger {
|
||||
|
||||
case class DuplicateOfferException(message: String)
|
||||
extends RuntimeException(message)
|
||||
|
||||
case class InvalidAnnouncementSignature(message: String)
|
||||
extends RuntimeException(message)
|
||||
|
||||
@ -1765,4 +1753,22 @@ object DLCWallet extends WalletLogger {
|
||||
ec: ExecutionContext): DLCWallet = {
|
||||
DLCWalletImpl(nodeApi, chainQueryApi, feeRateApi)
|
||||
}
|
||||
|
||||
private object AcceptingOffersLatch {
|
||||
|
||||
private val tempContractIds =
|
||||
new java.util.concurrent.ConcurrentHashMap[Sha256Digest, Sha256Digest]()
|
||||
|
||||
def startAccepting(tempContractId: Sha256Digest): Unit = {
|
||||
if (tempContractIds.putIfAbsent(tempContractId, tempContractId) != null) {
|
||||
throw DuplicateOfferException(
|
||||
s"Offer with temporary contract ID ${tempContractId.hex} is already being accepted")
|
||||
}
|
||||
}
|
||||
|
||||
def doneAccepting(tempContractId: Sha256Digest): Unit = {
|
||||
val _ = tempContractIds.remove(tempContractId)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user