2021 10 15 createcontractinfo numeric (#3758)

* Implement payout function reader and tlv points reader/writer

* Add support for numeric contract infos in the createcontractinfo rpc

* Adjust logging level

* Remove unused endpoint

* Add new format to site
This commit is contained in:
Chris Stewart 2021-10-15 19:56:00 -05:00 committed by GitHub
parent 6c5f8fd84a
commit 7a3497ab9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 190 additions and 21 deletions

View file

@ -0,0 +1,47 @@
package org.bitcoins.commons.jsonmodels.cli
import org.bitcoins.commons.serializers.Picklers
import org.bitcoins.core.protocol.tlv.{
ContractDescriptorTLV,
ContractDescriptorV0TLV,
ContractDescriptorV1TLV,
DigitDecompositionEventDescriptorV0TLV,
OracleAnnouncementTLV,
PayoutFunctionV0TLV,
RoundingIntervalsV0TLV,
TLVPoint
}
import ujson.{Arr, Bool, Null, Num, Obj, Str}
object ContractDescriptorParser {
def parseCmdLine(
value: ujson.Value,
announcementTLV: OracleAnnouncementTLV): ContractDescriptorTLV = {
value match {
case obj: Obj =>
upickle.default
.read[ContractDescriptorV0TLV](obj)(Picklers.contractDescriptorV0)
case arr: Arr =>
//we read the number of digits from the announcement,
//take in tlv points for the payout curve
//and don't provide access to give a rounding mode as a parameter
val payoutPoints = arr.value.toVector.map { pointJs =>
upickle.default
.read[TLVPoint](pointJs)(Picklers.tlvPointReader)
}
val payoutCurve = PayoutFunctionV0TLV(payoutPoints)
val numDigits = announcementTLV.eventTLV.eventDescriptor
.asInstanceOf[DigitDecompositionEventDescriptorV0TLV]
.numDigits
.toInt
ContractDescriptorV1TLV(numDigits,
payoutCurve,
RoundingIntervalsV0TLV.noRounding)
case fail @ (_: Num | _: Bool | Null | _: Str) =>
sys.error(
s"Cannot parse contract descriptor from $fail, expected json object or array")
}
}
}

View file

@ -11,4 +11,10 @@ object PicklerKeys {
final val outcomeKey: String = "outcome"
final val localPayoutKey: String = "localPayout"
final val outcomesKey: String = "outcomes"
//tlv points
final val pointsKey = "points"
final val payoutKey: String = "payout"
final val extraPrecisionKey: String = "extraPrecision"
final val isEndpointKey: String = "isEndpoint"
}

View file

@ -234,22 +234,55 @@ object Picklers {
)
}
implicit val tlvPointReader: Reader[TLVPoint] = {
reader[Obj].map { obj: Obj =>
val map = obj.value
val outcome = map(PicklerKeys.outcomeKey).num.toLong
val payout = jsToSatoshis(map(PicklerKeys.payoutKey))
val extraPrecision = map(PicklerKeys.extraPrecisionKey).num.toInt
val isEndPoint = map(PicklerKeys.isEndpointKey).bool
TLVPoint(outcome, payout, extraPrecision, isEndPoint)
}
}
implicit val tlvPointWriter: Writer[TLVPoint] = {
writer[Obj].comap { point =>
Obj(
PicklerKeys.outcomeKey -> Num(point.outcome.toDouble),
PicklerKeys.payoutKey -> Num(point.value.toLong.toDouble),
PicklerKeys.extraPrecisionKey -> Num(point.extraPrecision.toDouble),
PicklerKeys.isEndpointKey -> Bool(point.isEndpoint)
)
}
}
implicit val payoutFunctionV0TLVWriter: Writer[PayoutFunctionV0TLV] =
writer[Obj].comap { payoutFunc =>
import payoutFunc._
val pointsJs = points.map { point =>
Obj(
"outcome" -> Num(point.outcome.toDouble),
"payout" -> Num(point.value.toLong.toDouble),
"extraPrecision" -> Num(point.extraPrecision.toDouble),
"isEndpoint" -> Bool(point.isEndpoint)
)
writeJs(point)
}
Obj("points" -> pointsJs)
Obj(PicklerKeys.pointsKey -> pointsJs)
}
implicit val payoutFunctionV0TLVReader: Reader[PayoutFunctionV0TLV] = {
reader[Obj].map { obj: Obj =>
val pointsArr = obj(PicklerKeys.pointsKey).arr
val points: Vector[TLVPoint] = pointsArr.map {
case x @ (_: Arr | _: Num | Null | _: Bool | _: Str) =>
sys.error(
s"Cannot have $x when parsing payout curve points, expected json object")
case obj: Obj =>
upickle.default.read[TLVPoint](obj)
}.toVector
PayoutFunctionV0TLV(points)
}
}
implicit val roundingIntervalsV0TLVWriter: Writer[RoundingIntervalsV0TLV] =
writer[Obj].comap { roundingIntervals =>
import roundingIntervals._

View file

@ -123,13 +123,21 @@ object CliReaders {
new Read[ContractDescriptorTLV] {
override def arity: Int = 1
override def reads: String => ContractDescriptorTLV = { str =>
val ujsonVal = ujson.read(str)
upickle.default.read[ContractDescriptorV0TLV](ujsonVal)(
upickle.default.read[ContractDescriptorV0TLV](str)(
Picklers.contractDescriptorV0)
}
}
}
implicit val jsonReader: Read[ujson.Value] = {
new Read[ujson.Value] {
override def arity: Int = 1
override def reads: String => ujson.Value = { str =>
ujson.read(str)
}
}
}
implicit val instantReads: Read[Instant] =
new Read[Instant] {
override def arity: Int = 1

View file

@ -1158,13 +1158,13 @@ object ConsoleCli {
create.copy(totalCollateral = totalCollateral)
case other => other
})),
arg[ContractDescriptorTLV]("contractDescriptor")
arg[ujson.Value]("contractDescriptor")
.text("The contract descriptor in the DLC. This is expected to be of format [[outcome1, payout1], [outcome2, payout2], ...]")
.required()
.action((contractDescriptor, conf) =>
conf.copy(command = conf.command match {
case create: CreateContractInfo =>
create.copy(contractDescriptorTLV = contractDescriptor)
create.copy(contractDescriptor = contractDescriptor)
case other => other
}))
),
@ -2223,7 +2223,7 @@ object CliCommand {
case class CreateContractInfo(
announcementTLV: OracleAnnouncementTLV,
totalCollateral: Satoshis,
contractDescriptorTLV: ContractDescriptorTLV)
contractDescriptor: ujson.Value)
extends AppServerCliCommand
object CreateContractInfo {
@ -2231,7 +2231,7 @@ object CliCommand {
lazy val empty: CreateContractInfo = {
CreateContractInfo(announcementTLV = OracleAnnouncementV0TLV.dummy,
totalCollateral = Satoshis.zero,
contractDescriptorTLV = ContractDescriptorTLV.empty)
contractDescriptor = ujson.Null)
}
}

View file

@ -4,7 +4,7 @@ import akka.http.scaladsl.model.ContentTypes
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.bitcoins.commons.serializers.PicklerKeys
import org.bitcoins.core.api.dlc.node.DLCNodeApi
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
import org.bitcoins.core.protocol.dlc.models.ContractInfo
import org.bitcoins.core.protocol.tlv.OracleAnnouncementTLV
import org.bitcoins.server.routes.ServerCommand
@ -24,12 +24,21 @@ class DLCRoutesSpec
val announcement = OracleAnnouncementTLV.fromHex(
"fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e")
//https://test.oracle.suredbits.com/announcement/362ae482860fc93bac5cbcca3f1f0e49b3c94eac92224a008bd81ef81292f43a
val numericAnnouncement = OracleAnnouncementTLV.fromHex(
"fdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65"
)
//https://test.oracle.suredbits.com/contract/enum/5fbc1d037bacd9ece32ff4b591143bce7fa1c22e0aec2fa8437cc336feb95138
val expectedContractInfo = ContractInfo.fromHex(
"fdd82efd01120000000005f5e100fda7103b030e52657075626c6963616e5f77696e00000000000000000c44656d6f637261745f77696e0000000005f5e100056f746865720000000003938700fda712c7fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e")
//https://test.oracle.suredbits.com/contract/numeric/d4d4df2892fb2cfd2e8f030f0e69a568e19668b5d355e7713f69853db09a4c33
val expectedNumericContractInfo = ContractInfo.fromHex(
"fdd82efd032500000000000186a0fda720540011fda72648000501000000000000000000000001fd9c400000000000000000000001fda604000000000000c350000001fdafc800000000000186a0000001fe0001ffff00000000000186a00000fda724020000fda712fd02bffdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65"
)
"DLC Routes" should {
"createcontractinfo" in {
"createcontractinfo with enum contract descriptor" in {
val totalCollateral = Bitcoins.one
val map = Map(
"Republican_win" -> ujson.Num(0),
@ -54,5 +63,68 @@ class DLCRoutesSpec
String] == s"""{"result":"${expectedContractInfo.hex}","error":null}""")
}
}
"createcontractinfo with a numeric contract descriptor" in {
val totalCollateral = Satoshis(100000)
val point0 = Vector(
(PicklerKeys.outcomeKey, ujson.Num(0)),
(PicklerKeys.payoutKey, ujson.Num(0)),
(PicklerKeys.extraPrecisionKey, ujson.Num(0)),
(PicklerKeys.isEndpointKey, ujson.Bool(true))
)
val point1 = Vector(
(PicklerKeys.outcomeKey, ujson.Num(40000)),
(PicklerKeys.payoutKey, ujson.Num(0)),
(PicklerKeys.extraPrecisionKey, ujson.Num(0)),
(PicklerKeys.isEndpointKey, ujson.Bool(true))
)
val point2 = Vector(
(PicklerKeys.outcomeKey, ujson.Num(42500)),
(PicklerKeys.payoutKey, ujson.Num(50000)),
(PicklerKeys.extraPrecisionKey, ujson.Num(0)),
(PicklerKeys.isEndpointKey, ujson.Bool(true))
)
val point3 = Vector(
(PicklerKeys.outcomeKey, ujson.Num(45000)),
(PicklerKeys.payoutKey, ujson.Num(100000)),
(PicklerKeys.extraPrecisionKey, ujson.Num(0)),
(PicklerKeys.isEndpointKey, ujson.Bool(true))
)
val point4 = Vector(
(PicklerKeys.outcomeKey, ujson.Num(131071)),
(PicklerKeys.payoutKey, ujson.Num(100000)),
(PicklerKeys.extraPrecisionKey, ujson.Num(0)),
(PicklerKeys.isEndpointKey, ujson.Bool(true))
)
val vec = Vector(
ujson.Obj.from(point0),
ujson.Obj.from(point1),
ujson.Obj.from(point2),
ujson.Obj.from(point3),
ujson.Obj.from(point4)
)
val outcomes = ujson.Arr.from(vec)
val args = ujson.Arr(
numericAnnouncement.hex,
ujson.Num(totalCollateral.satoshis.toLong.toDouble),
outcomes
)
val route =
dlcRoutes.handleCommand(ServerCommand("createcontractinfo", args))
Post() ~> route ~> check {
assert(contentType == ContentTypes.`application/json`)
assert(responseAs[
String] == s"""{"result":"${expectedNumericContractInfo.hex}","error":null}""")
}
}
}
}

View file

@ -19,7 +19,7 @@ case class DLCRoutes(dlcNode: DLCNodeApi)(implicit system: ActorSystem)
extends ServerRoute {
import system.dispatcher
def handleCommand: PartialFunction[ServerCommand, Route] = {
override def handleCommand: PartialFunction[ServerCommand, Route] = {
case ServerCommand("getdlchostaddress", _) =>
complete {
dlcNode.getHostAddress.map { addr =>

View file

@ -1,7 +1,8 @@
package org.bitcoins.server
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LockUnspentOutputParameter
import org.bitcoins.commons.serializers.{JsonReaders, Picklers}
import org.bitcoins.commons.jsonmodels.cli.ContractDescriptorParser
import org.bitcoins.commons.serializers.{JsonReaders}
import org.bitcoins.core.api.wallet.CoinSelectionAlgo
import org.bitcoins.core.crypto._
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
@ -1204,9 +1205,9 @@ object CreateContractInfo extends ServerJsonModels {
OracleAnnouncementTLV.fromHex(announcementVal.str)
val totalCollateral = Satoshis(totalCollateralVal.num.toLong)
//validate that these are part of the announcement?
val contractDescriptor = upickle.default
.read[ContractDescriptorV0TLV](payoutsVal)(
Picklers.contractDescriptorV0)
val contractDescriptor =
ContractDescriptorParser.parseCmdLine(payoutsVal, announcementTLV)
CreateContractInfo(announcementTLV,
totalCollateral,
contractDescriptor)

View file

@ -234,7 +234,9 @@ the `-p 9999:9999` port mapping on the docker container to adjust for this.
- `createcontractinfo` `announcement` `totalCollateral` `payouts`
- the announcement to build the contract info for
- the total amount of collateral in the DLC
- The payouts in `{ "outcomes" : { "outcome1" : 1, "outcome2": 2, ... }}`. The number is the amount of sats paid to YOU for that outcome.
- The payouts can be in two formats
1. `{ "outcomes" : { "outcome0" : 0, "outcome1": 1, ... }}`. The number is the amount of sats paid to YOU for that outcome.
2. `[{"outcome":0,"payout":0,"extraPrecision":0,"isEndpoint":true}, {"outcome":1,"payout":1,"extraPrecision":0,"isEndpoint":true}, ...]`
- `decodecontractinfo` `contractinfo` - Decodes a contract info into json
- `contractinfo` - Hex encoded contract info
- `decodeoffer` `offer` - Decodes an offer message into json