mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +01:00
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:
parent
6c5f8fd84a
commit
7a3497ab9c
9 changed files with 190 additions and 21 deletions
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue