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:
Chris Stewart 2021-05-25 18:53:13 -05:00 committed by GitHub
parent be8e965367
commit 701418f89f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 15 deletions

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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))