mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 18:02:54 +01:00
Fix issue 3102 to allow a user to create an offer with an announcement embedded inside a contract info that the user's wallet has seen before (#3140)
Fix bug where we were not taking into account all announcementTLVs
This commit is contained in:
parent
be8e965367
commit
701418f89f
@ -1,6 +1,8 @@
|
||||
package org.bitcoins.dlc.wallet
|
||||
|
||||
import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage.DLCOffer
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCStatus.{
|
||||
Claimed,
|
||||
Refunded,
|
||||
@ -13,16 +15,21 @@ import org.bitcoins.core.protocol.dlc.models.{
|
||||
NumericContractDescriptor
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
ContractInfoV0TLV,
|
||||
OracleAttestmentTLV,
|
||||
OracleAttestmentV0TLV,
|
||||
OracleEventV0TLV
|
||||
OracleEventV0TLV,
|
||||
OracleInfoV0TLV
|
||||
}
|
||||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.crypto.CryptoUtil
|
||||
import org.bitcoins.testkit.wallet.DLCWalletUtil._
|
||||
import org.bitcoins.testkit.wallet.{BitcoinSDualWalletTest, DLCWalletUtil}
|
||||
import org.scalatest.FutureOutcome
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class DLCExecutionTest extends BitcoinSDualWalletTest {
|
||||
type FixtureParam = (InitializedDLCWallet, InitializedDLCWallet)
|
||||
|
||||
@ -349,4 +356,41 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException](executeDLCForceCloseF)
|
||||
}
|
||||
|
||||
it must "create 2 offers with the same contract info" in { wallets =>
|
||||
//test for: https://github.com/bitcoin-s/bitcoin-s/issues/3127
|
||||
val walletA = wallets._1.wallet
|
||||
|
||||
//https://test.oracle.suredbits.com/contract/enum/75b08299654dca23b80cf359db6afb6cfd6e55bc898b5397d3c0fe796dfc13f0/12fb3e5f091086329ed0d2a12c3fcfa80111a36ef3fc1ac9c2567076a57d6a73
|
||||
val contractInfo = ContractInfoV0TLV.fromHex(
|
||||
"fdd82eeb00000000000186a0fda71026030359455300000000000186a0024e4f0000000000000000056f746865720000000000000000fda712b5fdd824b1596ec40d0dae3fdf54d9795ad51ec069970c6863a02d244663d39fd6bedadc0070349e1ba2e17583ee2d1cb3ae6fffaaa1c45039b61c5c4f1d0d864221c461745d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9fdd8224d0001fa5b84283852400b21a840d5d5ca1cc31867c37326ad521aa50bebf3df4eea1a60b03280fdd8060f000303594553024e4f056f74686572135465746865722d52657365727665732d363342")
|
||||
val announcement =
|
||||
contractInfo.oracleInfo.asInstanceOf[OracleInfoV0TLV].announcement
|
||||
val feeRateOpt = Some(SatoshisPerVirtualByte(Satoshis.one))
|
||||
val totalCollateral = Satoshis(50000)
|
||||
|
||||
//helper method to make an offer
|
||||
def makeOffer(): Future[DLCOffer] = {
|
||||
walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
||||
collateral = totalCollateral,
|
||||
feeRateOpt = feeRateOpt,
|
||||
locktime = UInt32.zero,
|
||||
refundLT = UInt32.one)
|
||||
}
|
||||
|
||||
//simply try to make 2 offers with the same contract info
|
||||
//if this works, we are good
|
||||
for {
|
||||
_ <- makeOffer()
|
||||
announcementVec1 <- walletA.announcementDAO.findByAnnouncementSignatures(
|
||||
Vector(announcement.announcementSignature))
|
||||
_ = assert(announcementVec1.length == 1,
|
||||
s"Got length=${announcementVec1.length}")
|
||||
_ <- makeOffer()
|
||||
announcementVec2 <- walletA.announcementDAO.findByAnnouncementSignatures(
|
||||
Vector(announcement.announcementSignature))
|
||||
_ = assert(announcementVec2.length == 1,
|
||||
s"Got length=${announcementVec2.length}")
|
||||
} yield succeed
|
||||
}
|
||||
}
|
||||
|
@ -288,24 +288,32 @@ abstract class DLCWallet
|
||||
val announcements =
|
||||
contractInfo.oracleInfo.singleOracleInfos.map(_.announcement)
|
||||
|
||||
val announcementDataDbs =
|
||||
OracleAnnouncementDbHelper.fromAnnouncements(announcements)
|
||||
//hack for now to get around https://github.com/bitcoin-s/bitcoin-s/issues/3127
|
||||
//filter announcements that we already have in the db
|
||||
val groupedAnnouncementsF: Future[AnnouncementGrouping] = {
|
||||
groupByExistingAnnouncements(announcements)
|
||||
}
|
||||
|
||||
val feeRateF = determineFeeRate(feeRateOpt).map { fee =>
|
||||
SatoshisPerVirtualByte(fee.currencyUnit)
|
||||
}
|
||||
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt).map { fee =>
|
||||
SatoshisPerVirtualByte(fee.currencyUnit)
|
||||
}
|
||||
|
||||
announcementDataDbs <- announcementDAO.createAll(announcementDataDbs)
|
||||
feeRate <- feeRateF
|
||||
groupedAnnouncements <- groupedAnnouncementsF
|
||||
announcementDataDbs <- announcementDAO.createAll(
|
||||
groupedAnnouncements.newAnnouncements)
|
||||
allAnnouncementDbs =
|
||||
announcementDataDbs ++ groupedAnnouncements.existingAnnouncements
|
||||
announcementsWithId = announcements.map { tlv =>
|
||||
val idOpt = announcementDataDbs
|
||||
.find(_.announcementSignature == tlv.announcementSignature)
|
||||
.flatMap(_.id)
|
||||
val idOpt: Option[Long] =
|
||||
allAnnouncementDbs
|
||||
.find(_.announcementSignature == tlv.announcementSignature)
|
||||
.flatMap(_.id)
|
||||
(tlv, idOpt.get)
|
||||
}
|
||||
nonceDbs = OracleNonceDbHelper.fromAnnouncements(announcementsWithId)
|
||||
_ <- oracleNonceDAO.upsertAll(nonceDbs)
|
||||
|
||||
chainType = HDChainType.External
|
||||
|
||||
account <- getDefaultAccountForType(AddressType.SegWit)
|
||||
@ -326,7 +334,7 @@ abstract class DLCWallet
|
||||
}
|
||||
|
||||
dlcId = calcDLCId(utxos.map(_.outPoint))
|
||||
dlcAnnouncementDbs = announcementDataDbs.zipWithIndex.map {
|
||||
dlcAnnouncementDbs = allAnnouncementDbs.zipWithIndex.map {
|
||||
case (a, index) =>
|
||||
DLCAnnouncementDb(dlcId = dlcId,
|
||||
announcementId = a.id.get,
|
||||
@ -398,7 +406,6 @@ abstract class DLCWallet
|
||||
_ <- dlcDAO.create(dlcDb)
|
||||
_ <- contractDataDAO.create(contractDataDb)
|
||||
_ <- dlcAnnouncementDAO.createAll(dlcAnnouncementDbs)
|
||||
|
||||
dlcOfferDb = DLCOfferDbHelper.fromDLCOffer(dlcId, offer)
|
||||
|
||||
dlcInputs = spendingInfos.zip(utxos).map { case (utxo, fundingInput) =>
|
||||
@ -1211,7 +1218,6 @@ abstract class DLCWallet
|
||||
dlcDbOpt <- dlcDAO.read(dlcId)
|
||||
contractDataOpt <- contractDataDAO.read(dlcId)
|
||||
offerDbOpt <- dlcOfferDAO.read(dlcId)
|
||||
|
||||
(announcements, announcementData, nonceDbs) <- getDLCAnnouncementDbs(
|
||||
dlcId)
|
||||
} yield (dlcDbOpt, contractDataOpt, offerDbOpt) match {
|
||||
@ -1355,6 +1361,50 @@ abstract class DLCWallet
|
||||
case (_, _, _) => None
|
||||
}
|
||||
}
|
||||
|
||||
/** @param newAnnouncements announcements we do not have in our db
|
||||
* @param existingAnnouncements announcements we already have in our db
|
||||
*/
|
||||
private case class AnnouncementGrouping(
|
||||
newAnnouncements: Vector[OracleAnnouncementDataDb],
|
||||
existingAnnouncements: Vector[OracleAnnouncementDataDb]) {
|
||||
require(existingAnnouncements.forall(_.id.isDefined))
|
||||
require(newAnnouncements.forall(_.id.isEmpty),
|
||||
s"announcmeent had id defined=${newAnnouncements.map(_.id)}")
|
||||
}
|
||||
|
||||
/** This is needed because our upserts do not work
|
||||
* we need to filter announcements we already have in the database
|
||||
* to avoid issues below
|
||||
* @see https://github.com/bitcoin-s/bitcoin-s/issues/1623
|
||||
* @see https://github.com/bitcoin-s/bitcoin-s/issues/3127
|
||||
* @param announcementDataDbs
|
||||
*/
|
||||
private def groupByExistingAnnouncements(
|
||||
announcementTLVs: Vector[OracleAnnouncementTLV]): Future[
|
||||
AnnouncementGrouping] = {
|
||||
|
||||
val announcementSignatures: Vector[SchnorrDigitalSignature] = {
|
||||
announcementTLVs.map(a => a.announcementSignature)
|
||||
}
|
||||
|
||||
val existingAnnouncementsInDbF: Future[Vector[OracleAnnouncementDataDb]] =
|
||||
announcementDAO
|
||||
.findByAnnouncementSignatures(announcementSignatures)
|
||||
|
||||
for {
|
||||
existingAnnouncementsDb <- existingAnnouncementsInDbF
|
||||
newAnnouncements = announcementTLVs.filterNot(a =>
|
||||
existingAnnouncementsDb.exists(
|
||||
_.announcementSignature == a.announcementSignature))
|
||||
} yield {
|
||||
val newAnnouncementsDb =
|
||||
OracleAnnouncementDbHelper.fromAnnouncements(newAnnouncements)
|
||||
|
||||
AnnouncementGrouping(newAnnouncements = newAnnouncementsDb,
|
||||
existingAnnouncements = existingAnnouncementsDb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DLCWallet extends WalletLogger {
|
||||
|
@ -27,6 +27,15 @@ case class OracleAnnouncementDataDAO()(implicit
|
||||
safeDatabase.runVec(query.result)
|
||||
}
|
||||
|
||||
def findByAnnouncementSignatures(
|
||||
signatures: Vector[SchnorrDigitalSignature]): Future[
|
||||
Vector[OracleAnnouncementDataDb]] = {
|
||||
val query = table.filter(_.announcementSignature.inSet(signatures))
|
||||
|
||||
safeDatabase
|
||||
.runVec(query.result)
|
||||
}
|
||||
|
||||
def findByIds(ids: Vector[Long]): Future[Vector[OracleAnnouncementDataDb]] = {
|
||||
val query = table.filter(_.id.inSet(ids))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user