mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-26 21:42:48 +01:00
Disjoint union dlc (#3839)
* Implemented Disjoint Union DLC related data structures with dlcTest passing Broke DLCClientTest up into multiple test files Optimized DLC execution unit tests by checking all outcomes on a single setup DLC Refactored DLCTest to allow for Disjoint Union DLC construction and validation, added tests Responded to review Fixed after cherry-pick Fixed docs * Fixed things after rebase * Rebase * Fix json serializer * Finish fixing compile * Start trying to make APIs better for multi oracle contract infos * Clean things up in the GUI, try to make failures on disjoint union contracts as explicit as possible * Use less numDigits as optimization for non secp CI test cases * Fix compile * Refactor BroadcastDLCDialog * Fix test case optimization * Clean up comment Co-authored-by: nkohen <nadavk25@gmail.com>
This commit is contained in:
parent
cd3006c020
commit
8765c2f845
49 changed files with 1984 additions and 1298 deletions
|
@ -383,18 +383,49 @@ object Picklers {
|
|||
writer[Obj].comap { contractInfo =>
|
||||
import contractInfo._
|
||||
|
||||
Obj("totalCollateral" -> Num(totalCollateral.toLong.toDouble),
|
||||
"contractDescriptor" -> writeJs(contractDescriptor),
|
||||
"oracleInfo" -> writeJs(oracleInfo))
|
||||
Obj(
|
||||
PicklerKeys.totalCollateralKey -> writeJs(totalCollateral),
|
||||
PicklerKeys.contractDescriptorKey -> writeJs(contractDescriptor),
|
||||
PicklerKeys.oracleInfoKey -> writeJs(oracleInfo)
|
||||
)
|
||||
}
|
||||
|
||||
val contractInfoV1TLVJsonWriter: Writer[ContractInfoV1TLV] = {
|
||||
writer[Obj].comap { contractInfo =>
|
||||
val arrayVec: Vector[ujson.Obj] = contractInfo.contractOraclePairs.map {
|
||||
case (c, o) =>
|
||||
val contractDescriptorJson = writeJs(c)
|
||||
val oracleInfoJson = writeJs(o)
|
||||
ujson.Obj(PicklerKeys.contractDescriptorKey -> contractDescriptorJson,
|
||||
PicklerKeys.oracleInfoKey -> oracleInfoJson)
|
||||
}
|
||||
|
||||
val arrayJson = ujson.Arr.from(arrayVec)
|
||||
|
||||
Obj(
|
||||
PicklerKeys.totalCollateralKey -> Num(
|
||||
contractInfo.totalCollateral.toLong.toDouble),
|
||||
PicklerKeys.pairsKey -> arrayJson
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val contractInfoJsonWriter: Writer[ContractInfoTLV] = {
|
||||
writer[ujson.Value].comap {
|
||||
case contractInfoV0TLV: ContractInfoV0TLV =>
|
||||
writeJs(contractInfoV0TLV)(contractInfoV0TLVJsonWriter)
|
||||
case contractInfoV1TLV: ContractInfoV1TLV =>
|
||||
writeJs(contractInfoV1TLV)(contractInfoV1TLVJsonWriter)
|
||||
}
|
||||
}
|
||||
|
||||
implicit val offerTLVWriter: Writer[DLCOfferTLV] =
|
||||
writer[Obj].comap { offer =>
|
||||
import offer._
|
||||
Obj(
|
||||
"contractFlags" -> Str(contractFlags.toHexString),
|
||||
"chainHash" -> Str(chainHash.hex),
|
||||
"contractInfo" -> writeJs(contractInfo)(contractInfoV0TLVJsonWriter),
|
||||
"contractInfo" -> writeJs(contractInfo)(contractInfoJsonWriter),
|
||||
"fundingPubKey" -> Str(fundingPubKey.hex),
|
||||
"payoutSPK" -> Str(payoutSPK.hex),
|
||||
"payoutSerialId" -> Num(payoutSerialId.toBigInt.toDouble),
|
||||
|
|
|
@ -240,56 +240,61 @@ class DLCPaneModel(pane: DLCPane)(implicit ec: ExecutionContext)
|
|||
}
|
||||
|
||||
def cancelDLC(status: DLCStatus): Unit = {
|
||||
val eventId =
|
||||
status.oracleInfo.singleOracleInfos.head.announcement.eventTLV.eventId
|
||||
status.contractInfo match {
|
||||
case _: SingleContractInfo =>
|
||||
val eventId = status.eventIds.head
|
||||
|
||||
val confirmed = status.state match {
|
||||
case DLCState.Offered | DLCState.Accepted =>
|
||||
new Alert(AlertType.Confirmation) {
|
||||
initOwner(owner)
|
||||
headerText = "Confirm Canceling DLC"
|
||||
contentText =
|
||||
s"Are you sure you want to cancel this DLC for $eventId?\n" +
|
||||
"This cannot be undone."
|
||||
}.showAndWait() match {
|
||||
case Some(ButtonType.OK) => true
|
||||
case None | Some(_) => false
|
||||
val confirmed = status.state match {
|
||||
case DLCState.Offered | DLCState.Accepted =>
|
||||
new Alert(AlertType.Confirmation) {
|
||||
initOwner(owner)
|
||||
headerText = "Confirm Canceling DLC"
|
||||
contentText =
|
||||
s"Are you sure you want to cancel this DLC for $eventId?\n" +
|
||||
"This cannot be undone."
|
||||
}.showAndWait() match {
|
||||
case Some(ButtonType.OK) => true
|
||||
case None | Some(_) => false
|
||||
}
|
||||
case DLCState.Signed =>
|
||||
new Alert(AlertType.Confirmation) {
|
||||
initOwner(owner)
|
||||
headerText = "Confirm Unsafe Canceling DLC"
|
||||
contentText =
|
||||
"Danger! If your counter-party has received your sign message then they will be able to execute the DLC even if you cancel!\n"
|
||||
s"Are you sure you want to cancel this DLC for $eventId?\n" +
|
||||
"This cannot be undone.\n"
|
||||
}.showAndWait() match {
|
||||
case Some(ButtonType.OK) => true
|
||||
case None | Some(_) => false
|
||||
}
|
||||
case DLCState.Broadcasted | DLCState.Confirmed | DLCState.Claimed |
|
||||
DLCState.RemoteClaimed | DLCState.Refunded =>
|
||||
new Alert(AlertType.Error) {
|
||||
initOwner(owner)
|
||||
headerText = "Failed to Cancel DLC"
|
||||
contentText = "Cannot cancel a DLC after it has been signed"
|
||||
}.showAndWait()
|
||||
false
|
||||
}
|
||||
case DLCState.Signed =>
|
||||
new Alert(AlertType.Confirmation) {
|
||||
initOwner(owner)
|
||||
headerText = "Confirm Unsafe Canceling DLC"
|
||||
contentText =
|
||||
"Danger! If your counter-party has received your sign message then they will be able to execute the DLC even if you cancel!\n"
|
||||
s"Are you sure you want to cancel this DLC for $eventId?\n" +
|
||||
"This cannot be undone.\n"
|
||||
}.showAndWait() match {
|
||||
case Some(ButtonType.OK) => true
|
||||
case None | Some(_) => false
|
||||
}
|
||||
case DLCState.Broadcasted | DLCState.Confirmed | DLCState.Claimed |
|
||||
DLCState.RemoteClaimed | DLCState.Refunded =>
|
||||
new Alert(AlertType.Error) {
|
||||
initOwner(owner)
|
||||
headerText = "Failed to Cancel DLC"
|
||||
contentText = "Cannot cancel a DLC after it has been signed"
|
||||
}.showAndWait()
|
||||
false
|
||||
}
|
||||
|
||||
if (confirmed) {
|
||||
taskRunner.run(
|
||||
caption = "Canceling DLC",
|
||||
op = {
|
||||
ConsoleCli.exec(CancelDLC(status.dlcId),
|
||||
GlobalData.consoleCliConfig) match {
|
||||
case Success(_) => ()
|
||||
case Failure(err) => throw err
|
||||
}
|
||||
updateDLCs()
|
||||
Platform.runLater(GUI.model.updateBalance())
|
||||
if (confirmed) {
|
||||
taskRunner.run(
|
||||
caption = "Canceling DLC",
|
||||
op = {
|
||||
ConsoleCli.exec(CancelDLC(status.dlcId),
|
||||
GlobalData.consoleCliConfig) match {
|
||||
case Success(_) => ()
|
||||
case Failure(err) => throw err
|
||||
}
|
||||
updateDLCs()
|
||||
Platform.runLater(GUI.model.updateBalance())
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
case _: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Disjoint contract infos are not supported via the GUI, cannot cancel")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,15 +309,24 @@ class DLCPaneModel(pane: DLCPane)(implicit ec: ExecutionContext)
|
|||
case Success(txId) =>
|
||||
logger.info(s"Successfully rebroadcast funding tx " + txId)
|
||||
// Looking for Event Hash in status, but don't see it
|
||||
val announcementHash =
|
||||
status.oracleInfo.singleOracleInfos.head.announcement.sha256.hex
|
||||
Platform.runLater(
|
||||
FundingTransactionDialog.show(
|
||||
parentWindow.value,
|
||||
txId,
|
||||
GUIUtil.epochToDateString(status.timeouts.contractTimeout),
|
||||
GlobalData.buildAnnouncementUrl(announcementHash),
|
||||
true))
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
val announcementHash =
|
||||
single.announcements.head.sha256.hex
|
||||
Platform.runLater(
|
||||
FundingTransactionDialog.show(
|
||||
parentWindow.value,
|
||||
txId,
|
||||
GUIUtil.epochToDateString(
|
||||
status.timeouts.contractTimeout),
|
||||
GlobalData.buildAnnouncementUrl(announcementHash),
|
||||
true))
|
||||
case disjointUnionContractInfo: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Don't know how to show correcit funding transaction dialog for" +
|
||||
s"disjoint union contracts, contracts=${disjointUnionContractInfo.contracts}")
|
||||
}
|
||||
|
||||
case Failure(err) => throw err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,15 @@ class DLCTableView(model: DLCPaneModel) {
|
|||
text = "Event Id"
|
||||
prefWidth = 230
|
||||
cellValueFactory = { status =>
|
||||
val eventIdStr =
|
||||
status.value.oracleInfo.singleOracleInfos.head.announcement.eventTLV.eventId
|
||||
new StringProperty(status, "Event Id", eventIdStr)
|
||||
status.value.contractInfo match {
|
||||
case _: SingleContractInfo =>
|
||||
val eventIdStr =
|
||||
status.value.eventIds.head
|
||||
new StringProperty(status, "Event Id", eventIdStr)
|
||||
case _: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Disjoint contracts are not supported via the GUI, cannot add to table")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,7 +169,7 @@ class DLCTableView(model: DLCPaneModel) {
|
|||
onAction = _ => {
|
||||
val dlc = selectionModel.value.getSelectedItem
|
||||
val primaryOracle =
|
||||
dlc.oracleInfo.singleOracleInfos.head.announcement
|
||||
dlc.announcements.head
|
||||
val url =
|
||||
GUIUtil.getAnnouncementUrl(GlobalData.network, primaryOracle)
|
||||
GUIUtil.openUrl(url)
|
||||
|
|
|
@ -107,11 +107,8 @@ class AcceptOfferDialog extends CliCommandProducer[AcceptDLCCliCommand] {
|
|||
}
|
||||
|
||||
def showOfferTerms(offer: DLCOfferTLV): Unit = {
|
||||
val (oracleKey, eventId) = offer.contractInfo.oracleInfo match {
|
||||
case OracleInfoV0TLV(announcement) =>
|
||||
(announcement.publicKey.hex, announcement.eventTLV.eventId)
|
||||
case _: MultiOracleInfoTLV =>
|
||||
throw new RuntimeException("This is impossible.")
|
||||
val (oracleKey, eventId) = {
|
||||
GUIUtil.getOraclePubKeyEventId(offer.contractInfo)
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Event Id"), 0, nextRow)
|
||||
|
@ -133,7 +130,7 @@ class AcceptOfferDialog extends CliCommandProducer[AcceptDLCCliCommand] {
|
|||
nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = oracleKey
|
||||
text = oracleKey.hex
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
|
@ -170,55 +167,61 @@ class AcceptOfferDialog extends CliCommandProducer[AcceptDLCCliCommand] {
|
|||
nextRow)
|
||||
nextRow += 1
|
||||
|
||||
offer.contractInfo.contractDescriptor match {
|
||||
case v0: ContractDescriptorV0TLV =>
|
||||
gridPane.add(new Label("Potential Outcome"), 0, nextRow)
|
||||
gridPane.add(new Label("Payouts"), 1, nextRow)
|
||||
nextRow += 1
|
||||
offer.contractInfo match {
|
||||
case contractInfoV0TLV: ContractInfoV0TLV =>
|
||||
contractInfoV0TLV.contractDescriptor match {
|
||||
case v0: ContractDescriptorV0TLV =>
|
||||
gridPane.add(new Label("Potential Outcome"), 0, nextRow)
|
||||
gridPane.add(new Label("Payouts"), 1, nextRow)
|
||||
nextRow += 1
|
||||
|
||||
val descriptor = EnumContractDescriptor
|
||||
.fromTLV(v0)
|
||||
.flip(offer.contractInfo.totalCollateral)
|
||||
descriptor.foreach { case (str, satoshis) =>
|
||||
gridPane.add(new TextField() {
|
||||
text = str.outcome
|
||||
editable = false
|
||||
},
|
||||
0,
|
||||
nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = satoshis.toString
|
||||
editable = false
|
||||
tooltip = Tooltip(
|
||||
s"""Amount you will win if the oracle signs for "$str".""")
|
||||
tooltip.value.setShowDelay(new javafx.util.Duration(100))
|
||||
},
|
||||
1,
|
||||
nextRow
|
||||
)
|
||||
nextRow += 1
|
||||
val descriptor = EnumContractDescriptor
|
||||
.fromTLV(v0)
|
||||
.flip(offer.contractInfo.totalCollateral)
|
||||
descriptor.foreach { case (str, satoshis) =>
|
||||
gridPane.add(new TextField() {
|
||||
text = str.outcome
|
||||
editable = false
|
||||
},
|
||||
0,
|
||||
nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = satoshis.toString
|
||||
editable = false
|
||||
tooltip = Tooltip(
|
||||
s"""Amount you will win if the oracle signs for "$str".""")
|
||||
tooltip.value.setShowDelay(new javafx.util.Duration(100))
|
||||
},
|
||||
1,
|
||||
nextRow
|
||||
)
|
||||
nextRow += 1
|
||||
}
|
||||
case v1: ContractDescriptorV1TLV =>
|
||||
val descriptor = NumericContractDescriptor
|
||||
.fromTLV(v1)
|
||||
.flip(offer.contractInfo.totalCollateral)
|
||||
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
DLCPlotUtil.plotCETsWithOriginalCurve(
|
||||
base = 2,
|
||||
descriptor.numDigits,
|
||||
descriptor.outcomeValueFunc,
|
||||
offer.contractInfo.totalCollateral,
|
||||
RoundingIntervals.fromTLV(v1.roundingIntervals))
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Payout Curve"), 0, nextRow)
|
||||
gridPane.add(previewGraphButton, 1, nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
case v1: ContractDescriptorV1TLV =>
|
||||
val descriptor = NumericContractDescriptor
|
||||
.fromTLV(v1)
|
||||
.flip(offer.contractInfo.totalCollateral)
|
||||
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
DLCPlotUtil.plotCETsWithOriginalCurve(
|
||||
base = 2,
|
||||
descriptor.numDigits,
|
||||
descriptor.outcomeValueFunc,
|
||||
offer.contractInfo.totalCollateral,
|
||||
RoundingIntervals.fromTLV(v1.roundingIntervals))
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Payout Curve"), 0, nextRow)
|
||||
gridPane.add(previewGraphButton, 1, nextRow)
|
||||
nextRow += 1
|
||||
case _: ContractInfoV1TLV =>
|
||||
sys.error(
|
||||
s"Disjoint union contracts cannot be accepted on the GUI currently")
|
||||
}
|
||||
|
||||
gridPane.add(
|
||||
|
|
|
@ -134,12 +134,8 @@ object BroadcastDLCDialog
|
|||
}
|
||||
vbox.children.add(gridPane)
|
||||
|
||||
val (oracleKey, eventId) = status.contractInfo.oracleInfo.toTLV match {
|
||||
case OracleInfoV0TLV(announcement) =>
|
||||
(announcement.publicKey.hex, announcement.eventTLV.eventId)
|
||||
case _: MultiOracleInfoTLV =>
|
||||
throw new RuntimeException("This is impossible.")
|
||||
}
|
||||
val (oracleKey, eventId) =
|
||||
GUIUtil.getOraclePubKeyEventId(status.contractInfo.toTLV)
|
||||
|
||||
gridPane.add(new Label("Event Id"), 0, nextRow)
|
||||
gridPane.add(
|
||||
|
@ -156,7 +152,7 @@ object BroadcastDLCDialog
|
|||
gridPane.add(new Label("Oracle Public Key"), 0, nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = oracleKey
|
||||
text = oracleKey.hex
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
|
@ -182,56 +178,62 @@ object BroadcastDLCDialog
|
|||
nextRow)
|
||||
nextRow += 1
|
||||
|
||||
status.contractInfo.contractDescriptor.toTLV match {
|
||||
case v0: ContractDescriptorV0TLV =>
|
||||
gridPane.add(new Label("Potential Outcome"), 0, nextRow)
|
||||
gridPane.add(new Label("Payouts"), 1, nextRow)
|
||||
nextRow += 1
|
||||
status.contractInfo match {
|
||||
case singleContractInfo: SingleContractInfo =>
|
||||
singleContractInfo.contractDescriptor.toTLV match {
|
||||
case v0: ContractDescriptorV0TLV =>
|
||||
gridPane.add(new Label("Potential Outcome"), 0, nextRow)
|
||||
gridPane.add(new Label("Payouts"), 1, nextRow)
|
||||
nextRow += 1
|
||||
|
||||
val descriptor = EnumContractDescriptor
|
||||
.fromTLV(v0)
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
val descriptor = EnumContractDescriptor
|
||||
.fromTLV(v0)
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
|
||||
descriptor.foreach { case (str, satoshis) =>
|
||||
gridPane.add(new TextField() {
|
||||
text = str.outcome
|
||||
editable = false
|
||||
},
|
||||
0,
|
||||
nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = satoshis.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
case v1: ContractDescriptorV1TLV =>
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
val descriptor = NumericContractDescriptor.fromTLV(v1)
|
||||
val payoutCurve = if (status.isInitiator) {
|
||||
descriptor.outcomeValueFunc
|
||||
} else {
|
||||
descriptor
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
.outcomeValueFunc
|
||||
descriptor.foreach { case (str, satoshis) =>
|
||||
gridPane.add(new TextField() {
|
||||
text = str.outcome
|
||||
editable = false
|
||||
},
|
||||
0,
|
||||
nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = satoshis.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
case v1: ContractDescriptorV1TLV =>
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
val descriptor = NumericContractDescriptor.fromTLV(v1)
|
||||
val payoutCurve = if (status.isInitiator) {
|
||||
descriptor.outcomeValueFunc
|
||||
} else {
|
||||
descriptor
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
.outcomeValueFunc
|
||||
}
|
||||
|
||||
DLCPlotUtil.plotCETs(base = 2,
|
||||
descriptor.numDigits,
|
||||
payoutCurve,
|
||||
status.contractInfo.totalCollateral,
|
||||
descriptor.roundingIntervals,
|
||||
None)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
DLCPlotUtil.plotCETs(base = 2,
|
||||
descriptor.numDigits,
|
||||
payoutCurve,
|
||||
status.contractInfo.totalCollateral,
|
||||
descriptor.roundingIntervals,
|
||||
None)
|
||||
()
|
||||
}
|
||||
gridPane.add(new Label("Payout Function"), 0, nextRow)
|
||||
gridPane.add(previewGraphButton, 1, nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Payout Function"), 0, nextRow)
|
||||
gridPane.add(previewGraphButton, 1, nextRow)
|
||||
nextRow += 1
|
||||
case _: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Disjoint union contract is not supported in the broadcast dialog")
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Fee Rate"), 0, nextRow)
|
||||
|
|
|
@ -561,6 +561,14 @@ class CreateDLCOfferDialog
|
|||
UInt32(instant.getEpochSecond)
|
||||
}
|
||||
|
||||
val inputs = fields.values.flatMap { case (str, value) =>
|
||||
if (str.text.value.nonEmpty && value.text.value.nonEmpty) {
|
||||
val amount =
|
||||
numberFormatter.parse(value.text.value).longValue()
|
||||
Some((str.text.value, amount))
|
||||
} else None
|
||||
}
|
||||
|
||||
val contractInfo = oracleInfo match {
|
||||
case oracleInfo: EnumOracleInfo =>
|
||||
val missingOutcomes = fields.values.filter(_._2.text.value.isEmpty)
|
||||
|
@ -570,27 +578,27 @@ class CreateDLCOfferDialog
|
|||
s"You missed outcomes $missing. Please enter payouts for these situations")
|
||||
}
|
||||
|
||||
val inputs = fields.values.flatMap { case (str, value) =>
|
||||
if (str.text.value.nonEmpty && value.text.value.nonEmpty) {
|
||||
val amount =
|
||||
numberFormatter.parse(value.text.value).longValue()
|
||||
Some((str.text.value, amount))
|
||||
} else None
|
||||
}
|
||||
val contractMap = inputs.map { case (str, value) =>
|
||||
EnumOutcome(str) -> Satoshis(value)
|
||||
}.toVector
|
||||
|
||||
val descriptor = EnumContractDescriptor(contractMap)
|
||||
|
||||
ContractInfo(descriptor, oracleInfo).toTLV
|
||||
SingleContractInfo(descriptor, oracleInfo).toTLV
|
||||
case oracleInfo: NumericOracleInfo =>
|
||||
val (totalCol, numeric) = getNumericContractInfo(
|
||||
decompOpt,
|
||||
pointMap.toVector.sortBy(_._1).map(_._2),
|
||||
roundingMap.toVector.sortBy(_._1).map(_._2))
|
||||
val textFields: Vector[(TextField, TextField)] = {
|
||||
roundingMap.toVector.sortBy(_._1).map(_._2)
|
||||
}
|
||||
val (totalCollateral, numericContractDescriptor) =
|
||||
getNumericContractInfo(
|
||||
decompOpt,
|
||||
pointMap.toVector.sortBy(_._1).map(_._2),
|
||||
textFields
|
||||
)
|
||||
|
||||
ContractInfo(totalCol, numeric, oracleInfo).toTLV
|
||||
SingleContractInfo(totalCollateral,
|
||||
numericContractDescriptor,
|
||||
oracleInfo).toTLV
|
||||
}
|
||||
|
||||
CreateDLCOffer(
|
||||
|
|
|
@ -149,12 +149,8 @@ object SignDLCDialog
|
|||
}
|
||||
vbox.children.add(gridPane)
|
||||
|
||||
val (oracleKey, eventId) = status.contractInfo.oracleInfo.toTLV match {
|
||||
case OracleInfoV0TLV(announcement) =>
|
||||
(announcement.publicKey.hex, announcement.eventTLV.eventId)
|
||||
case _: MultiOracleInfoTLV =>
|
||||
throw new RuntimeException("This is impossible.")
|
||||
}
|
||||
val (oracleKey, eventId) =
|
||||
GUIUtil.getOraclePubKeyEventId(status.contractInfo.toTLV)
|
||||
|
||||
gridPane.add(new Label("Event Id"), 0, nextRow)
|
||||
gridPane.add(
|
||||
|
@ -171,7 +167,7 @@ object SignDLCDialog
|
|||
gridPane.add(new Label("Oracle Public Key"), 0, nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = oracleKey
|
||||
text = oracleKey.hex
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
|
@ -197,54 +193,59 @@ object SignDLCDialog
|
|||
nextRow)
|
||||
nextRow += 1
|
||||
|
||||
status.contractInfo.contractDescriptor.toTLV match {
|
||||
case v0: ContractDescriptorV0TLV =>
|
||||
gridPane.add(new Label("Potential Outcome"), 0, nextRow)
|
||||
gridPane.add(new Label("Payouts"), 1, nextRow)
|
||||
nextRow += 1
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
single.contractDescriptors.head.toTLV match {
|
||||
case v0: ContractDescriptorV0TLV =>
|
||||
gridPane.add(new Label("Potential Outcome"), 0, nextRow)
|
||||
gridPane.add(new Label("Payouts"), 1, nextRow)
|
||||
nextRow += 1
|
||||
|
||||
val descriptor = EnumContractDescriptor.fromTLV(v0)
|
||||
val descriptor = EnumContractDescriptor.fromTLV(v0)
|
||||
|
||||
descriptor.foreach { case (str, satoshis) =>
|
||||
gridPane.add(new TextField() {
|
||||
text = str.outcome
|
||||
editable = false
|
||||
},
|
||||
0,
|
||||
nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = satoshis.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
case v1: ContractDescriptorV1TLV =>
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
val descriptor = NumericContractDescriptor.fromTLV(v1)
|
||||
val payoutCurve = if (status.isInitiator) {
|
||||
descriptor.outcomeValueFunc
|
||||
} else {
|
||||
descriptor
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
.outcomeValueFunc
|
||||
descriptor.foreach { case (str, satoshis) =>
|
||||
gridPane.add(new TextField() {
|
||||
text = str.outcome
|
||||
editable = false
|
||||
},
|
||||
0,
|
||||
nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = satoshis.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
case v1: ContractDescriptorV1TLV =>
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
val descriptor = NumericContractDescriptor.fromTLV(v1)
|
||||
val payoutCurve = if (status.isInitiator) {
|
||||
descriptor.outcomeValueFunc
|
||||
} else {
|
||||
descriptor
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
.outcomeValueFunc
|
||||
}
|
||||
|
||||
DLCPlotUtil.plotCETs(base = 2,
|
||||
descriptor.numDigits,
|
||||
payoutCurve,
|
||||
status.contractInfo.totalCollateral,
|
||||
descriptor.roundingIntervals,
|
||||
None)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
DLCPlotUtil.plotCETs(base = 2,
|
||||
descriptor.numDigits,
|
||||
payoutCurve,
|
||||
status.contractInfo.totalCollateral,
|
||||
descriptor.roundingIntervals,
|
||||
None)
|
||||
()
|
||||
}
|
||||
gridPane.add(new Label("Payout Function"), 0, nextRow)
|
||||
gridPane.add(previewGraphButton, 1, nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Payout Function"), 0, nextRow)
|
||||
gridPane.add(previewGraphButton, 1, nextRow)
|
||||
nextRow += 1
|
||||
case _: DisjointUnionContractInfo =>
|
||||
sys.error(s"Disjoint union contract info not supported in the GUI")
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Fee Rate"), 0, nextRow)
|
||||
|
|
|
@ -54,8 +54,23 @@ object ViewDLCDialog {
|
|||
}
|
||||
|
||||
def buildView(status: DLCStatus, model: DLCPaneModel) = {
|
||||
status.contractInfo match {
|
||||
case singleContractInfo: SingleContractInfo =>
|
||||
buildGridPane(status, singleContractInfo, model)
|
||||
case _: DisjointUnionContractInfo =>
|
||||
sys.error(s"Disjoint union contracts are not supported")
|
||||
}
|
||||
}
|
||||
|
||||
private def buildGridPane(
|
||||
status: DLCStatus,
|
||||
singleContractInfo: SingleContractInfo,
|
||||
model: DLCPaneModel) = {
|
||||
require(status.contractInfo == singleContractInfo,
|
||||
s"Conflicting contract infos")
|
||||
val closingTxId: StringProperty = StringProperty(
|
||||
DLCStatus.getClosingTxId(status).map(_.hex).getOrElse(""))
|
||||
|
||||
new GridPane() {
|
||||
alignment = Pos.Center
|
||||
padding = Insets(10)
|
||||
|
@ -68,11 +83,7 @@ object ViewDLCDialog {
|
|||
|
||||
row += 1
|
||||
add(getLabel("Event Id"), 0, row)
|
||||
add(
|
||||
getTextField(
|
||||
status.oracleInfo.singleOracleInfos.head.announcement.eventTLV.eventId),
|
||||
columnIndex = 1,
|
||||
rowIndex = row)
|
||||
add(getTextField(status.eventIds.head), columnIndex = 1, rowIndex = row)
|
||||
|
||||
row += 1
|
||||
add(getLabel("Initiator"), 0, row)
|
||||
|
@ -217,7 +228,7 @@ object ViewDLCDialog {
|
|||
// TODO : Refund button and discriminator
|
||||
|
||||
row += 1
|
||||
status.contractInfo.contractDescriptor match {
|
||||
singleContractInfo.contractDescriptor match {
|
||||
case _: EnumContractDescriptor => ()
|
||||
case descriptor: NumericContractDescriptor =>
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
|
|
|
@ -4,7 +4,13 @@ import javafx.beans.value.ObservableValue
|
|||
import org.bitcoins.commons.jsonmodels.ExplorerEnv
|
||||
import org.bitcoins.core.config.BitcoinNetwork
|
||||
import org.bitcoins.core.protocol.BlockTimeStamp
|
||||
import org.bitcoins.core.protocol.tlv.OracleAnnouncementTLV
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
ContractInfoTLV,
|
||||
ContractInfoV0TLV,
|
||||
ContractInfoV1TLV,
|
||||
OracleAnnouncementTLV
|
||||
}
|
||||
import org.bitcoins.crypto.SchnorrPublicKey
|
||||
import org.bitcoins.gui.{GUI, GlobalData}
|
||||
import scalafx.beans.property.StringProperty
|
||||
import scalafx.scene.control.{Button, TextField, Tooltip}
|
||||
|
@ -194,4 +200,16 @@ object GUIUtil {
|
|||
val logoTestnet = new Image("/icons/bitcoin-s-testnet.png")
|
||||
val logoSignet = new Image("/icons/bitcoin-s-signet.png")
|
||||
val logoRegtest = new Image("/icons/bitcoin-s-regtest.png")
|
||||
|
||||
def getOraclePubKeyEventId(
|
||||
contractInfo: ContractInfoTLV): (SchnorrPublicKey, String) = {
|
||||
contractInfo match {
|
||||
case ContractInfoV0TLV(_, _, oracleInfo) =>
|
||||
(oracleInfo.announcements.head.publicKey,
|
||||
oracleInfo.announcements.head.eventTLV.eventId)
|
||||
case ContractInfoV1TLV(_, contractOraclePairs) =>
|
||||
(contractOraclePairs.head._2.announcements.head.publicKey,
|
||||
contractOraclePairs.head._2.announcements.head.eventTLV.eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -923,7 +923,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
EnumOutcome(loseStr)).sigPoint -> ECAdaptorSignature.dummy
|
||||
)
|
||||
|
||||
val contractInfo = ContractInfo(contractDesc, oracleInfo)
|
||||
val contractInfo = SingleContractInfo(contractDesc, oracleInfo)
|
||||
val contractInfoTLV = contractInfo.toTLV
|
||||
|
||||
val offer = DLCOffer(
|
||||
|
@ -944,7 +944,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
|
||||
"create a dlc offer" in {
|
||||
(mockWalletApi
|
||||
.createDLCOffer(_: ContractInfoV0TLV,
|
||||
.createDLCOffer(_: ContractInfoTLV,
|
||||
_: Satoshis,
|
||||
_: Option[SatoshisPerVirtualByte],
|
||||
_: UInt32,
|
||||
|
|
|
@ -5,9 +5,9 @@ import akka.http.scaladsl.server.Directives._
|
|||
import akka.http.scaladsl.server._
|
||||
import org.bitcoins.core.api.dlc.node.DLCNodeApi
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
ContractInfo,
|
||||
EnumSingleOracleInfo,
|
||||
NumericSingleOracleInfo
|
||||
NumericSingleOracleInfo,
|
||||
SingleContractInfo
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
EnumEventDescriptorV0TLV,
|
||||
|
@ -50,9 +50,9 @@ case class DLCRoutes(dlcNode: DLCNodeApi)(implicit system: ActorSystem)
|
|||
case _: EnumEventDescriptorV0TLV =>
|
||||
EnumSingleOracleInfo(create.announcementTLV)
|
||||
}
|
||||
val contractInfo = ContractInfo(create.totalCollateral,
|
||||
create.contractDescriptor,
|
||||
oracleInfo)
|
||||
val contractInfo = SingleContractInfo(create.totalCollateral,
|
||||
create.contractDescriptor,
|
||||
oracleInfo)
|
||||
Server.httpSuccess(contractInfo.hex)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class DLCMessageTest extends BitcoinSJvmTest {
|
|||
assertThrows[IllegalArgumentException](
|
||||
DLCOffer(
|
||||
protocolVersionOpt = DLCOfferTLV.currentVersionOpt,
|
||||
contractInfo = ContractInfo.dummy,
|
||||
contractInfo = SingleContractInfo.dummy,
|
||||
pubKeys = DLCPublicKeys(dummyPubKey, dummyAddress),
|
||||
totalCollateral = Satoshis(-1),
|
||||
fundingInputs = Vector.empty,
|
||||
|
@ -65,7 +65,7 @@ class DLCMessageTest extends BitcoinSJvmTest {
|
|||
assertThrows[IllegalArgumentException](
|
||||
DLCOffer(
|
||||
protocolVersionOpt = DLCOfferTLV.currentVersionOpt,
|
||||
contractInfo = ContractInfo.dummy,
|
||||
contractInfo = SingleContractInfo.dummy,
|
||||
pubKeys = DLCPublicKeys(dummyPubKey, dummyAddress),
|
||||
totalCollateral = Satoshis(-1),
|
||||
fundingInputs = Vector.empty,
|
||||
|
|
|
@ -18,7 +18,7 @@ import scala.concurrent._
|
|||
trait DLCWalletApi { self: WalletApi =>
|
||||
|
||||
def createDLCOffer(
|
||||
contractInfoTLV: ContractInfoV0TLV,
|
||||
contractInfoTLV: ContractInfoTLV,
|
||||
collateral: Satoshis,
|
||||
feeRateOpt: Option[SatoshisPerVirtualByte],
|
||||
locktime: UInt32,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.bitcoins.core.protocol.dlc.compute
|
||||
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
ContractInfo,
|
||||
EnumContractDescriptor,
|
||||
NumericContractDescriptor
|
||||
NumericContractDescriptor,
|
||||
SingleContractInfo
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
EnumOutcome,
|
||||
|
@ -121,7 +121,8 @@ object DLCAdaptorPointComputer {
|
|||
/** Efficiently computes all adaptor points, in order, for a given ContractInfo.
|
||||
* @see https://medium.com/crypto-garage/optimizing-numeric-outcome-dlc-creation-6d6091ac0e47
|
||||
*/
|
||||
def computeAdaptorPoints(contractInfo: ContractInfo): Vector[ECPublicKey] = {
|
||||
def computeAdaptorPoints(
|
||||
contractInfo: SingleContractInfo): Vector[ECPublicKey] = {
|
||||
// The possible messages a single nonce may be used to sign
|
||||
val possibleOutcomes: Vector[ByteVector] =
|
||||
contractInfo.contractDescriptor match {
|
||||
|
|
|
@ -132,7 +132,18 @@ object DLCExecutor {
|
|||
fundingTx: Transaction,
|
||||
fundOutputIndex: Int
|
||||
): ExecutedDLCOutcome = {
|
||||
val threshold = contractInfo.oracleInfo.threshold
|
||||
val sigOracles = oracleSigs.map(_.oracle)
|
||||
|
||||
val oracleInfoOpt = contractInfo.oracleInfos.find { oracleInfo =>
|
||||
oracleInfo.threshold <= oracleSigs.length &&
|
||||
sigOracles.forall(oracleInfo.singleOracleInfos.contains)
|
||||
}
|
||||
|
||||
val oracleInfo = oracleInfoOpt.getOrElse(
|
||||
throw new IllegalArgumentException(
|
||||
s"Signatures do not correspond to any possible outcome! $oracleSigs"))
|
||||
|
||||
val threshold = oracleInfo.threshold
|
||||
val sigCombinations = CETCalculator.combinations(oracleSigs, threshold)
|
||||
|
||||
var msgOpt: Option[OracleOutcome] = None
|
||||
|
|
|
@ -9,9 +9,16 @@ import org.bitcoins.core.protocol.dlc.compute.{
|
|||
CETCalculator,
|
||||
DLCAdaptorPointComputer
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.models.ContractOraclePair.{
|
||||
EnumPair,
|
||||
NumericPair
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage.DLCAccept
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
ContractInfoTLV,
|
||||
ContractInfoV0TLV,
|
||||
ContractInfoV1TLV,
|
||||
OracleAnnouncementTLV,
|
||||
TLVDeserializable,
|
||||
TLVSerializable,
|
||||
UnsignedNumericOutcome
|
||||
|
@ -21,6 +28,139 @@ import org.bitcoins.crypto.ECPublicKey
|
|||
|
||||
import scala.collection.immutable.HashMap
|
||||
|
||||
sealed trait ContractInfo extends TLVSerializable[ContractInfoTLV] {
|
||||
|
||||
def contracts: Vector[SingleContractInfo]
|
||||
|
||||
require(contracts.nonEmpty, s"Cannot have empty contract: $this")
|
||||
|
||||
def totalCollateral: Satoshis
|
||||
|
||||
def oracleInfos: Vector[OracleInfo]
|
||||
|
||||
def contractDescriptors: Vector[ContractDescriptor]
|
||||
|
||||
/** Returns the maximum payout the offerer could win from this contract */
|
||||
def max: Satoshis
|
||||
|
||||
/** Computes the CET set and their corresponding payouts using CETCalculator. */
|
||||
def allOutcomesAndPayouts: Vector[(OracleOutcome, Satoshis)]
|
||||
|
||||
/** Corresponds with this DLC's CET set */
|
||||
lazy val allOutcomes: Vector[OracleOutcome] =
|
||||
allOutcomesAndPayouts.map(_._1)
|
||||
|
||||
/** Maps adpator points to their corresponding OracleOutcomes (which correspond to CETs) */
|
||||
lazy val sigPointMap: Map[ECPublicKey, OracleOutcome] =
|
||||
adaptorPoints.zip(allOutcomes).toMap
|
||||
|
||||
/** Map OracleOutcomes (which correspond to CETs) to their adpator point and payouts */
|
||||
lazy val outcomeMap: Map[OracleOutcome, (ECPublicKey, Satoshis, Satoshis)] = {
|
||||
val builder =
|
||||
HashMap.newBuilder[OracleOutcome, (ECPublicKey, Satoshis, Satoshis)]
|
||||
|
||||
allOutcomesAndPayouts.zip(adaptorPoints).foreach {
|
||||
case ((outcome, offerPayout), adaptorPoint) =>
|
||||
val acceptPayout = (totalCollateral - offerPayout).satoshis
|
||||
|
||||
builder.+=((outcome, (adaptorPoint, offerPayout, acceptPayout)))
|
||||
}
|
||||
|
||||
builder.result()
|
||||
}
|
||||
|
||||
lazy val adaptorPoints: Vector[ECPublicKey] = {
|
||||
contracts.flatMap(DLCAdaptorPointComputer.computeAdaptorPoints)
|
||||
}
|
||||
|
||||
lazy val adaptorPointsIndexed: Vector[Indexed[ECPublicKey]] = Indexed(
|
||||
adaptorPoints)
|
||||
|
||||
/** Checks if the given OracleSignatures exactly match the given OracleOutcome.
|
||||
*
|
||||
* Warning: This will return false if too many OracleSignatures are given.
|
||||
*
|
||||
* TODO: Needs a lot of optimization
|
||||
*/
|
||||
def verifySigs(
|
||||
outcome: OracleOutcome,
|
||||
sigs: Vector[OracleSignatures]): Boolean = {
|
||||
if (!sigs.map(_.oracle).forall(outcome.oracles.contains)) {
|
||||
false
|
||||
} else {
|
||||
outcome match {
|
||||
case EnumOracleOutcome(oracles, enumOutcome) =>
|
||||
oracles.foldLeft(true) { case (boolSoFar, oracle) =>
|
||||
lazy val sig = sigs.find(_.oracle == oracle)
|
||||
boolSoFar && sig.exists(_.verifySignatures(enumOutcome))
|
||||
}
|
||||
case NumericOracleOutcome(oraclesAndOutcomes) =>
|
||||
oraclesAndOutcomes.foldLeft(true) {
|
||||
case (boolSoFar, (oracle, digits)) =>
|
||||
lazy val sig = sigs.find(_.oracle == oracle)
|
||||
boolSoFar && sig.exists(_.verifySignatures(digits))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Searches all possible outcomes for one which corresponds to the given signatures.
|
||||
*
|
||||
* Warning: This will return false if too many OracleSignatures are given.
|
||||
*/
|
||||
def findOutcome(sigs: Vector[OracleSignatures]): Option[OracleOutcome] = {
|
||||
// TODO: Optimize by looking at nonces
|
||||
// TODO: Optimize using NumericOracleSignatures.computeOutcome
|
||||
allOutcomes.find(verifySigs(_, sigs))
|
||||
}
|
||||
|
||||
/** Returns the adaptor point and payouts for a given OracleOutcome */
|
||||
def resultOfOutcome(
|
||||
outcome: OracleOutcome): (ECPublicKey, Satoshis, Satoshis) = {
|
||||
outcomeMap(outcome)
|
||||
}
|
||||
|
||||
/** Returns the payouts for the signature as (toOffer, toAccept) */
|
||||
def getPayouts(sigs: Vector[OracleSignatures]): (Satoshis, Satoshis) = {
|
||||
val outcome = findOutcome(sigs) match {
|
||||
case Some(outcome) => outcome
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Signatures do not correspond to a possible outcome! $sigs")
|
||||
}
|
||||
getPayouts(outcome)
|
||||
}
|
||||
|
||||
/** Returns the payouts for the outcome as (toOffer, toAccept) */
|
||||
def getPayouts(outcome: OracleOutcome): (Satoshis, Satoshis) = {
|
||||
val (_, offerOutcome, acceptOutcome) = resultOfOutcome(outcome)
|
||||
|
||||
(offerOutcome, acceptOutcome)
|
||||
}
|
||||
|
||||
/** A ContractInfo can be constructed by the offerer, but it will not contain new
|
||||
* information which alters the DLC's contract which is received in the accept message.
|
||||
*
|
||||
* Specifically if the total collateral changes or negotiation fields are relevant.
|
||||
*
|
||||
* In these cases, this function should be called to update the ContractInfo.
|
||||
*/
|
||||
def updateOnAccept(
|
||||
newTotalCollateral: Satoshis,
|
||||
negotiationFields: DLCAccept.NegotiationFields): ContractInfo
|
||||
}
|
||||
|
||||
object ContractInfo
|
||||
extends TLVDeserializable[ContractInfoTLV, ContractInfo](ContractInfoTLV) {
|
||||
|
||||
override def fromTLV(tlv: ContractInfoTLV): ContractInfo = {
|
||||
tlv match {
|
||||
case tlv: ContractInfoV0TLV => SingleContractInfo.fromTLV(tlv)
|
||||
case tlv: ContractInfoV1TLV => DisjointUnionContractInfo.fromTLV(tlv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Fully determines a DLC up to public keys and funding UTXOs to be used.
|
||||
*
|
||||
* Contains all contract and oracle information and provides an external
|
||||
|
@ -32,31 +172,50 @@ import scala.collection.immutable.HashMap
|
|||
* done regarding CETs during DLC setup and execution.
|
||||
* @see https://github.com/discreetlogcontracts/dlcspecs/blob/a8876ed28ed33d5f7d5104f01aa2a8d80d128460/Messaging.md#the-contract_info-type
|
||||
*/
|
||||
case class ContractInfo(
|
||||
totalCollateral: Satoshis,
|
||||
case class SingleContractInfo(
|
||||
override val totalCollateral: Satoshis,
|
||||
contractOraclePair: ContractOraclePair)
|
||||
extends TLVSerializable[ContractInfoV0TLV] {
|
||||
extends ContractInfo
|
||||
with TLVSerializable[ContractInfoV0TLV] {
|
||||
|
||||
override def contracts: Vector[SingleContractInfo] = {
|
||||
Vector(this)
|
||||
}
|
||||
|
||||
def contractDescriptor: ContractDescriptor =
|
||||
contractOraclePair.contractDescriptor
|
||||
|
||||
def oracleInfo: OracleInfo = contractOraclePair.oracleInfo
|
||||
|
||||
def announcements: Vector[OracleAnnouncementTLV] = {
|
||||
contractOraclePair match {
|
||||
case EnumPair(_, oracleInfo) =>
|
||||
oracleInfo.singleOracleInfos.map(_.announcement)
|
||||
case NumericPair(_, oracleInfo) =>
|
||||
oracleInfo.singleOracleInfos.map(_.announcement)
|
||||
}
|
||||
}
|
||||
|
||||
override def contractDescriptors: Vector[ContractDescriptor] = Vector(
|
||||
contractDescriptor)
|
||||
|
||||
override def oracleInfos: Vector[OracleInfo] = Vector(oracleInfo)
|
||||
|
||||
override def toTLV: ContractInfoV0TLV = {
|
||||
ContractInfoV0TLV(totalCollateral,
|
||||
contractDescriptor.toTLV,
|
||||
oracleInfo.toTLV)
|
||||
}
|
||||
|
||||
/** Returns the maximum payout the offerer could win from this contract */
|
||||
val max: Satoshis = contractDescriptor match {
|
||||
/** @inheritdoc */
|
||||
override val max: Satoshis = contractDescriptor match {
|
||||
case descriptor: EnumContractDescriptor =>
|
||||
descriptor.values.maxBy(_.toLong)
|
||||
case _: NumericContractDescriptor => totalCollateral
|
||||
}
|
||||
|
||||
/** Computes the CET set and their corresponding payouts using CETCalculator. */
|
||||
lazy val allOutcomesAndPayouts: Vector[(OracleOutcome, Satoshis)] = {
|
||||
/** @inheritdoc */
|
||||
override lazy val allOutcomesAndPayouts: Vector[(OracleOutcome, Satoshis)] = {
|
||||
contractOraclePair match {
|
||||
case ContractOraclePair.EnumPair(descriptor,
|
||||
single: EnumSingleOracleInfo) =>
|
||||
|
@ -132,174 +291,149 @@ case class ContractInfo(
|
|||
}
|
||||
}
|
||||
|
||||
/** Corresponds with this DLC's CET set */
|
||||
lazy val allOutcomes: Vector[OracleOutcome] =
|
||||
allOutcomesAndPayouts.map(_._1)
|
||||
|
||||
/** Maps adpator points to their corresponding OracleOutcomes (which correspond to CETs) */
|
||||
lazy val sigPointMap: Map[ECPublicKey, OracleOutcome] =
|
||||
adaptorPoints.zip(allOutcomes).toMap
|
||||
|
||||
/** Map OracleOutcomes (which correspond to CETs) to their adpator point and payouts */
|
||||
lazy val outcomeMap: Map[OracleOutcome, (ECPublicKey, Satoshis, Satoshis)] = {
|
||||
val builder =
|
||||
HashMap.newBuilder[OracleOutcome, (ECPublicKey, Satoshis, Satoshis)]
|
||||
|
||||
allOutcomesAndPayouts.zip(adaptorPoints).foreach {
|
||||
case ((outcome, offerPayout), adaptorPoint) =>
|
||||
val acceptPayout = (totalCollateral - offerPayout).satoshis
|
||||
|
||||
builder.+=((outcome, (adaptorPoint, offerPayout, acceptPayout)))
|
||||
}
|
||||
|
||||
builder.result()
|
||||
}
|
||||
|
||||
lazy val adaptorPoints: Vector[ECPublicKey] = {
|
||||
DLCAdaptorPointComputer.computeAdaptorPoints(this)
|
||||
}
|
||||
|
||||
lazy val adaptorPointsIndexed: Vector[Indexed[ECPublicKey]] = Indexed(
|
||||
adaptorPoints)
|
||||
|
||||
/** Checks if the given OracleSignatures exactly match the given OracleOutcome.
|
||||
*
|
||||
* Warning: This will return false if too many OracleSignatures are given.
|
||||
*
|
||||
* TODO: Needs a lot of optimization
|
||||
*/
|
||||
def verifySigs(
|
||||
outcome: OracleOutcome,
|
||||
sigs: Vector[OracleSignatures]): Boolean = {
|
||||
outcome match {
|
||||
case EnumOracleOutcome(oracles, enumOutcome) =>
|
||||
oracleInfo match {
|
||||
case _: NumericOracleInfo =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Cannot handle $enumOutcome with numeric oracle commitments: $oracleInfo")
|
||||
case _: EnumOracleInfo =>
|
||||
oracles.foldLeft(true) { case (boolSoFar, oracle) =>
|
||||
lazy val sig = sigs.find(_.oracle == oracle)
|
||||
boolSoFar && sig.exists(_.verifySignatures(enumOutcome))
|
||||
}
|
||||
}
|
||||
case NumericOracleOutcome(oraclesAndOutcomes) =>
|
||||
oracleInfo match {
|
||||
case _: EnumOracleInfo =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Cannot handle numeric outcomes with enum oracle commitments: $oracleInfo")
|
||||
case _: NumericOracleInfo =>
|
||||
oraclesAndOutcomes.foldLeft(true) {
|
||||
case (boolSoFar, (oracle, digits)) =>
|
||||
lazy val sig = sigs.find(_.oracle == oracle)
|
||||
boolSoFar && sig.exists(_.verifySignatures(digits))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Searches all possible outcomes for one which corresponds to the given signatures.
|
||||
*
|
||||
* Warning: This will return false if too many OracleSignatures are given.
|
||||
*/
|
||||
def findOutcome(sigs: Vector[OracleSignatures]): Option[OracleOutcome] = {
|
||||
// TODO: Optimize by looking at nonces
|
||||
// TODO: Optimize using NumericOracleSignatures.computeOutcome
|
||||
allOutcomes.find(verifySigs(_, sigs))
|
||||
}
|
||||
|
||||
/** Returns the adaptor point and payouts for a given OracleOutcome */
|
||||
def resultOfOutcome(
|
||||
outcome: OracleOutcome): (ECPublicKey, Satoshis, Satoshis) = {
|
||||
outcomeMap(outcome)
|
||||
}
|
||||
|
||||
/** Returns the payouts for the signature as (toOffer, toAccept) */
|
||||
def getPayouts(sigs: Vector[OracleSignatures]): (Satoshis, Satoshis) = {
|
||||
val outcome = findOutcome(sigs) match {
|
||||
case Some(outcome) => outcome
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Signatures do not correspond to a possible outcome! $sigs")
|
||||
}
|
||||
getPayouts(outcome)
|
||||
}
|
||||
|
||||
/** Returns the payouts for the outcome as (toOffer, toAccept) */
|
||||
def getPayouts(outcome: OracleOutcome): (Satoshis, Satoshis) = {
|
||||
val (_, offerOutcome, acceptOutcome) = resultOfOutcome(outcome)
|
||||
|
||||
(offerOutcome, acceptOutcome)
|
||||
}
|
||||
|
||||
/** A ContractInfo can be constructed by the offerer, but it will not contain new
|
||||
* information which alters the DLC's contract which is received in the accept message.
|
||||
*
|
||||
* Specifically if the total collateral changes or negotiation fields are relevant.
|
||||
*
|
||||
* In these cases, this function should be called to update the ContractInfo.
|
||||
*/
|
||||
def updateOnAccept(
|
||||
/** @inheritdoc */
|
||||
override def updateOnAccept(
|
||||
newTotalCollateral: Satoshis,
|
||||
negotiationFields: DLCAccept.NegotiationFields): ContractInfo = {
|
||||
if (newTotalCollateral == totalCollateral) {
|
||||
this
|
||||
} else {
|
||||
contractOraclePair match {
|
||||
case ContractOraclePair.EnumPair(_, _) =>
|
||||
if (negotiationFields != DLCAccept.NoNegotiationFields) {
|
||||
throw new IllegalArgumentException(
|
||||
s"Cannot have rounding intervals for single nonce contract: $negotiationFields")
|
||||
}
|
||||
negotiationFields: DLCAccept.NegotiationFields): SingleContractInfo = {
|
||||
contractOraclePair match {
|
||||
case ContractOraclePair.EnumPair(_, _) =>
|
||||
if (negotiationFields != DLCAccept.NoNegotiationFields) {
|
||||
throw new IllegalArgumentException(
|
||||
s"Cannot have rounding intervals for single nonce contract: $negotiationFields")
|
||||
}
|
||||
|
||||
if (newTotalCollateral == totalCollateral) {
|
||||
this
|
||||
} else {
|
||||
this.copy(totalCollateral = newTotalCollateral)
|
||||
}
|
||||
|
||||
case ContractOraclePair.NumericPair(descriptor, oracleInfo) =>
|
||||
val newRoundingIntervals = negotiationFields match {
|
||||
case DLCAccept.NegotiationFieldsV1(acceptRoundingIntervals) =>
|
||||
descriptor.roundingIntervals.minRoundingWith(
|
||||
acceptRoundingIntervals)
|
||||
case DLCAccept.NoNegotiationFields => descriptor.roundingIntervals
|
||||
}
|
||||
case ContractOraclePair.NumericPair(descriptor, oracleInfo) =>
|
||||
val newRoundingIntervals = negotiationFields match {
|
||||
case DLCAccept.NegotiationFieldsV1(acceptRoundingIntervals) =>
|
||||
descriptor.roundingIntervals.minRoundingWith(
|
||||
acceptRoundingIntervals)
|
||||
case DLCAccept.NoNegotiationFields => descriptor.roundingIntervals
|
||||
case _: DLCAccept.NegotiationFieldsV2 =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Cannot use disjoint union negotiation fields for a SingleContractInfo, $negotiationFields")
|
||||
}
|
||||
|
||||
if (
|
||||
newTotalCollateral == totalCollateral && newRoundingIntervals == descriptor.roundingIntervals
|
||||
) {
|
||||
this
|
||||
} else {
|
||||
val newDescriptor =
|
||||
descriptor.copy(roundingIntervals = newRoundingIntervals)
|
||||
val contractOraclePair =
|
||||
ContractOraclePair.NumericPair(newDescriptor, oracleInfo)
|
||||
ContractInfo(totalCollateral = newTotalCollateral,
|
||||
contractOraclePair = contractOraclePair)
|
||||
}
|
||||
SingleContractInfo(totalCollateral = newTotalCollateral,
|
||||
contractOraclePair = contractOraclePair)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ContractInfo
|
||||
extends TLVDeserializable[ContractInfoV0TLV, ContractInfo](
|
||||
object SingleContractInfo
|
||||
extends TLVDeserializable[ContractInfoV0TLV, SingleContractInfo](
|
||||
ContractInfoV0TLV) {
|
||||
|
||||
lazy val dummy: ContractInfo = fromTLV(ContractInfoV0TLV.dummy)
|
||||
|
||||
override def fromTLV(tlv: ContractInfoV0TLV): ContractInfo = {
|
||||
override def fromTLV(tlv: ContractInfoV0TLV): SingleContractInfo = {
|
||||
val contract = ContractDescriptor.fromTLV(tlv.contractDescriptor)
|
||||
val oracleInfo = OracleInfo.fromTLV(tlv.oracleInfo)
|
||||
val contractOraclePair =
|
||||
ContractOraclePair.fromDescriptorOracle(contract, oracleInfo)
|
||||
ContractInfo(tlv.totalCollateral, contractOraclePair)
|
||||
SingleContractInfo(tlv.totalCollateral, contractOraclePair)
|
||||
}
|
||||
|
||||
def apply(
|
||||
enumDescriptor: EnumContractDescriptor,
|
||||
enumOracleInfo: EnumOracleInfo): ContractInfo = {
|
||||
enumOracleInfo: EnumOracleInfo): SingleContractInfo = {
|
||||
val enumPair = ContractOraclePair.EnumPair(enumDescriptor, enumOracleInfo)
|
||||
ContractInfo(totalCollateral = enumDescriptor.values.maxBy(_.toLong),
|
||||
enumPair)
|
||||
SingleContractInfo(totalCollateral = enumDescriptor.values.maxBy(_.toLong),
|
||||
enumPair)
|
||||
}
|
||||
|
||||
def apply(
|
||||
totalCollateral: Satoshis,
|
||||
contractDescriptor: ContractDescriptor,
|
||||
oracleInfo: OracleInfo): ContractInfo = {
|
||||
ContractInfo(
|
||||
oracleInfo: OracleInfo): SingleContractInfo = {
|
||||
SingleContractInfo(
|
||||
totalCollateral,
|
||||
ContractOraclePair.fromDescriptorOracle(contractDescriptor, oracleInfo))
|
||||
}
|
||||
}
|
||||
|
||||
case class DisjointUnionContractInfo(contracts: Vector[SingleContractInfo])
|
||||
extends ContractInfo
|
||||
with TLVSerializable[ContractInfoV1TLV] {
|
||||
|
||||
override val totalCollateral: Satoshis = contracts.head.totalCollateral
|
||||
|
||||
require(contracts.forall(_.totalCollateral == totalCollateral),
|
||||
"All contract total collaterals must be equal.")
|
||||
|
||||
override def oracleInfos: Vector[OracleInfo] = contracts.map(_.oracleInfo)
|
||||
|
||||
override def contractDescriptors: Vector[ContractDescriptor] =
|
||||
contracts.map(_.contractDescriptor)
|
||||
|
||||
override def toTLV: ContractInfoV1TLV = {
|
||||
ContractInfoV1TLV(
|
||||
totalCollateral,
|
||||
contracts.map(contract =>
|
||||
(contract.contractDescriptor.toTLV, contract.oracleInfo.toTLV)))
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override val max: Satoshis = contracts.map(_.max).max
|
||||
|
||||
/** @inheritdoc */
|
||||
override lazy val allOutcomesAndPayouts: Vector[(OracleOutcome, Satoshis)] = {
|
||||
contracts.flatMap(_.allOutcomesAndPayouts)
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def updateOnAccept(
|
||||
newTotalCollateral: Satoshis,
|
||||
negotiationFields: DLCAccept.NegotiationFields): DisjointUnionContractInfo = {
|
||||
negotiationFields match {
|
||||
case DLCAccept.NegotiationFieldsV2(nestedNegotiationFields) =>
|
||||
require(
|
||||
nestedNegotiationFields.length == contracts.length,
|
||||
s"Expected ${contracts.length} negotiation fields, got $negotiationFields.")
|
||||
|
||||
val newContracts = contracts.zip(nestedNegotiationFields).map {
|
||||
case (contract, negotiationFields) =>
|
||||
contract.updateOnAccept(newTotalCollateral, negotiationFields)
|
||||
}
|
||||
|
||||
DisjointUnionContractInfo(newContracts)
|
||||
case _: DLCAccept.NegotiationFields =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Required disjoint union negotiation fields for disjoint union contract info, got $negotiationFields")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DisjointUnionContractInfo
|
||||
extends TLVDeserializable[ContractInfoV1TLV, DisjointUnionContractInfo](
|
||||
ContractInfoV1TLV) {
|
||||
|
||||
override def fromTLV(tlv: ContractInfoV1TLV): DisjointUnionContractInfo = {
|
||||
val contracts = tlv.contractOraclePairs.map {
|
||||
case (descriptorTLV, oracleTLV) =>
|
||||
val contract = ContractDescriptor.fromTLV(descriptorTLV)
|
||||
val oracleInfo = OracleInfo.fromTLV(oracleTLV)
|
||||
val contractOraclePair =
|
||||
ContractOraclePair.fromDescriptorOracle(contract, oracleInfo)
|
||||
|
||||
SingleContractInfo(tlv.totalCollateral, contractOraclePair)
|
||||
}
|
||||
|
||||
DisjointUnionContractInfo(contracts)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,8 +98,10 @@ object DLCMessage {
|
|||
changeSerialId != fundOutputSerialId,
|
||||
s"changeSerialId ($changeSerialId) cannot be equal to fundOutputSerialId ($fundOutputSerialId)")
|
||||
|
||||
val oracleInfo: OracleInfo = contractInfo.oracleInfo
|
||||
val contractDescriptor: ContractDescriptor = contractInfo.contractDescriptor
|
||||
val oracleInfos: Vector[OracleInfo] = contractInfo.oracleInfos
|
||||
|
||||
val contractDescriptors: Vector[ContractDescriptor] =
|
||||
contractInfo.contractDescriptors
|
||||
|
||||
lazy val dlcId: Sha256Digest = calcDLCId(fundingInputs.map(_.outPoint))
|
||||
|
||||
|
@ -261,6 +263,18 @@ object DLCMessage {
|
|||
NegotiationFieldsV1TLV(roundingIntervals.toTLV)
|
||||
}
|
||||
|
||||
case class NegotiationFieldsV2(
|
||||
nestedNegotiationFields: Vector[NegotiationFields])
|
||||
extends TLVSerializable[NegotiationFieldsV2TLV]
|
||||
with NegotiationFields {
|
||||
require(
|
||||
nestedNegotiationFields.forall(!_.isInstanceOf[NegotiationFieldsV2]))
|
||||
|
||||
override def toTLV: NegotiationFieldsV2TLV = {
|
||||
NegotiationFieldsV2TLV(nestedNegotiationFields.map(_.toTLV))
|
||||
}
|
||||
}
|
||||
|
||||
object NegotiationFields {
|
||||
|
||||
def fromTLV(tlv: NegotiationFieldsTLV): NegotiationFields = {
|
||||
|
@ -268,6 +282,9 @@ object DLCMessage {
|
|||
case NoNegotiationFieldsTLV => NoNegotiationFields
|
||||
case NegotiationFieldsV1TLV(roundingIntervalsTLV) =>
|
||||
NegotiationFieldsV1(RoundingIntervals.fromTLV(roundingIntervalsTLV))
|
||||
case NegotiationFieldsV2TLV(nestedNegotiationFields) =>
|
||||
NegotiationFieldsV2(
|
||||
nestedNegotiationFields.map(NegotiationFields.fromTLV))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
|
|||
DLCOffer,
|
||||
DLCSign
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv.OracleAnnouncementTLV
|
||||
import org.bitcoins.core.protocol.transaction.WitnessTransaction
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.crypto._
|
||||
|
@ -24,13 +25,21 @@ sealed trait DLCStatus {
|
|||
def lastUpdated: Instant
|
||||
def tempContractId: Sha256Digest
|
||||
def contractInfo: ContractInfo
|
||||
def oracleInfo: OracleInfo = contractInfo.oracleInfo
|
||||
def oracleInfos: Vector[OracleInfo] = contractInfo.oracleInfos
|
||||
def timeouts: DLCTimeouts
|
||||
def feeRate: FeeUnit
|
||||
def totalCollateral: CurrencyUnit
|
||||
def localCollateral: CurrencyUnit
|
||||
def remoteCollateral: CurrencyUnit = totalCollateral - localCollateral
|
||||
|
||||
lazy val announcements: Vector[OracleAnnouncementTLV] = {
|
||||
oracleInfos.flatMap(_.singleOracleInfos.map(_.announcement))
|
||||
}
|
||||
|
||||
lazy val eventIds: Vector[String] = {
|
||||
announcements.map(_.eventTLV.eventId)
|
||||
}
|
||||
|
||||
lazy val statusString: String = state.toString
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ case class SingleOracleDLCTemplate(
|
|||
override val toContractInfo: ContractInfo = {
|
||||
val pair: NumericPair =
|
||||
ContractOraclePair.NumericPair(contractDescriptor, oracleInfo)
|
||||
ContractInfo(totalCollateral.satoshis, pair)
|
||||
SingleContractInfo(totalCollateral.satoshis, pair)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ case class MultiOracleDLCTemplate(
|
|||
override val toContractInfo: ContractInfo = {
|
||||
val pair: NumericPair =
|
||||
ContractOraclePair.NumericPair(contractDescriptor, oracleInfo)
|
||||
ContractInfo(totalCollateral.satoshis, pair)
|
||||
SingleContractInfo(totalCollateral.satoshis, pair)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,8 @@ object LnMessage extends Factory[LnMessage[TLV]] {
|
|||
case unknown: UnknownTLV =>
|
||||
throw new IllegalArgumentException(s"Parsed unknown TLV $unknown")
|
||||
case _: DLCSetupTLV | _: DLCSetupPieceTLV | _: InitTLV | _: DLCOracleTLV |
|
||||
_: ErrorTLV | _: PingTLV | _: PongTLV =>
|
||||
_: ErrorTLV | _: PingTLV | _: PongTLV | _: ContractInfoV0TLV |
|
||||
_: ContractInfoV1TLV =>
|
||||
()
|
||||
}
|
||||
|
||||
|
|
|
@ -162,7 +162,6 @@ object TLV extends TLVParentFactory[TLV] {
|
|||
RoundingIntervalsV0TLV,
|
||||
PayoutFunctionV0TLV,
|
||||
OracleParamsV0TLV,
|
||||
ContractInfoV0TLV,
|
||||
FundingInputV0TLV,
|
||||
CETSignaturesV0TLV,
|
||||
FundingSignaturesV0TLV,
|
||||
|
@ -172,6 +171,7 @@ object TLV extends TLVParentFactory[TLV] {
|
|||
) ++ EventDescriptorTLV.allFactories ++
|
||||
ContractDescriptorTLV.allFactories ++
|
||||
OracleInfoTLV.allFactories ++
|
||||
ContractInfoTLV.allFactories ++
|
||||
OracleAnnouncementTLV.allFactories ++
|
||||
OracleAttestmentTLV.allFactories ++
|
||||
NegotiationFieldsTLV.allFactories
|
||||
|
@ -1062,7 +1062,9 @@ object ContractDescriptorV1TLV extends TLVFactory[ContractDescriptorV1TLV] {
|
|||
override val typeName: String = "ContractDescriptorV1TLV"
|
||||
}
|
||||
|
||||
sealed trait OracleInfoTLV extends DLCSetupPieceTLV
|
||||
sealed trait OracleInfoTLV extends DLCSetupPieceTLV {
|
||||
def announcements: Vector[OracleAnnouncementTLV]
|
||||
}
|
||||
|
||||
object OracleInfoTLV extends TLVParentFactory[OracleInfoTLV] {
|
||||
|
||||
|
@ -1079,6 +1081,10 @@ case class OracleInfoV0TLV(announcement: OracleAnnouncementTLV)
|
|||
override val value: ByteVector = {
|
||||
announcement.bytes
|
||||
}
|
||||
|
||||
override val announcements: Vector[OracleAnnouncementTLV] = {
|
||||
Vector(announcement)
|
||||
}
|
||||
}
|
||||
|
||||
object OracleInfoV0TLV extends TLVFactory[OracleInfoV0TLV] {
|
||||
|
@ -1098,6 +1104,10 @@ object OracleInfoV0TLV extends TLVFactory[OracleInfoV0TLV] {
|
|||
sealed trait MultiOracleInfoTLV extends OracleInfoTLV {
|
||||
def threshold: Int
|
||||
def oracles: OrderedAnnouncements
|
||||
|
||||
override val announcements: Vector[OracleAnnouncementTLV] = {
|
||||
oracles.toVector
|
||||
}
|
||||
}
|
||||
|
||||
case class OracleInfoV1TLV(threshold: Int, oracles: OrderedAnnouncements)
|
||||
|
@ -1168,6 +1178,8 @@ case class OracleInfoV2TLV(
|
|||
override val value: ByteVector = {
|
||||
UInt16(threshold).bytes ++ u16PrefixedList(oracles.toVector) ++ params.bytes
|
||||
}
|
||||
|
||||
override val announcements: Vector[OracleAnnouncementTLV] = oracles.toVector
|
||||
}
|
||||
|
||||
object OracleInfoV2TLV extends TLVFactory[OracleInfoV2TLV] {
|
||||
|
@ -1187,11 +1199,23 @@ object OracleInfoV2TLV extends TLVFactory[OracleInfoV2TLV] {
|
|||
override val typeName: String = "OracleInfoV2TLV"
|
||||
}
|
||||
|
||||
sealed trait ContractInfoTLV extends DLCSetupPieceTLV {
|
||||
def totalCollateral: Satoshis
|
||||
}
|
||||
|
||||
object ContractInfoTLV extends TLVParentFactory[ContractInfoTLV] {
|
||||
|
||||
override val allFactories: Vector[TLVFactory[ContractInfoTLV]] =
|
||||
Vector(ContractInfoV0TLV, ContractInfoV1TLV)
|
||||
|
||||
override def typeName: String = "ContractInfoTLV"
|
||||
}
|
||||
|
||||
case class ContractInfoV0TLV(
|
||||
totalCollateral: Satoshis,
|
||||
contractDescriptor: ContractDescriptorTLV,
|
||||
oracleInfo: OracleInfoTLV)
|
||||
extends DLCSetupPieceTLV {
|
||||
extends ContractInfoTLV {
|
||||
override val tpe: BigSizeUInt = ContractInfoV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
|
@ -1224,6 +1248,43 @@ object ContractInfoV0TLV extends TLVFactory[ContractInfoV0TLV] {
|
|||
override val typeName: String = "ContractInfoV0TLV"
|
||||
}
|
||||
|
||||
case class ContractInfoV1TLV(
|
||||
totalCollateral: Satoshis,
|
||||
contractOraclePairs: Vector[(ContractDescriptorTLV, OracleInfoTLV)])
|
||||
extends ContractInfoTLV {
|
||||
override val tpe: BigSizeUInt = ContractInfoV0TLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
satBytes(totalCollateral) ++
|
||||
bigSizePrefixedList[(ContractDescriptorTLV, OracleInfoTLV)](
|
||||
contractOraclePairs,
|
||||
{ case (descriptor, oracleInfo) =>
|
||||
descriptor.bytes ++ oracleInfo.bytes
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object ContractInfoV1TLV extends TLVFactory[ContractInfoV1TLV] {
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(55344)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): ContractInfoV1TLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val totalCollateral = iter.takeSats()
|
||||
val contracts = iter.takeBigSizePrefixedList { () =>
|
||||
val descriptor = iter.take(ContractDescriptorTLV)
|
||||
val oracleInfo = iter.take(OracleInfoTLV)
|
||||
|
||||
(descriptor, oracleInfo)
|
||||
}
|
||||
|
||||
ContractInfoV1TLV(totalCollateral, contracts)
|
||||
}
|
||||
|
||||
override val typeName: String = "ContractInfoV1TLV"
|
||||
}
|
||||
|
||||
sealed trait FundingInputTLV extends DLCSetupPieceTLV {
|
||||
def inputSerialId: UInt64
|
||||
}
|
||||
|
@ -1377,7 +1438,7 @@ case class DLCOfferTLV(
|
|||
protocolVersionOpt: Option[Int],
|
||||
contractFlags: Byte,
|
||||
chainHash: DoubleSha256Digest,
|
||||
contractInfo: ContractInfoV0TLV,
|
||||
contractInfo: ContractInfoTLV,
|
||||
fundingPubKey: ECPublicKey,
|
||||
payoutSPK: ScriptPubKey,
|
||||
payoutSerialId: UInt64,
|
||||
|
@ -1523,6 +1584,34 @@ object NegotiationFieldsV1TLV extends TLVFactory[NegotiationFieldsV1TLV] {
|
|||
override val typeName: String = "NegotiationFieldsV1TLV"
|
||||
}
|
||||
|
||||
case class NegotiationFieldsV2TLV(
|
||||
nestedNegotiationFields: Vector[NegotiationFieldsTLV])
|
||||
extends NegotiationFieldsTLV {
|
||||
require(
|
||||
nestedNegotiationFields.forall(!_.isInstanceOf[NegotiationFieldsV2TLV]))
|
||||
|
||||
override val tpe: BigSizeUInt = NegotiationFieldsV2TLV.tpe
|
||||
|
||||
override val value: ByteVector = {
|
||||
bigSizePrefixedList(nestedNegotiationFields)
|
||||
}
|
||||
}
|
||||
|
||||
object NegotiationFieldsV2TLV extends TLVFactory[NegotiationFieldsV2TLV] {
|
||||
override val tpe: BigSizeUInt = BigSizeUInt(55346)
|
||||
|
||||
override def fromTLVValue(value: ByteVector): NegotiationFieldsV2TLV = {
|
||||
val iter = ValueIterator(value)
|
||||
|
||||
val nestedNegotiationFields =
|
||||
iter.takeBigSizePrefixedList(() => iter.take(NegotiationFieldsTLV))
|
||||
|
||||
NegotiationFieldsV2TLV(nestedNegotiationFields)
|
||||
}
|
||||
|
||||
override val typeName: String = "NegotiationFieldsV2TLV"
|
||||
}
|
||||
|
||||
case class DLCAcceptTLV(
|
||||
tempContractId: Sha256Digest,
|
||||
totalCollateralSatoshis: Satoshis,
|
||||
|
|
|
@ -20,4 +20,10 @@ object PicklerKeys {
|
|||
|
||||
//offers
|
||||
final val protocolVersionKey: String = "protocolVersion"
|
||||
|
||||
//contract info
|
||||
final val totalCollateralKey = "totalCollateral"
|
||||
final val contractDescriptorKey = "contractDescriptor"
|
||||
final val oracleInfoKey = "oracleInfo"
|
||||
final val pairsKey = "pairs"
|
||||
}
|
||||
|
|
|
@ -14,4 +14,9 @@ object EnvUtil {
|
|||
lazy val isCI: Boolean = Properties.envOrNone("CI").contains("true")
|
||||
|
||||
def getVersion: String = getClass.getPackage.getImplementationVersion
|
||||
|
||||
def isNativeSecp256k1Disabled: Boolean = {
|
||||
val secpDisabled = System.getenv("DISABLE_SECP256K1")
|
||||
secpDisabled != null && (secpDisabled.toLowerCase == "true" || secpDisabled == "1")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package org.bitcoins.dlc
|
||||
|
||||
import org.bitcoins.core.protocol.tlv.OracleParamsV0TLV
|
||||
import org.bitcoins.testkitcore.dlc.DLCTest
|
||||
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
|
||||
|
||||
class DLCAdaptorPointComputerTest extends BitcoinSJvmTest with DLCTest {
|
||||
behavior of "DLCAdaptorPointComputer"
|
||||
|
||||
it should "compute sigpoints correctly" in {
|
||||
runTestsForParam(Vector(4, 6, 8)) { numDigitsOrOutcomes =>
|
||||
runTestsForParam(Vector(true, false)) { isNumeric =>
|
||||
runTestsForParam(Vector((1, 1), (2, 3), (3, 5))) {
|
||||
case (threshold, numOracles) =>
|
||||
runTestsForParam(
|
||||
Vector(None,
|
||||
Some(
|
||||
OracleParamsV0TLV(numDigitsOrOutcomes / 2 + 1,
|
||||
numDigitsOrOutcomes / 2,
|
||||
maximizeCoverage = true)))) {
|
||||
oracleParams =>
|
||||
val contractParams = SingleContractParams(numDigitsOrOutcomes,
|
||||
isNumeric,
|
||||
threshold,
|
||||
numOracles,
|
||||
oracleParams)
|
||||
val (client, _, _) = constructDLCClients(contractParams)
|
||||
val contract = client.offer.contractInfo
|
||||
val outcomes = contract.allOutcomes
|
||||
|
||||
val adaptorPoints = contract.adaptorPoints
|
||||
val expectedAdaptorPoints = outcomes.map(_.sigPoint)
|
||||
|
||||
assert(adaptorPoints == expectedAdaptorPoints)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,659 +0,0 @@
|
|||
package org.bitcoins.dlc
|
||||
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
|
||||
import org.bitcoins.core.protocol.dlc.execution.{
|
||||
DLCOutcome,
|
||||
ExecutedDLCOutcome,
|
||||
RefundDLCOutcome,
|
||||
SetupDLC
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.dlc.verify.DLCSignatureVerifier
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
DLCOutcomeType,
|
||||
EnumOutcome,
|
||||
OracleParamsV0TLV,
|
||||
UnsignedNumericOutcome
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.util.{BitcoinScriptUtil, Indexed, NumberUtil}
|
||||
import org.bitcoins.core.wallet.utxo._
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.testkitcore.dlc.{DLCFeeTestUtil, DLCTest, TestDLCClient}
|
||||
import org.bitcoins.testkitcore.util.{BitcoinSJvmTest, BytesUtil}
|
||||
import org.scalatest.Assertion
|
||||
import scodec.bits.BitVector
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.util.Random
|
||||
|
||||
class DLCClientTest extends BitcoinSJvmTest with DLCTest {
|
||||
behavior of "AdaptorDLCClient"
|
||||
|
||||
def validateOutcome(
|
||||
outcome: DLCOutcome,
|
||||
dlcOffer: TestDLCClient,
|
||||
dlcAccept: TestDLCClient): Assertion = {
|
||||
val fundingTx = outcome.fundingTx
|
||||
assert(noEmptySPKOutputs(fundingTx))
|
||||
|
||||
val fundOutputIndex = dlcOffer.dlcTxBuilder.fundOutputIndex
|
||||
|
||||
val signers = Vector(dlcOffer.fundingPrivKey, dlcAccept.fundingPrivKey)
|
||||
val closingSpendingInfo = ScriptSignatureParams(
|
||||
P2WSHV0InputInfo(
|
||||
TransactionOutPoint(fundingTx.txId, UInt32(fundOutputIndex)),
|
||||
fundingTx.outputs(fundOutputIndex).value,
|
||||
P2WSHWitnessV0(
|
||||
MultiSignatureScriptPubKey(2,
|
||||
signers.map(_.publicKey).sortBy(_.hex))),
|
||||
ConditionalPath.NoCondition
|
||||
),
|
||||
fundingTx,
|
||||
signers,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
outcome match {
|
||||
case ExecutedDLCOutcome(fundingTx, cet, _, _) =>
|
||||
DLCFeeTestUtil.validateFees(dlcOffer.dlcTxBuilder,
|
||||
fundingTx,
|
||||
cet,
|
||||
fundingTxSigs = 5)
|
||||
assert(noEmptySPKOutputs(cet))
|
||||
assert(BitcoinScriptUtil.verifyScript(cet, Vector(closingSpendingInfo)))
|
||||
case RefundDLCOutcome(fundingTx, refundTx) =>
|
||||
DLCFeeTestUtil.validateFees(dlcOffer.dlcTxBuilder,
|
||||
fundingTx,
|
||||
refundTx,
|
||||
fundingTxSigs = 5)
|
||||
assert(noEmptySPKOutputs(refundTx))
|
||||
assert(
|
||||
BitcoinScriptUtil.verifyScript(refundTx, Vector(closingSpendingInfo)))
|
||||
}
|
||||
}
|
||||
|
||||
def setupDLC(
|
||||
dlcOffer: TestDLCClient,
|
||||
dlcAccept: TestDLCClient): Future[(SetupDLC, SetupDLC)] = {
|
||||
|
||||
setupDLC(dlcOffer,
|
||||
dlcAccept,
|
||||
_.map(_.fundingTx),
|
||||
_ => Future.successful(()))
|
||||
}
|
||||
|
||||
def constructAndSetupDLC(
|
||||
numOutcomes: Int,
|
||||
isMultiDigit: Boolean,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int,
|
||||
paramsOpt: Option[OracleParamsV0TLV] = None): Future[
|
||||
(
|
||||
TestDLCClient,
|
||||
SetupDLC,
|
||||
TestDLCClient,
|
||||
SetupDLC,
|
||||
Vector[DLCOutcomeType])] = {
|
||||
val (offerDLC, acceptDLC, outcomes) =
|
||||
constructDLCClients(numOutcomes,
|
||||
isMultiDigit,
|
||||
oracleThreshold,
|
||||
numOracles,
|
||||
paramsOpt)
|
||||
|
||||
for {
|
||||
(offerSetup, acceptSetup) <- setupDLC(offerDLC, acceptDLC)
|
||||
} yield (offerDLC, offerSetup, acceptDLC, acceptSetup, outcomes)
|
||||
}
|
||||
|
||||
def executeForCase(
|
||||
outcomeIndex: Long,
|
||||
numOutcomes: Int,
|
||||
isMultiDigit: Boolean,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int,
|
||||
paramsOpt: Option[OracleParamsV0TLV] = None): Future[Assertion] = {
|
||||
constructAndSetupDLC(numOutcomes,
|
||||
isMultiDigit,
|
||||
oracleThreshold,
|
||||
numOracles,
|
||||
paramsOpt)
|
||||
.flatMap {
|
||||
case (dlcOffer, offerSetup, dlcAccept, acceptSetup, outcomes) =>
|
||||
val oracleSigs = genOracleSignatures(numOutcomes,
|
||||
isMultiDigit,
|
||||
dlcOffer,
|
||||
outcomes,
|
||||
outcomeIndex,
|
||||
paramsOpt)
|
||||
|
||||
for {
|
||||
offerOutcome <-
|
||||
dlcOffer.executeDLC(offerSetup, Future.successful(oracleSigs))
|
||||
acceptOutcome <-
|
||||
dlcAccept.executeDLC(acceptSetup, Future.successful(oracleSigs))
|
||||
} yield {
|
||||
assert(offerOutcome.fundingTx == acceptOutcome.fundingTx)
|
||||
|
||||
validateOutcome(offerOutcome, dlcOffer, dlcAccept)
|
||||
validateOutcome(acceptOutcome, dlcOffer, dlcAccept)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def executeRefundCase(
|
||||
numOutcomes: Int,
|
||||
isMultiNonce: Boolean,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int,
|
||||
paramsOpt: Option[OracleParamsV0TLV] = None): Future[Assertion] = {
|
||||
constructAndSetupDLC(numOutcomes,
|
||||
isMultiNonce,
|
||||
oracleThreshold,
|
||||
numOracles,
|
||||
paramsOpt)
|
||||
.flatMap { case (dlcOffer, offerSetup, dlcAccept, acceptSetup, _) =>
|
||||
val offerOutcome = dlcOffer.executeRefundDLC(offerSetup)
|
||||
val acceptOutcome = dlcAccept.executeRefundDLC(acceptSetup)
|
||||
|
||||
validateOutcome(offerOutcome, dlcOffer, dlcAccept)
|
||||
validateOutcome(acceptOutcome, dlcOffer, dlcAccept)
|
||||
|
||||
assert(acceptOutcome.fundingTx == offerOutcome.fundingTx)
|
||||
assert(acceptOutcome.refundTx == offerOutcome.refundTx)
|
||||
}
|
||||
}
|
||||
|
||||
val enumOracleSchemesToTest: Vector[(Int, Int)] =
|
||||
Vector((1, 1), (1, 2), (2, 2), (2, 3), (3, 5), (5, 8))
|
||||
|
||||
val numEnumOutcomesToTest: Vector[Int] = Vector(2, 3, 5, 8)
|
||||
|
||||
def runSingleNonceTests(
|
||||
exec: (Long, Int, Boolean, Int, Int, Option[OracleParamsV0TLV]) => Future[
|
||||
Assertion]): Future[Assertion] = {
|
||||
runTestsForParam(numEnumOutcomesToTest) { numOutcomes =>
|
||||
runTestsForParam(0.until(numOutcomes).toVector) { outcomeIndex =>
|
||||
runTestsForParam(enumOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
exec(outcomeIndex, numOutcomes, false, threshold, numOracles, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val numericOracleSchemesToTest: Vector[(Int, Int)] =
|
||||
Vector((1, 1), (2, 2), (2, 3))
|
||||
val numDigitsToTest: Vector[Int] = Vector(4, 5, 10)
|
||||
|
||||
def runMultiNonceTests(
|
||||
exec: (Long, Int, Boolean, Int, Int, Option[OracleParamsV0TLV]) => Future[
|
||||
Assertion]): Future[Assertion] = {
|
||||
runTestsForParam(numDigitsToTest) { numDigits =>
|
||||
runTestsForParam(numericOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
val randDigits = (0 until numDigits).toVector.map { _ =>
|
||||
scala.util.Random.nextInt(2)
|
||||
}
|
||||
val num =
|
||||
BitVector
|
||||
.fromValidBin(randDigits.mkString(""))
|
||||
.toLong(signed = false)
|
||||
|
||||
exec(num, numDigits, true, threshold, numOracles, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the normal enum case" in {
|
||||
runSingleNonceTests(executeForCase)
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the normal numeric case" in {
|
||||
runMultiNonceTests(executeForCase)
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the large numeric case" in {
|
||||
val numDigits = 17
|
||||
|
||||
val randDigits = (0 until numDigits).toVector.map { _ =>
|
||||
scala.util.Random.nextInt(2)
|
||||
}
|
||||
val num =
|
||||
BitVector.fromValidBin(randDigits.mkString("")).toLong(signed = false)
|
||||
|
||||
executeForCase(num,
|
||||
numDigits,
|
||||
isMultiDigit = true,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1)
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for a normal multi-oracle numeric case with bounded differences allowed" in {
|
||||
val threshold = 3
|
||||
val numOracles = 5
|
||||
val numDigits = 8
|
||||
val params = OracleParamsV0TLV(maxErrorExp = 4,
|
||||
minFailExp = 2,
|
||||
maximizeCoverage = false)
|
||||
|
||||
val randDigits = (0 until numDigits).toVector.map { _ =>
|
||||
scala.util.Random.nextInt(2)
|
||||
}
|
||||
val num =
|
||||
BitVector
|
||||
.fromValidBin(randDigits.mkString(""))
|
||||
.toLong(signed = false)
|
||||
|
||||
executeForCase(num,
|
||||
numDigits,
|
||||
isMultiDigit = true,
|
||||
threshold,
|
||||
numOracles,
|
||||
Some(params))
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the refund case" in {
|
||||
runTestsForParam(Vector(false, true)) { isNumeric =>
|
||||
val numOutcomesOrDigitsToTest =
|
||||
if (isNumeric) numDigitsToTest else numEnumOutcomesToTest
|
||||
runTestsForParam(numOutcomesOrDigitsToTest) { numOutcomesOrDigits =>
|
||||
runTestsForParam(numericOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
executeRefundCase(numOutcomesOrDigits,
|
||||
isMultiNonce = isNumeric,
|
||||
oracleThreshold = threshold,
|
||||
numOracles = numOracles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "all work for a 100 outcome DLC" in {
|
||||
val numOutcomes = 100
|
||||
val testFs = (0 until 10).map(_ * 10).map { outcomeIndex =>
|
||||
for {
|
||||
_ <- executeForCase(outcomeIndex,
|
||||
numOutcomes,
|
||||
isMultiDigit = false,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1)
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
Future
|
||||
.sequence(testFs)
|
||||
.flatMap(_ =>
|
||||
executeRefundCase(numOutcomes,
|
||||
isMultiNonce = false,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1))
|
||||
}
|
||||
|
||||
it should "fail on invalid funding signatures" in {
|
||||
val (offerClient, acceptClient, _) =
|
||||
constructDLCClients(numOutcomesOrDigits = 3,
|
||||
isNumeric = false,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1,
|
||||
paramsOpt = None)
|
||||
val builder = offerClient.dlcTxBuilder
|
||||
val offerVerifier = DLCSignatureVerifier(builder, isInitiator = true)
|
||||
val acceptVerifier = DLCSignatureVerifier(builder, isInitiator = false)
|
||||
|
||||
val offerFundingSigs = offerClient.dlcTxSigner.signFundingTx().get
|
||||
val acceptFundingSigs = acceptClient.dlcTxSigner.signFundingTx().get
|
||||
|
||||
val badOfferFundingSigs = BytesUtil.flipBit(offerFundingSigs)
|
||||
val badAcceptFundingSigs = BytesUtil.flipBit(acceptFundingSigs)
|
||||
|
||||
assert(
|
||||
offerClient.dlcTxSigner
|
||||
.completeFundingTx(badAcceptFundingSigs)
|
||||
.failed
|
||||
.get
|
||||
.isInstanceOf[RuntimeException])
|
||||
assert(
|
||||
acceptClient.dlcTxSigner
|
||||
.completeFundingTx(badOfferFundingSigs)
|
||||
.failed
|
||||
.get
|
||||
.isInstanceOf[RuntimeException])
|
||||
|
||||
assert(offerVerifier.verifyRemoteFundingSigs(acceptFundingSigs))
|
||||
assert(acceptVerifier.verifyRemoteFundingSigs(offerFundingSigs))
|
||||
|
||||
assert(!offerVerifier.verifyRemoteFundingSigs(badAcceptFundingSigs))
|
||||
assert(!acceptVerifier.verifyRemoteFundingSigs(badOfferFundingSigs))
|
||||
assert(!offerVerifier.verifyRemoteFundingSigs(offerFundingSigs))
|
||||
assert(!acceptVerifier.verifyRemoteFundingSigs(acceptFundingSigs))
|
||||
}
|
||||
|
||||
it should "succeed on valid CET signatures" in {
|
||||
val (offerClient, acceptClient, outcomes) =
|
||||
constructDLCClients(numOutcomesOrDigits = 2,
|
||||
isNumeric = false,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1,
|
||||
paramsOpt = None)
|
||||
val builder = offerClient.dlcTxBuilder
|
||||
val offerVerifier = DLCSignatureVerifier(builder, isInitiator = true)
|
||||
val acceptVerifier = DLCSignatureVerifier(builder, isInitiator = false)
|
||||
|
||||
val offerCETSigs = offerClient.dlcTxSigner.createCETSigs()
|
||||
val acceptCETSigs = acceptClient.dlcTxSigner.createCETSigs()
|
||||
|
||||
outcomes.zipWithIndex.foreach { case (outcomeUncast, index) =>
|
||||
val outcome = EnumOracleOutcome(
|
||||
Vector(offerClient.offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo]),
|
||||
outcomeUncast.asInstanceOf[EnumOutcome])
|
||||
|
||||
assert(
|
||||
offerVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
acceptCETSigs(outcome.sigPoint)))
|
||||
assert(
|
||||
acceptVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
offerCETSigs(outcome.sigPoint)))
|
||||
}
|
||||
|
||||
assert(offerVerifier.verifyRefundSig(acceptCETSigs.refundSig))
|
||||
assert(offerVerifier.verifyRefundSig(offerCETSigs.refundSig))
|
||||
assert(acceptVerifier.verifyRefundSig(offerCETSigs.refundSig))
|
||||
assert(acceptVerifier.verifyRefundSig(acceptCETSigs.refundSig))
|
||||
}
|
||||
|
||||
it should "fail on invalid CET signatures" in {
|
||||
val (offerClient, acceptClient, outcomes) =
|
||||
constructDLCClients(numOutcomesOrDigits = 3,
|
||||
isNumeric = false,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1,
|
||||
paramsOpt = None)
|
||||
val builder = offerClient.dlcTxBuilder
|
||||
val offerVerifier = DLCSignatureVerifier(builder, isInitiator = true)
|
||||
val acceptVerifier = DLCSignatureVerifier(builder, isInitiator = false)
|
||||
|
||||
val offerCETSigs = offerClient.dlcTxSigner.createCETSigs()
|
||||
val acceptCETSigs = acceptClient.dlcTxSigner.createCETSigs()
|
||||
|
||||
val badOfferCETSigs = BytesUtil.flipBit(offerCETSigs)
|
||||
val badAcceptCETSigs = BytesUtil.flipBit(acceptCETSigs)
|
||||
|
||||
outcomes.foreach { outcomeUncast =>
|
||||
val outcome = outcomeUncast.asInstanceOf[EnumOutcome]
|
||||
val oracleInfo =
|
||||
offerClient.offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo]
|
||||
val oracleOutcome = EnumOracleOutcome(Vector(oracleInfo), outcome)
|
||||
|
||||
val oracleSig = genEnumOracleSignature(oracleInfo, outcome.outcome)
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
offerClient.dlcTxSigner.completeCET(
|
||||
oracleOutcome,
|
||||
badAcceptCETSigs(oracleOutcome.sigPoint),
|
||||
Vector(oracleSig))
|
||||
}
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
acceptClient.dlcTxSigner
|
||||
.completeCET(oracleOutcome,
|
||||
badOfferCETSigs(oracleOutcome.sigPoint),
|
||||
Vector(oracleSig))
|
||||
}
|
||||
}
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
offerClient.dlcTxSigner.completeRefundTx(badAcceptCETSigs.refundSig)
|
||||
}
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
acceptClient.dlcTxSigner.completeRefundTx(badOfferCETSigs.refundSig)
|
||||
}
|
||||
|
||||
outcomes.zipWithIndex.foreach { case (outcomeUncast, index) =>
|
||||
val outcome = EnumOracleOutcome(
|
||||
Vector(offerClient.offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo]),
|
||||
outcomeUncast.asInstanceOf[EnumOutcome])
|
||||
val adaptorPoint = Indexed(outcome.sigPoint, index)
|
||||
|
||||
assert(
|
||||
!offerVerifier.verifyCETSig(adaptorPoint,
|
||||
badAcceptCETSigs(outcome.sigPoint)))
|
||||
assert(
|
||||
!acceptVerifier.verifyCETSig(adaptorPoint,
|
||||
badOfferCETSigs(outcome.sigPoint)))
|
||||
|
||||
assert(
|
||||
!offerVerifier.verifyCETSig(adaptorPoint,
|
||||
offerCETSigs(outcome.sigPoint)))
|
||||
assert(
|
||||
!acceptVerifier.verifyCETSig(adaptorPoint,
|
||||
acceptCETSigs(outcome.sigPoint)))
|
||||
}
|
||||
assert(!offerVerifier.verifyRefundSig(badAcceptCETSigs.refundSig))
|
||||
assert(!offerVerifier.verifyRefundSig(badOfferCETSigs.refundSig))
|
||||
assert(!acceptVerifier.verifyRefundSig(badOfferCETSigs.refundSig))
|
||||
assert(!acceptVerifier.verifyRefundSig(badAcceptCETSigs.refundSig))
|
||||
}
|
||||
|
||||
it should "compute sigpoints correctly" in {
|
||||
runTestsForParam(Vector(4, 6, 8)) { numDigitsOrOutcomes =>
|
||||
runTestsForParam(Vector(true, false)) { isNumeric =>
|
||||
runTestsForParam(Vector((1, 1), (2, 3), (3, 5))) {
|
||||
case (threshold, numOracles) =>
|
||||
runTestsForParam(
|
||||
Vector(None,
|
||||
Some(
|
||||
OracleParamsV0TLV(numDigitsOrOutcomes / 2 + 1,
|
||||
numDigitsOrOutcomes / 2,
|
||||
maximizeCoverage = true)))) {
|
||||
oracleParams =>
|
||||
val (client, _, _) = constructDLCClients(numDigitsOrOutcomes,
|
||||
isNumeric,
|
||||
threshold,
|
||||
numOracles,
|
||||
oracleParams)
|
||||
val contract = client.offer.contractInfo
|
||||
val outcomes = contract.allOutcomes
|
||||
|
||||
val adaptorPoints = contract.adaptorPoints
|
||||
val expectedAdaptorPoints = outcomes.map(_.sigPoint)
|
||||
|
||||
assert(adaptorPoints == expectedAdaptorPoints)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def assertCorrectSigDerivation(
|
||||
offerSetup: SetupDLC,
|
||||
dlcOffer: TestDLCClient,
|
||||
acceptSetup: SetupDLC,
|
||||
dlcAccept: TestDLCClient,
|
||||
oracleSigs: Vector[OracleSignatures],
|
||||
outcome: OracleOutcome): Future[Assertion] = {
|
||||
val aggR = outcome.aggregateNonce
|
||||
val aggS = outcome match {
|
||||
case EnumOracleOutcome(oracles, _) =>
|
||||
assert(oracles.length == oracleSigs.length)
|
||||
|
||||
val sVals = oracleSigs.map {
|
||||
case EnumOracleSignature(oracle, sig) =>
|
||||
assert(oracles.contains(oracle))
|
||||
sig.sig
|
||||
case _: NumericOracleSignatures =>
|
||||
fail("Expected EnumOracleSignature")
|
||||
}
|
||||
|
||||
sVals.reduce(_.add(_))
|
||||
case NumericOracleOutcome(oraclesAndOutcomes) =>
|
||||
assert(oraclesAndOutcomes.length == oracleSigs.length)
|
||||
|
||||
val sVals = oracleSigs.map {
|
||||
case NumericOracleSignatures(oracle, sigs) =>
|
||||
val oracleAndOutcomeOpt = oraclesAndOutcomes.find(_._1 == oracle)
|
||||
assert(oracleAndOutcomeOpt.isDefined)
|
||||
val outcome = oracleAndOutcomeOpt.get._2
|
||||
val sVals = sigs.take(outcome.digits.length).map(_.sig)
|
||||
sVals.reduce(_.add(_))
|
||||
case _: EnumOracleSignature =>
|
||||
fail("Expected NumericOracleSignatures")
|
||||
}
|
||||
sVals.reduce(_.add(_))
|
||||
}
|
||||
|
||||
val aggSig = SchnorrDigitalSignature(aggR, aggS)
|
||||
|
||||
// Must use stored adaptor sigs because adaptor signing nonce is not deterministic (auxRand)
|
||||
val offerRefundSig = dlcOffer.dlcTxSigner.signRefundTx
|
||||
val acceptRefundSig = dlcAccept.dlcTxSigner.signRefundTx
|
||||
val acceptAdaptorSigs = offerSetup.cets.map { case (outcome, info) =>
|
||||
(outcome, info.remoteSignature)
|
||||
}
|
||||
val acceptCETSigs = CETSignatures(acceptAdaptorSigs, acceptRefundSig)
|
||||
val offerAdaptorSigs = acceptSetup.cets.map { case (outcome, info) =>
|
||||
(outcome, info.remoteSignature)
|
||||
}
|
||||
val offerCETSigs = CETSignatures(offerAdaptorSigs, offerRefundSig)
|
||||
|
||||
for {
|
||||
offerFundingSigs <- Future.fromTry(dlcOffer.dlcTxSigner.signFundingTx())
|
||||
offerOutcome <-
|
||||
dlcOffer.executeDLC(offerSetup, Future.successful(oracleSigs))
|
||||
acceptOutcome <-
|
||||
dlcAccept.executeDLC(acceptSetup, Future.successful(oracleSigs))
|
||||
} yield {
|
||||
val builder = DLCTxBuilder(dlcOffer.offer, dlcAccept.accept)
|
||||
val contractId = builder.buildFundingTx.txIdBE.bytes
|
||||
.xor(dlcAccept.accept.tempContractId.bytes)
|
||||
|
||||
val offer = dlcOffer.offer
|
||||
val accept = dlcOffer.accept.withSigs(acceptCETSigs)
|
||||
val sign = DLCSign(offerCETSigs, offerFundingSigs, contractId)
|
||||
|
||||
val (offerOracleSig, offerDLCOutcome) =
|
||||
DLCStatus
|
||||
.calculateOutcomeAndSig(isInitiator = true,
|
||||
offer,
|
||||
accept,
|
||||
sign,
|
||||
acceptOutcome.cet)
|
||||
.get
|
||||
|
||||
val (acceptOracleSig, acceptDLCOutcome) =
|
||||
DLCStatus
|
||||
.calculateOutcomeAndSig(isInitiator = false,
|
||||
offer,
|
||||
accept,
|
||||
sign,
|
||||
offerOutcome.cet)
|
||||
.get
|
||||
|
||||
assert(offerDLCOutcome == outcome)
|
||||
assert(acceptDLCOutcome == outcome)
|
||||
assert(offerOracleSig == aggSig)
|
||||
assert(acceptOracleSig == aggSig)
|
||||
}
|
||||
}
|
||||
|
||||
it should "be able to derive oracle signature from remote CET signature" in {
|
||||
val outcomeIndex = 1
|
||||
|
||||
runTestsForParam(numEnumOutcomesToTest) { numOutcomes =>
|
||||
runTestsForParam(enumOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
constructAndSetupDLC(numOutcomes,
|
||||
isMultiDigit = false,
|
||||
oracleThreshold = threshold,
|
||||
numOracles = numOracles).flatMap {
|
||||
case (dlcOffer, offerSetup, dlcAccept, acceptSetup, outcomes) =>
|
||||
val (oracleOutcome, sigs) =
|
||||
genOracleOutcomeAndSignatures(numOutcomes,
|
||||
isNumeric = false,
|
||||
dlcOffer,
|
||||
outcomes,
|
||||
outcomeIndex,
|
||||
paramsOpt = None)
|
||||
|
||||
assertCorrectSigDerivation(offerSetup = offerSetup,
|
||||
dlcOffer = dlcOffer,
|
||||
acceptSetup = acceptSetup,
|
||||
dlcAccept = dlcAccept,
|
||||
oracleSigs = sigs,
|
||||
outcome = oracleOutcome)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "be able to derive aggregate oracle signature from remote CET signatures" in {
|
||||
// Larger numbers of digits make tests take too long.
|
||||
val numDigitsToTest = Vector(5, 9)
|
||||
runTestsForParam(numDigitsToTest) { numDigits =>
|
||||
runTestsForParam(Vector((1, 1), (2, 2), (2, 3))) {
|
||||
case (threshold, numOracles) =>
|
||||
val oracleParamOptsToTest = if (threshold > 1) {
|
||||
Vector(None,
|
||||
Some(
|
||||
OracleParamsV0TLV(numDigits - 2,
|
||||
numDigits - 4,
|
||||
maximizeCoverage = false)))
|
||||
} else Vector(None)
|
||||
runTestsForParam(oracleParamOptsToTest) { oracleParamsOpt =>
|
||||
val max = (1L << numDigits) - 1
|
||||
val outcomesToTest = 0
|
||||
.until(9)
|
||||
.toVector
|
||||
.map(num => (max / num.toDouble).toLong)
|
||||
.map(num => NumberUtil.decompose(num, 2, numDigits))
|
||||
|
||||
constructAndSetupDLC(numDigits,
|
||||
isMultiDigit = true,
|
||||
oracleThreshold = threshold,
|
||||
numOracles = numOracles,
|
||||
paramsOpt = oracleParamsOpt).flatMap {
|
||||
case (dlcOffer, offerSetup, dlcAccept, acceptSetup, outcomes) =>
|
||||
runTestsForParam(outcomesToTest) { outcomeToTest =>
|
||||
val possibleOutcomes = outcomes
|
||||
.collect { case ds: UnsignedNumericOutcome => ds }
|
||||
.filter(outcome => outcomeToTest.startsWith(outcome.digits))
|
||||
val outcome =
|
||||
possibleOutcomes(Random.nextInt(possibleOutcomes.length))
|
||||
|
||||
val oracleInfo = dlcOffer.offer.oracleInfo
|
||||
|
||||
val oracleIndices =
|
||||
0.until(oracleInfo.numOracles).toVector
|
||||
val chosenOracles =
|
||||
Random
|
||||
.shuffle(oracleIndices)
|
||||
.take(oracleInfo.threshold)
|
||||
.sorted
|
||||
|
||||
val oracleOutcome =
|
||||
genNumericOracleOutcome(chosenOracles,
|
||||
dlcOffer.offer.contractInfo,
|
||||
outcome.digits,
|
||||
oracleParamsOpt)
|
||||
|
||||
val oracleSigs = genNumericOracleSignatures(oracleOutcome)
|
||||
|
||||
assertCorrectSigDerivation(offerSetup = offerSetup,
|
||||
dlcOffer = dlcOffer,
|
||||
acceptSetup = acceptSetup,
|
||||
dlcAccept = dlcAccept,
|
||||
oracleSigs = oracleSigs,
|
||||
outcome = oracleOutcome)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
150
dlc-test/src/test/scala/org/bitcoins/dlc/DLCValidationTest.scala
Normal file
150
dlc-test/src/test/scala/org/bitcoins/dlc/DLCValidationTest.scala
Normal file
|
@ -0,0 +1,150 @@
|
|||
package org.bitcoins.dlc
|
||||
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
EnumOracleOutcome,
|
||||
EnumSingleOracleInfo
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.verify.DLCSignatureVerifier
|
||||
import org.bitcoins.core.protocol.tlv.EnumOutcome
|
||||
import org.bitcoins.core.util.Indexed
|
||||
import org.bitcoins.testkitcore.dlc.DLCTest
|
||||
import org.bitcoins.testkitcore.util.{BitcoinSJvmTest, BytesUtil}
|
||||
|
||||
class DLCValidationTest extends BitcoinSJvmTest with DLCTest {
|
||||
behavior of "DLC Validation"
|
||||
|
||||
it should "fail on invalid funding signatures" in {
|
||||
val contractParms =
|
||||
EnumContractParams(numOutcomes = 3, oracleThreshold = 1, numOracles = 1)
|
||||
val (offerClient, acceptClient, _) = constructDLCClients(contractParms)
|
||||
val builder = offerClient.dlcTxBuilder
|
||||
val offerVerifier = DLCSignatureVerifier(builder, isInitiator = true)
|
||||
val acceptVerifier = DLCSignatureVerifier(builder, isInitiator = false)
|
||||
|
||||
val offerFundingSigs = offerClient.dlcTxSigner.signFundingTx().get
|
||||
val acceptFundingSigs = acceptClient.dlcTxSigner.signFundingTx().get
|
||||
|
||||
val badOfferFundingSigs = BytesUtil.flipBit(offerFundingSigs)
|
||||
val badAcceptFundingSigs = BytesUtil.flipBit(acceptFundingSigs)
|
||||
|
||||
assert(
|
||||
offerClient.dlcTxSigner
|
||||
.completeFundingTx(badAcceptFundingSigs)
|
||||
.isFailure)
|
||||
assert(
|
||||
acceptClient.dlcTxSigner
|
||||
.completeFundingTx(badOfferFundingSigs)
|
||||
.isFailure)
|
||||
|
||||
assert(offerVerifier.verifyRemoteFundingSigs(acceptFundingSigs))
|
||||
assert(acceptVerifier.verifyRemoteFundingSigs(offerFundingSigs))
|
||||
|
||||
assert(!offerVerifier.verifyRemoteFundingSigs(badAcceptFundingSigs))
|
||||
assert(!acceptVerifier.verifyRemoteFundingSigs(badOfferFundingSigs))
|
||||
assert(!offerVerifier.verifyRemoteFundingSigs(offerFundingSigs))
|
||||
assert(!acceptVerifier.verifyRemoteFundingSigs(acceptFundingSigs))
|
||||
}
|
||||
|
||||
it should "succeed on valid CET signatures" in {
|
||||
val contractParms =
|
||||
EnumContractParams(numOutcomes = 3, oracleThreshold = 1, numOracles = 1)
|
||||
val (offerClient, acceptClient, outcomes) =
|
||||
constructDLCClients(contractParms)
|
||||
val builder = offerClient.dlcTxBuilder
|
||||
val offerVerifier = DLCSignatureVerifier(builder, isInitiator = true)
|
||||
val acceptVerifier = DLCSignatureVerifier(builder, isInitiator = false)
|
||||
|
||||
val offerCETSigs = offerClient.dlcTxSigner.createCETSigs()
|
||||
val acceptCETSigs = acceptClient.dlcTxSigner.createCETSigs()
|
||||
|
||||
outcomes.zipWithIndex.foreach { case (outcomeUncast, index) =>
|
||||
val outcome = EnumOracleOutcome(Vector(
|
||||
offerClient.offer.oracleInfos.head
|
||||
.asInstanceOf[EnumSingleOracleInfo]),
|
||||
outcomeUncast.asInstanceOf[EnumOutcome])
|
||||
|
||||
assert(
|
||||
offerVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
acceptCETSigs(outcome.sigPoint)))
|
||||
assert(
|
||||
acceptVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
offerCETSigs(outcome.sigPoint)))
|
||||
}
|
||||
assert(offerVerifier.verifyRefundSig(acceptCETSigs.refundSig))
|
||||
assert(offerVerifier.verifyRefundSig(offerCETSigs.refundSig))
|
||||
assert(acceptVerifier.verifyRefundSig(offerCETSigs.refundSig))
|
||||
assert(acceptVerifier.verifyRefundSig(acceptCETSigs.refundSig))
|
||||
}
|
||||
|
||||
it should "fail on invalid CET signatures" in {
|
||||
val contractParms =
|
||||
EnumContractParams(numOutcomes = 3, oracleThreshold = 1, numOracles = 1)
|
||||
val (offerClient, acceptClient, outcomes) =
|
||||
constructDLCClients(contractParms)
|
||||
val builder = offerClient.dlcTxBuilder
|
||||
val offerVerifier = DLCSignatureVerifier(builder, isInitiator = true)
|
||||
val acceptVerifier = DLCSignatureVerifier(builder, isInitiator = false)
|
||||
|
||||
val offerCETSigs = offerClient.dlcTxSigner.createCETSigs()
|
||||
val acceptCETSigs = acceptClient.dlcTxSigner.createCETSigs()
|
||||
|
||||
val badOfferCETSigs = BytesUtil.flipBit(offerCETSigs)
|
||||
val badAcceptCETSigs = BytesUtil.flipBit(acceptCETSigs)
|
||||
|
||||
outcomes.foreach { outcomeUncast =>
|
||||
val outcome = outcomeUncast.asInstanceOf[EnumOutcome]
|
||||
val oracleInfo =
|
||||
offerClient.offer.oracleInfos.head.asInstanceOf[EnumSingleOracleInfo]
|
||||
val oracleOutcome = EnumOracleOutcome(Vector(oracleInfo), outcome)
|
||||
|
||||
val oracleSig = genEnumOracleSignature(oracleInfo, outcome.outcome)
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
offerClient.dlcTxSigner.completeCET(
|
||||
oracleOutcome,
|
||||
badAcceptCETSigs(oracleOutcome.sigPoint),
|
||||
Vector(oracleSig))
|
||||
}
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
acceptClient.dlcTxSigner
|
||||
.completeCET(oracleOutcome,
|
||||
badOfferCETSigs(oracleOutcome.sigPoint),
|
||||
Vector(oracleSig))
|
||||
}
|
||||
}
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
offerClient.dlcTxSigner.completeRefundTx(badAcceptCETSigs.refundSig)
|
||||
}
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
acceptClient.dlcTxSigner.completeRefundTx(badOfferCETSigs.refundSig)
|
||||
}
|
||||
|
||||
outcomes.zipWithIndex.foreach { case (outcomeUncast, index) =>
|
||||
val outcome = EnumOracleOutcome(Vector(
|
||||
offerClient.offer.oracleInfos.head
|
||||
.asInstanceOf[EnumSingleOracleInfo]),
|
||||
outcomeUncast.asInstanceOf[EnumOutcome])
|
||||
|
||||
assert(
|
||||
!offerVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
badAcceptCETSigs(outcome.sigPoint)))
|
||||
assert(
|
||||
!acceptVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
badOfferCETSigs(outcome.sigPoint)))
|
||||
|
||||
assert(
|
||||
!offerVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
offerCETSigs(outcome.sigPoint)))
|
||||
assert(
|
||||
!acceptVerifier.verifyCETSig(Indexed(outcome.sigPoint, index),
|
||||
acceptCETSigs(outcome.sigPoint)))
|
||||
}
|
||||
assert(!offerVerifier.verifyRefundSig(badAcceptCETSigs.refundSig))
|
||||
assert(!offerVerifier.verifyRefundSig(badOfferCETSigs.refundSig))
|
||||
assert(!acceptVerifier.verifyRefundSig(badOfferCETSigs.refundSig))
|
||||
assert(!acceptVerifier.verifyRefundSig(badAcceptCETSigs.refundSig))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package org.bitcoins.dlc
|
||||
|
||||
import org.bitcoins.core.protocol.tlv.OracleParamsV0TLV
|
||||
import org.bitcoins.testkitcore.dlc.DLCTest
|
||||
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
|
||||
|
||||
class DisjointUnionDLCTest extends BitcoinSJvmTest with DLCTest {
|
||||
behavior of "Disjoint Union DLC"
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a double enum contract" in {
|
||||
val numDisjoint = 2
|
||||
val numOutcomes = 10
|
||||
val singleParams = 0.until(numDisjoint).toVector.map { _ =>
|
||||
EnumContractParams(numOutcomes, oracleThreshold = 1, numOracles = 1)
|
||||
}
|
||||
val contractParams = DisjointUnionContractParams(singleParams)
|
||||
val outcomes = 0.until(numDisjoint).toVector.flatMap { contractIndex =>
|
||||
0L.until(numOutcomes).toVector.map { outcomeIndex =>
|
||||
(contractIndex, outcomeIndex)
|
||||
}
|
||||
}
|
||||
|
||||
executeForCasesInUnion(outcomes, contractParams)
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a double numeric contract" in {
|
||||
val numDisjoint = 2
|
||||
val numDigits = 10
|
||||
val singleParams = 0.until(numDisjoint).toVector.map { _ =>
|
||||
val oracleParams = OracleParamsV0TLV(maxErrorExp = 6,
|
||||
minFailExp = 2,
|
||||
maximizeCoverage = true)
|
||||
NumericContractParams(numDigits,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1,
|
||||
Some(oracleParams))
|
||||
}
|
||||
val contractParams = DisjointUnionContractParams(singleParams)
|
||||
val outcomes = 0.until(numDisjoint).toVector.flatMap { contractIndex =>
|
||||
tenRandomNums(numDigits).map { outcomeIndex =>
|
||||
(contractIndex, outcomeIndex)
|
||||
}
|
||||
}
|
||||
|
||||
executeForCasesInUnion(outcomes, contractParams)
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a mixed enum and numeric contract" in {
|
||||
val numOutcomes = 10
|
||||
val numDigits = 10
|
||||
val enumParams =
|
||||
EnumContractParams(numOutcomes, oracleThreshold = 1, numOracles = 1)
|
||||
val oracleParams = OracleParamsV0TLV(maxErrorExp = 6,
|
||||
minFailExp = 2,
|
||||
maximizeCoverage = true)
|
||||
val numericParams =
|
||||
NumericContractParams(numDigits,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1,
|
||||
Some(oracleParams))
|
||||
|
||||
val contractParams =
|
||||
DisjointUnionContractParams(Vector(enumParams, numericParams))
|
||||
|
||||
val enumOutcomes = 0L.until(numOutcomes).toVector.map((0, _))
|
||||
val numericOutcomes = tenRandomNums(numDigits).map((1, _))
|
||||
val outcomes = enumOutcomes ++ numericOutcomes
|
||||
|
||||
executeForCasesInUnion(outcomes, contractParams)
|
||||
}
|
||||
}
|
81
dlc-test/src/test/scala/org/bitcoins/dlc/EnumDLCTest.scala
Normal file
81
dlc-test/src/test/scala/org/bitcoins/dlc/EnumDLCTest.scala
Normal file
|
@ -0,0 +1,81 @@
|
|||
package org.bitcoins.dlc
|
||||
|
||||
import org.bitcoins.testkitcore.dlc.DLCTest
|
||||
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
|
||||
|
||||
class EnumDLCTest extends BitcoinSJvmTest with DLCTest {
|
||||
behavior of "Enum DLC"
|
||||
|
||||
val enumOracleSchemesToTest: Vector[(Int, Int)] =
|
||||
Vector((1, 1), (1, 2), (2, 2), (2, 3), (3, 5), (5, 8))
|
||||
|
||||
val numEnumOutcomesToTest: Vector[Int] = 2.until(10).toVector
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the normal enum case" in {
|
||||
runTestsForParam(numEnumOutcomesToTest) { numOutcomes =>
|
||||
runTestsForParam(enumOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
val contractParams =
|
||||
EnumContractParams(numOutcomes, threshold, numOracles)
|
||||
val outcomes = 0L.until(numOutcomes).toVector
|
||||
|
||||
executeForCases(outcomes, contractParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the refund enum case" in {
|
||||
runTestsForParam(numEnumOutcomesToTest) { numOutcomes =>
|
||||
runTestsForParam(enumOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
val contractParams =
|
||||
EnumContractParams(numOutcomes, threshold, numOracles)
|
||||
|
||||
executeRefundCase(contractParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "all work for a 100 outcome DLC" in {
|
||||
val numOutcomes = 100
|
||||
val outcomes = 0L.until(numOutcomes).toVector
|
||||
val contractParams =
|
||||
EnumContractParams(numOutcomes, oracleThreshold = 1, numOracles = 1)
|
||||
|
||||
for {
|
||||
_ <- executeForCases(outcomes, contractParams)
|
||||
_ <- executeRefundCase(contractParams)
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
it should "be able to derive oracle signature from remote CET signature" in {
|
||||
val outcomeIndex = 1
|
||||
|
||||
runTestsForParam(numEnumOutcomesToTest) { numOutcomes =>
|
||||
runTestsForParam(enumOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
val contractParams =
|
||||
EnumContractParams(numOutcomes, threshold, numOracles)
|
||||
|
||||
constructAndSetupDLC(contractParams).flatMap {
|
||||
case (dlcOffer, offerSetup, dlcAccept, acceptSetup, outcomes) =>
|
||||
val (oracleOutcome, sigs) =
|
||||
genOracleOutcomeAndSignatures(
|
||||
numOutcomes,
|
||||
isNumeric = false,
|
||||
dlcOffer.offer.contractInfo.contracts.head,
|
||||
outcomes,
|
||||
outcomeIndex,
|
||||
paramsOpt = None)
|
||||
|
||||
assertCorrectSigDerivation(offerSetup = offerSetup,
|
||||
dlcOffer = dlcOffer,
|
||||
acceptSetup = acceptSetup,
|
||||
dlcAccept = dlcAccept,
|
||||
oracleSigs = sigs,
|
||||
outcome = oracleOutcome)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
137
dlc-test/src/test/scala/org/bitcoins/dlc/NumericDLCTest.scala
Normal file
137
dlc-test/src/test/scala/org/bitcoins/dlc/NumericDLCTest.scala
Normal file
|
@ -0,0 +1,137 @@
|
|||
package org.bitcoins.dlc
|
||||
|
||||
import org.bitcoins.core.protocol.tlv.{
|
||||
OracleParamsV0TLV,
|
||||
UnsignedNumericOutcome
|
||||
}
|
||||
import org.bitcoins.core.util.{EnvUtil, NumberUtil}
|
||||
import org.bitcoins.testkitcore.dlc.DLCTest
|
||||
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
class NumericDLCTest extends BitcoinSJvmTest with DLCTest {
|
||||
behavior of "Numeric DLC"
|
||||
|
||||
val numericOracleSchemesToTest: Vector[(Int, Int)] =
|
||||
Vector((1, 1), (2, 2), (2, 3))
|
||||
val numDigitsToTest: Vector[Int] = Vector(4, 5, 10)
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the normal numeric case" in {
|
||||
runTestsForParam(numDigitsToTest) { numDigits =>
|
||||
runTestsForParam(numericOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
val contractParams =
|
||||
NumericContractParams(numDigits, threshold, numOracles)
|
||||
val nums = tenRandomNums(numDigits)
|
||||
|
||||
executeForCases(nums, contractParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the large numeric case" in {
|
||||
val numDigits = if (EnvUtil.isNativeSecp256k1Disabled) {
|
||||
12
|
||||
} else {
|
||||
//optimization for CI, tests are much slower when secp256k1 isnt used
|
||||
17
|
||||
}
|
||||
val contractParams =
|
||||
NumericContractParams(numDigits, oracleThreshold = 1, numOracles = 1)
|
||||
val nums = tenRandomNums(numDigits)
|
||||
|
||||
executeForCases(nums, contractParams)
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for a normal multi-oracle numeric case with bounded differences allowed" in {
|
||||
val threshold = 3
|
||||
val numOracles = 5
|
||||
val numDigits = 8
|
||||
val params = OracleParamsV0TLV(maxErrorExp = 4,
|
||||
minFailExp = 2,
|
||||
maximizeCoverage = false)
|
||||
val contractParams =
|
||||
NumericContractParams(numDigits, threshold, numOracles, Some(params))
|
||||
val nums = tenRandomNums(numDigits)
|
||||
|
||||
executeForCases(nums, contractParams)
|
||||
}
|
||||
|
||||
it should "be able to construct and verify with ScriptInterpreter every tx in a DLC for the refund numeric case" in {
|
||||
runTestsForParam(numDigitsToTest) { numDigits =>
|
||||
runTestsForParam(numericOracleSchemesToTest) {
|
||||
case (threshold, numOracles) =>
|
||||
val contractParams =
|
||||
NumericContractParams(numDigits, threshold, numOracles)
|
||||
|
||||
executeRefundCase(contractParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "be able to derive aggregate oracle signature from remote CET signatures" in {
|
||||
// Larger numbers of digits make tests take too long.
|
||||
val numDigitsToTest = Vector(5, 9)
|
||||
runTestsForParam(numDigitsToTest) { numDigits =>
|
||||
runTestsForParam(Vector((1, 1), (2, 2), (2, 3))) {
|
||||
case (threshold, numOracles) =>
|
||||
val oracleParamOptsToTest = if (threshold > 1) {
|
||||
Vector(None,
|
||||
Some(
|
||||
OracleParamsV0TLV(numDigits - 2,
|
||||
numDigits - 4,
|
||||
maximizeCoverage = false)))
|
||||
} else Vector(None)
|
||||
runTestsForParam(oracleParamOptsToTest) { oracleParamsOpt =>
|
||||
val max = (1L << numDigits) - 1
|
||||
val outcomesToTest = 0
|
||||
.until(9)
|
||||
.toVector
|
||||
.map(num => (max / num.toDouble).toLong)
|
||||
.map(num => NumberUtil.decompose(num, 2, numDigits))
|
||||
val contractParams = NumericContractParams(numDigits,
|
||||
threshold,
|
||||
numOracles,
|
||||
oracleParamsOpt)
|
||||
|
||||
constructAndSetupDLC(contractParams).flatMap {
|
||||
case (dlcOffer, offerSetup, dlcAccept, acceptSetup, outcomes) =>
|
||||
runTestsForParam(outcomesToTest) { outcomeToTest =>
|
||||
val possibleOutcomes = outcomes
|
||||
.collect { case ds: UnsignedNumericOutcome => ds }
|
||||
.filter(outcome => outcomeToTest.startsWith(outcome.digits))
|
||||
val outcome =
|
||||
possibleOutcomes(Random.nextInt(possibleOutcomes.length))
|
||||
|
||||
val oracleInfo = dlcOffer.offer.oracleInfos.head
|
||||
|
||||
val oracleIndices =
|
||||
0.until(oracleInfo.numOracles).toVector
|
||||
val chosenOracles =
|
||||
Random
|
||||
.shuffle(oracleIndices)
|
||||
.take(oracleInfo.threshold)
|
||||
.sorted
|
||||
|
||||
val oracleOutcome =
|
||||
genNumericOracleOutcome(chosenOracles,
|
||||
dlcOffer.offer.contractInfo,
|
||||
outcome.digits,
|
||||
oracleParamsOpt)
|
||||
|
||||
val oracleSigs = genNumericOracleSignatures(oracleOutcome)
|
||||
|
||||
assertCorrectSigDerivation(offerSetup = offerSetup,
|
||||
dlcOffer = dlcOffer,
|
||||
acceptSetup = acceptSetup,
|
||||
dlcAccept = dlcAccept,
|
||||
oracleSigs = oracleSigs,
|
||||
outcome = oracleOutcome)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -236,6 +236,22 @@ object DLCParsingTestVector extends TestVectorParser[DLCParsingTestVector] {
|
|||
"oracleInfo" -> Element(oracleInfo)
|
||||
)
|
||||
DLCTLVTestVector(tlv, "contract_info_v0", fields)
|
||||
case ContractInfoV1TLV(totalCollateral, contracts) =>
|
||||
val fields = Vector(
|
||||
"tpe" -> Element(ContractInfoV1TLV.tpe),
|
||||
"length" -> Element(tlv.length),
|
||||
"totalCollateral" -> Element(totalCollateral.toUInt64),
|
||||
"numDisjointEvents" -> Element(BigSizeUInt(contracts.length)),
|
||||
"contracts" -> MultiElement(contracts.map {
|
||||
case (descriptor, oracleInfo) =>
|
||||
NamedMultiElement(
|
||||
Vector(
|
||||
"contractDescriptor" -> Element(descriptor),
|
||||
"oracleInfo" -> Element(oracleInfo)
|
||||
))
|
||||
})
|
||||
)
|
||||
DLCTLVTestVector(tlv, "contract_info_v1", fields)
|
||||
case FundingInputV0TLV(inputSerialId,
|
||||
prevTx,
|
||||
prevTxVout,
|
||||
|
@ -343,6 +359,14 @@ object DLCParsingTestVector extends TestVectorParser[DLCParsingTestVector] {
|
|||
"rounding_intervals_v0" -> Element(roundingIntervalsV0TLV)
|
||||
)
|
||||
DLCTLVTestVector(tlv, "negotiation_fields_v1", fields)
|
||||
case NegotiationFieldsV2TLV(nestedNegotiationFields) =>
|
||||
val fields = Vector(
|
||||
"tpe" -> Element(NegotiationFieldsV2TLV.tpe),
|
||||
"length" -> Element(tlv.length),
|
||||
"nested_negotiation_fields" -> MultiElement(
|
||||
nestedNegotiationFields.map(Element(_)))
|
||||
)
|
||||
DLCTLVTestVector(tlv, "negotiation_fields_v2", fields)
|
||||
case DLCAcceptTLV(tempContractId,
|
||||
totalCollateralSatoshis,
|
||||
fundingPubKey,
|
||||
|
|
|
@ -74,8 +74,7 @@ object DLCTLVGen {
|
|||
outcomes,
|
||||
totalInput)
|
||||
|
||||
//this doesn't ever try numeric contracts?
|
||||
ContractInfo(totalInput.satoshis, pair)
|
||||
SingleContractInfo(totalInput.satoshis, pair)
|
||||
}
|
||||
|
||||
def contractInfoParsingTestVector(
|
||||
|
@ -403,7 +402,7 @@ object DLCTLVGen {
|
|||
cetSigs(
|
||||
offer.contractInfo.allOutcomes.map(
|
||||
_.asInstanceOf[EnumOracleOutcome].outcome),
|
||||
offer.contractInfo.oracleInfo.asInstanceOf[EnumSingleOracleInfo],
|
||||
offer.contractInfo.oracleInfos.head.asInstanceOf[EnumSingleOracleInfo],
|
||||
fundingPubKey
|
||||
)
|
||||
|
||||
|
@ -470,7 +469,7 @@ object DLCTLVGen {
|
|||
cetSigs(
|
||||
offer.contractInfo.allOutcomes.map(
|
||||
_.asInstanceOf[EnumOracleOutcome].outcome),
|
||||
offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo],
|
||||
offer.oracleInfos.head.asInstanceOf[EnumSingleOracleInfo],
|
||||
offer.pubKeys.fundingKey
|
||||
)
|
||||
val fundingSignatures = fundingSigs(offer.fundingInputs.map(_.outPoint))
|
||||
|
|
|
@ -118,7 +118,7 @@ case class DLCPartyParams(
|
|||
def toOffer(params: DLCParams): DLCOffer = {
|
||||
DLCOffer(
|
||||
DLCOfferTLV.currentVersionOpt,
|
||||
ContractInfo(
|
||||
SingleContractInfo(
|
||||
EnumContractDescriptor(params.contractInfo.map(_.toMapEntry)),
|
||||
params.oracleInfo),
|
||||
DLCPublicKeys(fundingPrivKey.publicKey, payoutAddress),
|
||||
|
|
|
@ -255,10 +255,10 @@ class DLCClientIntegrationTest extends BitcoindRpcTest with DLCTest {
|
|||
)
|
||||
}
|
||||
|
||||
val params =
|
||||
EnumContractParams(numOutcomes, oracleThreshold = 1, numOracles = 1)
|
||||
constructEnumDLCClients(
|
||||
numOutcomes,
|
||||
oracleThreshold = 1,
|
||||
numOracles = 1,
|
||||
params,
|
||||
localFundingPrivKey,
|
||||
localPayoutPrivKey,
|
||||
remoteFundingPrivKey,
|
||||
|
@ -362,7 +362,7 @@ class DLCClientIntegrationTest extends BitcoindRpcTest with DLCTest {
|
|||
constructAndSetupDLC(numOutcomes)
|
||||
|
||||
oracleSig = genEnumOracleSignature(
|
||||
offerDLC.offer.oracleInfo.asInstanceOf[EnumSingleOracleInfo],
|
||||
offerDLC.offer.oracleInfos.head.asInstanceOf[EnumSingleOracleInfo],
|
||||
outcomes(outcomeIndex).outcome)
|
||||
|
||||
(unilateralDLC, unilateralSetup, otherDLC, otherSetup) = {
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package org.bitcoins.dlc.wallet
|
||||
|
||||
import org.bitcoins.core.protocol.dlc.models.{DLCState, DLCStatus}
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
DLCState,
|
||||
DLCStatus,
|
||||
DisjointUnionContractInfo,
|
||||
SingleContractInfo
|
||||
}
|
||||
import org.bitcoins.testkit.rpc.CachedBitcoindNewest
|
||||
import org.bitcoins.testkit.wallet.{BitcoinSDualWalletTest, DLCWalletUtil}
|
||||
import org.bitcoins.testkit.wallet.DLCWalletUtil.InitializedDLCWallet
|
||||
|
@ -60,7 +65,15 @@ class DLCExecutionBitcoindBackendTest
|
|||
contractInfo = broadcastB.contractInfo
|
||||
contractId = broadcastB.contractId
|
||||
dlcId = broadcastB.dlcId
|
||||
(oracleSigs, _) = DLCWalletUtil.getSigs(contractInfo)
|
||||
(oracleSigs, _) = {
|
||||
contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
closingTx <- dlcB.executeDLC(contractId, oracleSigs)
|
||||
//broadcast the closing tx
|
||||
_ <- dlcB.broadcastTransaction(closingTx)
|
||||
|
|
|
@ -3,7 +3,11 @@ 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.DLCState
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
DLCState,
|
||||
DisjointUnionContractInfo,
|
||||
SingleContractInfo
|
||||
}
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCStatus.{
|
||||
Claimed,
|
||||
Refunded,
|
||||
|
@ -95,7 +99,15 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
for {
|
||||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._1.wallet)
|
||||
(sig, _) = getSigs(status.contractInfo)
|
||||
(sig, _) = {
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
|
@ -131,7 +143,15 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
for {
|
||||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(wallets._2.wallet)
|
||||
(_, sig) = getSigs(status.contractInfo)
|
||||
(_, sig) = {
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
|
@ -171,7 +191,15 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
for {
|
||||
contractId <- getContractId(wallet)
|
||||
status <- getDLCStatus(wallet)
|
||||
(sig, _) = getSigs(status.contractInfo)
|
||||
(sig, _) = {
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
|
||||
tx1 <- wallet.executeDLC(contractId, sig)
|
||||
tx2 <- wallet.executeDLC(contractId, sig)
|
||||
|
@ -185,7 +213,15 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
contractId <- getContractId(wallets._1.wallet)
|
||||
status <- getDLCStatus(dlcA)
|
||||
// use dlcB winning sigs
|
||||
(_, sig) = getSigs(status.contractInfo)
|
||||
(_, sig) = {
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig)
|
||||
|
||||
|
@ -354,7 +390,15 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
|||
for {
|
||||
contractId <- getContractId(walletA)
|
||||
status <- getDLCStatus(walletB)
|
||||
(_, sig) = getSigs(status.contractInfo)
|
||||
(_, sig) = {
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig)
|
||||
|
||||
result <- dlcExecutionTest(wallets = wallets,
|
||||
|
|
|
@ -48,7 +48,7 @@ class DLCMultiOracleExactNumericExecutionTest extends BitcoinSDualWalletTest {
|
|||
def getSigs(contractInfo: ContractInfo): (
|
||||
Vector[OracleAttestmentTLV],
|
||||
Vector[OracleAttestmentTLV]) = {
|
||||
contractInfo.contractDescriptor match {
|
||||
contractInfo.contractDescriptors.head match {
|
||||
case _: NumericContractDescriptor => ()
|
||||
case _: EnumContractDescriptor =>
|
||||
throw new IllegalArgumentException("Unexpected Contract Info")
|
||||
|
|
|
@ -55,7 +55,7 @@ class DLCMultiOracleNumericExecutionTest
|
|||
def getSigs(contractInfo: ContractInfo): (
|
||||
Vector[OracleAttestmentTLV],
|
||||
Vector[OracleAttestmentTLV]) = {
|
||||
contractInfo.contractDescriptor match {
|
||||
contractInfo.contractDescriptors.head match {
|
||||
case _: NumericContractDescriptor => ()
|
||||
case _: EnumContractDescriptor =>
|
||||
throw new IllegalArgumentException("Unexpected Contract Info")
|
||||
|
|
|
@ -21,13 +21,13 @@ class DLCNumericExecutionTest extends BitcoinSDualWalletTest {
|
|||
def getSigs(contractInfo: ContractInfo): (
|
||||
OracleAttestmentTLV,
|
||||
OracleAttestmentTLV) = {
|
||||
contractInfo.contractDescriptor match {
|
||||
contractInfo.contractDescriptors.head match {
|
||||
case _: NumericContractDescriptor => ()
|
||||
case _: EnumContractDescriptor =>
|
||||
throw new IllegalArgumentException("Unexpected Contract Info")
|
||||
}
|
||||
|
||||
val oracleInfo = DLCWalletUtil.multiNonceContractInfo.oracleInfo
|
||||
val oracleInfo = DLCWalletUtil.multiNonceContractInfo.oracleInfos.head
|
||||
.asInstanceOf[NumericSingleOracleInfo]
|
||||
|
||||
val initiatorWinVec =
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
package org.bitcoins.dlc.wallet
|
||||
|
||||
import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.protocol.BlockStamp.BlockHash
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
ContractInfo,
|
||||
DLCState,
|
||||
EnumContractDescriptor,
|
||||
NumericContractDescriptor
|
||||
DisjointUnionContractInfo,
|
||||
SingleContractInfo
|
||||
}
|
||||
import org.bitcoins.core.protocol.tlv._
|
||||
import org.bitcoins.crypto.CryptoUtil
|
||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||
import org.bitcoins.testkit.wallet.DLCWalletUtil._
|
||||
import org.bitcoins.testkit.wallet.{DLCWalletUtil, DualWalletTestCachedBitcoind}
|
||||
|
@ -35,7 +31,15 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
|
|||
for {
|
||||
contractId <- getContractId(wallet)
|
||||
status <- getDLCStatus(wallet)
|
||||
(sig, _) = getSigs(status.contractInfo)
|
||||
(sig, _) = {
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig)
|
||||
|
||||
result <- dlcExecutionTest(wallets = (walletA, walletB),
|
||||
|
@ -66,7 +70,15 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
|
|||
for {
|
||||
contractId <- getContractId(wallet)
|
||||
status <- getDLCStatus(wallet)
|
||||
(sig, _) = getSigs(status.contractInfo)
|
||||
(sig, _) = {
|
||||
status.contractInfo match {
|
||||
case single: SingleContractInfo =>
|
||||
DLCWalletUtil.getSigs(single)
|
||||
case disjoint: DisjointUnionContractInfo =>
|
||||
sys.error(
|
||||
s"Cannot retrieve sigs for disjoint union contract, got=$disjoint")
|
||||
}
|
||||
}
|
||||
func = (wallet: DLCWallet) => wallet.executeDLC(contractId, sig)
|
||||
|
||||
result <- dlcExecutionTest(wallets = (walletA, walletB),
|
||||
|
@ -87,49 +99,4 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
|
|||
postStatus <- getDLCStatus(wallet)
|
||||
} yield assert(postStatus.state == DLCState.RemoteClaimed)
|
||||
}
|
||||
|
||||
private def getSigs(contractInfo: ContractInfo): (
|
||||
OracleAttestmentTLV,
|
||||
OracleAttestmentTLV) = {
|
||||
val desc: EnumContractDescriptor = contractInfo.contractDescriptor match {
|
||||
case desc: EnumContractDescriptor => desc
|
||||
case _: NumericContractDescriptor =>
|
||||
throw new IllegalArgumentException("Unexpected Contract Info")
|
||||
}
|
||||
|
||||
// Get a hash that the initiator wins for
|
||||
val initiatorWinStr =
|
||||
desc
|
||||
.maxBy(_._2.toLong)
|
||||
._1
|
||||
.outcome
|
||||
val initiatorWinSig = DLCWalletUtil.oraclePrivKey
|
||||
.schnorrSignWithNonce(CryptoUtil
|
||||
.sha256DLCAttestation(initiatorWinStr)
|
||||
.bytes,
|
||||
DLCWalletUtil.kValue)
|
||||
|
||||
// Get a hash that the recipient wins for
|
||||
val recipientWinStr =
|
||||
desc.find(_._2 == Satoshis.zero).get._1.outcome
|
||||
val recipientWinSig = DLCWalletUtil.oraclePrivKey
|
||||
.schnorrSignWithNonce(CryptoUtil
|
||||
.sha256DLCAttestation(recipientWinStr)
|
||||
.bytes,
|
||||
DLCWalletUtil.kValue)
|
||||
|
||||
val publicKey = DLCWalletUtil.oraclePrivKey.schnorrPublicKey
|
||||
val eventId = DLCWalletUtil.sampleOracleInfo.announcement.eventTLV match {
|
||||
case v0: OracleEventV0TLV => v0.eventId
|
||||
}
|
||||
|
||||
(OracleAttestmentV0TLV(eventId,
|
||||
publicKey,
|
||||
Vector(initiatorWinSig),
|
||||
Vector(initiatorWinStr)),
|
||||
OracleAttestmentV0TLV(eventId,
|
||||
publicKey,
|
||||
Vector(recipientWinSig),
|
||||
Vector(recipientWinStr)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
_ = {
|
||||
assert(find1.isDefined)
|
||||
assert(dlcA1Opt.get.state == DLCState.Offered)
|
||||
assert(offer.oracleInfo == offerData.oracleInfo)
|
||||
assert(offer.oracleInfos == offerData.oracleInfos)
|
||||
assert(offer.contractInfo == offerData.contractInfo)
|
||||
assert(offer.totalCollateral == offerData.totalCollateral)
|
||||
assert(offer.feeRate == offerData.feeRate)
|
||||
|
@ -152,7 +152,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
ContractOraclePair.EnumPair(EnumContractDescriptor(outcomes),
|
||||
sampleOracleInfo)
|
||||
|
||||
val contractInfo: ContractInfo = ContractInfo(totalCol, oraclePair)
|
||||
val contractInfo: ContractInfo = SingleContractInfo(totalCol, oraclePair)
|
||||
|
||||
val offerData =
|
||||
sampleDLCOffer.copy(contractInfo = contractInfo,
|
||||
|
@ -209,7 +209,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
ContractOraclePair.EnumPair(EnumContractDescriptor(outcomes),
|
||||
sampleOracleInfo)
|
||||
|
||||
val contractInfo: ContractInfo = ContractInfo(totalCol, oraclePair)
|
||||
val contractInfo: ContractInfo = SingleContractInfo(totalCol, oraclePair)
|
||||
|
||||
val offerData =
|
||||
sampleDLCOffer.copy(contractInfo = contractInfo,
|
||||
|
@ -270,7 +270,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
_ = {
|
||||
assert(dlcA1Opt.isDefined)
|
||||
assert(dlcA1Opt.get.state == DLCState.Offered)
|
||||
assert(offer.oracleInfo == offerData.oracleInfo)
|
||||
assert(offer.oracleInfos == offerData.oracleInfos)
|
||||
assert(offer.contractInfo == offerData.contractInfo)
|
||||
assert(offer.totalCollateral == offerData.totalCollateral)
|
||||
assert(offer.feeRate == offerData.feeRate)
|
||||
|
@ -495,7 +495,8 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
val offerData: DLCOffer = DLCWalletUtil.sampleDLCOffer
|
||||
|
||||
val announcementTLVs =
|
||||
offerData.contractInfo.oracleInfo.singleOracleInfos.map(_.announcement)
|
||||
offerData.contractInfo.oracleInfos.head.singleOracleInfos
|
||||
.map(_.announcement)
|
||||
assert(announcementTLVs.size == 1)
|
||||
val announcementTLV = announcementTLVs.head
|
||||
|
||||
|
@ -710,7 +711,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
|
||||
val offerData = DLCOffer(
|
||||
DLCOfferTLV.currentVersionOpt,
|
||||
ContractInfo(contractDescriptor, oracleInfo),
|
||||
SingleContractInfo(contractDescriptor, oracleInfo),
|
||||
dummyDLCKeys,
|
||||
Satoshis(5000),
|
||||
Vector(dummyFundingInputs.head),
|
||||
|
@ -736,7 +737,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
offerData.timeouts.contractTimeout.toUInt32
|
||||
)
|
||||
_ = {
|
||||
assert(offer.oracleInfo == offerData.oracleInfo)
|
||||
assert(offer.oracleInfos == offerData.oracleInfos)
|
||||
assert(offer.contractInfo == offerData.contractInfo)
|
||||
assert(offer.totalCollateral == offerData.totalCollateral)
|
||||
assert(offer.feeRate == offerData.feeRate)
|
||||
|
|
|
@ -291,7 +291,7 @@ abstract class DLCWallet
|
|||
refundLocktime: UInt32): Future[DLCOffer] = {
|
||||
logger.info("Creating DLC Offer")
|
||||
val announcements =
|
||||
contractInfo.oracleInfo.singleOracleInfos.map(_.announcement)
|
||||
contractInfo.oracleInfos.head.singleOracleInfos.map(_.announcement)
|
||||
|
||||
//hack for now to get around https://github.com/bitcoin-s/bitcoin-s/issues/3127
|
||||
//filter announcements that we already have in the db
|
||||
|
@ -385,7 +385,8 @@ abstract class DLCWallet
|
|||
timeouts = timeouts
|
||||
)
|
||||
|
||||
oracleParamsOpt = OracleInfo.getOracleParamsOpt(contractInfo.oracleInfo)
|
||||
oracleParamsOpt = OracleInfo.getOracleParamsOpt(
|
||||
contractInfo.oracleInfos.head)
|
||||
|
||||
dlcDb = DLCDb(
|
||||
dlcId = dlcId,
|
||||
|
@ -408,9 +409,9 @@ abstract class DLCWallet
|
|||
|
||||
contractDataDb = DLCContractDataDb(
|
||||
dlcId = dlcId,
|
||||
oracleThreshold = contractInfo.oracleInfo.threshold,
|
||||
oracleThreshold = contractInfo.oracleInfos.head.threshold,
|
||||
oracleParamsTLVOpt = oracleParamsOpt,
|
||||
contractDescriptorTLV = contractInfo.contractDescriptor.toTLV,
|
||||
contractDescriptorTLV = contractInfo.contractDescriptors.head.toTLV,
|
||||
contractMaturity = timeouts.contractMaturity,
|
||||
contractTimeout = timeouts.contractTimeout,
|
||||
totalCollateral = contractInfo.totalCollateral
|
||||
|
@ -454,7 +455,8 @@ abstract class DLCWallet
|
|||
.map(account => (dlcDb, account.get))
|
||||
case None =>
|
||||
val announcements =
|
||||
offer.contractInfo.oracleInfo.singleOracleInfos.map(_.announcement)
|
||||
offer.contractInfo.oracleInfos.head.singleOracleInfos
|
||||
.map(_.announcement)
|
||||
|
||||
//filter announcements that we already have in the db
|
||||
val groupedAnnouncementsF: Future[AnnouncementGrouping] = {
|
||||
|
@ -492,12 +494,13 @@ abstract class DLCWallet
|
|||
|
||||
contractDataDb = {
|
||||
val oracleParamsOpt =
|
||||
OracleInfo.getOracleParamsOpt(contractInfo.oracleInfo)
|
||||
OracleInfo.getOracleParamsOpt(contractInfo.oracleInfos.head)
|
||||
DLCContractDataDb(
|
||||
dlcId = dlcId,
|
||||
oracleThreshold = contractInfo.oracleInfo.threshold,
|
||||
oracleThreshold = contractInfo.oracleInfos.head.threshold,
|
||||
oracleParamsTLVOpt = oracleParamsOpt,
|
||||
contractDescriptorTLV = contractInfo.contractDescriptor.toTLV,
|
||||
contractDescriptorTLV =
|
||||
contractInfo.contractDescriptors.head.toTLV,
|
||||
contractMaturity = offer.timeouts.contractMaturity,
|
||||
contractTimeout = offer.timeouts.contractTimeout,
|
||||
totalCollateral = contractInfo.totalCollateral
|
||||
|
|
|
@ -153,7 +153,9 @@ private[bitcoins] trait DLCDataManagement { self: DLCWallet =>
|
|||
EnumMultiOracleInfo(contractDataDb.oracleThreshold,
|
||||
announcementTLVs)
|
||||
}
|
||||
ContractInfo(contractDataDb.totalCollateral.satoshis, enum, oracleInfo)
|
||||
SingleContractInfo(contractDataDb.totalCollateral.satoshis,
|
||||
enum,
|
||||
oracleInfo)
|
||||
case numeric: NumericContractDescriptor =>
|
||||
val oracleInfo =
|
||||
if (announcementTLVs.size == 1) {
|
||||
|
@ -169,9 +171,9 @@ private[bitcoins] trait DLCDataManagement { self: DLCWallet =>
|
|||
announcementTLVs)
|
||||
}
|
||||
}
|
||||
ContractInfo(contractDataDb.totalCollateral.satoshis,
|
||||
numeric,
|
||||
oracleInfo)
|
||||
SingleContractInfo(contractDataDb.totalCollateral.satoshis,
|
||||
numeric,
|
||||
oracleInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ val oracleInfo = NumericMultiOracleInfo(
|
|||
maximizeCoverage = false
|
||||
)
|
||||
|
||||
val contractInfo = ContractInfo(totalCollateral, ContractOraclePair.NumericPair(descriptor, oracleInfo))
|
||||
val contractInfo = SingleContractInfo(totalCollateral, ContractOraclePair.NumericPair(descriptor, oracleInfo))
|
||||
contractInfo.max
|
||||
contractInfo.allOutcomes.length
|
||||
|
||||
|
|
|
@ -5,25 +5,41 @@ import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
|
|||
import org.bitcoins.core.number.{UInt16, UInt32, UInt64}
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.BlockStamp.BlockTime
|
||||
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
|
||||
import org.bitcoins.core.protocol.dlc.compute.CETCalculator
|
||||
import org.bitcoins.core.protocol.dlc.execution.{CETInfo, SetupDLC}
|
||||
import org.bitcoins.core.protocol.dlc.execution._
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage.DLCSign
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.tlv.{NumericDLCOutcomeType, _}
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.util.sorted.OrderedAnnouncements
|
||||
import org.bitcoins.core.util.{FutureUtil, NumberUtil}
|
||||
import org.bitcoins.core.util.{BitcoinScriptUtil, FutureUtil, NumberUtil}
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo._
|
||||
import org.bitcoins.crypto._
|
||||
import org.scalatest.Assertions.{assert, fail, succeed}
|
||||
import org.scalatest.{Assertion, Assertions}
|
||||
import scodec.bits.BitVector
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future, Promise}
|
||||
import scala.util.Random
|
||||
|
||||
trait DLCTest {
|
||||
|
||||
def tenRandomNums(numDigits: Int): Vector[Long] = {
|
||||
0.until(10).toVector.map { _ =>
|
||||
val randDigits = (0 until numDigits).toVector.map { _ =>
|
||||
scala.util.Random.nextInt(2)
|
||||
}
|
||||
|
||||
BitVector
|
||||
.fromValidBin(randDigits.mkString(""))
|
||||
.toLong(signed = false)
|
||||
}
|
||||
}
|
||||
|
||||
val oraclePrivKeys: Vector[ECPrivateKey] =
|
||||
(0 until 50).toVector.map(_ => ECPrivateKey.freshPrivateKey)
|
||||
|
||||
|
@ -241,6 +257,46 @@ trait DLCTest {
|
|||
|
||||
val feeRate: SatoshisPerVirtualByte = SatoshisPerVirtualByte(Satoshis(10))
|
||||
|
||||
sealed trait ContractParams
|
||||
|
||||
sealed trait SingleContractParams extends ContractParams
|
||||
|
||||
object SingleContractParams {
|
||||
|
||||
def apply(
|
||||
numDigitsOrOutcomes: Int,
|
||||
isNumeric: Boolean,
|
||||
threshold: Int,
|
||||
numOracles: Int,
|
||||
paramsOpt: Option[OracleParamsV0TLV]): SingleContractParams = {
|
||||
if (isNumeric) {
|
||||
EnumContractParams(numDigitsOrOutcomes, threshold, numOracles)
|
||||
} else {
|
||||
NumericContractParams(numDigitsOrOutcomes,
|
||||
threshold,
|
||||
numOracles,
|
||||
paramsOpt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class EnumContractParams(
|
||||
numOutcomes: Int,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int)
|
||||
extends SingleContractParams
|
||||
|
||||
case class NumericContractParams(
|
||||
numDigits: Int,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int,
|
||||
paramsOpt: Option[OracleParamsV0TLV] = None)
|
||||
extends SingleContractParams
|
||||
|
||||
case class DisjointUnionContractParams(
|
||||
singleParams: Vector[SingleContractParams])
|
||||
extends ContractParams
|
||||
|
||||
def constructDLCClientsFromInfos(
|
||||
offerInfo: ContractInfo,
|
||||
acceptInfo: ContractInfo,
|
||||
|
@ -310,10 +366,40 @@ trait DLCTest {
|
|||
(offerDLC, acceptDLC)
|
||||
}
|
||||
|
||||
def constructEnumContractInfos(
|
||||
params: EnumContractParams,
|
||||
oracleShift: Int = 0): (SingleContractInfo, SingleContractInfo) = {
|
||||
val outcomeStrs = DLCTestUtil.genOutcomes(params.numOutcomes)
|
||||
val outcomes = outcomeStrs.map(EnumOutcome.apply)
|
||||
|
||||
val announcements =
|
||||
oraclePrivKeys
|
||||
.slice(oracleShift, oracleShift + params.numOracles)
|
||||
.zip(
|
||||
preCommittedRsPerOracle
|
||||
.slice(oracleShift, oracleShift + params.numOracles)
|
||||
.map(_.head))
|
||||
.map { case (privKey, rVal) =>
|
||||
OracleAnnouncementV0TLV.dummyForEventsAndKeys(privKey, rVal, outcomes)
|
||||
}
|
||||
val oracleInfo = if (params.numOracles == 1) {
|
||||
EnumSingleOracleInfo(announcements.head)
|
||||
} else {
|
||||
val ordered = OrderedAnnouncements(announcements)
|
||||
EnumMultiOracleInfo(params.oracleThreshold, ordered)
|
||||
}
|
||||
|
||||
val (outcomesDesc, otherOutcomesDesc) =
|
||||
DLCTestUtil.genContractDescriptors(outcomeStrs, totalInput)
|
||||
|
||||
val offerInfo = SingleContractInfo(outcomesDesc, oracleInfo)
|
||||
val acceptInfo = SingleContractInfo(otherOutcomesDesc, oracleInfo)
|
||||
|
||||
(offerInfo, acceptInfo)
|
||||
}
|
||||
|
||||
def constructEnumDLCClients(
|
||||
numOutcomes: Int,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int,
|
||||
contractParams: EnumContractParams,
|
||||
offerFundingPrivKey: ECPrivateKey = this.offerFundingPrivKey,
|
||||
offerPayoutPrivKey: ECPrivateKey = this.offerPayoutPrivKey,
|
||||
acceptFundingPrivKey: ECPrivateKey = this.acceptFundingPrivKey,
|
||||
|
@ -329,28 +415,7 @@ trait DLCTest {
|
|||
TestDLCClient,
|
||||
TestDLCClient,
|
||||
Vector[EnumOutcome]) = {
|
||||
val outcomeStrs = DLCTestUtil.genOutcomes(numOutcomes)
|
||||
val outcomes = outcomeStrs.map(EnumOutcome.apply)
|
||||
|
||||
val announcements =
|
||||
oraclePrivKeys.take(numOracles).zip(preCommittedRs.take(numOracles)).map {
|
||||
case (privKey, rVal) =>
|
||||
OracleAnnouncementV0TLV.dummyForEventsAndKeys(privKey, rVal, outcomes)
|
||||
}
|
||||
|
||||
val orderedAnnouncements = OrderedAnnouncements(announcements)
|
||||
|
||||
val oracleInfo = if (numOracles == 1) {
|
||||
EnumSingleOracleInfo(announcements.head)
|
||||
} else {
|
||||
EnumMultiOracleInfo(oracleThreshold, orderedAnnouncements)
|
||||
}
|
||||
|
||||
val (outcomesDesc, otherOutcomesDesc) =
|
||||
DLCTestUtil.genContractDescriptors(outcomeStrs, totalInput)
|
||||
|
||||
val offerInfo = ContractInfo(outcomesDesc, oracleInfo)
|
||||
val acceptInfo = ContractInfo(otherOutcomesDesc, oracleInfo)
|
||||
val (offerInfo, acceptInfo) = constructEnumContractInfos(contractParams)
|
||||
|
||||
val (offerDLC, acceptDLC) = constructDLCClientsFromInfos(
|
||||
offerInfo,
|
||||
|
@ -367,14 +432,54 @@ trait DLCTest {
|
|||
timeouts
|
||||
)
|
||||
|
||||
val outcomes = offerInfo.contractDescriptor
|
||||
.asInstanceOf[EnumContractDescriptor]
|
||||
.outcomeValueMap
|
||||
.map(_._1)
|
||||
|
||||
(offerDLC, acceptDLC, outcomes)
|
||||
}
|
||||
|
||||
def constructNumericContractInfos(
|
||||
params: NumericContractParams,
|
||||
oracleShift: Int = 0): (SingleContractInfo, SingleContractInfo) = {
|
||||
val (offerDesc, acceptDesc) =
|
||||
DLCTestUtil.genMultiDigitContractInfo(params.numDigits,
|
||||
totalInput,
|
||||
numRounds = 4)
|
||||
|
||||
val announcements =
|
||||
oraclePrivKeys
|
||||
.slice(oracleShift, oracleShift + params.numOracles)
|
||||
.zip(preCommittedRsPerOracle
|
||||
.slice(oracleShift, oracleShift + params.numOracles))
|
||||
.map { case (privKey, rVals) =>
|
||||
OracleAnnouncementV0TLV.dummyForKeys(privKey,
|
||||
rVals.take(params.numDigits))
|
||||
}
|
||||
val oracleInfo = if (params.numOracles == 1) {
|
||||
NumericSingleOracleInfo(announcements.head)
|
||||
} else {
|
||||
val ordered = OrderedAnnouncements(announcements)
|
||||
params.paramsOpt match {
|
||||
case None =>
|
||||
NumericExactMultiOracleInfo(params.oracleThreshold, ordered)
|
||||
case Some(boundParams) =>
|
||||
NumericMultiOracleInfo(params.oracleThreshold, ordered, boundParams)
|
||||
}
|
||||
}
|
||||
|
||||
val numericPairOffer = ContractOraclePair.NumericPair(offerDesc, oracleInfo)
|
||||
val numericPairAccept =
|
||||
ContractOraclePair.NumericPair(acceptDesc, oracleInfo)
|
||||
val offerInfo = SingleContractInfo(totalInput.satoshis, numericPairOffer)
|
||||
val acceptInfo = SingleContractInfo(totalInput.satoshis, numericPairAccept)
|
||||
|
||||
(offerInfo, acceptInfo)
|
||||
}
|
||||
|
||||
def constructNumericDLCClients(
|
||||
numDigits: Int,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int,
|
||||
paramsOpt: Option[OracleParamsV0TLV],
|
||||
contractParams: NumericContractParams,
|
||||
offerFundingPrivKey: ECPrivateKey = this.offerFundingPrivKey,
|
||||
offerPayoutPrivKey: ECPrivateKey = this.offerPayoutPrivKey,
|
||||
acceptFundingPrivKey: ECPrivateKey = this.acceptFundingPrivKey,
|
||||
|
@ -390,37 +495,7 @@ trait DLCTest {
|
|||
TestDLCClient,
|
||||
TestDLCClient,
|
||||
Vector[UnsignedNumericOutcome]) = {
|
||||
val (offerDesc, acceptDesc) =
|
||||
DLCTestUtil.genMultiDigitContractInfo(numDigits,
|
||||
totalInput,
|
||||
numRounds = 4)
|
||||
|
||||
val announcements =
|
||||
oraclePrivKeys
|
||||
.take(numOracles)
|
||||
.zip(preCommittedRsPerOracle.take(numOracles))
|
||||
.map { case (privKey, rVals) =>
|
||||
OracleAnnouncementV0TLV.dummyForKeys(privKey, rVals.take(numDigits))
|
||||
}
|
||||
|
||||
val orderedAnnouncements = OrderedAnnouncements(announcements)
|
||||
|
||||
val oracleInfo = if (numOracles == 1) {
|
||||
NumericSingleOracleInfo(announcements.head)
|
||||
} else {
|
||||
paramsOpt match {
|
||||
case None =>
|
||||
NumericExactMultiOracleInfo(oracleThreshold, orderedAnnouncements)
|
||||
case Some(params) =>
|
||||
NumericMultiOracleInfo(oracleThreshold, orderedAnnouncements, params)
|
||||
}
|
||||
}
|
||||
|
||||
val numericPairOffer = ContractOraclePair.NumericPair(offerDesc, oracleInfo)
|
||||
val numericPairAccept =
|
||||
ContractOraclePair.NumericPair(acceptDesc, oracleInfo)
|
||||
val offerInfo = ContractInfo(totalInput.satoshis, numericPairOffer)
|
||||
val acceptInfo = ContractInfo(totalInput.satoshis, numericPairAccept)
|
||||
val (offerInfo, acceptInfo) = constructNumericContractInfos(contractParams)
|
||||
val outcomes =
|
||||
offerInfo.allOutcomes.map(_.asInstanceOf[NumericOracleOutcome].outcome)
|
||||
|
||||
|
@ -442,12 +517,8 @@ trait DLCTest {
|
|||
(offerDLC, acceptDLC, outcomes)
|
||||
}
|
||||
|
||||
def constructDLCClients(
|
||||
numOutcomesOrDigits: Int,
|
||||
isNumeric: Boolean,
|
||||
oracleThreshold: Int,
|
||||
numOracles: Int,
|
||||
paramsOpt: Option[OracleParamsV0TLV],
|
||||
def constructDisjointUnionDLCClients(
|
||||
contractParams: DisjointUnionContractParams,
|
||||
offerFundingPrivKey: ECPrivateKey = this.offerFundingPrivKey,
|
||||
offerPayoutPrivKey: ECPrivateKey = this.offerPayoutPrivKey,
|
||||
acceptFundingPrivKey: ECPrivateKey = this.acceptFundingPrivKey,
|
||||
|
@ -463,39 +534,108 @@ trait DLCTest {
|
|||
TestDLCClient,
|
||||
TestDLCClient,
|
||||
Vector[DLCOutcomeType]) = {
|
||||
if (isNumeric) {
|
||||
constructNumericDLCClients(
|
||||
numOutcomesOrDigits,
|
||||
oracleThreshold,
|
||||
numOracles,
|
||||
paramsOpt,
|
||||
offerFundingPrivKey,
|
||||
offerPayoutPrivKey,
|
||||
acceptFundingPrivKey,
|
||||
acceptPayoutPrivKey,
|
||||
offerFundingUtxos,
|
||||
offerFundingInputs,
|
||||
acceptFundingUtxos,
|
||||
acceptFundingInputs,
|
||||
feeRate,
|
||||
timeouts
|
||||
)
|
||||
} else {
|
||||
constructEnumDLCClients(
|
||||
numOutcomesOrDigits,
|
||||
oracleThreshold,
|
||||
numOracles,
|
||||
offerFundingPrivKey,
|
||||
offerPayoutPrivKey,
|
||||
acceptFundingPrivKey,
|
||||
acceptPayoutPrivKey,
|
||||
offerFundingUtxos,
|
||||
offerFundingInputs,
|
||||
acceptFundingUtxos,
|
||||
acceptFundingInputs,
|
||||
feeRate,
|
||||
timeouts
|
||||
)
|
||||
var oraclesSoFar = 0
|
||||
val singleInfosAndOutcomes = contractParams.singleParams.map {
|
||||
case enumParams: EnumContractParams =>
|
||||
val (offerInfo, acceptInfo) =
|
||||
constructEnumContractInfos(enumParams, oraclesSoFar)
|
||||
oraclesSoFar += enumParams.numOracles
|
||||
val outcomes =
|
||||
offerInfo.allOutcomes.map(_.asInstanceOf[EnumOracleOutcome].outcome)
|
||||
(offerInfo, acceptInfo, outcomes)
|
||||
case numericParams: NumericContractParams =>
|
||||
val (offerInfo, acceptInfo) =
|
||||
constructNumericContractInfos(numericParams, oraclesSoFar)
|
||||
oraclesSoFar += numericParams.numOracles
|
||||
val outcomes = offerInfo.allOutcomes.map(
|
||||
_.asInstanceOf[NumericOracleOutcome].outcome)
|
||||
(offerInfo, acceptInfo, outcomes)
|
||||
}
|
||||
val offerInfos = singleInfosAndOutcomes.map(_._1)
|
||||
val acceptInfos = singleInfosAndOutcomes.map(_._2)
|
||||
val outcomes = singleInfosAndOutcomes.flatMap(_._3)
|
||||
|
||||
val offerInfo = DisjointUnionContractInfo(offerInfos)
|
||||
val acceptInfo = DisjointUnionContractInfo(acceptInfos)
|
||||
|
||||
val (offerDLC, acceptDLC) = constructDLCClientsFromInfos(
|
||||
offerInfo,
|
||||
acceptInfo,
|
||||
offerFundingPrivKey,
|
||||
offerPayoutPrivKey,
|
||||
acceptFundingPrivKey,
|
||||
acceptPayoutPrivKey,
|
||||
offerFundingUtxos,
|
||||
offerFundingInputs,
|
||||
acceptFundingUtxos,
|
||||
acceptFundingInputs,
|
||||
feeRate,
|
||||
timeouts
|
||||
)
|
||||
|
||||
(offerDLC, acceptDLC, outcomes)
|
||||
}
|
||||
|
||||
def constructDLCClients(
|
||||
contractParams: ContractParams,
|
||||
offerFundingPrivKey: ECPrivateKey = this.offerFundingPrivKey,
|
||||
offerPayoutPrivKey: ECPrivateKey = this.offerPayoutPrivKey,
|
||||
acceptFundingPrivKey: ECPrivateKey = this.acceptFundingPrivKey,
|
||||
acceptPayoutPrivKey: ECPrivateKey = this.acceptPayoutPrivKey,
|
||||
offerFundingUtxos: Vector[SpendingInfoWithSerialId] =
|
||||
this.offerFundingUtxos,
|
||||
offerFundingInputs: Vector[DLCFundingInput] = this.offerFundingInputs,
|
||||
acceptFundingUtxos: Vector[SpendingInfoWithSerialId] =
|
||||
this.acceptFundingUtxos,
|
||||
acceptFundingInputs: Vector[DLCFundingInput] = this.acceptFundingInputs,
|
||||
feeRate: SatoshisPerVirtualByte = this.feeRate,
|
||||
timeouts: DLCTimeouts = this.timeouts)(implicit ec: ExecutionContext): (
|
||||
TestDLCClient,
|
||||
TestDLCClient,
|
||||
Vector[DLCOutcomeType]) = {
|
||||
contractParams match {
|
||||
case enumParams: EnumContractParams =>
|
||||
constructEnumDLCClients(
|
||||
enumParams,
|
||||
offerFundingPrivKey,
|
||||
offerPayoutPrivKey,
|
||||
acceptFundingPrivKey,
|
||||
acceptPayoutPrivKey,
|
||||
offerFundingUtxos,
|
||||
offerFundingInputs,
|
||||
acceptFundingUtxos,
|
||||
acceptFundingInputs,
|
||||
feeRate,
|
||||
timeouts
|
||||
)
|
||||
case numericParams: NumericContractParams =>
|
||||
constructNumericDLCClients(
|
||||
numericParams,
|
||||
offerFundingPrivKey,
|
||||
offerPayoutPrivKey,
|
||||
acceptFundingPrivKey,
|
||||
acceptPayoutPrivKey,
|
||||
offerFundingUtxos,
|
||||
offerFundingInputs,
|
||||
acceptFundingUtxos,
|
||||
acceptFundingInputs,
|
||||
feeRate,
|
||||
timeouts
|
||||
)
|
||||
case disjointUnionParams: DisjointUnionContractParams =>
|
||||
constructDisjointUnionDLCClients(
|
||||
disjointUnionParams,
|
||||
offerFundingPrivKey,
|
||||
offerPayoutPrivKey,
|
||||
acceptFundingPrivKey,
|
||||
acceptPayoutPrivKey,
|
||||
offerFundingUtxos,
|
||||
offerFundingInputs,
|
||||
acceptFundingUtxos,
|
||||
acceptFundingInputs,
|
||||
feeRate,
|
||||
timeouts
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -548,6 +688,30 @@ trait DLCTest {
|
|||
}
|
||||
}
|
||||
|
||||
def setupDLC(dlcOffer: TestDLCClient, dlcAccept: TestDLCClient)(implicit
|
||||
ec: ExecutionContext): Future[(SetupDLC, SetupDLC)] = {
|
||||
|
||||
setupDLC(dlcOffer,
|
||||
dlcAccept,
|
||||
_.map(_.fundingTx),
|
||||
_ => Future.successful(()))
|
||||
}
|
||||
|
||||
def constructAndSetupDLC(contractParams: ContractParams)(implicit
|
||||
ec: ExecutionContext): Future[
|
||||
(
|
||||
TestDLCClient,
|
||||
SetupDLC,
|
||||
TestDLCClient,
|
||||
SetupDLC,
|
||||
Vector[DLCOutcomeType])] = {
|
||||
val (offerDLC, acceptDLC, outcomes) = constructDLCClients(contractParams)
|
||||
|
||||
for {
|
||||
(offerSetup, acceptSetup) <- setupDLC(offerDLC, acceptDLC)
|
||||
} yield (offerDLC, offerSetup, acceptDLC, acceptSetup, outcomes)
|
||||
}
|
||||
|
||||
/** Computes an EnumOracleSignature for the given outcome and oracle */
|
||||
def genEnumOracleSignature(
|
||||
oracleInfo: EnumSingleOracleInfo,
|
||||
|
@ -564,13 +728,13 @@ trait DLCTest {
|
|||
|
||||
def genEnumOracleOutcome(
|
||||
chosenOracles: Vector[Int],
|
||||
dlcOffer: TestDLCClient,
|
||||
contractInfo: SingleContractInfo,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long): EnumOracleOutcome = {
|
||||
outcomes(outcomeIndex.toInt) match {
|
||||
case outcome: EnumOutcome =>
|
||||
val oracles = chosenOracles
|
||||
.map(dlcOffer.offer.oracleInfo.singleOracleInfos.apply)
|
||||
.map(contractInfo.oracleInfos.head.singleOracleInfos.apply)
|
||||
.map(_.asInstanceOf[EnumSingleOracleInfo])
|
||||
EnumOracleOutcome(oracles, outcome)
|
||||
case _: NumericDLCOutcomeType =>
|
||||
|
@ -589,17 +753,17 @@ trait DLCTest {
|
|||
genEnumOracleSignature(oracle,
|
||||
outcome.outcome.outcome,
|
||||
oraclePrivKeys(index),
|
||||
preCommittedKs(index))
|
||||
preCommittedKsPerOracle(index).head)
|
||||
}
|
||||
}
|
||||
|
||||
def genEnumOracleSignatures(
|
||||
chosenOracles: Vector[Int],
|
||||
dlcOffer: TestDLCClient,
|
||||
contractInfo: SingleContractInfo,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long): Vector[EnumOracleSignature] = {
|
||||
val outcome =
|
||||
genEnumOracleOutcome(chosenOracles, dlcOffer, outcomes, outcomeIndex)
|
||||
genEnumOracleOutcome(chosenOracles, contractInfo, outcomes, outcomeIndex)
|
||||
|
||||
genEnumOracleSignatures(outcome)
|
||||
}
|
||||
|
@ -646,7 +810,7 @@ trait DLCTest {
|
|||
contractInfo: ContractInfo,
|
||||
digits: Vector[Int],
|
||||
paramsOpt: Option[OracleParamsV0TLV]): NumericOracleOutcome = {
|
||||
contractInfo.contractOraclePair match {
|
||||
contractInfo.contracts.head.contractOraclePair match {
|
||||
case e: ContractOraclePair.EnumPair =>
|
||||
Assertions.fail(s"Expected Numeric Contract, got enum=$e")
|
||||
case ContractOraclePair.NumericPair(_, oracleInfo) =>
|
||||
|
@ -686,20 +850,17 @@ trait DLCTest {
|
|||
def genNumericOracleOutcome(
|
||||
numDigits: Int,
|
||||
chosenOracles: Vector[Int],
|
||||
dlcOffer: TestDLCClient,
|
||||
contractInfo: SingleContractInfo,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long,
|
||||
paramsOpt: Option[OracleParamsV0TLV]): NumericOracleOutcome = {
|
||||
dlcOffer.offer.contractInfo.contractOraclePair match {
|
||||
contractInfo.contractOraclePair match {
|
||||
case e: ContractOraclePair.EnumPair =>
|
||||
Assertions.fail(s"Expected Numeric Contract, got enum=$e")
|
||||
case ContractOraclePair.NumericPair(descriptor, _) =>
|
||||
val digits =
|
||||
computeNumericOutcome(numDigits, descriptor, outcomes, outcomeIndex)
|
||||
genNumericOracleOutcome(chosenOracles,
|
||||
dlcOffer.offer.contractInfo,
|
||||
digits,
|
||||
paramsOpt)
|
||||
genNumericOracleOutcome(chosenOracles, contractInfo, digits, paramsOpt)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -723,13 +884,13 @@ trait DLCTest {
|
|||
def genNumericOracleSignatures(
|
||||
numDigits: Int,
|
||||
chosenOracles: Vector[Int],
|
||||
dlcOffer: TestDLCClient,
|
||||
contractInfo: SingleContractInfo,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long,
|
||||
paramsOpt: Option[OracleParamsV0TLV]): Vector[NumericOracleSignatures] = {
|
||||
val outcome = genNumericOracleOutcome(numDigits,
|
||||
chosenOracles,
|
||||
dlcOffer,
|
||||
contractInfo,
|
||||
outcomes,
|
||||
outcomeIndex,
|
||||
paramsOpt)
|
||||
|
@ -739,11 +900,11 @@ trait DLCTest {
|
|||
def genOracleOutcome(
|
||||
numOutcomesOrDigits: Int,
|
||||
isNumeric: Boolean,
|
||||
dlcOffer: TestDLCClient,
|
||||
contractInfo: SingleContractInfo,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long,
|
||||
paramsOpt: Option[OracleParamsV0TLV]): OracleOutcome = {
|
||||
val oracleInfo = dlcOffer.offer.oracleInfo
|
||||
val oracleInfo = contractInfo.oracleInfos.head
|
||||
|
||||
val oracleIndices =
|
||||
0.until(oracleInfo.numOracles).toVector
|
||||
|
@ -751,11 +912,11 @@ trait DLCTest {
|
|||
Random.shuffle(oracleIndices).take(oracleInfo.threshold).sorted
|
||||
|
||||
if (!isNumeric) {
|
||||
genEnumOracleOutcome(chosenOracles, dlcOffer, outcomes, outcomeIndex)
|
||||
genEnumOracleOutcome(chosenOracles, contractInfo, outcomes, outcomeIndex)
|
||||
} else {
|
||||
genNumericOracleOutcome(numOutcomesOrDigits,
|
||||
chosenOracles,
|
||||
dlcOffer,
|
||||
contractInfo,
|
||||
outcomes,
|
||||
outcomeIndex,
|
||||
paramsOpt)
|
||||
|
@ -765,7 +926,7 @@ trait DLCTest {
|
|||
def genOracleOutcomeAndSignatures(
|
||||
numOutcomesOrDigits: Int,
|
||||
isNumeric: Boolean,
|
||||
dlcOffer: TestDLCClient,
|
||||
contractInfo: SingleContractInfo,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long,
|
||||
paramsOpt: Option[OracleParamsV0TLV]): (
|
||||
|
@ -773,7 +934,7 @@ trait DLCTest {
|
|||
Vector[OracleSignatures]) = {
|
||||
val outcome = genOracleOutcome(numOutcomesOrDigits,
|
||||
isNumeric,
|
||||
dlcOffer,
|
||||
contractInfo,
|
||||
outcomes,
|
||||
outcomeIndex,
|
||||
paramsOpt)
|
||||
|
@ -792,19 +953,282 @@ trait DLCTest {
|
|||
def genOracleSignatures(
|
||||
numOutcomesOrDigits: Int,
|
||||
isNumeric: Boolean,
|
||||
dlcOffer: TestDLCClient,
|
||||
contractInfo: SingleContractInfo,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
outcomeIndex: Long,
|
||||
paramsOpt: Option[OracleParamsV0TLV]): Vector[OracleSignatures] = {
|
||||
val (_, sigs) = genOracleOutcomeAndSignatures(numOutcomesOrDigits,
|
||||
isNumeric,
|
||||
dlcOffer,
|
||||
contractInfo,
|
||||
outcomes,
|
||||
outcomeIndex,
|
||||
paramsOpt)
|
||||
sigs
|
||||
}
|
||||
|
||||
def validateOutcome(
|
||||
outcome: DLCOutcome,
|
||||
dlcOffer: TestDLCClient,
|
||||
dlcAccept: TestDLCClient): Assertion = {
|
||||
val fundingTx = outcome.fundingTx
|
||||
assert(noEmptySPKOutputs(fundingTx))
|
||||
|
||||
val fundOutputIndex = dlcOffer.dlcTxBuilder.fundOutputIndex
|
||||
|
||||
val signers = Vector(dlcOffer.fundingPrivKey, dlcAccept.fundingPrivKey)
|
||||
val closingSpendingInfo = ScriptSignatureParams(
|
||||
P2WSHV0InputInfo(
|
||||
TransactionOutPoint(fundingTx.txId, UInt32(fundOutputIndex)),
|
||||
fundingTx.outputs(fundOutputIndex).value,
|
||||
P2WSHWitnessV0(
|
||||
MultiSignatureScriptPubKey(2,
|
||||
signers.map(_.publicKey).sortBy(_.hex))),
|
||||
ConditionalPath.NoCondition
|
||||
),
|
||||
fundingTx,
|
||||
signers,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
outcome match {
|
||||
case ExecutedDLCOutcome(fundingTx, cet, _, _) =>
|
||||
DLCFeeTestUtil.validateFees(dlcOffer.dlcTxBuilder,
|
||||
fundingTx,
|
||||
cet,
|
||||
fundingTxSigs = 5)
|
||||
assert(noEmptySPKOutputs(cet))
|
||||
assert(BitcoinScriptUtil.verifyScript(cet, Vector(closingSpendingInfo)))
|
||||
case RefundDLCOutcome(fundingTx, refundTx) =>
|
||||
DLCFeeTestUtil.validateFees(dlcOffer.dlcTxBuilder,
|
||||
fundingTx,
|
||||
refundTx,
|
||||
fundingTxSigs = 5)
|
||||
assert(noEmptySPKOutputs(refundTx))
|
||||
assert(
|
||||
BitcoinScriptUtil.verifyScript(refundTx, Vector(closingSpendingInfo)))
|
||||
}
|
||||
}
|
||||
|
||||
def executeForCase(outcomeIndex: Long, contractParams: ContractParams)(
|
||||
implicit ec: ExecutionContext): Future[Assertion] = {
|
||||
executeForCase(contractIndex = 0, outcomeIndex, contractParams)
|
||||
}
|
||||
|
||||
def executeForCase(
|
||||
contractIndex: Int,
|
||||
outcomeIndex: Long,
|
||||
contractParams: ContractParams)(implicit
|
||||
ec: ExecutionContext): Future[Assertion] = {
|
||||
constructAndSetupDLC(contractParams)
|
||||
.flatMap {
|
||||
case (dlcOffer, offerSetup, dlcAccept, acceptSetup, outcomes) =>
|
||||
executeForOutcome(outcomeIndex,
|
||||
dlcOffer,
|
||||
offerSetup,
|
||||
dlcAccept,
|
||||
acceptSetup,
|
||||
outcomes,
|
||||
contractIndex)
|
||||
}
|
||||
}
|
||||
|
||||
def executeForCases(
|
||||
outcomeIndices: Vector[Long],
|
||||
contractParams: ContractParams)(implicit
|
||||
ec: ExecutionContext): Future[Assertion] = {
|
||||
executeForCasesInUnion(outcomeIndices.map((0, _)), contractParams)
|
||||
}
|
||||
|
||||
def executeForCasesInUnion(
|
||||
outcomeIndices: Vector[(Int, Long)],
|
||||
contractParams: ContractParams)(implicit
|
||||
ec: ExecutionContext): Future[Assertion] = {
|
||||
constructAndSetupDLC(contractParams)
|
||||
.flatMap {
|
||||
case (dlcOffer, offerSetup, dlcAccept, acceptSetup, outcomes) =>
|
||||
val testFs = outcomeIndices.map {
|
||||
case (contractIndex, outcomeIndex) =>
|
||||
executeForOutcome(outcomeIndex,
|
||||
dlcOffer,
|
||||
offerSetup,
|
||||
dlcAccept,
|
||||
acceptSetup,
|
||||
outcomes,
|
||||
contractIndex)
|
||||
}
|
||||
|
||||
Future.sequence(testFs).map(_ => succeed)
|
||||
}
|
||||
}
|
||||
|
||||
def executeForOutcome(
|
||||
outcomeIndex: Long,
|
||||
dlcOffer: TestDLCClient,
|
||||
offerSetup: SetupDLC,
|
||||
dlcAccept: TestDLCClient,
|
||||
acceptSetup: SetupDLC,
|
||||
outcomes: Vector[DLCOutcomeType],
|
||||
contractIndex: Int = 0)(implicit
|
||||
ec: ExecutionContext): Future[Assertion] = {
|
||||
val contractInfo = dlcOffer.offer.contractInfo
|
||||
val contractSizes = contractInfo.contracts.map { contract =>
|
||||
contract.allOutcomes.length
|
||||
}
|
||||
|
||||
val indexOfOutcomeStart = contractSizes.take(contractIndex).sum
|
||||
|
||||
val singleContractInfo = contractInfo.contracts(contractIndex)
|
||||
|
||||
val possibleOutcomesForContract =
|
||||
outcomes.slice(
|
||||
indexOfOutcomeStart,
|
||||
indexOfOutcomeStart + singleContractInfo.allOutcomes.length)
|
||||
|
||||
val contractDesc = singleContractInfo.contractDescriptor
|
||||
val (numOutcomes, isMultiDigit, paramsOpt) = contractDesc match {
|
||||
case EnumContractDescriptor(outcomeValueMap) =>
|
||||
(outcomeValueMap.length, false, None)
|
||||
case NumericContractDescriptor(_, numDigits, _) =>
|
||||
val paramsOpt = contractInfo.oracleInfos.head match {
|
||||
case NumericMultiOracleInfo(_,
|
||||
_,
|
||||
maxErrorExp,
|
||||
minFailExp,
|
||||
maximizeCoverage) =>
|
||||
Some(OracleParamsV0TLV(maxErrorExp, minFailExp, maximizeCoverage))
|
||||
case _: OracleInfo => None
|
||||
}
|
||||
|
||||
(numDigits, true, paramsOpt)
|
||||
}
|
||||
|
||||
val oracleSigs = genOracleSignatures(numOutcomes,
|
||||
isMultiDigit,
|
||||
singleContractInfo,
|
||||
possibleOutcomesForContract,
|
||||
outcomeIndex,
|
||||
paramsOpt)
|
||||
|
||||
for {
|
||||
offerOutcome <-
|
||||
dlcOffer.executeDLC(offerSetup, Future.successful(oracleSigs))
|
||||
acceptOutcome <-
|
||||
dlcAccept.executeDLC(acceptSetup, Future.successful(oracleSigs))
|
||||
} yield {
|
||||
assert(offerOutcome.fundingTx == acceptOutcome.fundingTx)
|
||||
|
||||
validateOutcome(offerOutcome, dlcOffer, dlcAccept)
|
||||
validateOutcome(acceptOutcome, dlcOffer, dlcAccept)
|
||||
}
|
||||
}
|
||||
|
||||
def executeRefundCase(contractParams: ContractParams)(implicit
|
||||
ec: ExecutionContext): Future[Assertion] = {
|
||||
constructAndSetupDLC(contractParams)
|
||||
.map { case (dlcOffer, offerSetup, dlcAccept, acceptSetup, _) =>
|
||||
val offerOutcome = dlcOffer.executeRefundDLC(offerSetup)
|
||||
val acceptOutcome = dlcAccept.executeRefundDLC(acceptSetup)
|
||||
|
||||
validateOutcome(offerOutcome, dlcOffer, dlcAccept)
|
||||
validateOutcome(acceptOutcome, dlcOffer, dlcAccept)
|
||||
|
||||
assert(acceptOutcome.fundingTx == offerOutcome.fundingTx)
|
||||
assert(acceptOutcome.refundTx == offerOutcome.refundTx)
|
||||
}
|
||||
}
|
||||
|
||||
def assertCorrectSigDerivation(
|
||||
offerSetup: SetupDLC,
|
||||
dlcOffer: TestDLCClient,
|
||||
acceptSetup: SetupDLC,
|
||||
dlcAccept: TestDLCClient,
|
||||
oracleSigs: Vector[OracleSignatures],
|
||||
outcome: OracleOutcome)(implicit
|
||||
ec: ExecutionContext): Future[Assertion] = {
|
||||
val aggR = outcome.aggregateNonce
|
||||
val aggS = outcome match {
|
||||
case EnumOracleOutcome(oracles, _) =>
|
||||
assert(oracles.length == oracleSigs.length)
|
||||
|
||||
val sVals = oracleSigs.map {
|
||||
case EnumOracleSignature(oracle, sig) =>
|
||||
assert(oracles.contains(oracle))
|
||||
sig.sig
|
||||
case _: NumericOracleSignatures =>
|
||||
fail("Expected EnumOracleSignature")
|
||||
}
|
||||
|
||||
sVals.reduce(_.add(_))
|
||||
case NumericOracleOutcome(oraclesAndOutcomes) =>
|
||||
assert(oraclesAndOutcomes.length == oracleSigs.length)
|
||||
|
||||
val sVals = oracleSigs.map {
|
||||
case NumericOracleSignatures(oracle, sigs) =>
|
||||
val oracleAndOutcomeOpt = oraclesAndOutcomes.find(_._1 == oracle)
|
||||
assert(oracleAndOutcomeOpt.isDefined)
|
||||
val outcome = oracleAndOutcomeOpt.get._2
|
||||
val sVals = sigs.take(outcome.digits.length).map(_.sig)
|
||||
sVals.reduce(_.add(_))
|
||||
case _: EnumOracleSignature =>
|
||||
fail("Expected NumericOracleSignatures")
|
||||
}
|
||||
sVals.reduce(_.add(_))
|
||||
}
|
||||
|
||||
val aggSig = SchnorrDigitalSignature(aggR, aggS)
|
||||
|
||||
// Must use stored adaptor sigs because adaptor signing nonce is not deterministic (auxRand)
|
||||
val offerRefundSig = dlcOffer.dlcTxSigner.signRefundTx
|
||||
val acceptRefundSig = dlcAccept.dlcTxSigner.signRefundTx
|
||||
val acceptAdaptorSigs = offerSetup.cets.map { case (outcome, info) =>
|
||||
(outcome, info.remoteSignature)
|
||||
}
|
||||
val acceptCETSigs = CETSignatures(acceptAdaptorSigs, acceptRefundSig)
|
||||
val offerAdaptorSigs = acceptSetup.cets.map { case (outcome, info) =>
|
||||
(outcome, info.remoteSignature)
|
||||
}
|
||||
val offerCETSigs = CETSignatures(offerAdaptorSigs, offerRefundSig)
|
||||
|
||||
for {
|
||||
offerFundingSigs <- Future.fromTry(dlcOffer.dlcTxSigner.signFundingTx())
|
||||
offerOutcome <-
|
||||
dlcOffer.executeDLC(offerSetup, Future.successful(oracleSigs))
|
||||
acceptOutcome <-
|
||||
dlcAccept.executeDLC(acceptSetup, Future.successful(oracleSigs))
|
||||
} yield {
|
||||
val builder = DLCTxBuilder(dlcOffer.offer, dlcAccept.accept)
|
||||
val contractId = builder.buildFundingTx.txIdBE.bytes
|
||||
.xor(dlcAccept.accept.tempContractId.bytes)
|
||||
|
||||
val offer = dlcOffer.offer
|
||||
val accept = dlcOffer.accept.withSigs(acceptCETSigs)
|
||||
val sign = DLCSign(offerCETSigs, offerFundingSigs, contractId)
|
||||
|
||||
val (offerOracleSig, offerDLCOutcome) =
|
||||
DLCStatus
|
||||
.calculateOutcomeAndSig(isInitiator = true,
|
||||
offer,
|
||||
accept,
|
||||
sign,
|
||||
acceptOutcome.cet)
|
||||
.get
|
||||
|
||||
val (acceptOracleSig, acceptDLCOutcome) =
|
||||
DLCStatus
|
||||
.calculateOutcomeAndSig(isInitiator = false,
|
||||
offer,
|
||||
accept,
|
||||
sign,
|
||||
offerOutcome.cet)
|
||||
.get
|
||||
|
||||
assert(offerDLCOutcome == outcome)
|
||||
assert(acceptDLCOutcome == outcome)
|
||||
assert(offerOracleSig == aggSig)
|
||||
assert(acceptOracleSig == aggSig)
|
||||
}
|
||||
}
|
||||
|
||||
/** Synchronously runs the test function on each paramsToTest in turn. */
|
||||
def runTestsForParam[T](paramsToTest: Vector[T])(
|
||||
test: T => Future[Assertion])(implicit
|
||||
|
|
|
@ -155,11 +155,21 @@ object TestDLCClient {
|
|||
)
|
||||
|
||||
val remoteOutcomes: ContractInfo = {
|
||||
val descriptor =
|
||||
outcomes.contractDescriptor.flip((input + remoteInput).satoshis)
|
||||
val pair =
|
||||
ContractOraclePair.fromDescriptorOracle(descriptor, outcomes.oracleInfo)
|
||||
outcomes.copy(contractOraclePair = pair)
|
||||
val descriptors =
|
||||
outcomes.contractDescriptors.map(_.flip((input + remoteInput).satoshis))
|
||||
|
||||
val contracts = descriptors.zip(outcomes.oracleInfos).map {
|
||||
case (descriptor, oracleInfo) =>
|
||||
val pair =
|
||||
ContractOraclePair.fromDescriptorOracle(descriptor, oracleInfo)
|
||||
SingleContractInfo(outcomes.totalCollateral, pair)
|
||||
}
|
||||
|
||||
outcomes match {
|
||||
case _: SingleContractInfo => contracts.head
|
||||
case _: DisjointUnionContractInfo =>
|
||||
DisjointUnionContractInfo(contracts)
|
||||
}
|
||||
}
|
||||
|
||||
val changeAddress = BitcoinAddress.fromScriptPubKey(changeSPK, network)
|
||||
|
@ -222,6 +232,13 @@ object TestDLCClient {
|
|||
timeouts = timeouts
|
||||
)
|
||||
|
||||
val negotiationFields = offerOutcomes match {
|
||||
case _: SingleContractInfo => DLCAccept.NoNegotiationFields
|
||||
case DisjointUnionContractInfo(contracts) =>
|
||||
DLCAccept.NegotiationFieldsV2(
|
||||
contracts.map(_ => DLCAccept.NoNegotiationFields))
|
||||
}
|
||||
|
||||
val accept = DLCMessage.DLCAcceptWithoutSigs(
|
||||
totalCollateral = acceptInput.satoshis,
|
||||
pubKeys = acceptPubKeys,
|
||||
|
@ -229,7 +246,7 @@ object TestDLCClient {
|
|||
changeAddress = acceptChangeAddress,
|
||||
payoutSerialId = acceptPayoutSerialId,
|
||||
changeSerialId = acceptChangeSerialId,
|
||||
negotiationFields = DLCAccept.NoNegotiationFields,
|
||||
negotiationFields = negotiationFields,
|
||||
tempContractId = offer.tempContractId
|
||||
)
|
||||
|
||||
|
|
|
@ -428,8 +428,9 @@ trait TLVGen {
|
|||
offer <- dlcOfferTLV
|
||||
(oracleInfo, oraclePrivKey, oracleRValue) <- oracleInfoV0TLVWithKeys
|
||||
} yield {
|
||||
(offer.copy(contractInfo =
|
||||
offer.contractInfo.copy(oracleInfo = oracleInfo)),
|
||||
(offer.copy(contractInfo = offer.contractInfo
|
||||
.asInstanceOf[ContractInfoV0TLV]
|
||||
.copy(oracleInfo = oracleInfo)),
|
||||
oraclePrivKey,
|
||||
oracleRValue)
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@ import org.bitcoins.commons.config.AppConfig
|
|||
import org.bitcoins.core.api.chain.ChainQueryApi
|
||||
import org.bitcoins.core.api.node.NodeApi
|
||||
import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.protocol.dlc.models.{ContractInfo, ContractOraclePair}
|
||||
import org.bitcoins.core.protocol.dlc.models.ContractOraclePair
|
||||
import org.bitcoins.dlc.wallet.{DLCAppConfig, DLCWallet}
|
||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||
import org.bitcoins.core.protocol.dlc.models.SingleContractInfo
|
||||
import org.bitcoins.server.BitcoinSAppConfig
|
||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||
import org.bitcoins.testkit.wallet.DLCWalletUtil.InitializedDLCWallet
|
||||
|
@ -159,7 +160,7 @@ trait BitcoinSDualWalletTest extends BitcoinSWalletTest {
|
|||
bip39PasswordOpt = getBIP39PasswordOpt(),
|
||||
extraConfig = Some(segwitWalletConf))(config2, system)
|
||||
amt = expectedDefaultAmt / Satoshis(2)
|
||||
contractInfo = ContractInfo(amt.satoshis, contractOraclePair)
|
||||
contractInfo = SingleContractInfo(amt.satoshis, contractOraclePair)
|
||||
(dlcWalletA, dlcWalletB) <-
|
||||
DLCWalletUtil.initDLC(walletA, walletB, contractInfo)
|
||||
} yield (dlcWalletA, dlcWalletB)
|
||||
|
@ -182,7 +183,7 @@ trait BitcoinSDualWalletTest extends BitcoinSWalletTest {
|
|||
bip39PasswordOpt = getBIP39PasswordOpt(),
|
||||
extraConfig = Some(segwitWalletConf))(config2, system)
|
||||
amt = expectedDefaultAmt / Satoshis(2)
|
||||
contractInfo = ContractInfo(amt.satoshis, contractOraclePair)
|
||||
contractInfo = SingleContractInfo(amt.satoshis, contractOraclePair)
|
||||
(dlcWalletA, dlcWalletB) <-
|
||||
DLCWalletUtil.initDLC(walletA, walletB, contractInfo)
|
||||
} yield (dlcWalletA, dlcWalletB)
|
||||
|
|
|
@ -71,7 +71,7 @@ object DLCWalletUtil extends Logging {
|
|||
ContractOraclePair.EnumPair(sampleContractDescriptor, sampleOracleInfo)
|
||||
|
||||
lazy val sampleContractInfo: ContractInfo =
|
||||
ContractInfo(half, sampleContractOraclePair)
|
||||
SingleContractInfo(half, sampleContractOraclePair)
|
||||
|
||||
lazy val sampleOracleWinSig: SchnorrDigitalSignature =
|
||||
oraclePrivKey.schnorrSignWithNonce(winHash.bytes, kValue)
|
||||
|
@ -95,7 +95,7 @@ object DLCWalletUtil extends Logging {
|
|||
}
|
||||
|
||||
lazy val multiNonceContractInfo: ContractInfo =
|
||||
ContractInfo(total, multiNonceContractOraclePair)
|
||||
SingleContractInfo(total, multiNonceContractOraclePair)
|
||||
|
||||
lazy val dummyContractMaturity: BlockTimeStamp = BlockTimeStamp(0)
|
||||
lazy val dummyContractTimeout: BlockTimeStamp = BlockTimeStamp(1)
|
||||
|
@ -356,7 +356,7 @@ object DLCWalletUtil extends Logging {
|
|||
}
|
||||
}
|
||||
|
||||
def getSigs(contractInfo: ContractInfo): (
|
||||
def getSigs(contractInfo: SingleContractInfo): (
|
||||
OracleAttestmentTLV,
|
||||
OracleAttestmentTLV) = {
|
||||
val desc: EnumContractDescriptor = contractInfo.contractDescriptor match {
|
||||
|
|
|
@ -2,7 +2,10 @@ package org.bitcoins.testkit.wallet
|
|||
|
||||
import org.bitcoins.commons.config.AppConfig
|
||||
import org.bitcoins.core.currency.Satoshis
|
||||
import org.bitcoins.core.protocol.dlc.models.{ContractInfo, ContractOraclePair}
|
||||
import org.bitcoins.core.protocol.dlc.models.{
|
||||
ContractOraclePair,
|
||||
SingleContractInfo
|
||||
}
|
||||
import org.bitcoins.dlc.wallet.DLCAppConfig
|
||||
import org.bitcoins.server.BitcoinSAppConfig
|
||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||
|
@ -87,7 +90,7 @@ trait DualWalletTestCachedBitcoind
|
|||
walletA <- walletAF
|
||||
walletB <- walletBF
|
||||
amt = expectedDefaultAmt / Satoshis(2)
|
||||
contractInfo = ContractInfo(amt.satoshis, contractOraclePair)
|
||||
contractInfo = SingleContractInfo(amt.satoshis, contractOraclePair)
|
||||
(dlcWalletA, dlcWalletB) <-
|
||||
DLCWalletUtil.initDLC(walletA, walletB, contractInfo)
|
||||
bitcoind <- bitcoindF
|
||||
|
|
Loading…
Add table
Reference in a new issue