Update scalafmt-core to 3.8.1 (#5501)

* Update scalafmt-core to 3.8.1

* Update .scalafmt.conf settings to be factory default settings

* Fix typo

* scalafmt

* Empty commit to re-run CI

* Revert some scalafmt back to original scalafmt.conf

---------

Co-authored-by: Chris Stewart <stewart.chris1234@gmail.com>
This commit is contained in:
Scala Steward 2024-04-21 02:55:49 +02:00 committed by GitHub
parent 9442dba217
commit afddf73c48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1090 changed files with 46186 additions and 30144 deletions

View File

@ -1,14 +1,6 @@
version = "2.7.5" version = "3.8.1"
# See Documentation at https://scalameta.org/scalafmt/#Configuration # See Documentation at https://scalameta.org/scalafmt/#Configuration
trailingCommas = never runner.dialect=scala213
maxColumn = 80
lineEndings = preserve
docstrings = ScalaDoc
continuationIndent {
callSite = 2
defnSite = 4
}
align = some align = some
align { align {
openParenDefnSite = false openParenDefnSite = false
@ -16,32 +8,6 @@ align {
} }
danglingParentheses { danglingParentheses {
callSite = false callSite = false
defnSite = false defnSite = false
} }
newlines {
alwaysBeforeTopLevelStatements = true
sometimesBeforeColonInMethodReturnType = false
alwaysBeforeCurlyBraceLambdaParams = false
}
assumeStandardLibraryStripMargin = true
rewrite.rules = [
SortModifiers,
RedundantParens,
SortImports
]
binPack.literalArgumentLists = true
project {
excludeFilters = [
.bloop,
.metals,
target
]
}
# Consider Rewrite Rules

View File

@ -49,163 +49,186 @@ class DLCStatusTest extends BitcoinSJvmTest {
assert(status.state == DLCState.Offered) assert(status.state == DLCState.Offered)
assert(read[DLCStatus](write(status)) == status) assert(read[DLCStatus](write(status)) == status)
assert(read[DLCStatus]( assert(
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
it must "have json symmetry in DLCStatus.Accepted" in { it must "have json symmetry in DLCStatus.Accepted" in {
forAllParallel(NumberGenerator.bool, forAllParallel(
TLVGen.dlcOfferTLV, NumberGenerator.bool,
NumberGenerator.bytevector) { TLVGen.dlcOfferTLV,
case (isInit, offerTLV, contractId) => NumberGenerator.bytevector
val offer = DLCOffer.fromTLV(offerTLV) ) { case (isInit, offerTLV, contractId) =>
val offer = DLCOffer.fromTLV(offerTLV)
val totalCollateral = offer.contractInfo.totalCollateral val totalCollateral = offer.contractInfo.totalCollateral
// random testnet address // random testnet address
val payoutAddress = val payoutAddress =
Some( Some(
PayoutAddress(BitcoinAddress.fromString( PayoutAddress(
"tb1q4ps6c9ewa7uca5v39fakykq9q6hpgjkxje8gve"), BitcoinAddress.fromString(
true)) "tb1q4ps6c9ewa7uca5v39fakykq9q6hpgjkxje8gve"
),
val contact = Option.empty[String] true
val status =
DLCStatus.Accepted(
Sha256Digest.empty,
isInit,
TimeUtil.now,
offer.tempContractId,
contractId,
offer.contractInfo,
offer.timeouts,
offer.feeRate,
totalCollateral,
offer.collateral,
payoutAddress,
contact
) )
)
assert(status.state == DLCState.Accepted) val contact = Option.empty[String]
assert(read[DLCStatus](write(status)) == status)
assert(read[DLCStatus]( val status =
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) DLCStatus.Accepted(
Sha256Digest.empty,
isInit,
TimeUtil.now,
offer.tempContractId,
contractId,
offer.contractInfo,
offer.timeouts,
offer.feeRate,
totalCollateral,
offer.collateral,
payoutAddress,
contact
)
assert(status.state == DLCState.Accepted)
assert(read[DLCStatus](write(status)) == status)
assert(
read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
it must "have json symmetry in DLCStatus.Signed" in { it must "have json symmetry in DLCStatus.Signed" in {
forAllParallel(NumberGenerator.bool, forAllParallel(
TLVGen.dlcOfferTLV, NumberGenerator.bool,
NumberGenerator.bytevector, TLVGen.dlcOfferTLV,
CryptoGenerators.doubleSha256DigestBE) { NumberGenerator.bytevector,
case (isInit, offerTLV, contractId, txId) => CryptoGenerators.doubleSha256DigestBE
val offer = DLCOffer.fromTLV(offerTLV) ) { case (isInit, offerTLV, contractId, txId) =>
val offer = DLCOffer.fromTLV(offerTLV)
val totalCollateral = offer.contractInfo.totalCollateral val totalCollateral = offer.contractInfo.totalCollateral
val payoutAddress = Option.empty[PayoutAddress] val payoutAddress = Option.empty[PayoutAddress]
val contact = Option.empty[String] val contact = Option.empty[String]
val status = val status =
DLCStatus.Signed( DLCStatus.Signed(
Sha256Digest.empty, Sha256Digest.empty,
isInit, isInit,
TimeUtil.now, TimeUtil.now,
offer.tempContractId, offer.tempContractId,
contractId, contractId,
offer.contractInfo, offer.contractInfo,
offer.timeouts, offer.timeouts,
offer.feeRate, offer.feeRate,
totalCollateral, totalCollateral,
offer.collateral, offer.collateral,
txId, txId,
payoutAddress, payoutAddress,
contact contact
) )
assert(status.state == DLCState.Signed) assert(status.state == DLCState.Signed)
assert(read[DLCStatus](write(status)) == status) assert(read[DLCStatus](write(status)) == status)
assert(read[DLCStatus]( assert(
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
it must "have json symmetry in DLCStatus.Broadcasted" in { it must "have json symmetry in DLCStatus.Broadcasted" in {
forAllParallel(NumberGenerator.bool, forAllParallel(
TLVGen.dlcOfferTLV, NumberGenerator.bool,
NumberGenerator.bytevector, TLVGen.dlcOfferTLV,
CryptoGenerators.doubleSha256DigestBE) { NumberGenerator.bytevector,
case (isInit, offerTLV, contractId, fundingTxId) => CryptoGenerators.doubleSha256DigestBE
val offer = DLCOffer.fromTLV(offerTLV) ) { case (isInit, offerTLV, contractId, fundingTxId) =>
val offer = DLCOffer.fromTLV(offerTLV)
val totalCollateral = offer.contractInfo.totalCollateral val totalCollateral = offer.contractInfo.totalCollateral
val payoutAddress = Option.empty[PayoutAddress] val payoutAddress = Option.empty[PayoutAddress]
val contact = Option.empty[String] val contact = Option.empty[String]
val status = val status =
DLCStatus.Broadcasted( DLCStatus.Broadcasted(
Sha256Digest.empty, Sha256Digest.empty,
isInit, isInit,
TimeUtil.now, TimeUtil.now,
offer.tempContractId, offer.tempContractId,
contractId, contractId,
offer.contractInfo, offer.contractInfo,
offer.timeouts, offer.timeouts,
offer.feeRate, offer.feeRate,
totalCollateral, totalCollateral,
offer.collateral, offer.collateral,
fundingTxId, fundingTxId,
payoutAddress, payoutAddress,
contact contact
) )
assert(status.state == DLCState.Broadcasted) assert(status.state == DLCState.Broadcasted)
assert(read[DLCStatus](write(status)) == status) assert(read[DLCStatus](write(status)) == status)
assert(read[DLCStatus]( assert(
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
it must "have json symmetry in DLCStatus.Confirmed" in { it must "have json symmetry in DLCStatus.Confirmed" in {
forAllParallel(NumberGenerator.bool, forAllParallel(
TLVGen.dlcOfferTLV, NumberGenerator.bool,
NumberGenerator.bytevector, TLVGen.dlcOfferTLV,
CryptoGenerators.doubleSha256DigestBE) { NumberGenerator.bytevector,
case (isInit, offerTLV, contractId, fundingTxId) => CryptoGenerators.doubleSha256DigestBE
val offer = DLCOffer.fromTLV(offerTLV) ) { case (isInit, offerTLV, contractId, fundingTxId) =>
val offer = DLCOffer.fromTLV(offerTLV)
val totalCollateral = offer.contractInfo.totalCollateral val totalCollateral = offer.contractInfo.totalCollateral
val payoutAddress = Option.empty[PayoutAddress] val payoutAddress = Option.empty[PayoutAddress]
val contact = Option.empty[String] val contact = Option.empty[String]
val status = val status =
DLCStatus.Confirmed( DLCStatus.Confirmed(
Sha256Digest.empty, Sha256Digest.empty,
isInit, isInit,
TimeUtil.now, TimeUtil.now,
offer.tempContractId, offer.tempContractId,
contractId, contractId,
offer.contractInfo, offer.contractInfo,
offer.timeouts, offer.timeouts,
offer.feeRate, offer.feeRate,
totalCollateral, totalCollateral,
offer.collateral, offer.collateral,
fundingTxId, fundingTxId,
payoutAddress, payoutAddress,
contact contact
) )
assert(status.state == DLCState.Confirmed) assert(status.state == DLCState.Confirmed)
assert(read[DLCStatus](write(status)) == status) assert(read[DLCStatus](write(status)) == status)
assert(read[DLCStatus]( assert(
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
@ -260,7 +283,9 @@ class DLCStatusTest extends BitcoinSJvmTest {
assert(read[DLCStatus](write(status)) == status) assert(read[DLCStatus](write(status)) == status)
assert( assert(
read[DLCStatus]( read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
@ -317,7 +342,9 @@ class DLCStatusTest extends BitcoinSJvmTest {
assert(read[DLCStatus](write(status)) == status) assert(read[DLCStatus](write(status)) == status)
assert( assert(
read[DLCStatus]( read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
@ -368,7 +395,9 @@ class DLCStatusTest extends BitcoinSJvmTest {
assert(read[DLCStatus](write(status)) == status) assert(read[DLCStatus](write(status)) == status)
assert( assert(
read[DLCStatus]( read[DLCStatus](
write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)) == status) write(status.asInstanceOf[DLCStatus])(Picklers.dlcStatusW)
) == status
)
} }
} }
} }

View File

@ -13,7 +13,8 @@ class SerializedPSBTTest extends BitcoinSUnitTest {
it must "correctly decode a psbt" in { it must "correctly decode a psbt" in {
val psbt = PSBT.fromBase64( val psbt = PSBT.fromBase64(
"cHNidP9Fq2AoiZroZUZZ7/Fl0n4dcF8zKWfpD3QzRcAm1QTxUQzzGnHjM5xU+xYUvYSPokH86tLWHVVmhrOQE2d//fPeu6Px6r0LW2DbYkBubiYldhPvO50/3M0wHtHncJ6w/UmdpFVMt/z1iQfbH9U4bq6iLS930BnOiRlc0KX8DQmDnKFdTdiyceBPOmWKSJT+cR1RIQabSiKY6plO4jkSbZ2yFGMBAP3dAgIAAAABG2Z29d/AhiGmmjjrgO9p0LwwYozRbr404YcnQQ+LTQMEAAAAAAAAAAADGQVGPusCAAD9vwFjVyEDkIsYrb9OJzC2oJUtRDntXxhNo7cZeByTHPkuJeCnAmshA4B9R4qr45hWk4fD3F+0um+wJVmP8kFRyXWflgEYKi61IQMLERF8Yx7x85yRmZyD7888+GENPbV2xACyxsAbEJoZpCEDLfkiP2NbzOWOTrM5qIiGFWLDrAYEoYj+6B/p474XWbQhAuNDwvhwWxJHuG/S7CS/bsk/wr8pR9LzCm8eEEqoQ58NIQPlPMn3Y69Fovcn4cexpU9r1whI0yWZ1OwvYHlsngyAyCECyGo6/HPABsWFUwjKuE+mG57duFkwbk17rug85rd0MCohA8ObOSpT1WPHeKKjc3MwDtFM8xr+LRlrDETYazNJhawyIQNqTHMWXX5dNDAuY71BaXfFF8yoHEaQDnDyNudqgfuMQSEC410iVARHWVmvKpcQUFJJx/H41G2/9sJ5jvRnyYIXwQIhA4APXyr35MW/4PPuMRtYg+caUGFqk+12x9wW+JFxHqnwIQMiS1b6HfL93efBgysG0PrlMPwA7eKMSFexdnOGHhOQP1yuZwXdzsmnALF1dqkUCzczWAtdSewwYE3SNlcLhnxJInGIrGjelDmdMwkAABYAFO26WaDgWxcVeSe4lfoysDTitaKJp/r3t2YNAAC4Y1IhA6WEMWgMOWjUk1FZvxYDTRvyVZKDWeCKsBvgJruAngCOIQKeg5DPhM1ghA26JAP1gvhyZ7K/Z2ttavmOd7AyFv2OyyEDjgniAeDz5qiX/5tyDxMJcyMN4yRoYXZwfFgT9x6iKqghAxmojRQWODhnuwBaEtxJHK9AlJXUkPjIOs4o08raE5zfVK5nBF6KYiOxdSEDvbxvoF42LUei1lcblCHsUzeXo7Wr1zKxw5agIRZVWqysaAAAAAAAAQMEggAAAAAAA6jMPk3fqYXY2pAV+YsGMMB2CAwXDBpwMObcmgEz8M0NO07ZYF60Sf8jj0zms7HOPzU3tBtRMROIXzyZreEJOfmqBIZKk6tg78xgjIW8mR+WuQAA") "cHNidP9Fq2AoiZroZUZZ7/Fl0n4dcF8zKWfpD3QzRcAm1QTxUQzzGnHjM5xU+xYUvYSPokH86tLWHVVmhrOQE2d//fPeu6Px6r0LW2DbYkBubiYldhPvO50/3M0wHtHncJ6w/UmdpFVMt/z1iQfbH9U4bq6iLS930BnOiRlc0KX8DQmDnKFdTdiyceBPOmWKSJT+cR1RIQabSiKY6plO4jkSbZ2yFGMBAP3dAgIAAAABG2Z29d/AhiGmmjjrgO9p0LwwYozRbr404YcnQQ+LTQMEAAAAAAAAAAADGQVGPusCAAD9vwFjVyEDkIsYrb9OJzC2oJUtRDntXxhNo7cZeByTHPkuJeCnAmshA4B9R4qr45hWk4fD3F+0um+wJVmP8kFRyXWflgEYKi61IQMLERF8Yx7x85yRmZyD7888+GENPbV2xACyxsAbEJoZpCEDLfkiP2NbzOWOTrM5qIiGFWLDrAYEoYj+6B/p474XWbQhAuNDwvhwWxJHuG/S7CS/bsk/wr8pR9LzCm8eEEqoQ58NIQPlPMn3Y69Fovcn4cexpU9r1whI0yWZ1OwvYHlsngyAyCECyGo6/HPABsWFUwjKuE+mG57duFkwbk17rug85rd0MCohA8ObOSpT1WPHeKKjc3MwDtFM8xr+LRlrDETYazNJhawyIQNqTHMWXX5dNDAuY71BaXfFF8yoHEaQDnDyNudqgfuMQSEC410iVARHWVmvKpcQUFJJx/H41G2/9sJ5jvRnyYIXwQIhA4APXyr35MW/4PPuMRtYg+caUGFqk+12x9wW+JFxHqnwIQMiS1b6HfL93efBgysG0PrlMPwA7eKMSFexdnOGHhOQP1yuZwXdzsmnALF1dqkUCzczWAtdSewwYE3SNlcLhnxJInGIrGjelDmdMwkAABYAFO26WaDgWxcVeSe4lfoysDTitaKJp/r3t2YNAAC4Y1IhA6WEMWgMOWjUk1FZvxYDTRvyVZKDWeCKsBvgJruAngCOIQKeg5DPhM1ghA26JAP1gvhyZ7K/Z2ttavmOd7AyFv2OyyEDjgniAeDz5qiX/5tyDxMJcyMN4yRoYXZwfFgT9x6iKqghAxmojRQWODhnuwBaEtxJHK9AlJXUkPjIOs4o08raE5zfVK5nBF6KYiOxdSEDvbxvoF42LUei1lcblCHsUzeXo7Wr1zKxw5agIRZVWqysaAAAAAAAAQMEggAAAAAAA6jMPk3fqYXY2pAV+YsGMMB2CAwXDBpwMObcmgEz8M0NO07ZYF60Sf8jj0zms7HOPzU3tBtRMROIXzyZreEJOfmqBIZKk6tg78xgjIW8mR+WuQAA"
)
val tx = psbt.transaction val tx = psbt.transaction
val decoded = SerializedPSBT.decodePSBT(psbt) val decoded = SerializedPSBT.decodePSBT(psbt)
@ -21,10 +22,13 @@ class SerializedPSBTTest extends BitcoinSUnitTest {
assert(decoded.global.tx == SerializedTransaction.decodeRawTransaction(tx)) assert(decoded.global.tx == SerializedTransaction.decodeRawTransaction(tx))
assert(decoded.global.version == UInt32.zero) assert(decoded.global.version == UInt32.zero)
assert( assert(
decoded.global.unknowns == Vector(GlobalPSBTRecord.Unknown( decoded.global.unknowns == Vector(
hex"ab6028899ae8654659eff165d27e1d705f332967e90f743345c026d504f1510cf31a71e3339c54fb1614bd848fa241fcead2d61d556686b39013677ffdf3debba3f1eabd0b", GlobalPSBTRecord.Unknown(
hex"60db62406e6e26257613ef3b9d3fdccd301ed1e7709eb0fd499da4554cb7fcf58907db1fd5386eaea22d2f77d019ce89195cd0a5fc0d09839ca15d4dd8b271e04f3a658a4894fe711d5121069b4a2298ea994ee239126d9db21463" hex"ab6028899ae8654659eff165d27e1d705f332967e90f743345c026d504f1510cf31a71e3339c54fb1614bd848fa241fcead2d61d556686b39013677ffdf3debba3f1eabd0b",
))) hex"60db62406e6e26257613ef3b9d3fdccd301ed1e7709eb0fd499da4554cb7fcf58907db1fd5386eaea22d2f77d019ce89195cd0a5fc0d09839ca15d4dd8b271e04f3a658a4894fe711d5121069b4a2298ea994ee239126d9db21463"
)
)
)
assert(decoded.inputs.size == 1) assert(decoded.inputs.size == 1)
assert(decoded.inputs.head.bip32Paths.isEmpty) assert(decoded.inputs.head.bip32Paths.isEmpty)
@ -39,7 +43,8 @@ class SerializedPSBTTest extends BitcoinSUnitTest {
assert(decoded.inputs.head.unknowns.isEmpty) assert(decoded.inputs.head.unknowns.isEmpty)
assert( assert(
decoded.inputs.head.sigHashType decoded.inputs.head.sigHashType
.contains(HashType.sigHashNoneAnyoneCanPay)) .contains(HashType.sigHashNoneAnyoneCanPay)
)
assert(decoded.outputs.size == 3) assert(decoded.outputs.size == 3)
assert(decoded.outputs.head.bip32Paths.isEmpty) assert(decoded.outputs.head.bip32Paths.isEmpty)
@ -50,10 +55,13 @@ class SerializedPSBTTest extends BitcoinSUnitTest {
assert(decoded.outputs(1).redeemScript.isEmpty) assert(decoded.outputs(1).redeemScript.isEmpty)
assert(decoded.outputs(1).witScript.isEmpty) assert(decoded.outputs(1).witScript.isEmpty)
assert( assert(
decoded.outputs(1).unknowns == Vector(OutputPSBTRecord.Unknown( decoded.outputs(1).unknowns == Vector(
hex"a8cc3e", OutputPSBTRecord.Unknown(
hex"dfa985d8da9015f98b0630c076080c170c1a7030e6dc9a0133f0cd0d3b4ed9605eb449ff238f4ce6b3b1ce3f3537b41b513113885f3c99ade10939f9aa04864a93ab60efcc608c85bc991f96b9" hex"a8cc3e",
))) hex"dfa985d8da9015f98b0630c076080c170c1a7030e6dc9a0133f0cd0d3b4ed9605eb449ff238f4ce6b3b1ce3f3537b41b513113885f3c99ade10939f9aa04864a93ab60efcc608c85bc991f96b9"
)
)
)
assert(decoded.outputs.last.bip32Paths.isEmpty) assert(decoded.outputs.last.bip32Paths.isEmpty)
assert(decoded.outputs.last.redeemScript.isEmpty) assert(decoded.outputs.last.redeemScript.isEmpty)
assert(decoded.outputs.last.witScript.isEmpty) assert(decoded.outputs.last.witScript.isEmpty)
@ -61,7 +69,8 @@ class SerializedPSBTTest extends BitcoinSUnitTest {
it must "correctly decode a finalized psbt" in { it must "correctly decode a finalized psbt" in {
val psbt = PSBT.fromBase64( val psbt = PSBT.fromBase64(
"cHNidP8BAP1FAQIAAAABGYuEj8rH1dQ8DYG/wIJoOmA07cMG3qUA2joduT39u3QBAAAAAEhEAAAGZi6ggGgAAAAqBOhp5BGydSEDT4d2r4kKjBDT7N3xtlQRvQOoZMDnvl9kcu+Yz0N+zOWsB9sJYW0AAAAmaiSqIantmOy8chX8u/gjrWjoJwzItqupL01TN2d15WGchnbpGlW08zmpugUAAAcEuQUHI7J1tyGqaVkFAABJZCEDd0QjA5VNPMrHshsJ5ToPa6aXjJClUKFubu0Z3qUgGCmsZyEDCXN1QPwcYCN+5PBjESbL9eI3/Fd2SW/L1kOKOJ6lAsqsaMnuN1dBTAAAFlMU75XVn2xj3vMdoWxBKP6yYswBfsdaZnFQHAAAACZqJKohqe2Q60JHII6xx+YrJFTmibrNi7e8pICEfsbYEMYNKhOtMwAAAAAAAQCKAgAAAAAC7ekoBUtTAABQYyED6l4Vptz8do0rhD+X5Xlmyl5Wwi/fM/EwJmYE/LE4XWhnBfcyAqcAsnUhA/spF0dsJg2ZiBFYnA80MM2Pxou90wok6B8hA44T/vJlaKy4YEecR1gAAB4CSESydXapFHyp4KCUiJddycf+SqnAZI21/aJFiKwAAAAAAQdqRzBEAiBMdpIM1wDaIgn1BJarrd27uzx9kcTixUzQMFGbb0KTsQIgBxNIy+cxhoe1Bnxy/X3+ankfNtC4ydjJcMHlxV0MJqOAIQJUjewAnrbEt88DGQklYoDSLGlNS5Z2C6ukMaVGy+p6EgAAAAAAAAA=") "cHNidP8BAP1FAQIAAAABGYuEj8rH1dQ8DYG/wIJoOmA07cMG3qUA2joduT39u3QBAAAAAEhEAAAGZi6ggGgAAAAqBOhp5BGydSEDT4d2r4kKjBDT7N3xtlQRvQOoZMDnvl9kcu+Yz0N+zOWsB9sJYW0AAAAmaiSqIantmOy8chX8u/gjrWjoJwzItqupL01TN2d15WGchnbpGlW08zmpugUAAAcEuQUHI7J1tyGqaVkFAABJZCEDd0QjA5VNPMrHshsJ5ToPa6aXjJClUKFubu0Z3qUgGCmsZyEDCXN1QPwcYCN+5PBjESbL9eI3/Fd2SW/L1kOKOJ6lAsqsaMnuN1dBTAAAFlMU75XVn2xj3vMdoWxBKP6yYswBfsdaZnFQHAAAACZqJKohqe2Q60JHII6xx+YrJFTmibrNi7e8pICEfsbYEMYNKhOtMwAAAAAAAQCKAgAAAAAC7ekoBUtTAABQYyED6l4Vptz8do0rhD+X5Xlmyl5Wwi/fM/EwJmYE/LE4XWhnBfcyAqcAsnUhA/spF0dsJg2ZiBFYnA80MM2Pxou90wok6B8hA44T/vJlaKy4YEecR1gAAB4CSESydXapFHyp4KCUiJddycf+SqnAZI21/aJFiKwAAAAAAQdqRzBEAiBMdpIM1wDaIgn1BJarrd27uzx9kcTixUzQMFGbb0KTsQIgBxNIy+cxhoe1Bnxy/X3+ankfNtC4ydjJcMHlxV0MJqOAIQJUjewAnrbEt88DGQklYoDSLGlNS5Z2C6ukMaVGy+p6EgAAAAAAAAA="
)
val tx = psbt.transaction val tx = psbt.transaction
val decoded = SerializedPSBT.decodePSBT(psbt) val decoded = SerializedPSBT.decodePSBT(psbt)

View File

@ -80,10 +80,10 @@ class AppConfigTest extends BitcoinSAsyncTest {
for { for {
_ <- walletAppConfig.start() _ <- walletAppConfig.start()
} yield { } yield {
//this should get substituted as all default sqlite // this should get substituted as all default sqlite
//configuration is saved in the "bitcoin-s.sqlite" key // configuration is saved in the "bitcoin-s.sqlite" key
//if in the future we change our default database behavior // if in the future we change our default database behavior
//this test case will need to change to check that the profile is correct // this test case will need to change to check that the profile is correct
assert(walletAppConfig.dbConfig.profile.isInstanceOf[SQLiteProfile]) assert(walletAppConfig.dbConfig.profile.isInstanceOf[SQLiteProfile])
} }
} }
@ -92,8 +92,8 @@ class AppConfigTest extends BitcoinSAsyncTest {
val datadir = BitcoinSTestAppConfig.tmpDir() val datadir = BitcoinSTestAppConfig.tmpDir()
System.setProperty("bitcoin-s.wallet.requiredConfirmations", "1") System.setProperty("bitcoin-s.wallet.requiredConfirmations", "1")
//need to invalidate the config cache to force typesafe config // need to invalidate the config cache to force typesafe config
//to freshly load all system properties // to freshly load all system properties
ConfigFactory.invalidateCaches() ConfigFactory.invalidateCaches()
val walletAppConfig = val walletAppConfig =

View File

@ -16,7 +16,8 @@ class AppServerCliJsonTest extends BitcoinSUnitTest {
assert(parsed1.isSuccess) assert(parsed1.isSuccess)
assert(parsed1.get.walletNameOpt == Some(walletName)) assert(parsed1.get.walletNameOpt == Some(walletName))
assert( assert(
parsed1.get.passwordOpt.map(_.toStringSensitive) == Some(aesPassword)) parsed1.get.passwordOpt.map(_.toStringSensitive) == Some(aesPassword)
)
assert(parsed1.get.bip39PasswordOpt == Some(bip39Password)) assert(parsed1.get.bip39PasswordOpt == Some(bip39Password))
val arr2 = ujson.Arr(walletName, aesPassword, ujson.Null) val arr2 = ujson.Arr(walletName, aesPassword, ujson.Null)
@ -24,7 +25,8 @@ class AppServerCliJsonTest extends BitcoinSUnitTest {
assert(parsed2.isSuccess) assert(parsed2.isSuccess)
assert(parsed2.get.walletNameOpt == Some(walletName)) assert(parsed2.get.walletNameOpt == Some(walletName))
assert( assert(
parsed2.get.passwordOpt.map(_.toStringSensitive) == Some(aesPassword)) parsed2.get.passwordOpt.map(_.toStringSensitive) == Some(aesPassword)
)
} }
} }

View File

@ -51,7 +51,8 @@ class DLCAcceptJsonSerializerTest extends BitcoinSUnitTest {
it must "have serialization symmetry for a accept json message" in { it must "have serialization symmetry for a accept json message" in {
val accept = upickle.default.read[DLCAcceptTLV](testString)( val accept = upickle.default.read[DLCAcceptTLV](testString)(
Picklers.dlcAcceptTLVPickler) Picklers.dlcAcceptTLVPickler
)
val json: String = val json: String =
upickle.default.write(accept)(Picklers.dlcAcceptTLVPickler) upickle.default.write(accept)(Picklers.dlcAcceptTLVPickler)
assert(json == testString.replaceAll("\\s", "")) assert(json == testString.replaceAll("\\s", ""))

View File

@ -11,7 +11,8 @@ class SpendingInfoDbSerializerTest extends BitcoinSUnitTest {
it must "be symmetrical" in { it must "be symmetrical" in {
val original = TransactionTestUtil.spendingInfoDb val original = TransactionTestUtil.spendingInfoDb
val json = upickle.default.writeJs[SpendingInfoDb](original)( val json = upickle.default.writeJs[SpendingInfoDb](original)(
Picklers.spendingInfoDbPickler) Picklers.spendingInfoDbPickler
)
val parsed = upickle.default.read(json)(Picklers.spendingInfoDbPickler) val parsed = upickle.default.read(json)(Picklers.spendingInfoDbPickler)

View File

@ -13,7 +13,7 @@ class ServerArgParserTest extends BitcoinSUnitTest {
it must "handle no command line flags" in { it must "handle no command line flags" in {
val parser = ServerArgParser(Vector.empty) val parser = ServerArgParser(Vector.empty)
//config must be empty // config must be empty
assert(parser.toConfig == ConfigFactory.empty()) assert(parser.toConfig == ConfigFactory.empty())
} }

View File

@ -13,38 +13,36 @@ import org.bitcoins.core.compat.JavaConverters._
import scala.util.Properties import scala.util.Properties
import scala.util.matching.Regex import scala.util.matching.Regex
/** Everything needed to configure functionality /** Everything needed to configure functionality of bitcoin-s applications is
* of bitcoin-s applications is found in here. * found in here.
* *
* @see [[https://github.com/bitcoin-s/bitcoin-s-core/blob/master/doc/configuration.md `configuration.md`]] * @see
* for more information. * [[https://github.com/bitcoin-s/bitcoin-s-core/blob/master/doc/configuration.md `configuration.md`]]
* for more information.
*/ */
abstract class AppConfig extends StartStopAsync[Unit] with BitcoinSLogger { abstract class AppConfig extends StartStopAsync[Unit] with BitcoinSLogger {
/** Starts this project. /** Starts this project. After this future resolves, all operations should be
* After this future resolves, all operations should be
* able to be performed correctly. * able to be performed correctly.
* *
* Starting may include creating database tables, * Starting may include creating database tables, making directories or files
* making directories or files needed later or * needed later or something else entirely.
* something else entirely.
*/ */
override def start(): Future[Unit] = { override def start(): Future[Unit] = {
Future.unit Future.unit
} }
/** Sub members of AppConfig should override this type with /** Sub members of AppConfig should override this type with the type of
* the type of themselves, ensuring `withOverrides` return * themselves, ensuring `withOverrides` return the correct type
* the correct type
*/ */
protected[bitcoins] type ConfigType <: AppConfig protected[bitcoins] type ConfigType <: AppConfig
/** Constructor to make a new instance of this config type */ /** Constructor to make a new instance of this config type */
protected[bitcoins] def newConfigOfType( protected[bitcoins] def newConfigOfType(
configOverrides: Vector[Config]): ConfigType configOverrides: Vector[Config]
): ConfigType
/** List of user-provided configs that should /** List of user-provided configs that should override defaults
* override defaults
*/ */
protected[bitcoins] def configOverrides: Vector[Config] protected[bitcoins] def configOverrides: Vector[Config]
@ -52,14 +50,11 @@ abstract class AppConfig extends StartStopAsync[Unit] with BitcoinSLogger {
withOverrides(Vector(configOverrides)) withOverrides(Vector(configOverrides))
} }
/** This method returns a new `AppConfig`, where every /** This method returns a new `AppConfig`, where every key under `bitcoin-s`
* key under `bitcoin-s` overrides the configuration * overrides the configuration picked up by other means (the `reference.conf`
* picked up by other means (the `reference.conf` * provided by bitcoin-s and the `application.conf` provided by the user). If
* provided by bitcoin-s and the `application.conf` * you pass in configs with overlapping keys (e.g. several configs with the
* provided by the user). If you pass in configs with * key `bitcoin-s.network`), the latter config overrides the first.
* overlapping keys (e.g. several configs with the key
* `bitcoin-s.network`), the latter config overrides the
* first.
*/ */
def withOverrides(configOverrides: Vector[Config]): ConfigType = { def withOverrides(configOverrides: Vector[Config]): ConfigType = {
val numOverrides = configOverrides.length val numOverrides = configOverrides.length
@ -180,7 +175,8 @@ object AppConfig extends BitcoinSLogger {
def getBaseConfig( def getBaseConfig(
baseDatadir: Path, baseDatadir: Path,
configFileName: String, configFileName: String,
configOverrides: Vector[Config]): Config = { configOverrides: Vector[Config]
): Config = {
val configOptions = val configOptions =
ConfigParseOptions ConfigParseOptions
.defaults() .defaults()
@ -195,7 +191,8 @@ object AppConfig extends BitcoinSLogger {
val withDatadir = val withDatadir =
ConfigFactory.parseString( ConfigFactory.parseString(
s"bitcoin-s.datadir = ${safePathToString(baseDatadir)}") s"bitcoin-s.datadir = ${safePathToString(baseDatadir)}"
)
withDatadir.withFallback(config) withDatadir.withFallback(config)
} }
@ -237,8 +234,8 @@ object AppConfig extends BitcoinSLogger {
/** The default data directory /** The default data directory
* *
* TODO: use different directories on Windows and Mac, * TODO: use different directories on Windows and Mac, should probably mimic
* should probably mimic what Bitcoin Core does * what Bitcoin Core does
*/ */
private[bitcoins] lazy val DEFAULT_BITCOIN_S_DATADIR: Path = private[bitcoins] lazy val DEFAULT_BITCOIN_S_DATADIR: Path =
Paths.get(Properties.userHome, ".bitcoin-s") Paths.get(Properties.userHome, ".bitcoin-s")
@ -246,9 +243,8 @@ object AppConfig extends BitcoinSLogger {
private[bitcoins] lazy val DEFAULT_BITCOIN_S_CONF_FILE: String = private[bitcoins] lazy val DEFAULT_BITCOIN_S_CONF_FILE: String =
"bitcoin-s.conf" "bitcoin-s.conf"
/** Matches the default data directory location /** Matches the default data directory location with a network appended, both
* with a network appended, * with and without a trailing `/`
* both with and without a trailing `/`
*/ */
private lazy val defaultDatadirRegex: Regex = { private lazy val defaultDatadirRegex: Regex = {
// Fix for windows // Fix for windows
@ -257,8 +253,8 @@ object AppConfig extends BitcoinSLogger {
(home + "/.bitcoin-s/(testnet3|mainnet|regtest)/?$").r (home + "/.bitcoin-s/(testnet3|mainnet|regtest)/?$").r
} }
/** Throws if the encountered datadir is the default one. Useful /** Throws if the encountered datadir is the default one. Useful in tests, to
* in tests, to make sure you don't blow up important data. * make sure you don't blow up important data.
*/ */
private[bitcoins] def throwIfDefaultDatadir(config: AppConfig): Unit = { private[bitcoins] def throwIfDefaultDatadir(config: AppConfig): Unit = {
val datadirStr = config.datadir.toString() val datadirStr = config.datadir.toString()

View File

@ -5,7 +5,10 @@ import com.typesafe.config.{Config, ConfigFactory}
import java.nio.file.{Path, Paths} import java.nio.file.{Path, Paths}
import scala.concurrent.ExecutionContext import scala.concurrent.ExecutionContext
/** @tparam I - the implicit argument. This is usually an execution context or an actor system */ /** @tparam I
* \- the implicit argument. This is usually an execution context or an actor
* system
*/
trait AppConfigFactoryBase[C <: AppConfig, I] { trait AppConfigFactoryBase[C <: AppConfig, I] {
def moduleName: String def moduleName: String
@ -18,13 +21,15 @@ trait AppConfigFactoryBase[C <: AppConfig, I] {
fromConfig(ConfigFactory.load()) fromConfig(ConfigFactory.load())
} }
def fromDefaultDatadir(confs: Vector[Config] = Vector.empty)(implicit def fromDefaultDatadir(
i: I): C = { confs: Vector[Config] = Vector.empty
)(implicit i: I): C = {
fromDatadir(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs) fromDatadir(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs)
} }
def fromDatadir(datadir: Path, confs: Vector[Config] = Vector.empty)(implicit def fromDatadir(datadir: Path, confs: Vector[Config] = Vector.empty)(implicit
i: I): C i: I
): C
} }
trait AppConfigFactory[C <: AppConfig] trait AppConfigFactory[C <: AppConfig]

View File

@ -12,17 +12,20 @@ object FileUtil extends BitcoinSLogger {
/** Zips the [[directory]] into a zip file and then stores it at [[target]] /** Zips the [[directory]] into a zip file and then stores it at [[target]]
* *
* @see https://www.quickprogrammingtips.com/java/how-to-zip-a-folder-in-java.html * @see
* https://www.quickprogrammingtips.com/java/how-to-zip-a-folder-in-java.html
*/ */
def zipDirectory( def zipDirectory(
source: Path, source: Path,
target: Path, target: Path,
fileNameFilter: Vector[Regex] = Vector.empty): Path = { fileNameFilter: Vector[Regex] = Vector.empty
): Path = {
require( require(
!Files.exists(target), !Files.exists(target),
s"Cannot overwrite existing target directory=${target.toAbsolutePath}") s"Cannot overwrite existing target directory=${target.toAbsolutePath}"
)
//create directories for target if they DNE // create directories for target if they DNE
Files.createDirectories(target.getParent) Files.createDirectories(target.getParent)
val zos = new ZipOutputStream(new FileOutputStream(target.toFile)) val zos = new ZipOutputStream(new FileOutputStream(target.toFile))
@ -33,16 +36,18 @@ object FileUtil extends BitcoinSLogger {
@throws[IOException] @throws[IOException]
override def visitFile( override def visitFile(
file: Path, file: Path,
attrs: BasicFileAttributes): FileVisitResult = { attrs: BasicFileAttributes
): FileVisitResult = {
if ( if (
fileNameFilter.exists(reg => fileNameFilter
file.toAbsolutePath.toString.matches(reg.regex)) .exists(reg => file.toAbsolutePath.toString.matches(reg.regex))
) { ) {
logger.info(s"Skipping ${file.toAbsolutePath} for zip") logger.info(s"Skipping ${file.toAbsolutePath} for zip")
FileVisitResult.CONTINUE FileVisitResult.CONTINUE
} else { } else {
logger.info( logger.info(
s"Zipping file=${file.toAbsolutePath} to ${target.toAbsolutePath}") s"Zipping file=${file.toAbsolutePath} to ${target.toAbsolutePath}"
)
zos.putNextEntry(new ZipEntry(source.relativize(file).toString)) zos.putNextEntry(new ZipEntry(source.relativize(file).toString))
Files.copy(file, zos) Files.copy(file, zos)
zos.closeEntry() zos.closeEntry()
@ -63,24 +68,27 @@ object FileUtil extends BitcoinSLogger {
def copyDirectory( def copyDirectory(
source: Path, source: Path,
target: Path, target: Path,
fileNameFilter: Vector[Regex] = Vector.empty): Path = { fileNameFilter: Vector[Regex] = Vector.empty
): Path = {
Files.walkFileTree( Files.walkFileTree(
source, source,
new SimpleFileVisitor[Path]() { new SimpleFileVisitor[Path]() {
@throws[IOException] @throws[IOException]
override def visitFile( override def visitFile(
file: Path, file: Path,
attrs: BasicFileAttributes): FileVisitResult = { attrs: BasicFileAttributes
): FileVisitResult = {
if ( if (
fileNameFilter.exists(reg => fileNameFilter
file.toAbsolutePath.toString.matches(reg.regex)) .exists(reg => file.toAbsolutePath.toString.matches(reg.regex))
) { ) {
logger.info(s"Skipping ${file.toAbsolutePath} for copy") logger.info(s"Skipping ${file.toAbsolutePath} for copy")
FileVisitResult.CONTINUE FileVisitResult.CONTINUE
} else { } else {
val targetPath = target.resolve(source.relativize(file)) val targetPath = target.resolve(source.relativize(file))
logger.info( logger.info(
s"Copying file=${file.toAbsolutePath} to ${targetPath.toAbsolutePath}") s"Copying file=${file.toAbsolutePath} to ${targetPath.toAbsolutePath}"
)
Files.createDirectories(targetPath.getParent) Files.createDirectories(targetPath.getParent)
Files.copy(file, targetPath) Files.copy(file, targetPath)
logger.info(s"Done copying file=${file.toAbsolutePath}") logger.info(s"Done copying file=${file.toAbsolutePath}")
@ -100,14 +108,16 @@ object FileUtil extends BitcoinSLogger {
new SimpleFileVisitor[Path] { new SimpleFileVisitor[Path] {
override def visitFile( override def visitFile(
file: Path, file: Path,
attrs: BasicFileAttributes): FileVisitResult = { attrs: BasicFileAttributes
): FileVisitResult = {
Files.delete(file) Files.delete(file)
FileVisitResult.CONTINUE FileVisitResult.CONTINUE
} }
override def postVisitDirectory( override def postVisitDirectory(
dir: Path, dir: Path,
exc: IOException): FileVisitResult = { exc: IOException
): FileVisitResult = {
Files.delete(dir) Files.delete(dir)
FileVisitResult.CONTINUE FileVisitResult.CONTINUE
} }

View File

@ -12,7 +12,8 @@ case class BitcoinSServerInfo(
blockHash: DoubleSha256DigestBE, blockHash: DoubleSha256DigestBE,
torStarted: Boolean, torStarted: Boolean,
syncing: Boolean, syncing: Boolean,
isInitialBlockDownload: Boolean) { isInitialBlockDownload: Boolean
) {
lazy val toJson: Value = { lazy val toJson: Value = {
Obj( Obj(
@ -38,11 +39,13 @@ object BitcoinSServerInfo {
val sync = obj(PicklerKeys.syncKey).bool val sync = obj(PicklerKeys.syncKey).bool
val isIBD = obj(PicklerKeys.isInitialBlockDownload).bool val isIBD = obj(PicklerKeys.isInitialBlockDownload).bool
BitcoinSServerInfo(network = network, BitcoinSServerInfo(
blockHeight = height, network = network,
blockHash = blockHash, blockHeight = height,
torStarted = torStarted, blockHash = blockHash,
syncing = sync, torStarted = torStarted,
isInitialBlockDownload = isIBD) syncing = sync,
isInitialBlockDownload = isIBD
)
} }
} }

View File

@ -14,7 +14,8 @@ import scodec.bits.ByteVector
case class SerializedPSBT( case class SerializedPSBT(
global: SerializedPSBTGlobalMap, global: SerializedPSBTGlobalMap,
inputs: Vector[SerializedPSBTInputMap], inputs: Vector[SerializedPSBTInputMap],
outputs: Vector[SerializedPSBTOutputMap]) { outputs: Vector[SerializedPSBTOutputMap]
) {
val toJson: JsValue = Json.toJson(this) val toJson: JsValue = Json.toJson(this)
} }
@ -22,7 +23,8 @@ case class SerializedPSBTGlobalMap(
tx: SerializedTransaction, tx: SerializedTransaction,
version: UInt32, version: UInt32,
xpubs: Option[Vector[ExtPublicKey]], xpubs: Option[Vector[ExtPublicKey]],
unknowns: Vector[GlobalPSBTRecord.Unknown]) unknowns: Vector[GlobalPSBTRecord.Unknown]
)
case class SerializedPSBTInputMap( case class SerializedPSBTInputMap(
nonWitnessUtxo: Option[SerializedTransaction], nonWitnessUtxo: Option[SerializedTransaction],
@ -35,13 +37,15 @@ case class SerializedPSBTInputMap(
finalizedScriptSig: Option[Vector[ScriptToken]], finalizedScriptSig: Option[Vector[ScriptToken]],
finalizedScriptWitness: Option[SerializedTransactionWitness], finalizedScriptWitness: Option[SerializedTransactionWitness],
proofOfReservesCommitment: Option[ByteVector], proofOfReservesCommitment: Option[ByteVector],
unknowns: Vector[InputPSBTRecord.Unknown]) unknowns: Vector[InputPSBTRecord.Unknown]
)
case class SerializedPSBTOutputMap( case class SerializedPSBTOutputMap(
redeemScript: Option[Vector[ScriptToken]], redeemScript: Option[Vector[ScriptToken]],
witScript: Option[Vector[ScriptToken]], witScript: Option[Vector[ScriptToken]],
bip32Paths: Option[Vector[OutputPSBTRecord.BIP32DerivationPath]], bip32Paths: Option[Vector[OutputPSBTRecord.BIP32DerivationPath]],
unknowns: Vector[OutputPSBTRecord.Unknown]) unknowns: Vector[OutputPSBTRecord.Unknown]
)
object SerializedPSBT { object SerializedPSBT {
@ -57,7 +61,8 @@ object SerializedPSBT {
def decodeInputMap( def decodeInputMap(
input: InputPSBTMap, input: InputPSBTMap,
index: Int): SerializedPSBTInputMap = { index: Int
): SerializedPSBTInputMap = {
val prevTxOpt = input.nonWitnessOrUnknownUTXOOpt.map(_.transactionSpent) val prevTxOpt = input.nonWitnessOrUnknownUTXOOpt.map(_.transactionSpent)
val nonWitnessUtxo = prevTxOpt.map(decodeRawTransaction) val nonWitnessUtxo = prevTxOpt.map(decodeRawTransaction)
val witnessUtxo = input.witnessUTXOOpt.map(rec => val witnessUtxo = input.witnessUTXOOpt.map(rec =>
@ -80,17 +85,19 @@ object SerializedPSBT {
val unknowns = input.getRecords(PSBTInputKeyId.UnknownKeyId) val unknowns = input.getRecords(PSBTInputKeyId.UnknownKeyId)
SerializedPSBTInputMap(nonWitnessUtxo, SerializedPSBTInputMap(
witnessUtxo, nonWitnessUtxo,
sigsOpt, witnessUtxo,
hashType, sigsOpt,
redeemScript, hashType,
witScript, redeemScript,
bip32PathsOpt, witScript,
finalizedScriptSig, bip32PathsOpt,
finalizedWitScript, finalizedScriptSig,
porCommit, finalizedWitScript,
unknowns) porCommit,
unknowns
)
} }
def decodeOutputMap(output: OutputPSBTMap): SerializedPSBTOutputMap = { def decodeOutputMap(output: OutputPSBTMap): SerializedPSBTOutputMap = {

View File

@ -26,7 +26,8 @@ case class SerializedTransaction(
weight: Long, weight: Long,
locktime: UInt32, locktime: UInt32,
vin: Vector[SerializedTransactionInput], vin: Vector[SerializedTransactionInput],
vout: Vector[SerializedTransactionOutput]) { vout: Vector[SerializedTransactionOutput]
) {
val toJson: JsValue = Json.toJson(this) val toJson: JsValue = Json.toJson(this)
} }
@ -45,7 +46,8 @@ case class SerializedTransactionWitness(
script: Option[Vector[ScriptToken]], script: Option[Vector[ScriptToken]],
pubKey: Option[ECPublicKeyBytes], pubKey: Option[ECPublicKeyBytes],
signature: Option[ECDigitalSignature], signature: Option[ECDigitalSignature],
stack: Option[Vector[ByteVector]]) stack: Option[Vector[ByteVector]]
)
case class SerializedTransactionOutput( case class SerializedTransactionOutput(
value: BigDecimal, value: BigDecimal,
@ -65,35 +67,43 @@ object SerializedTransaction {
} }
def decodeRawTransactionWitness( def decodeRawTransactionWitness(
witness: ScriptWitness): Option[SerializedTransactionWitness] = { witness: ScriptWitness
): Option[SerializedTransactionWitness] = {
witness match { witness match {
case EmptyScriptWitness => None case EmptyScriptWitness => None
case p2wpkh: P2WPKHWitnessV0 => case p2wpkh: P2WPKHWitnessV0 =>
Some( Some(
SerializedTransactionWitness(hex = p2wpkh.hex, SerializedTransactionWitness(
scriptType = Some("P2WPKH"), hex = p2wpkh.hex,
script = None, scriptType = Some("P2WPKH"),
pubKey = Some(p2wpkh.pubKey), script = None,
signature = Some(p2wpkh.signature), pubKey = Some(p2wpkh.pubKey),
stack = None)) signature = Some(p2wpkh.signature),
stack = None
)
)
case p2wsh: P2WSHWitnessV0 => case p2wsh: P2WSHWitnessV0 =>
Some( Some(
SerializedTransactionWitness(hex = p2wsh.hex, SerializedTransactionWitness(
scriptType = Some("P2WSH"), hex = p2wsh.hex,
script = scriptType = Some("P2WSH"),
Some(p2wsh.redeemScript.asm.toVector), script = Some(p2wsh.redeemScript.asm.toVector),
pubKey = None, pubKey = None,
signature = None, signature = None,
stack = Some(p2wsh.stack.toVector.tail))) stack = Some(p2wsh.stack.toVector.tail)
)
)
case taprootWitness: TaprootWitness => case taprootWitness: TaprootWitness =>
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
s"Taproot not supported, got=$taprootWitness") s"Taproot not supported, got=$taprootWitness"
)
} }
} }
def decodeTransactionInput( def decodeTransactionInput(
input: TransactionInput, input: TransactionInput,
witnessOpt: Option[ScriptWitness]): SerializedTransactionInput = { witnessOpt: Option[ScriptWitness]
): SerializedTransactionInput = {
val decodedWitnessOpt = witnessOpt.flatMap(decodeRawTransactionWitness) val decodedWitnessOpt = witnessOpt.flatMap(decodeRawTransactionWitness)
SerializedTransactionInput( SerializedTransactionInput(
@ -108,11 +118,14 @@ object SerializedTransaction {
def decodeTransactionOutput( def decodeTransactionOutput(
output: TransactionOutput, output: TransactionOutput,
index: Int): SerializedTransactionOutput = { index: Int
SerializedTransactionOutput(value = output.value.toBigDecimal, ): SerializedTransactionOutput = {
n = UInt32(index), SerializedTransactionOutput(
scriptPubKey = output.scriptPubKey.asm.toVector, value = output.value.toBigDecimal,
hex = output.hex) n = UInt32(index),
scriptPubKey = output.scriptPubKey.asm.toVector,
hex = output.hex
)
} }
def decodeRawTransaction(tx: Transaction): SerializedTransaction = { def decodeRawTransaction(tx: Transaction): SerializedTransaction = {
@ -135,14 +148,16 @@ object SerializedTransaction {
case wtx: WitnessTransaction => Some(wtx.wTxIdBE) case wtx: WitnessTransaction => Some(wtx.wTxIdBE)
} }
SerializedTransaction(txid = tx.txIdBE, SerializedTransaction(
wtxid = wtxIdOpt, txid = tx.txIdBE,
version = tx.version, wtxid = wtxIdOpt,
size = tx.byteSize, version = tx.version,
vsize = tx.vsize, size = tx.byteSize,
weight = tx.weight, vsize = tx.vsize,
locktime = tx.lockTime, weight = tx.weight,
vin = inputs, locktime = tx.lockTime,
vout = outputs) vin = inputs,
vout = outputs
)
} }
} }

View File

@ -24,8 +24,8 @@ case class DumpTxOutSetResult(
coins_written: Int, coins_written: Int,
base_hash: DoubleSha256DigestBE, base_hash: DoubleSha256DigestBE,
base_height: Int, base_height: Int,
path: Path) path: Path
extends BlockchainResult ) extends BlockchainResult
case class GetBlockResult( case class GetBlockResult(
hash: DoubleSha256DigestBE, hash: DoubleSha256DigestBE,
@ -45,8 +45,8 @@ case class GetBlockResult(
difficulty: BigDecimal, difficulty: BigDecimal,
chainwork: String, chainwork: String,
previousblockhash: Option[DoubleSha256DigestBE], previousblockhash: Option[DoubleSha256DigestBE],
nextblockhash: Option[DoubleSha256DigestBE]) nextblockhash: Option[DoubleSha256DigestBE]
extends BlockchainResult ) extends BlockchainResult
abstract trait GetBlockWithTransactionsResult extends BlockchainResult { abstract trait GetBlockWithTransactionsResult extends BlockchainResult {
def hash: DoubleSha256DigestBE def hash: DoubleSha256DigestBE
@ -87,8 +87,8 @@ case class GetBlockWithTransactionsResultV22(
difficulty: BigDecimal, difficulty: BigDecimal,
chainwork: String, chainwork: String,
previousblockhash: Option[DoubleSha256DigestBE], previousblockhash: Option[DoubleSha256DigestBE],
nextblockhash: Option[DoubleSha256DigestBE]) nextblockhash: Option[DoubleSha256DigestBE]
extends GetBlockWithTransactionsResult ) extends GetBlockWithTransactionsResult
sealed trait GetBlockChainInfoResult extends BlockchainResult { sealed trait GetBlockChainInfoResult extends BlockchainResult {
def chain: NetworkParameters def chain: NetworkParameters
@ -121,8 +121,8 @@ case class GetBlockChainInfoResultPreV19(
pruneheight: Option[Int], pruneheight: Option[Int],
softforks: Vector[SoftforkPreV19], softforks: Vector[SoftforkPreV19],
bip9_softforks: Map[String, Bip9SoftforkPreV19], bip9_softforks: Map[String, Bip9SoftforkPreV19],
warnings: String) warnings: String
extends GetBlockChainInfoResult ) extends GetBlockChainInfoResult
case class GetBlockChainInfoResultPostV19( case class GetBlockChainInfoResultPostV19(
chain: NetworkParameters, chain: NetworkParameters,
@ -138,8 +138,8 @@ case class GetBlockChainInfoResultPostV19(
pruned: Boolean, pruned: Boolean,
pruneheight: Option[Int], pruneheight: Option[Int],
softforks: Map[String, SoftforkPostV19], softforks: Map[String, SoftforkPostV19],
warnings: String) warnings: String
extends GetBlockChainInfoResult ) extends GetBlockChainInfoResult
// adds time field removes softforks field // adds time field removes softforks field
case class GetBlockChainInfoResultPostV23( case class GetBlockChainInfoResultPostV23(
@ -156,30 +156,30 @@ case class GetBlockChainInfoResultPostV23(
size_on_disk: Long, size_on_disk: Long,
pruned: Boolean, pruned: Boolean,
pruneheight: Option[Int], pruneheight: Option[Int],
warnings: String) warnings: String
extends GetBlockChainInfoResult ) extends GetBlockChainInfoResult
case class SoftforkPreV19( case class SoftforkPreV19(
id: String, id: String,
version: Int, version: Int,
enforce: Option[Map[String, SoftforkProgressPreV19]], enforce: Option[Map[String, SoftforkProgressPreV19]],
reject: SoftforkProgressPreV19) reject: SoftforkProgressPreV19
extends BlockchainResult ) extends BlockchainResult
case class SoftforkProgressPreV19( case class SoftforkProgressPreV19(
status: Option[Boolean], status: Option[Boolean],
found: Option[Int], found: Option[Int],
required: Option[Int], required: Option[Int],
window: Option[Int]) window: Option[Int]
extends BlockchainResult ) extends BlockchainResult
case class Bip9SoftforkPreV19( case class Bip9SoftforkPreV19(
status: String, status: String,
bit: Option[Int], bit: Option[Int],
startTime: Int, startTime: Int,
timeout: BigInt, timeout: BigInt,
since: Int) since: Int
extends BlockchainResult ) extends BlockchainResult
sealed trait SoftforkPostV19 extends BlockchainResult sealed trait SoftforkPostV19 extends BlockchainResult
@ -194,8 +194,8 @@ case class Bip9SoftforkDetails(
bit: Option[Int], bit: Option[Int],
start_time: Int, start_time: Int,
timeout: BigInt, timeout: BigInt,
since: Int) since: Int
extends BlockchainResult ) extends BlockchainResult
case class GetBlockHeaderResult( case class GetBlockHeaderResult(
hash: DoubleSha256DigestBE, hash: DoubleSha256DigestBE,
@ -211,8 +211,8 @@ case class GetBlockHeaderResult(
difficulty: BigDecimal, difficulty: BigDecimal,
chainwork: String, chainwork: String,
previousblockhash: Option[DoubleSha256DigestBE], previousblockhash: Option[DoubleSha256DigestBE],
nextblockhash: Option[DoubleSha256DigestBE]) nextblockhash: Option[DoubleSha256DigestBE]
extends BlockchainResult { ) extends BlockchainResult {
lazy val blockHeaderDb: BlockHeaderDb = { lazy val blockHeaderDb: BlockHeaderDb = {
val bytes = ByteVector.fromValidHex(chainwork).dropWhile(_ == 0x00).toArray val bytes = ByteVector.fromValidHex(chainwork).dropWhile(_ == 0x00).toArray
@ -222,8 +222,8 @@ case class GetBlockHeaderResult(
def blockHeader: BlockHeader = { def blockHeader: BlockHeader = {
//prevblockhash is only empty if we have the genesis block // prevblockhash is only empty if we have the genesis block
//we assume the prevhash of the gensis block is the empty hash // we assume the prevhash of the gensis block is the empty hash
val prevHash = { val prevHash = {
if (height == 0 && previousblockhash.isEmpty) { if (height == 0 && previousblockhash.isEmpty) {
DoubleSha256DigestBE.empty DoubleSha256DigestBE.empty
@ -231,12 +231,14 @@ case class GetBlockHeaderResult(
previousblockhash.get previousblockhash.get
} }
} }
BlockHeader(version = Int32(version), BlockHeader(
previousBlockHash = prevHash.flip, version = Int32(version),
merkleRootHash = merkleroot.flip, previousBlockHash = prevHash.flip,
time = time, merkleRootHash = merkleroot.flip,
nBits = bits, time = time,
nonce = nonce) nBits = bits,
nonce = nonce
)
} }
} }
@ -244,8 +246,8 @@ case class ChainTip(
height: Int, height: Int,
hash: DoubleSha256DigestBE, hash: DoubleSha256DigestBE,
branchlen: Int, branchlen: Int,
status: String) status: String
extends BlockchainResult ) extends BlockchainResult
case class GetChainTxStatsResult( case class GetChainTxStatsResult(
time: UInt32, time: UInt32,
@ -254,8 +256,8 @@ case class GetChainTxStatsResult(
window_final_block_height: Option[Int], window_final_block_height: Option[Int],
window_tx_count: Option[Int], window_tx_count: Option[Int],
window_interval: Option[UInt32], window_interval: Option[UInt32],
txrate: Option[BigDecimal]) txrate: Option[BigDecimal]
extends BlockchainResult ) extends BlockchainResult
sealed trait GetMemPoolResult extends BlockchainResult { sealed trait GetMemPoolResult extends BlockchainResult {
def size: Int def size: Int
@ -284,8 +286,8 @@ case class GetMemPoolResultPreV19(
ancestorfees: Option[Bitcoins], ancestorfees: Option[Bitcoins],
wtxid: DoubleSha256DigestBE, wtxid: DoubleSha256DigestBE,
fees: FeeInfo, fees: FeeInfo,
depends: Vector[DoubleSha256DigestBE]) depends: Vector[DoubleSha256DigestBE]
extends GetMemPoolResult ) extends GetMemPoolResult
case class GetMemPoolResultPostV19( case class GetMemPoolResultPostV19(
vsize: Int, vsize: Int,
@ -301,8 +303,8 @@ case class GetMemPoolResultPostV19(
ancestorfees: Option[Bitcoins], ancestorfees: Option[Bitcoins],
wtxid: DoubleSha256DigestBE, wtxid: DoubleSha256DigestBE,
fees: FeeInfo, fees: FeeInfo,
depends: Vector[DoubleSha256DigestBE]) depends: Vector[DoubleSha256DigestBE]
extends GetMemPoolResult { ) extends GetMemPoolResult {
override def size: Int = vsize override def size: Int = vsize
} }
@ -317,8 +319,8 @@ case class GetMemPoolResultPostV23(
ancestorsize: Int, ancestorsize: Int,
wtxid: DoubleSha256DigestBE, wtxid: DoubleSha256DigestBE,
fees: FeeInfo, fees: FeeInfo,
depends: Vector[DoubleSha256DigestBE]) depends: Vector[DoubleSha256DigestBE]
extends GetMemPoolResult { ) extends GetMemPoolResult {
override def size: Int = vsize override def size: Int = vsize
} }
@ -356,8 +358,8 @@ case class GetMemPoolEntryResultPreV19(
ancestorfees: BitcoinFeeUnit, ancestorfees: BitcoinFeeUnit,
wtxid: DoubleSha256DigestBE, wtxid: DoubleSha256DigestBE,
fees: FeeInfo, fees: FeeInfo,
depends: Option[Vector[DoubleSha256DigestBE]]) depends: Option[Vector[DoubleSha256DigestBE]]
extends GetMemPoolEntryResult ) extends GetMemPoolEntryResult
case class GetMemPoolEntryResultPostV19( case class GetMemPoolEntryResultPostV19(
vsize: Int, vsize: Int,
@ -374,8 +376,8 @@ case class GetMemPoolEntryResultPostV19(
ancestorfees: BitcoinFeeUnit, ancestorfees: BitcoinFeeUnit,
wtxid: DoubleSha256DigestBE, wtxid: DoubleSha256DigestBE,
fees: FeeInfo, fees: FeeInfo,
depends: Option[Vector[DoubleSha256DigestBE]]) depends: Option[Vector[DoubleSha256DigestBE]]
extends GetMemPoolEntryResult { ) extends GetMemPoolEntryResult {
override def size: Int = vsize override def size: Int = vsize
} }
@ -390,8 +392,8 @@ case class GetMemPoolEntryResultPostV23(
ancestorsize: Int, ancestorsize: Int,
wtxid: DoubleSha256DigestBE, wtxid: DoubleSha256DigestBE,
fees: FeeInfo, fees: FeeInfo,
depends: Option[Vector[DoubleSha256DigestBE]]) depends: Option[Vector[DoubleSha256DigestBE]]
extends GetMemPoolEntryResult { ) extends GetMemPoolEntryResult {
override def size: Int = vsize override def size: Int = vsize
} }
@ -401,8 +403,8 @@ case class GetMemPoolInfoResult(
usage: Int, usage: Int,
maxmempool: Int, maxmempool: Int,
mempoolminfee: BitcoinFeeUnit, mempoolminfee: BitcoinFeeUnit,
minrelaytxfee: Bitcoins) minrelaytxfee: Bitcoins
extends BlockchainResult ) extends BlockchainResult
sealed abstract trait GetTxOutResult extends BlockchainResult { sealed abstract trait GetTxOutResult extends BlockchainResult {
def bestblock: DoubleSha256DigestBE def bestblock: DoubleSha256DigestBE
@ -417,8 +419,8 @@ case class GetTxOutResultV22(
confirmations: Int, confirmations: Int,
value: Bitcoins, value: Bitcoins,
scriptPubKey: RpcScriptPubKeyPostV22, scriptPubKey: RpcScriptPubKeyPostV22,
coinbase: Boolean) coinbase: Boolean
extends GetTxOutResult ) extends GetTxOutResult
case class GetTxOutSetInfoResult( case class GetTxOutSetInfoResult(
height: Int, height: Int,
@ -428,17 +430,18 @@ case class GetTxOutSetInfoResult(
bogosize: Int, bogosize: Int,
hash_serialized_2: DoubleSha256DigestBE, hash_serialized_2: DoubleSha256DigestBE,
disk_size: Int, disk_size: Int,
total_amount: Bitcoins) total_amount: Bitcoins
extends BlockchainResult ) extends BlockchainResult
case class GetBlockFilterResult( case class GetBlockFilterResult(
filter: GolombFilter, filter: GolombFilter,
header: DoubleSha256DigestBE) header: DoubleSha256DigestBE
extends BlockchainResult { ) extends BlockchainResult {
def filterDb( def filterDb(
height: Int, height: Int,
blockHashBE: DoubleSha256DigestBE): CompactFilterDb = { blockHashBE: DoubleSha256DigestBE
): CompactFilterDb = {
CompactFilterDbHelper.fromGolombFilter(filter, blockHashBE, height) CompactFilterDbHelper.fromGolombFilter(filter, blockHashBE, height)
} }
} }
@ -446,7 +449,8 @@ case class GetBlockFilterResult(
case class GetTxSpendingPrevOutResult( case class GetTxSpendingPrevOutResult(
txid: DoubleSha256DigestBE, txid: DoubleSha256DigestBE,
vout: Int, vout: Int,
spendingtxid: Option[DoubleSha256DigestBE]) { spendingtxid: Option[DoubleSha256DigestBE]
) {
def outpoint: TransactionOutPoint = TransactionOutPoint(txid, UInt32(vout)) def outpoint: TransactionOutPoint = TransactionOutPoint(txid, UInt32(vout))
} }

View File

@ -13,8 +13,8 @@ sealed abstract class NetworkResult
case class Node( case class Node(
addednode: URI, addednode: URI,
connected: Option[Boolean], connected: Option[Boolean],
addresses: Option[Vector[NodeAddress]]) addresses: Option[Vector[NodeAddress]]
extends NetworkResult ) extends NetworkResult
case class NodeAddress(address: URI, connected: String) extends NetworkResult case class NodeAddress(address: URI, connected: String) extends NetworkResult
@ -22,8 +22,8 @@ case class GetNetTotalsResult(
totalbytesrecv: Int, totalbytesrecv: Int,
totalbytessent: Int, totalbytessent: Int,
timemillis: UInt64, timemillis: UInt64,
uploadtarget: NetTarget) uploadtarget: NetTarget
extends NetworkResult ) extends NetworkResult
case class NetTarget( case class NetTarget(
timeframe: UInt32, timeframe: UInt32,
@ -31,8 +31,8 @@ case class NetTarget(
target_reached: Boolean, target_reached: Boolean,
serve_historical_blocks: Boolean, serve_historical_blocks: Boolean,
bytes_left_in_cycle: Int, bytes_left_in_cycle: Int,
time_left_in_cycle: UInt32) time_left_in_cycle: UInt32
extends NetworkResult ) extends NetworkResult
trait GetNetworkInfoResult extends NetworkResult { trait GetNetworkInfoResult extends NetworkResult {
def version: Int def version: Int
@ -65,8 +65,8 @@ case class GetNetworkInfoResultPreV21(
relayfee: Bitcoins, relayfee: Bitcoins,
incrementalfee: Bitcoins, incrementalfee: Bitcoins,
localaddresses: Vector[NetworkAddress], localaddresses: Vector[NetworkAddress],
warnings: String) warnings: String
extends GetNetworkInfoResult ) extends GetNetworkInfoResult
case class GetNetworkInfoResultPostV21( case class GetNetworkInfoResultPostV21(
version: Int, version: Int,
@ -84,16 +84,16 @@ case class GetNetworkInfoResultPostV21(
relayfee: Bitcoins, relayfee: Bitcoins,
incrementalfee: Bitcoins, incrementalfee: Bitcoins,
localaddresses: Vector[NetworkAddress], localaddresses: Vector[NetworkAddress],
warnings: String) warnings: String
extends GetNetworkInfoResult ) extends GetNetworkInfoResult
case class Network( case class Network(
name: String, name: String,
limited: Boolean, limited: Boolean,
reachable: Boolean, reachable: Boolean,
proxy: String, proxy: String,
proxy_randomize_credentials: Boolean) proxy_randomize_credentials: Boolean
extends NetworkResult ) extends NetworkResult
case class NetworkAddress(address: String, port: Int, score: Int) case class NetworkAddress(address: String, port: Int, score: Int)
extends NetworkResult extends NetworkResult
@ -127,8 +127,8 @@ case class PeerPostV21(
inflight: Vector[Int], inflight: Vector[Int],
bytessent_per_msg: Map[String, Int], bytessent_per_msg: Map[String, Int],
bytesrecv_per_msg: Map[String, Int], bytesrecv_per_msg: Map[String, Int],
minfeefilter: Option[SatoshisPerKiloByte]) minfeefilter: Option[SatoshisPerKiloByte]
extends Peer { ) extends Peer {
override val addnode: Boolean = connection_type == "manual" override val addnode: Boolean = connection_type == "manual"
} }
@ -148,8 +148,8 @@ case class PeerV22(
minfeefilter: Option[SatoshisPerKiloByte], minfeefilter: Option[SatoshisPerKiloByte],
bip152_hb_to: Boolean, bip152_hb_to: Boolean,
bip152_hb_from: Boolean, bip152_hb_from: Boolean,
permissions: Vector[String]) permissions: Vector[String]
extends Peer { ) extends Peer {
override val addnode: Boolean = connection_type == "manual" override val addnode: Boolean = connection_type == "manual"
} }
@ -186,8 +186,8 @@ case class PeerNetworkInfoPreV21(
timeoffset: Int, timeoffset: Int,
pingtime: Option[BigDecimal], pingtime: Option[BigDecimal],
minping: Option[BigDecimal], minping: Option[BigDecimal],
pingwait: Option[BigDecimal]) pingwait: Option[BigDecimal]
extends PeerNetworkInfo ) extends PeerNetworkInfo
case class PeerNetworkInfoPostV21( case class PeerNetworkInfoPostV21(
addr: URI, addr: URI,
@ -208,8 +208,8 @@ case class PeerNetworkInfoPostV21(
timeoffset: Int, timeoffset: Int,
pingtime: Option[BigDecimal], pingtime: Option[BigDecimal],
minping: Option[BigDecimal], minping: Option[BigDecimal],
pingwait: Option[BigDecimal]) pingwait: Option[BigDecimal]
extends PeerNetworkInfo ) extends PeerNetworkInfo
trait NodeBan extends NetworkResult { trait NodeBan extends NetworkResult {
def address: URI def address: URI
@ -221,8 +221,8 @@ case class NodeBanPreV20(
address: URI, address: URI,
banned_until: UInt32, banned_until: UInt32,
ban_created: UInt32, ban_created: UInt32,
ban_reason: String) ban_reason: String
extends NodeBan ) extends NodeBan
case class NodeBanPostV22( case class NodeBanPostV22(
address: URI, address: URI,

View File

@ -40,8 +40,8 @@ case class GetBlockTemplateResult(
weightlimit: Int, weightlimit: Int,
curtime: UInt32, curtime: UInt32,
bits: String, // What should this be? bits: String, // What should this be?
height: Int) height: Int
extends OtherResult ) extends OtherResult
case class BlockTransaction( case class BlockTransaction(
data: Transaction, data: Transaction,
@ -51,8 +51,8 @@ case class BlockTransaction(
fee: Satoshis, fee: Satoshis,
sigops: Int, sigops: Int,
weight: Int, weight: Int,
required: Option[Boolean]) required: Option[Boolean]
extends OtherResult ) extends OtherResult
case class GetMiningInfoResult( case class GetMiningInfoResult(
blocks: Int, blocks: Int,
@ -62,8 +62,8 @@ case class GetMiningInfoResult(
networkhashps: BigDecimal, networkhashps: BigDecimal,
pooledtx: Int, pooledtx: Int,
chain: String, chain: String,
warnings: String) warnings: String
extends OtherResult ) extends OtherResult
case class GetMemoryInfoResult(locked: MemoryManager) extends OtherResult case class GetMemoryInfoResult(locked: MemoryManager) extends OtherResult
@ -75,14 +75,12 @@ case class MemoryManager(
total: Int, total: Int,
locked: Int, locked: Int,
chunks_used: Int, chunks_used: Int,
chunks_free: Int) chunks_free: Int
extends OtherResult ) extends OtherResult
/** @note This is defined as a trait /** @note
* and not just a raw case class * This is defined as a trait and not just a raw case class (as is done in
* (as is done in other RPC return * other RPC return values) in order to make it possible to deprecate fields.
* values) in order to make it possible
* to deprecate fields.
*/ */
trait ValidateAddressResult { trait ValidateAddressResult {
@ -149,14 +147,14 @@ case class ValidateAddressResultImpl(
hdmasterkeyid: Option[Sha256Hash160Digest], hdmasterkeyid: Option[Sha256Hash160Digest],
ischange: Option[Boolean], ischange: Option[Boolean],
solvable: Option[Boolean], solvable: Option[Boolean],
desc: Option[String]) desc: Option[String]
extends ValidateAddressResult ) extends ValidateAddressResult
case class EstimateSmartFeeResult( case class EstimateSmartFeeResult(
feerate: Option[BitcoinFeeUnit], feerate: Option[BitcoinFeeUnit],
errors: Option[Vector[String]], errors: Option[Vector[String]],
blocks: Int) blocks: Int
extends OtherResult ) extends OtherResult
case class TestMempoolAcceptResult( case class TestMempoolAcceptResult(
txid: DoubleSha256DigestBE, txid: DoubleSha256DigestBE,
@ -164,17 +162,12 @@ case class TestMempoolAcceptResult(
rejectReason: Option[String] rejectReason: Option[String]
) )
/** sealed trait TestMempoolAcceptResult { /** sealed trait TestMempoolAcceptResult { def txid: DoubleSha256DigestBE def
* def txid: DoubleSha256DigestBE * allowed: Boolean def rejectReason: Option[String] }
* def allowed: Boolean
* def rejectReason: Option[String]
* }
* *
* case class TestMempoolAcceptResultPreV22( * case class TestMempoolAcceptResultPreV22( txid: DoubleSha256DigestBE,
* txid: DoubleSha256DigestBE, * allowed: Boolean, rejectReason: Option[String] ) extends
* allowed: Boolean, * TestMempoolAcceptResult
* rejectReason: Option[String]
* ) extends TestMempoolAcceptResult
*/ */
case class FeeInfoTwo( case class FeeInfoTwo(

View File

@ -33,8 +33,8 @@ case class RpcTransactionV22(
locktime: UInt32, locktime: UInt32,
vin: Vector[TransactionInput], vin: Vector[TransactionInput],
vout: Vector[RpcTransactionOutputV22], vout: Vector[RpcTransactionOutputV22],
hex: Option[Transaction]) hex: Option[Transaction]
extends RpcTransaction ) extends RpcTransaction
sealed trait RpcTransactionOutput extends RawTransactionResult { sealed trait RpcTransactionOutput extends RawTransactionResult {
def value: Bitcoins def value: Bitcoins
@ -45,14 +45,14 @@ sealed trait RpcTransactionOutput extends RawTransactionResult {
case class RpcTransactionOutputPreV22( case class RpcTransactionOutputPreV22(
value: Bitcoins, value: Bitcoins,
n: Int, n: Int,
scriptPubKey: RpcScriptPubKeyPreV22) scriptPubKey: RpcScriptPubKeyPreV22
extends RpcTransactionOutput ) extends RpcTransactionOutput
case class RpcTransactionOutputV22( case class RpcTransactionOutputV22(
value: Bitcoins, value: Bitcoins,
n: Int, n: Int,
scriptPubKey: RpcScriptPubKeyPostV22) scriptPubKey: RpcScriptPubKeyPostV22
extends RpcTransactionOutput ) extends RpcTransactionOutput
sealed trait RpcScriptPubKey extends RawTransactionResult { sealed trait RpcScriptPubKey extends RawTransactionResult {
def asm: String def asm: String
@ -66,16 +66,16 @@ case class RpcScriptPubKeyPreV22(
hex: String, hex: String,
reqSigs: Option[Int], reqSigs: Option[Int],
scriptType: ScriptType, scriptType: ScriptType,
addresses: Option[Vector[BitcoinAddress]]) addresses: Option[Vector[BitcoinAddress]]
extends RpcScriptPubKey ) extends RpcScriptPubKey
case class RpcScriptPubKeyPostV22( case class RpcScriptPubKeyPostV22(
asm: String, asm: String,
hex: String, hex: String,
scriptType: ScriptType, scriptType: ScriptType,
addresses: Option[Vector[BitcoinAddress]], addresses: Option[Vector[BitcoinAddress]],
address: Option[BitcoinAddress]) address: Option[BitcoinAddress]
extends RpcScriptPubKey ) extends RpcScriptPubKey
sealed trait DecodeScriptResult extends RawTransactionResult { sealed trait DecodeScriptResult extends RawTransactionResult {
def asm: String def asm: String
@ -86,14 +86,14 @@ sealed trait DecodeScriptResult extends RawTransactionResult {
case class DecodeScriptResultV22( case class DecodeScriptResultV22(
asm: String, asm: String,
typeOfScript: Option[ScriptType], typeOfScript: Option[ScriptType],
p2sh: P2SHAddress) p2sh: P2SHAddress
extends DecodeScriptResult ) extends DecodeScriptResult
case class FundRawTransactionResult( case class FundRawTransactionResult(
hex: Transaction, hex: Transaction,
fee: Bitcoins, fee: Bitcoins,
changepos: Int) changepos: Int
extends RawTransactionResult ) extends RawTransactionResult
case class SignRawTransactionWithWalletResult( case class SignRawTransactionWithWalletResult(
hex: Transaction, hex: Transaction,
@ -131,8 +131,8 @@ case class GetRawTransactionResultV22(
blockhash: Option[DoubleSha256DigestBE], blockhash: Option[DoubleSha256DigestBE],
confirmations: Option[Int], confirmations: Option[Int],
time: Option[UInt32], time: Option[UInt32],
blocktime: Option[UInt32]) blocktime: Option[UInt32]
extends GetRawTransactionResult ) extends GetRawTransactionResult
case class GetRawTransactionVin( case class GetRawTransactionVin(
txid: Option[DoubleSha256DigestBE], txid: Option[DoubleSha256DigestBE],
@ -148,16 +148,16 @@ case class GetRawTransactionScriptSig(asm: String, hex: ScriptSignature)
case class SignRawTransactionResult( case class SignRawTransactionResult(
hex: Transaction, hex: Transaction,
complete: Boolean, complete: Boolean,
errors: Option[Vector[SignRawTransactionError]]) errors: Option[Vector[SignRawTransactionError]]
extends RawTransactionResult ) extends RawTransactionResult
case class SignRawTransactionError( case class SignRawTransactionError(
txid: DoubleSha256DigestBE, txid: DoubleSha256DigestBE,
vout: Int, vout: Int,
scriptSig: ScriptPubKey, scriptSig: ScriptPubKey,
sequence: UInt32, sequence: UInt32,
error: String) error: String
extends RawTransactionResult ) extends RawTransactionResult
final case class GetRpcInfoResult( final case class GetRpcInfoResult(
active_commands: Vector[RpcCommands] active_commands: Vector[RpcCommands]
@ -165,5 +165,5 @@ final case class GetRpcInfoResult(
final case class RpcCommands( final case class RpcCommands(
method: String, method: String,
duration: FiniteDuration //this time is in microseconds duration: FiniteDuration // this time is in microseconds
) extends RawTransactionResult ) extends RawTransactionResult

View File

@ -43,7 +43,8 @@ object RpcOpts {
lockUnspents: Boolean = false, lockUnspents: Boolean = false,
reverseChangeKey: Boolean = true, reverseChangeKey: Boolean = true,
feeRate: Option[Bitcoins] = None, feeRate: Option[Bitcoins] = None,
subtractFeeFromOutputs: Option[Vector[Int]]) subtractFeeFromOutputs: Option[Vector[Int]]
)
sealed abstract class FeeEstimationMode sealed abstract class FeeEstimationMode
@ -75,8 +76,9 @@ object RpcOpts {
} }
} }
implicit val fundRawTransactionOptionsWrites: Writes[ implicit val fundRawTransactionOptionsWrites
FundRawTransactionOptions] = Json.writes[FundRawTransactionOptions] : Writes[FundRawTransactionOptions] =
Json.writes[FundRawTransactionOptions]
case class SignRawTransactionOutputParameter( case class SignRawTransactionOutputParameter(
txid: DoubleSha256DigestBE, txid: DoubleSha256DigestBE,
@ -84,10 +86,11 @@ object RpcOpts {
scriptPubKey: ScriptPubKey, scriptPubKey: ScriptPubKey,
redeemScript: Option[ScriptPubKey] = None, redeemScript: Option[ScriptPubKey] = None,
witnessScript: Option[WitnessScriptPubKey] = None, witnessScript: Option[WitnessScriptPubKey] = None,
amount: Option[Bitcoins] = None) amount: Option[Bitcoins] = None
)
implicit val signRawTransactionOutputParameterWrites: Writes[ implicit val signRawTransactionOutputParameterWrites
SignRawTransactionOutputParameter] = : Writes[SignRawTransactionOutputParameter] =
Json.writes[SignRawTransactionOutputParameter] Json.writes[SignRawTransactionOutputParameter]
object SignRawTransactionOutputParameter { object SignRawTransactionOutputParameter {
@ -97,7 +100,8 @@ object RpcOpts {
scriptPubKey: ScriptPubKey, scriptPubKey: ScriptPubKey,
redeemScript: Option[ScriptPubKey] = None, redeemScript: Option[ScriptPubKey] = None,
witnessScript: Option[WitnessScriptPubKey] = None, witnessScript: Option[WitnessScriptPubKey] = None,
amount: Option[Bitcoins] = None): SignRawTransactionOutputParameter = { amount: Option[Bitcoins] = None
): SignRawTransactionOutputParameter = {
SignRawTransactionOutputParameter( SignRawTransactionOutputParameter(
txid = transactionInput.previousOutput.txIdBE, txid = transactionInput.previousOutput.txIdBE,
vout = transactionInput.previousOutput.vout.toInt, vout = transactionInput.previousOutput.vout.toInt,
@ -117,7 +121,8 @@ object RpcOpts {
keys: Option[Vector[ECPrivateKeyBytes]] = None, keys: Option[Vector[ECPrivateKeyBytes]] = None,
internal: Option[Boolean] = None, internal: Option[Boolean] = None,
watchonly: Option[Boolean] = None, watchonly: Option[Boolean] = None,
label: Option[String] = None) label: Option[String] = None
)
case class ImportMultiAddress(address: BitcoinAddress) case class ImportMultiAddress(address: BitcoinAddress)
@ -140,7 +145,8 @@ object RpcOpts {
object LockUnspentOutputParameter { object LockUnspentOutputParameter {
def fromOutPoint( def fromOutPoint(
outPoint: TransactionOutPoint): LockUnspentOutputParameter = { outPoint: TransactionOutPoint
): LockUnspentOutputParameter = {
LockUnspentOutputParameter(outPoint.txIdBE, outPoint.vout.toInt) LockUnspentOutputParameter(outPoint.txIdBE, outPoint.vout.toInt)
} }
@ -216,7 +222,9 @@ object RpcOpts {
override def fromString(string: String): AddressType = { override def fromString(string: String): AddressType = {
fromStringOpt(string).getOrElse( fromStringOpt(string).getOrElse(
throw new IllegalArgumentException( throw new IllegalArgumentException(
s"Could not find AddressType for string: $string")) s"Could not find AddressType for string: $string"
)
)
} }
} }
@ -236,7 +244,8 @@ object RpcOpts {
case class BlockTemplateRequest( case class BlockTemplateRequest(
mode: String, mode: String,
capabilities: Vector[String], capabilities: Vector[String],
rules: Vector[String]) rules: Vector[String]
)
implicit val blockTemplateRequest: Writes[BlockTemplateRequest] = implicit val blockTemplateRequest: Writes[BlockTemplateRequest] =
Json.writes[BlockTemplateRequest] Json.writes[BlockTemplateRequest]

View File

@ -35,8 +35,8 @@ final case class DecodePsbtResultV22(
unknown: Map[String, String], unknown: Map[String, String],
inputs: Vector[RpcPsbtInputV22], inputs: Vector[RpcPsbtInputV22],
outputs: Vector[RpcPsbtOutput], outputs: Vector[RpcPsbtOutput],
fee: Option[Bitcoins]) fee: Option[Bitcoins]
extends DecodePsbtResult ) extends DecodePsbtResult
sealed abstract class RpcPsbtInput extends RpcPsbtResult { sealed abstract class RpcPsbtInput extends RpcPsbtResult {
def nonWitnessUtxo: Option[RpcTransaction] def nonWitnessUtxo: Option[RpcTransaction]

View File

@ -30,15 +30,16 @@ case class MultiSigResultPostV20(
address: BitcoinAddress, address: BitcoinAddress,
redeemScript: ScriptPubKey, redeemScript: ScriptPubKey,
descriptor: String, descriptor: String,
warnings: Option[String]) //available in v23 warnings: Option[String]
) //available in v23
extends MultiSigResult extends MultiSigResult
case class BumpFeeResult( case class BumpFeeResult(
txid: DoubleSha256DigestBE, txid: DoubleSha256DigestBE,
origfee: Bitcoins, origfee: Bitcoins,
fee: Bitcoins, // TODO: Should be BitcoinFeeUnit fee: Bitcoins, // TODO: Should be BitcoinFeeUnit
errors: Vector[String]) errors: Vector[String]
extends WalletResult ) extends WalletResult
case class GetTransactionResult( case class GetTransactionResult(
amount: Bitcoins, amount: Bitcoins,
@ -56,14 +57,14 @@ case class GetTransactionResult(
comment: Option[String], comment: Option[String],
to: Option[String], to: Option[String],
details: Vector[TransactionDetails], details: Vector[TransactionDetails],
hex: Transaction) hex: Transaction
extends WalletResult ) extends WalletResult
case class SetWalletFlagResult( case class SetWalletFlagResult(
flag_name: String, flag_name: String,
flag_state: Boolean, flag_state: Boolean,
warnings: Option[String]) warnings: Option[String]
extends WalletResult ) extends WalletResult
case class GetBalancesResult(mine: BalanceInfo, watchonly: Option[BalanceInfo]) case class GetBalancesResult(mine: BalanceInfo, watchonly: Option[BalanceInfo])
extends WalletResult extends WalletResult
@ -71,7 +72,8 @@ case class GetBalancesResult(mine: BalanceInfo, watchonly: Option[BalanceInfo])
case class BalanceInfo( case class BalanceInfo(
trusted: Bitcoins, trusted: Bitcoins,
untrusted_pending: Bitcoins, untrusted_pending: Bitcoins,
immature: Bitcoins) immature: Bitcoins
)
case class TransactionDetails( case class TransactionDetails(
involvesWatchonly: Option[Boolean], involvesWatchonly: Option[Boolean],
@ -81,8 +83,8 @@ case class TransactionDetails(
amount: Bitcoins, amount: Bitcoins,
vout: Int, vout: Int,
fee: Option[Bitcoins], fee: Option[Bitcoins],
abandoned: Option[Boolean]) abandoned: Option[Boolean]
extends WalletResult ) extends WalletResult
sealed trait GetWalletInfoResult extends WalletResult { sealed trait GetWalletInfoResult extends WalletResult {
def walletname: String def walletname: String
@ -114,8 +116,8 @@ case class GetWalletInfoResultPostV22(
hdmasterkeyid: Option[Sha256Hash160Digest], hdmasterkeyid: Option[Sha256Hash160Digest],
unlocked_until: Option[Int], unlocked_until: Option[Int],
private_keys_enabled: Boolean, private_keys_enabled: Boolean,
descriptors: Boolean) descriptors: Boolean
extends GetWalletInfoResult ) extends GetWalletInfoResult
case class ImportMultiResult(success: Boolean, error: Option[ImportMultiError]) case class ImportMultiResult(success: Boolean, error: Option[ImportMultiError])
extends WalletResult extends WalletResult
@ -125,15 +127,15 @@ case class ImportMultiError(code: Int, message: String) extends WalletResult
case class RpcAddress( case class RpcAddress(
address: BitcoinAddress, address: BitcoinAddress,
balance: Bitcoins, balance: Bitcoins,
account: Option[String]) account: Option[String]
extends WalletResult ) extends WalletResult
case class RpcAccount( case class RpcAccount(
involvesWatchonly: Boolean, involvesWatchonly: Boolean,
account: String, account: String,
amount: Bitcoins, amount: Bitcoins,
confirmations: Int) confirmations: Int
extends WalletResult ) extends WalletResult
case class LoadWalletResult(name: String, warning: String) extends WalletResult case class LoadWalletResult(name: String, warning: String) extends WalletResult
case class RescanBlockChainResult(start_height: Int, stop_height: Int) case class RescanBlockChainResult(start_height: Int, stop_height: Int)
@ -146,28 +148,28 @@ case class ReceivedAddress(
amount: Bitcoins, amount: Bitcoins,
confirmations: Int, confirmations: Int,
label: String, label: String,
txids: Vector[DoubleSha256DigestBE]) txids: Vector[DoubleSha256DigestBE]
extends WalletResult ) extends WalletResult
case class ReceivedAccount( case class ReceivedAccount(
involvesWatchonly: Option[Boolean], involvesWatchonly: Option[Boolean],
account: String, account: String,
amount: Bitcoins, amount: Bitcoins,
confirmations: Int, confirmations: Int,
lable: Option[String]) lable: Option[String]
extends WalletResult ) extends WalletResult
case class ReceivedLabel( case class ReceivedLabel(
involvesWatchonly: Option[Boolean], involvesWatchonly: Option[Boolean],
amount: Bitcoins, amount: Bitcoins,
confirmations: Int, confirmations: Int,
label: String) label: String
extends WalletResult ) extends WalletResult
case class ListSinceBlockResult( case class ListSinceBlockResult(
transactions: Vector[Payment], transactions: Vector[Payment],
lastblock: DoubleSha256DigestBE) lastblock: DoubleSha256DigestBE
extends WalletResult ) extends WalletResult
case class Payment( case class Payment(
involvesWatchonly: Option[Boolean], involvesWatchonly: Option[Boolean],
@ -188,8 +190,8 @@ case class Payment(
timereceived: UInt32, timereceived: UInt32,
bip125_replaceable: String, bip125_replaceable: String,
comment: Option[String], comment: Option[String],
to: Option[String]) to: Option[String]
extends WalletResult ) extends WalletResult
case class ListTransactionsResult( case class ListTransactionsResult(
account: Option[String], account: Option[String],
@ -214,8 +216,8 @@ case class ListTransactionsResult(
to: Option[String], to: Option[String],
otheraccount: Option[String], otheraccount: Option[String],
bip125_replaceable: String, bip125_replaceable: String,
abandoned: Option[Boolean]) abandoned: Option[Boolean]
extends WalletResult ) extends WalletResult
case class UnspentOutput( case class UnspentOutput(
txid: DoubleSha256DigestBE, txid: DoubleSha256DigestBE,
@ -228,8 +230,8 @@ case class UnspentOutput(
confirmations: Int, confirmations: Int,
spendable: Boolean, spendable: Boolean,
solvable: Boolean, solvable: Boolean,
reused: Option[Boolean]) reused: Option[Boolean]
extends WalletResult ) extends WalletResult
sealed trait AddressInfoResult extends WalletResult { sealed trait AddressInfoResult extends WalletResult {
def address: BitcoinAddress def address: BitcoinAddress
@ -274,8 +276,8 @@ case class AddressInfoResultPreV18(
hdkeypath: Option[BIP32Path], hdkeypath: Option[BIP32Path],
hdseedid: Option[RipeMd160Digest], hdseedid: Option[RipeMd160Digest],
hdmasterkeyid: Option[RipeMd160Digest], hdmasterkeyid: Option[RipeMd160Digest],
labels: Vector[LabelResult]) labels: Vector[LabelResult]
extends AddressInfoResult ) extends AddressInfoResult
// The split into two case classes is to deal with the 22 param limit for case classes // The split into two case classes is to deal with the 22 param limit for case classes
case class AddressInfoResultPostV18( case class AddressInfoResultPostV18(
@ -297,8 +299,8 @@ case class AddressInfoResultPostV18(
hdkeypath: Option[BIP32Path], hdkeypath: Option[BIP32Path],
hdseedid: Option[RipeMd160Digest], hdseedid: Option[RipeMd160Digest],
hdmasterfingerprint: Option[String], hdmasterfingerprint: Option[String],
labels: Vector[LabelResult]) labels: Vector[LabelResult]
extends AddressInfoResult { ) extends AddressInfoResult {
override def ismine: Boolean = isProps.ismine override def ismine: Boolean = isProps.ismine
def solvable: Boolean = isProps.solvable def solvable: Boolean = isProps.solvable
override def iswatchonly: Boolean = isProps.iswatchonly override def iswatchonly: Boolean = isProps.iswatchonly
@ -315,7 +317,8 @@ object AddressInfoResultPostV18 {
iswatchonly: Boolean, iswatchonly: Boolean,
isscript: Boolean, isscript: Boolean,
iswitness: Boolean, iswitness: Boolean,
iscompressed: Option[Boolean]) iscompressed: Option[Boolean]
)
case class AddressInfoResultPostV18WithoutIsProps( case class AddressInfoResultPostV18WithoutIsProps(
address: BitcoinAddress, address: BitcoinAddress,
@ -335,11 +338,13 @@ object AddressInfoResultPostV18 {
hdkeypath: Option[BIP32Path], hdkeypath: Option[BIP32Path],
hdseedid: Option[RipeMd160Digest], hdseedid: Option[RipeMd160Digest],
hdmasterfingerprint: Option[String], hdmasterfingerprint: Option[String],
labels: Vector[LabelResult]) labels: Vector[LabelResult]
)
def apply( def apply(
info: AddressInfoResultPostV18WithoutIsProps, info: AddressInfoResultPostV18WithoutIsProps,
isProps: AddressInfoIsProps): AddressInfoResultPostV18 = { isProps: AddressInfoIsProps
): AddressInfoResultPostV18 = {
AddressInfoResultPostV18( AddressInfoResultPostV18(
address = info.address, address = info.address,
scriptPubKey = info.scriptPubKey, scriptPubKey = info.scriptPubKey,
@ -401,7 +406,8 @@ object AddressInfoResultPostV21 {
iswatchonly: Boolean, iswatchonly: Boolean,
isscript: Boolean, isscript: Boolean,
iswitness: Boolean, iswitness: Boolean,
iscompressed: Option[Boolean]) iscompressed: Option[Boolean]
)
case class AddressInfoResultPostV21WithoutIsProps( case class AddressInfoResultPostV21WithoutIsProps(
address: BitcoinAddress, address: BitcoinAddress,
@ -420,11 +426,13 @@ object AddressInfoResultPostV21 {
hdkeypath: Option[BIP32Path], hdkeypath: Option[BIP32Path],
hdseedid: Option[RipeMd160Digest], hdseedid: Option[RipeMd160Digest],
hdmasterfingerprint: Option[String], hdmasterfingerprint: Option[String],
labels: Vector[String]) labels: Vector[String]
)
def apply( def apply(
info: AddressInfoResultPostV21WithoutIsProps, info: AddressInfoResultPostV21WithoutIsProps,
isProps: AddressInfoIsProps): AddressInfoResultPostV21 = { isProps: AddressInfoIsProps
): AddressInfoResultPostV21 = {
AddressInfoResultPostV21( AddressInfoResultPostV21(
address = info.address, address = info.address,
scriptPubKey = info.scriptPubKey, scriptPubKey = info.scriptPubKey,
@ -474,8 +482,8 @@ case class EmbeddedResult(
witness_program: Option[String], witness_program: Option[String],
pubkey: ECPublicKey, pubkey: ECPublicKey,
address: BitcoinAddress, address: BitcoinAddress,
scriptPubKey: ScriptPubKey) scriptPubKey: ScriptPubKey
extends WalletResult ) extends WalletResult
case class LabelResult(name: String, purpose: LabelPurpose) extends WalletResult case class LabelResult(name: String, purpose: LabelPurpose) extends WalletResult
@ -494,5 +502,5 @@ final case class CreateWalletResult(
case class ImportDescriptorResult( case class ImportDescriptorResult(
success: Boolean, success: Boolean,
warnings: Option[Vector[String]]) warnings: Option[Vector[String]]
extends WalletResult ) extends WalletResult

View File

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

View File

@ -94,8 +94,8 @@ object CLightningJsonModels {
case class ListFundsResult( case class ListFundsResult(
outputs: Vector[Output], outputs: Vector[Output],
channels: Vector[ChannelFunds]) channels: Vector[ChannelFunds]
extends CLightningJsonModel ) extends CLightningJsonModel
case class Channel( case class Channel(
source: NodeId, source: NodeId,
@ -159,8 +159,8 @@ object CLightningJsonModels {
connected: Boolean, connected: Boolean,
features: ByteVector, features: ByteVector,
netaddr: Vector[String], netaddr: Vector[String],
channels: Vector[CLightningPeerChannel]) channels: Vector[CLightningPeerChannel]
extends CLightningJsonModel ) extends CLightningJsonModel
case class CLightningPeers(peers: Vector[CLightningPeer]) case class CLightningPeers(peers: Vector[CLightningPeer])
extends CLightningJsonModel extends CLightningJsonModel
@ -228,8 +228,8 @@ object CLightningJsonModels {
) extends CLightningJsonModel ) extends CLightningJsonModel
case class CLightningListInvoicesResult( case class CLightningListInvoicesResult(
invoices: Vector[CLightningLookupInvoiceResult]) invoices: Vector[CLightningLookupInvoiceResult]
extends CLightningJsonModel ) extends CLightningJsonModel
case class CLightningPsbtResult(signed_psbt: PSBT) extends CLightningJsonModel case class CLightningPsbtResult(signed_psbt: PSBT) extends CLightningJsonModel

View File

@ -33,22 +33,27 @@ case class GetInfoResult(
network: BitcoinNetwork, network: BitcoinNetwork,
blockHeight: Long, blockHeight: Long,
publicAddresses: Seq[InetSocketAddress], publicAddresses: Seq[InetSocketAddress],
instanceId: UUID) instanceId: UUID
)
case class PeerInfo( case class PeerInfo(
nodeId: NodeId, nodeId: NodeId,
state: PeerState, state: PeerState,
address: Option[String], address: Option[String],
channels: Int) channels: Int
)
case class ChannelCommandResult( case class ChannelCommandResult(
results: scala.collection.Map[ results: scala.collection.Map[Either[
Either[ShortChannelId, FundedChannelId], ShortChannelId,
State] FundedChannelId
],
State]
) )
case class UpdateRelayFeeResult( case class UpdateRelayFeeResult(
results: Map[Either[ShortChannelId, FundedChannelId], UpdateRelayFee]) results: Map[Either[ShortChannelId, FundedChannelId], UpdateRelayFee]
)
sealed trait UpdateRelayFee sealed trait UpdateRelayFee
@ -57,8 +62,8 @@ object UpdateRelayFee {
case class OK( case class OK(
channelId: ChannelId, channelId: ChannelId,
feeBaseMsat: MilliSatoshis, feeBaseMsat: MilliSatoshis,
feeProportionalMillionths: Long) feeProportionalMillionths: Long
extends UpdateRelayFee ) extends UpdateRelayFee
case class Error(message: String) extends UpdateRelayFee case class Error(message: String) extends UpdateRelayFee
} }
@ -84,12 +89,10 @@ object ChannelCommandResult extends StringFactory[State] {
} }
} }
/** This is the data model returned by the RPC call /** This is the data model returned by the RPC call `channels nodeId`. The
* `channels nodeId`. The content of the objects * content of the objects being returne differ based on whatever state the
* being returne differ based on whatever state * channel is in. The member of this abstract class are in eveyr channel state,
* the channel is in. The member of this abstract * whereas other channel states may have extra information.
* class are in eveyr channel state, whereas other
* channel states may have extra information.
*/ */
sealed abstract class ChannelInfo { sealed abstract class ChannelInfo {
def nodeId: NodeId def nodeId: NodeId
@ -111,8 +114,8 @@ case class BaseChannelInfo(
state: ChannelState state: ChannelState
) extends ChannelInfo ) extends ChannelInfo
/** This represents the case where the channel is /** This represents the case where the channel is in state `NORMAL` (i.e. an
* in state `NORMAL` (i.e. an open channel) * open channel)
*/ */
case class OpenChannelInfo( case class OpenChannelInfo(
nodeId: NodeId, nodeId: NodeId,
@ -129,7 +132,8 @@ case class UnknownFeature(bitIndex: Int)
case class Features( case class Features(
activated: Set[ActivatedFeature], activated: Set[ActivatedFeature],
unknown: Set[UnknownFeature]) unknown: Set[UnknownFeature]
)
case class NodeInfo( case class NodeInfo(
signature: ECDigitalSignature, signature: ECDigitalSignature,
@ -138,7 +142,8 @@ case class NodeInfo(
nodeId: NodeId, nodeId: NodeId,
rgbColor: String, rgbColor: String,
alias: String, alias: String,
addresses: Vector[InetSocketAddress]) addresses: Vector[InetSocketAddress]
)
case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId) case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId)
@ -154,7 +159,7 @@ case class NetworkFeesResult(
txId: DoubleSha256DigestBE, txId: DoubleSha256DigestBE,
fee: Satoshis, fee: Satoshis,
txType: String, txType: String,
timestamp: Instant //milliseconds timestamp: Instant // milliseconds
) )
case class ChannelStats( case class ChannelStats(
@ -189,7 +194,8 @@ case class RealChannelId(status: String, realScid: ShortChannelId)
case class ShortIds( case class ShortIds(
real: RealChannelId, real: RealChannelId,
localAlias: String, localAlias: String,
remoteAlias: String) remoteAlias: String
)
case class UsableBalancesResult( case class UsableBalancesResult(
remoteNodeId: NodeId, remoteNodeId: NodeId,
@ -210,7 +216,7 @@ object ReceivedPayment {
case class Part( case class Part(
amount: MilliSatoshis, amount: MilliSatoshis,
fromChannelId: FundedChannelId, fromChannelId: FundedChannelId,
timestamp: Instant //milliseconds timestamp: Instant // milliseconds
) )
} }
@ -220,7 +226,7 @@ case class RelayedPayment(
paymentHash: Sha256Digest, paymentHash: Sha256Digest,
fromChannelId: FundedChannelId, fromChannelId: FundedChannelId,
toChannelId: FundedChannelId, toChannelId: FundedChannelId,
timestamp: Instant //milliseconds timestamp: Instant // milliseconds
) )
case class SentPayment( case class SentPayment(
@ -239,7 +245,7 @@ object SentPayment {
amount: MilliSatoshis, amount: MilliSatoshis,
feesPaid: MilliSatoshis, feesPaid: MilliSatoshis,
toChannelId: FundedChannelId, toChannelId: FundedChannelId,
timestamp: Instant //milliseconds timestamp: Instant // milliseconds
) )
} }
@ -249,13 +255,14 @@ case class ChannelUpdate(
signature: ECDigitalSignature, signature: ECDigitalSignature,
chainHash: DoubleSha256Digest, chainHash: DoubleSha256Digest,
shortChannelId: ShortChannelId, shortChannelId: ShortChannelId,
timestamp: Instant, //seconds timestamp: Instant, // seconds
channelFlags: ChannelFlags, channelFlags: ChannelFlags,
cltvExpiryDelta: Int, cltvExpiryDelta: Int,
htlcMinimumMsat: MilliSatoshis, htlcMinimumMsat: MilliSatoshis,
feeProportionalMillionths: FeeProportionalMillionths, feeProportionalMillionths: FeeProportionalMillionths,
htlcMaximumMsat: Option[MilliSatoshis], htlcMaximumMsat: Option[MilliSatoshis],
feeBaseMsat: MilliSatoshis) feeBaseMsat: MilliSatoshis
)
case class ChannelResult( case class ChannelResult(
nodeId: NodeId, nodeId: NodeId,
@ -263,7 +270,8 @@ case class ChannelResult(
state: ChannelState, state: ChannelState,
feeBaseMsat: Option[MilliSatoshis], feeBaseMsat: Option[MilliSatoshis],
feeProportionalMillionths: Option[FeeProportionalMillionths], feeProportionalMillionths: Option[FeeProportionalMillionths],
data: JsObject) { data: JsObject
) {
lazy val shortChannelId: Option[ShortChannelId] = lazy val shortChannelId: Option[ShortChannelId] =
(data \ "shortIds" \ "real" \ "realScid").validate[ShortChannelId].asOpt (data \ "shortIds" \ "real" \ "realScid").validate[ShortChannelId].asOpt
@ -273,12 +281,13 @@ case class ChannelResult(
case class InvoiceResult( case class InvoiceResult(
prefix: LnHumanReadablePart, prefix: LnHumanReadablePart,
timestamp: Instant, //seconds timestamp: Instant, // seconds
nodeId: NodeId, nodeId: NodeId,
serialized: String, serialized: String,
description: String, description: String,
paymentHash: Sha256Digest, paymentHash: Sha256Digest,
expiry: FiniteDuration) expiry: FiniteDuration
)
case class PaymentId(value: UUID) { case class PaymentId(value: UUID) {
override def toString: String = value.toString override def toString: String = value.toString
@ -288,16 +297,17 @@ case class SendToRouteResult(paymentId: PaymentId, parentId: PaymentId)
case class PaymentRequest( case class PaymentRequest(
prefix: LnHumanReadablePart, prefix: LnHumanReadablePart,
timestamp: Instant, //seconds timestamp: Instant, // seconds
nodeId: NodeId, nodeId: NodeId,
serialized: String, serialized: String,
description: String, description: String,
paymentHash: Sha256Digest, paymentHash: Sha256Digest,
paymentMetadata: String, paymentMetadata: String,
expiry: FiniteDuration, //seconds expiry: FiniteDuration, // seconds
minFinalCltvExpiry: Int, minFinalCltvExpiry: Int,
amount: Option[MilliSatoshis], amount: Option[MilliSatoshis],
features: Features) features: Features
)
sealed trait PaymentType sealed trait PaymentType
@ -314,7 +324,7 @@ object PaymentType extends StringFactory[PaymentType] {
case "SwapIn" => SwapIn case "SwapIn" => SwapIn
case "SwapOut" => SwapOut case "SwapOut" => SwapOut
case "placeholder" => Placeholder case "placeholder" => Placeholder
case _ => throw new RuntimeException(s"Unknown payment type `$str`") case _ => throw new RuntimeException(s"Unknown payment type `$str`")
} }
} }
@ -328,16 +338,18 @@ case class OutgoingPayment(
amount: MilliSatoshis, amount: MilliSatoshis,
recipientAmount: MilliSatoshis, recipientAmount: MilliSatoshis,
recipientNodeId: NodeId, recipientNodeId: NodeId,
createdAt: Instant, //milliseconds createdAt: Instant, // milliseconds
paymentRequest: Option[PaymentRequest], paymentRequest: Option[PaymentRequest],
status: OutgoingPaymentStatus) status: OutgoingPaymentStatus
)
case class IncomingPayment( case class IncomingPayment(
paymentRequest: PaymentRequest, paymentRequest: PaymentRequest,
paymentPreimage: PaymentPreimage, paymentPreimage: PaymentPreimage,
paymentType: PaymentType, paymentType: PaymentType,
createdAt: Instant, //milliseconds createdAt: Instant, // milliseconds
status: IncomingPaymentStatus) status: IncomingPaymentStatus
)
sealed trait IncomingPaymentStatus sealed trait IncomingPaymentStatus
@ -349,7 +361,7 @@ object IncomingPaymentStatus {
case class Received( case class Received(
amount: MilliSatoshis, amount: MilliSatoshis,
receivedAt: Instant //milliseconds receivedAt: Instant // milliseconds
) extends IncomingPaymentStatus ) extends IncomingPaymentStatus
} }
@ -363,7 +375,7 @@ object OutgoingPaymentStatus {
paymentPreimage: PaymentPreimage, paymentPreimage: PaymentPreimage,
feesPaid: MilliSatoshis, feesPaid: MilliSatoshis,
route: Seq[Hop], route: Seq[Hop],
completedAt: Instant //milliseconds completedAt: Instant // milliseconds
) extends OutgoingPaymentStatus ) extends OutgoingPaymentStatus
case class Failed(failures: Seq[PaymentFailure], completedAt: Instant) case class Failed(failures: Seq[PaymentFailure], completedAt: Instant)
@ -373,7 +385,8 @@ object OutgoingPaymentStatus {
case class PaymentFailure( case class PaymentFailure(
failureType: PaymentFailure.Type, failureType: PaymentFailure.Type,
failureMessage: String, failureMessage: String,
failedRoute: Seq[Hop]) failedRoute: Seq[Hop]
)
object PaymentFailure { object PaymentFailure {
sealed trait Type sealed trait Type
@ -385,7 +398,8 @@ object PaymentFailure {
case class Hop( case class Hop(
nodeId: NodeId, nodeId: NodeId,
nextNodeId: NodeId, nextNodeId: NodeId,
shortChannelId: Option[ShortChannelId]) shortChannelId: Option[ShortChannelId]
)
sealed trait WebSocketEvent sealed trait WebSocketEvent
@ -397,7 +411,7 @@ object WebSocketEvent {
paymentHash: Sha256Digest, paymentHash: Sha256Digest,
fromChannelId: FundedChannelId, fromChannelId: FundedChannelId,
toChannelId: FundedChannelId, toChannelId: FundedChannelId,
timestamp: Instant //milliseconds timestamp: Instant // milliseconds
) extends WebSocketEvent ) extends WebSocketEvent
case class PaymentReceived( case class PaymentReceived(
@ -442,7 +456,7 @@ object WebSocketEvent {
case class PaymentSettlingOnchain( case class PaymentSettlingOnchain(
amount: MilliSatoshis, amount: MilliSatoshis,
paymentHash: Sha256Digest, paymentHash: Sha256Digest,
timestamp: Instant //milliseconds timestamp: Instant // milliseconds
) extends WebSocketEvent ) extends WebSocketEvent
} }
@ -456,4 +470,5 @@ case class WalletTransaction(
blockHash: DoubleSha256DigestBE, blockHash: DoubleSha256DigestBE,
confirmations: Long, confirmations: Long,
txid: DoubleSha256DigestBE, txid: DoubleSha256DigestBE,
timestamp: Long) timestamp: Long
)

View File

@ -16,8 +16,8 @@ case class AddInvoiceResult(
rHash: PaymentHashTag, rHash: PaymentHashTag,
invoice: LnInvoice, invoice: LnInvoice,
addIndex: UInt64, addIndex: UInt64,
paymentAddr: ByteVector) paymentAddr: ByteVector
extends LndModel ) extends LndModel
case class UTXOResult( case class UTXOResult(
address: BitcoinAddress, address: BitcoinAddress,

View File

@ -14,7 +14,9 @@ import ujson.Value
import java.net.InetSocketAddress import java.net.InetSocketAddress
/** The event type being sent over the websocket. An example is [[WalletWsType.NewAddress]] */ /** The event type being sent over the websocket. An example is
* [[WalletWsType.NewAddress]]
*/
sealed trait WsType sealed trait WsType
object WsType extends StringFactory[WsType] { object WsType extends StringFactory[WsType] {
@ -46,15 +48,17 @@ object WalletWsType extends StringFactory[WalletWsType] {
case object FeeRateChange extends WalletWsType case object FeeRateChange extends WalletWsType
private val all = private val all =
Vector(TxProcessed, Vector(
TxBroadcast, TxProcessed,
ReservedUtxos, TxBroadcast,
NewAddress, ReservedUtxos,
DLCStateChange, NewAddress,
DLCOfferAdd, DLCStateChange,
DLCOfferRemove, DLCOfferAdd,
RescanComplete, DLCOfferRemove,
FeeRateChange) RescanComplete,
FeeRateChange
)
override def fromStringOpt(string: String): Option[WalletWsType] = { override def fromStringOpt(string: String): Option[WalletWsType] = {
all.find(_.toString.toLowerCase() == string.toLowerCase) all.find(_.toString.toLowerCase() == string.toLowerCase)
@ -140,9 +144,9 @@ object DLCNodeWsType extends StringFactory[DLCNodeWsType] {
} }
} }
/** A notification that we send over the websocket. /** A notification that we send over the websocket. The type of the notification
* The type of the notification is indicated by [[WsType]]. * is indicated by [[WsType]]. An example is
* An example is [[org.bitcoins.commons.jsonmodels.ws.WalletNotification.NewAddressNotification]] * [[org.bitcoins.commons.jsonmodels.ws.WalletNotification.NewAddressNotification]]
* This sends a notification that the wallet generated a new address * This sends a notification that the wallet generated a new address
*/ */
sealed trait WsNotification[T] { sealed trait WsNotification[T] {
@ -263,13 +267,14 @@ object ChainNotification {
} }
case class CompactFilterHeaderProcessedNotification( case class CompactFilterHeaderProcessedNotification(
payload: CompactFilterHeaderDb) payload: CompactFilterHeaderDb
extends ChainNotification[CompactFilterHeaderDb] { ) extends ChainNotification[CompactFilterHeaderDb] {
override val `type`: ChainWsType = ChainWsType.CompactFilterHeaderProcessed override val `type`: ChainWsType = ChainWsType.CompactFilterHeaderProcessed
override val json: ujson.Value = { override val json: ujson.Value = {
upickle.default.writeJs(this)( upickle.default.writeJs(this)(
WsPicklers.compactFilterHeaderProcessedPickler) WsPicklers.compactFilterHeaderProcessedPickler
)
} }
} }
@ -311,7 +316,8 @@ object DLCNodeNotification {
override def `type`: DLCNodeWsType = DLCNodeWsType.DLCConnectionInitiated override def `type`: DLCNodeWsType = DLCNodeWsType.DLCConnectionInitiated
override def json: Value = upickle.default.writeJs(this)( override def json: Value = upickle.default.writeJs(this)(
WsPicklers.dlcNodeConnectionInitiatedPickler) WsPicklers.dlcNodeConnectionInitiatedPickler
)
} }
case class DLCNodeConnectionEstablished(payload: InetSocketAddress) case class DLCNodeConnectionEstablished(payload: InetSocketAddress)
@ -319,7 +325,8 @@ object DLCNodeNotification {
override def `type`: DLCNodeWsType = DLCNodeWsType.DLCConnectionEstablished override def `type`: DLCNodeWsType = DLCNodeWsType.DLCConnectionEstablished
override def json: Value = upickle.default.writeJs(this)( override def json: Value = upickle.default.writeJs(this)(
WsPicklers.dlcNodeConnectionEstablishedPickler) WsPicklers.dlcNodeConnectionEstablishedPickler
)
} }
case class DLCNodeConnectionFailed(payload: InetSocketAddress) case class DLCNodeConnectionFailed(payload: InetSocketAddress)

View File

@ -51,21 +51,21 @@ import scala.util.{Failure, Success, Try}
object JsonReaders { object JsonReaders {
/** Tries to prase the provided JSON into a map with keys of /** Tries to prase the provided JSON into a map with keys of type `K` and
* type `K` and values of type `V` * values of type `V`
*/ */
def mapReads[K, V](js: JsValue)(implicit def mapReads[K, V](
readsK: Reads[K], js: JsValue
readsV: Reads[V]): JsResult[Map[K, V]] = { )(implicit readsK: Reads[K], readsV: Reads[V]): JsResult[Map[K, V]] = {
js.validate[JsObject].flatMap { jsObj => js.validate[JsObject].flatMap { jsObj =>
val jsResults: scala.collection.Seq[(JsResult[K], JsResult[V])] = val jsResults: scala.collection.Seq[(JsResult[K], JsResult[V])] =
jsObj.fields.map { case (key, value) => jsObj.fields.map { case (key, value) =>
JsString(key).validate[K] -> value.validate[V] JsString(key).validate[K] -> value.validate[V]
} }
val allErrors: scala.collection.Seq[( val allErrors: scala.collection.Seq[
JsPath, (JsPath, scala.collection.Seq[JsonValidationError])
scala.collection.Seq[JsonValidationError])] = ] =
jsResults.collect { jsResults.collect {
case (JsError(keyErrors), _) => keyErrors case (JsError(keyErrors), _) => keyErrors
case (_, JsError(valueErrors)) => valueErrors case (_, JsError(valueErrors)) => valueErrors
@ -91,16 +91,20 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[ZonedDateTime] = override def reads(json: JsValue): JsResult[ZonedDateTime] =
SerializerUtil.processJsNumberBigInt[ZonedDateTime](bigInt => SerializerUtil.processJsNumberBigInt[ZonedDateTime](bigInt =>
ZonedDateTime.ofInstant(Instant.ofEpochSecond(bigInt.toLong), ZonedDateTime.ofInstant(
ZoneOffset.UTC))(json) Instant.ofEpochSecond(bigInt.toLong),
ZoneOffset.UTC
))(json)
} }
implicit object LocalDateTimeReads extends Reads[LocalDateTime] { implicit object LocalDateTimeReads extends Reads[LocalDateTime] {
override def reads(json: JsValue): JsResult[LocalDateTime] = override def reads(json: JsValue): JsResult[LocalDateTime] =
SerializerUtil.processJsNumberBigInt[LocalDateTime](bigInt => SerializerUtil.processJsNumberBigInt[LocalDateTime](bigInt =>
LocalDateTime.ofInstant(Instant.ofEpochSecond(bigInt.toLong), LocalDateTime.ofInstant(
ZoneId.systemDefault()))(json) Instant.ofEpochSecond(bigInt.toLong),
ZoneId.systemDefault()
))(json)
} }
implicit object BigIntReads extends Reads[BigInt] { implicit object BigIntReads extends Reads[BigInt] {
@ -119,21 +123,24 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[RipeMd160Digest] = override def reads(json: JsValue): JsResult[RipeMd160Digest] =
SerializerUtil.processJsString[RipeMd160Digest](RipeMd160Digest.fromHex)( SerializerUtil.processJsString[RipeMd160Digest](RipeMd160Digest.fromHex)(
json) json
)
} }
implicit object RipeMd160DigestBEReads extends Reads[RipeMd160DigestBE] { implicit object RipeMd160DigestBEReads extends Reads[RipeMd160DigestBE] {
override def reads(json: JsValue): JsResult[RipeMd160DigestBE] = override def reads(json: JsValue): JsResult[RipeMd160DigestBE] =
SerializerUtil.processJsString[RipeMd160DigestBE]( SerializerUtil.processJsString[RipeMd160DigestBE](
RipeMd160DigestBE.fromHex)(json) RipeMd160DigestBE.fromHex
)(json)
} }
implicit object DoubleSha256DigestReads extends Reads[DoubleSha256Digest] { implicit object DoubleSha256DigestReads extends Reads[DoubleSha256Digest] {
override def reads(json: JsValue): JsResult[DoubleSha256Digest] = override def reads(json: JsValue): JsResult[DoubleSha256Digest] =
SerializerUtil.processJsString[DoubleSha256Digest]( SerializerUtil.processJsString[DoubleSha256Digest](
DoubleSha256Digest.fromHex)(json) DoubleSha256Digest.fromHex
)(json)
} }
implicit object DoubleSha256DigestBEReads implicit object DoubleSha256DigestBEReads
@ -141,7 +148,8 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[DoubleSha256DigestBE] = override def reads(json: JsValue): JsResult[DoubleSha256DigestBE] =
SerializerUtil.processJsString[DoubleSha256DigestBE]( SerializerUtil.processJsString[DoubleSha256DigestBE](
DoubleSha256DigestBE.fromHex)(json) DoubleSha256DigestBE.fromHex
)(json)
} }
implicit object BitcoinsReads extends Reads[Bitcoins] { implicit object BitcoinsReads extends Reads[Bitcoins] {
@ -161,7 +169,8 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[Satoshis] = override def reads(json: JsValue): JsResult[Satoshis] =
SerializerUtil.processJsNumber[Satoshis](num => Satoshis(num.toBigInt))( SerializerUtil.processJsNumber[Satoshis](num => Satoshis(num.toBigInt))(
json) json
)
} }
implicit object BlockHeaderReads extends Reads[BlockHeader] { implicit object BlockHeaderReads extends Reads[BlockHeader] {
@ -266,8 +275,10 @@ object JsonReaders {
case JsNumber(num) if num != 0 => case JsNumber(num) if num != 0 =>
SerializerUtil.buildErrorMsg("Expected witness_version 0", num) SerializerUtil.buildErrorMsg("Expected witness_version 0", num)
case err => case err =>
SerializerUtil.buildErrorMsg("Expected numerical witness_version", SerializerUtil.buildErrorMsg(
err) "Expected numerical witness_version",
err
)
} }
} }
@ -316,21 +327,24 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[ScriptPubKey] = override def reads(json: JsValue): JsResult[ScriptPubKey] =
SerializerUtil.processJsString[ScriptPubKey](ScriptPubKey.fromAsmHex)( SerializerUtil.processJsString[ScriptPubKey](ScriptPubKey.fromAsmHex)(
json) json
)
} }
implicit object ScriptWitnessReads extends Reads[ScriptWitness] { implicit object ScriptWitnessReads extends Reads[ScriptWitness] {
override def reads(json: JsValue): JsResult[ScriptWitness] = override def reads(json: JsValue): JsResult[ScriptWitness] =
SerializerUtil.processJsStringOpt[ScriptWitness]( SerializerUtil.processJsStringOpt[ScriptWitness](
ScriptWitness.fromHexOpt)(json) ScriptWitness.fromHexOpt
)(json)
} }
implicit object DescriptorReads extends Reads[Descriptor] { implicit object DescriptorReads extends Reads[Descriptor] {
override def reads(json: JsValue): JsResult[Descriptor] = { override def reads(json: JsValue): JsResult[Descriptor] = {
SerializerUtil.processJsStringOpt[Descriptor](Descriptor.fromStringOpt)( SerializerUtil.processJsStringOpt[Descriptor](Descriptor.fromStringOpt)(
json) json
)
} }
} }
@ -344,7 +358,8 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[Sha256Hash160Digest] = override def reads(json: JsValue): JsResult[Sha256Hash160Digest] =
SerializerUtil.processJsString[Sha256Hash160Digest]( SerializerUtil.processJsString[Sha256Hash160Digest](
Sha256Hash160Digest.fromHex)(json) Sha256Hash160Digest.fromHex
)(json)
} }
implicit object ECPublicKeyReads extends Reads[ECPublicKey] { implicit object ECPublicKeyReads extends Reads[ECPublicKey] {
@ -357,14 +372,16 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[ECPublicKeyBytes] = override def reads(json: JsValue): JsResult[ECPublicKeyBytes] =
SerializerUtil.processJsString[ECPublicKeyBytes]( SerializerUtil.processJsString[ECPublicKeyBytes](
ECPublicKeyBytes.fromHex)(json) ECPublicKeyBytes.fromHex
)(json)
} }
implicit object SchnorrPublicKeyReads extends Reads[SchnorrPublicKey] { implicit object SchnorrPublicKeyReads extends Reads[SchnorrPublicKey] {
override def reads(json: JsValue): JsResult[SchnorrPublicKey] = override def reads(json: JsValue): JsResult[SchnorrPublicKey] =
SerializerUtil.processJsString[SchnorrPublicKey]( SerializerUtil.processJsString[SchnorrPublicKey](
SchnorrPublicKey.fromHex)(json) SchnorrPublicKey.fromHex
)(json)
} }
implicit object SchnorrNonceReads extends Reads[SchnorrNonce] { implicit object SchnorrNonceReads extends Reads[SchnorrNonce] {
@ -378,7 +395,8 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[SchnorrDigitalSignature] = override def reads(json: JsValue): JsResult[SchnorrDigitalSignature] =
SerializerUtil.processJsString[SchnorrDigitalSignature]( SerializerUtil.processJsString[SchnorrDigitalSignature](
SchnorrDigitalSignature.fromHex)(json) SchnorrDigitalSignature.fromHex
)(json)
} }
implicit object FieldElementReads extends Reads[FieldElement] { implicit object FieldElementReads extends Reads[FieldElement] {
@ -423,7 +441,8 @@ object JsonReaders {
override def reads(json: JsValue): JsResult[ScriptSignature] = override def reads(json: JsValue): JsResult[ScriptSignature] =
SerializerUtil.processJsString[ScriptSignature]( SerializerUtil.processJsString[ScriptSignature](
ScriptSignature.fromAsmHex)(json) ScriptSignature.fromAsmHex
)(json)
} }
implicit object TransactionInputReads extends Reads[TransactionInput] { implicit object TransactionInputReads extends Reads[TransactionInput] {
@ -433,7 +452,8 @@ object JsonReaders {
(json \ "coinbase").validate[String] match { (json \ "coinbase").validate[String] match {
case s: JsSuccess[String] => case s: JsSuccess[String] =>
JsSuccess( JsSuccess(
CoinbaseInput(ScriptSignature.fromAsmHex(s.value), sequence)) CoinbaseInput(ScriptSignature.fromAsmHex(s.value), sequence)
)
case _ => case _ =>
(json \ "txid").validate[DoubleSha256DigestBE].flatMap { txid => (json \ "txid").validate[DoubleSha256DigestBE].flatMap { txid =>
(json \ "vout").validate[UInt32].flatMap { vout => (json \ "vout").validate[UInt32].flatMap { vout =>
@ -441,9 +461,12 @@ object JsonReaders {
.validate[ScriptSignature] .validate[ScriptSignature]
.flatMap { scriptSig => .flatMap { scriptSig =>
JsSuccess( JsSuccess(
TransactionInput(TransactionOutPoint(txid.flip, vout), TransactionInput(
scriptSig, TransactionOutPoint(txid.flip, vout),
sequence)) scriptSig,
sequence
)
)
} }
} }
} }
@ -510,7 +533,8 @@ object JsonReaders {
case Some(JsNumber(n)) => JsSuccess(Bitcoins(n)) case Some(JsNumber(n)) => JsSuccess(Bitcoins(n))
case Some( case Some(
err @ (JsNull | _: JsBoolean | _: JsString | _: JsArray | err @ (JsNull | _: JsBoolean | _: JsString | _: JsArray |
_: JsObject)) => _: JsObject)
) =>
SerializerUtil.buildJsErrorMsg("jsnumber", err) SerializerUtil.buildJsErrorMsg("jsnumber", err)
case None => JsError("error.expected.balance") case None => JsError("error.expected.balance")
} }
@ -605,9 +629,11 @@ object JsonReaders {
pubkey <- (json \ "pubkey").validate[ECPublicKey] pubkey <- (json \ "pubkey").validate[ECPublicKey]
masterFingerprint <- (json \ "master_fingerprint").validate[String] masterFingerprint <- (json \ "master_fingerprint").validate[String]
path <- (json \ "path").validate[String] path <- (json \ "path").validate[String]
} yield PsbtBIP32Deriv(pubkey = pubkey, } yield PsbtBIP32Deriv(
masterFingerprint = masterFingerprint, pubkey = pubkey,
path = path) masterFingerprint = masterFingerprint,
path = path
)
} }
implicit object RpcPsbtScriptReads extends Reads[RpcPsbtScript] { implicit object RpcPsbtScriptReads extends Reads[RpcPsbtScript] {
@ -618,19 +644,24 @@ object JsonReaders {
hex <- (json \ "hex").validate[ScriptPubKey] hex <- (json \ "hex").validate[ScriptPubKey]
scriptType <- (json \ "type").validateOpt[ScriptType] scriptType <- (json \ "type").validateOpt[ScriptType]
address <- (json \ "address").validateOpt[BitcoinAddress] address <- (json \ "address").validateOpt[BitcoinAddress]
} yield RpcPsbtScript(asm = asm, } yield RpcPsbtScript(
hex = hex, asm = asm,
scriptType = scriptType, hex = hex,
address = address) scriptType = scriptType,
address = address
)
} }
implicit object MapPubKeySignatureReads implicit object MapPubKeySignatureReads
extends Reads[Map[ECPublicKey, ECDigitalSignature]] { extends Reads[Map[ECPublicKey, ECDigitalSignature]] {
override def reads( override def reads(
json: JsValue): JsResult[Map[ECPublicKey, ECDigitalSignature]] = json: JsValue
JsonReaders.mapReads(json)(implicitly[Reads[ECPublicKey]], ): JsResult[Map[ECPublicKey, ECDigitalSignature]] =
implicitly[Reads[ECDigitalSignature]]) JsonReaders.mapReads(json)(
implicitly[Reads[ECPublicKey]],
implicitly[Reads[ECDigitalSignature]]
)
} }
implicit object RpcPsbtInputV22Reads extends Reads[RpcPsbtInputV22] { implicit object RpcPsbtInputV22Reads extends Reads[RpcPsbtInputV22] {
@ -740,7 +771,8 @@ object JsonReaders {
case Failure(err) => case Failure(err) =>
SerializerUtil.buildJsErrorMsg( SerializerUtil.buildJsErrorMsg(
s"Unexpected Service Identifier: $err", s"Unexpected Service Identifier: $err",
json) json
)
} }
case err @ (JsNull | _: JsBoolean | _: JsNumber | _: JsArray | case err @ (JsNull | _: JsBoolean | _: JsNumber | _: JsArray |
_: JsObject) => _: JsObject) =>
@ -759,7 +791,8 @@ object JsonReaders {
case Failure(err) => case Failure(err) =>
SerializerUtil.buildJsErrorMsg( SerializerUtil.buildJsErrorMsg(
s"Unexpected Service Identifier: $err", s"Unexpected Service Identifier: $err",
json) json
)
} }
case err @ (JsNull | _: JsBoolean | _: JsNumber | _: JsArray | case err @ (JsNull | _: JsBoolean | _: JsNumber | _: JsArray |
_: JsObject) => _: JsObject) =>
@ -778,7 +811,8 @@ object JsonReaders {
case Failure(err) => case Failure(err) =>
SerializerUtil.buildJsErrorMsg( SerializerUtil.buildJsErrorMsg(
s"Unexpected Service Identifier: $err", s"Unexpected Service Identifier: $err",
json) json
)
} }
case err @ (JsNull | _: JsBoolean | _: JsNumber | _: JsArray | case err @ (JsNull | _: JsBoolean | _: JsNumber | _: JsArray |
_: JsObject) => _: JsObject) =>
@ -786,10 +820,11 @@ object JsonReaders {
} }
} }
implicit val feeProportionalMillionthsReads: Reads[ implicit val feeProportionalMillionthsReads
FeeProportionalMillionths] = Reads { js => : Reads[FeeProportionalMillionths] = Reads { js =>
SerializerUtil.processJsNumberBigInt(FeeProportionalMillionths.fromBigInt)( SerializerUtil.processJsNumberBigInt(FeeProportionalMillionths.fromBigInt)(
js) js
)
} }
implicit val channelStateReads: Reads[ChannelState] = { implicit val channelStateReads: Reads[ChannelState] = {
@ -845,7 +880,8 @@ object JsonReaders {
implicit val lnHrpReads: Reads[LnHumanReadablePart] = { implicit val lnHrpReads: Reads[LnHumanReadablePart] = {
Reads { jsValue => Reads { jsValue =>
SerializerUtil.processJsStringOpt(LnHumanReadablePart.fromStringOpt(_))( SerializerUtil.processJsStringOpt(LnHumanReadablePart.fromStringOpt(_))(
jsValue) jsValue
)
} }
} }
@ -861,7 +897,8 @@ object JsonReaders {
addr.split(":") match { addr.split(":") match {
case Array(host, portStr) => case Array(host, portStr) =>
val port = Try(portStr.toInt).getOrElse( val port = Try(portStr.toInt).getOrElse(
throw new RuntimeException(s"Invalid port number `$portStr`")) throw new RuntimeException(s"Invalid port number `$portStr`")
)
InetSocketAddress.createUnresolved(host, port.toInt) InetSocketAddress.createUnresolved(host, port.toInt)
case _ => throw new RuntimeException(s"Invalid inet address `$addr`") case _ => throw new RuntimeException(s"Invalid inet address `$addr`")
} }
@ -922,7 +959,8 @@ object JsonReaders {
implicit val shortChannelIdReads: Reads[ShortChannelId] = { implicit val shortChannelIdReads: Reads[ShortChannelId] = {
Reads { jsValue => Reads { jsValue =>
SerializerUtil.processJsString(ShortChannelId.fromHumanReadableString)( SerializerUtil.processJsString(ShortChannelId.fromHumanReadableString)(
jsValue) jsValue
)
} }
} }
@ -943,13 +981,15 @@ object JsonReaders {
rgbColor <- (jsValue \ "rgbColor").validate[String] rgbColor <- (jsValue \ "rgbColor").validate[String]
alias <- (jsValue \ "alias").validate[String] alias <- (jsValue \ "alias").validate[String]
addresses <- (jsValue \ "addresses").validate[Vector[InetSocketAddress]] addresses <- (jsValue \ "addresses").validate[Vector[InetSocketAddress]]
} yield NodeInfo(signature, } yield NodeInfo(
features, signature,
timestamp, features,
nodeId, timestamp,
rgbColor, nodeId,
alias, rgbColor,
addresses) alias,
addresses
)
} }
} }
@ -986,13 +1026,15 @@ object JsonReaders {
description <- (jsValue \ "description").validate[String] description <- (jsValue \ "description").validate[String]
paymentHash <- (jsValue \ "paymentHash").validate[Sha256Digest] paymentHash <- (jsValue \ "paymentHash").validate[Sha256Digest]
expiry <- (jsValue \ "expiry").validate[Long] expiry <- (jsValue \ "expiry").validate[Long]
} yield InvoiceResult(prefix, } yield InvoiceResult(
timestamp, prefix,
nodeId, timestamp,
serialized, nodeId,
description, serialized,
paymentHash, description,
expiry.seconds) paymentHash,
expiry.seconds
)
} }
} }
@ -1010,12 +1052,14 @@ object JsonReaders {
(jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal") (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal")
.validate[MilliSatoshis] .validate[MilliSatoshis]
} yield OpenChannelInfo(nodeId = nodeId, } yield OpenChannelInfo(
shortChannelId = shortChannelId, nodeId = nodeId,
channelId = channelId, shortChannelId = shortChannelId,
localMsat = localMsat, channelId = channelId,
remoteMsat = remoteMsat, localMsat = localMsat,
state = state) remoteMsat = remoteMsat,
state = state
)
} }
implicit val baseChannelInfoReads: Reads[BaseChannelInfo] = Reads { jsValue => implicit val baseChannelInfoReads: Reads[BaseChannelInfo] = Reads { jsValue =>
@ -1030,11 +1074,13 @@ object JsonReaders {
(jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal") (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal")
.validate[MilliSatoshis] .validate[MilliSatoshis]
} yield BaseChannelInfo(nodeId = nodeId, } yield BaseChannelInfo(
channelId = channelId, nodeId = nodeId,
localMsat = localMsat, channelId = channelId,
remoteMsat = remoteMsat, localMsat = localMsat,
state = state) remoteMsat = remoteMsat,
state = state
)
} }
implicit val channelInfoReads: Reads[ChannelInfo] = Reads { jsValue => implicit val channelInfoReads: Reads[ChannelInfo] = Reads { jsValue =>
@ -1078,12 +1124,14 @@ object JsonReaders {
val result = Try( val result = Try(
UpdateRelayFee.OK( UpdateRelayFee.OK(
channelId = FundedChannelId.fromHex( channelId = FundedChannelId.fromHex(
(x._2 \ "channelId").validate[String].get), (x._2 \ "channelId").validate[String].get
),
feeBaseMsat = feeBaseMsat =
(x._2 \ "cmd" \ "feeBase").validate[MilliSatoshis].get, (x._2 \ "cmd" \ "feeBase").validate[MilliSatoshis].get,
feeProportionalMillionths = feeProportionalMillionths =
(x._2 \ "cmd" \ "feeProportionalMillionths").validate[Long].get (x._2 \ "cmd" \ "feeProportionalMillionths").validate[Long].get
)) match { )
) match {
case Success(ok) => ok case Success(ok) => ok
case Failure(_) => UpdateRelayFee.Error(x._2.toString()) case Failure(_) => UpdateRelayFee.Error(x._2.toString())
} }
@ -1136,13 +1184,13 @@ object JsonReaders {
implicit val sendToRouteResultReads: Reads[SendToRouteResult] = implicit val sendToRouteResultReads: Reads[SendToRouteResult] =
Json.reads[SendToRouteResult] Json.reads[SendToRouteResult]
//don't make this implicit so we don't accidentally read the wrong time unit // don't make this implicit so we don't accidentally read the wrong time unit
val finiteDurationReadsMilliseconds: Reads[FiniteDuration] = val finiteDurationReadsMilliseconds: Reads[FiniteDuration] =
Reads { js => Reads { js =>
SerializerUtil.processJsNumberBigInt(_.longValue.millis)(js) SerializerUtil.processJsNumberBigInt(_.longValue.millis)(js)
} }
//don't make this implicit so we don't accidentally read the wrong time unit // don't make this implicit so we don't accidentally read the wrong time unit
val finiteDurationReadsSeconds: Reads[FiniteDuration] = Reads { js => val finiteDurationReadsSeconds: Reads[FiniteDuration] = Reads { js =>
SerializerUtil.processJsNumberBigInt(_.longValue.seconds)(js) SerializerUtil.processJsNumberBigInt(_.longValue.seconds)(js)
} }
@ -1182,10 +1230,12 @@ object JsonReaders {
route <- (js \ "route").validate[Seq[Hop]] route <- (js \ "route").validate[Seq[Hop]]
completed <- (js \ "completedAt" \ "unix") completed <- (js \ "completedAt" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield OutgoingPaymentStatus.Succeeded(paymentPreimage = preimage, } yield OutgoingPaymentStatus.Succeeded(
feesPaid = feesPaid, paymentPreimage = preimage,
route = route, feesPaid = feesPaid,
completedAt = completed) route = route,
completedAt = completed
)
} }
implicit val paymentFailureTypeReads: Reads[PaymentFailure.Type] = Reads { implicit val paymentFailureTypeReads: Reads[PaymentFailure.Type] = Reads {
@ -1247,17 +1297,19 @@ object JsonReaders {
amount <- (js \ "amount").validateOpt[MilliSatoshis] amount <- (js \ "amount").validateOpt[MilliSatoshis]
minFinalCltvExpiry <- (js \ "minFinalCltvExpiry").validate[Int] minFinalCltvExpiry <- (js \ "minFinalCltvExpiry").validate[Int]
features <- (js \ "features").validate[Features] features <- (js \ "features").validate[Features]
} yield PaymentRequest(prefix, } yield PaymentRequest(
timestamp, prefix,
nodeId, timestamp,
serialized, nodeId,
description, serialized,
paymentHash, description,
paymentMetadata, paymentHash,
expiry, paymentMetadata,
minFinalCltvExpiry, expiry,
amount, minFinalCltvExpiry,
features) amount,
features
)
} }
implicit val paymentSucceededReads: Reads[OutgoingPayment] = Reads { js => implicit val paymentSucceededReads: Reads[OutgoingPayment] = Reads { js =>
@ -1274,17 +1326,19 @@ object JsonReaders {
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
paymentRequest <- (js \ "paymentRequest").validateOpt[PaymentRequest] paymentRequest <- (js \ "paymentRequest").validateOpt[PaymentRequest]
status <- (js \ "status").validate[OutgoingPaymentStatus] status <- (js \ "status").validate[OutgoingPaymentStatus]
} yield OutgoingPayment(id, } yield OutgoingPayment(
parentId, id,
externalId, parentId,
paymentHash, externalId,
paymentType, paymentHash,
amount, paymentType,
recipientAmount, amount,
recipientNodeId, recipientAmount,
createdAt, recipientNodeId,
paymentRequest, createdAt,
status) paymentRequest,
status
)
} }
implicit val receivedPaymentResultReads: Reads[IncomingPayment] = Reads { implicit val receivedPaymentResultReads: Reads[IncomingPayment] = Reads {
@ -1296,11 +1350,13 @@ object JsonReaders {
createdAt <- (js \ "createdAt" \ "unix") createdAt <- (js \ "createdAt" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
status <- (js \ "status").validate[IncomingPaymentStatus] status <- (js \ "status").validate[IncomingPaymentStatus]
} yield IncomingPayment(paymentRequest, } yield IncomingPayment(
paymentPreimage, paymentRequest,
paymentType, paymentPreimage,
createdAt, paymentType,
status) createdAt,
status
)
} }
implicit val channelResultReads: Reads[ChannelResult] = Reads { js => implicit val channelResultReads: Reads[ChannelResult] = Reads { js =>
@ -1314,12 +1370,14 @@ object JsonReaders {
(js \ "data" \ "channelUpdate" \ "feeProportionalMillionths") (js \ "data" \ "channelUpdate" \ "feeProportionalMillionths")
.validateOpt[FeeProportionalMillionths] .validateOpt[FeeProportionalMillionths]
data <- (js \ "data").validate[JsObject] data <- (js \ "data").validate[JsObject]
} yield ChannelResult(nodeId = nodeId, } yield ChannelResult(
state = state, nodeId = nodeId,
channelId = channelId, state = state,
feeBaseMsat = feeBaseMsat, channelId = channelId,
feeProportionalMillionths = feeProportional, feeBaseMsat = feeBaseMsat,
data = data) feeProportionalMillionths = feeProportional,
data = data
)
} }
implicit val lnInvoiceReads: Reads[LnInvoice] = implicit val lnInvoiceReads: Reads[LnInvoice] =
@ -1370,12 +1428,14 @@ object JsonReaders {
toChannelId <- (js \ "toChannelId").validate[FundedChannelId] toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
timestamp <- (js \ "timestamp" \ "unix") timestamp <- (js \ "timestamp" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield RelayedPayment(amountIn, } yield RelayedPayment(
amountOut, amountIn,
paymentHash, amountOut,
fromChannelId, paymentHash,
toChannelId, fromChannelId,
timestamp) toChannelId,
timestamp
)
} }
implicit val auditResultReads: Reads[AuditResult] = Json.reads[AuditResult] implicit val auditResultReads: Reads[AuditResult] = Json.reads[AuditResult]
@ -1388,12 +1448,14 @@ object JsonReaders {
txType <- (js \ "txType").validate[String] txType <- (js \ "txType").validate[String]
timestamp <- (js \ "timestamp" \ "unix") timestamp <- (js \ "timestamp" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield NetworkFeesResult(remoteNodeId, } yield NetworkFeesResult(
channelId, remoteNodeId,
txId, channelId,
fee, txId,
txType, fee,
timestamp) txType,
timestamp
)
} }
implicit val channelStatsDirectionReads: Reads[ChannelStats.Direction] = implicit val channelStatsDirectionReads: Reads[ChannelStats.Direction] =
@ -1417,28 +1479,32 @@ object JsonReaders {
toChannelId <- (js \ "toChannelId").validate[FundedChannelId] toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
timestamp <- (js \ "timestamp" \ "unix") timestamp <- (js \ "timestamp" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield WebSocketEvent.PaymentRelayed(amountIn, } yield WebSocketEvent.PaymentRelayed(
amountOut, amountIn,
paymentHash, amountOut,
fromChannelId, paymentHash,
toChannelId, fromChannelId,
timestamp) toChannelId,
timestamp
)
} }
implicit val paymentReceivedEventPartReads: Reads[ implicit val paymentReceivedEventPartReads
WebSocketEvent.PaymentReceived.Part] = Reads { js => : Reads[WebSocketEvent.PaymentReceived.Part] = Reads { js =>
for { for {
amount <- (js \ "amount").validate[MilliSatoshis] amount <- (js \ "amount").validate[MilliSatoshis]
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId] fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
timestamp <- (js \ "timestamp" \ "unix") timestamp <- (js \ "timestamp" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield WebSocketEvent.PaymentReceived.Part(amount, } yield WebSocketEvent.PaymentReceived.Part(
fromChannelId, amount,
timestamp) fromChannelId,
timestamp
)
} }
implicit val paymentReceivedEventReads: Reads[ implicit val paymentReceivedEventReads
WebSocketEvent.PaymentReceived] = Reads { js => : Reads[WebSocketEvent.PaymentReceived] = Reads { js =>
for { for {
paymentHash <- (js \ "paymentHash").validate[Sha256Digest] paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
parts <- (js \ "parts") parts <- (js \ "parts")
@ -1454,14 +1520,16 @@ object JsonReaders {
failures <- (js \ "failures").validate[Vector[JsObject]] failures <- (js \ "failures").validate[Vector[JsObject]]
timestamp <- (js \ "timestamp" \ "unix") timestamp <- (js \ "timestamp" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield WebSocketEvent.PaymentFailed(id, } yield WebSocketEvent.PaymentFailed(
paymentHash, id,
failures.map(_.toString()), paymentHash,
timestamp) failures.map(_.toString()),
timestamp
)
} }
implicit val paymentSentEventPartReads: Reads[ implicit val paymentSentEventPartReads
WebSocketEvent.PaymentSent.Part] = Reads { js => : Reads[WebSocketEvent.PaymentSent.Part] = Reads { js =>
for { for {
id <- (js \ "id").validate[PaymentId] id <- (js \ "id").validate[PaymentId]
amount <- (js \ "amount").validate[MilliSatoshis] amount <- (js \ "amount").validate[MilliSatoshis]
@ -1469,11 +1537,13 @@ object JsonReaders {
toChannelId <- (js \ "toChannelId").validate[FundedChannelId] toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
timestamp <- (js \ "timestamp" \ "unix") timestamp <- (js \ "timestamp" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield WebSocketEvent.PaymentSent.Part(id, } yield WebSocketEvent.PaymentSent.Part(
amount, id,
feesPaid, amount,
toChannelId, feesPaid,
timestamp) toChannelId,
timestamp
)
} }
implicit val paymentSentEventReads: Reads[WebSocketEvent.PaymentSent] = implicit val paymentSentEventReads: Reads[WebSocketEvent.PaymentSent] =
@ -1484,22 +1554,26 @@ object JsonReaders {
paymentPreimage <- (js \ "paymentPreimage").validate[PaymentPreimage] paymentPreimage <- (js \ "paymentPreimage").validate[PaymentPreimage]
parts <- (js \ "parts") parts <- (js \ "parts")
.validate[Vector[WebSocketEvent.PaymentSent.Part]] .validate[Vector[WebSocketEvent.PaymentSent.Part]]
} yield WebSocketEvent.PaymentSent(id, } yield WebSocketEvent.PaymentSent(
paymentHash, id,
paymentPreimage, paymentHash,
parts) paymentPreimage,
parts
)
} }
implicit val paymentSettlingOnchainEventReads: Reads[ implicit val paymentSettlingOnchainEventReads
WebSocketEvent.PaymentSettlingOnchain] = Reads { js => : Reads[WebSocketEvent.PaymentSettlingOnchain] = Reads { js =>
for { for {
amount <- (js \ "amount").validate[MilliSatoshis] amount <- (js \ "amount").validate[MilliSatoshis]
paymentHash <- (js \ "paymentHash").validate[Sha256Digest] paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
timestamp <- (js \ "timestamp" \ "unix") timestamp <- (js \ "timestamp" \ "unix")
.validate[Instant](instantReadsMilliseconds) .validate[Instant](instantReadsMilliseconds)
} yield WebSocketEvent.PaymentSettlingOnchain(amount, } yield WebSocketEvent.PaymentSettlingOnchain(
paymentHash, amount,
timestamp) paymentHash,
timestamp
)
} }
implicit val webSocketEventReads: Reads[WebSocketEvent] = implicit val webSocketEventReads: Reads[WebSocketEvent] =
@ -1549,7 +1623,8 @@ object JsonReaders {
implicit val connectionDirectionReads: Reads[ConnectionDirection] = { implicit val connectionDirectionReads: Reads[ConnectionDirection] = {
Reads { jsValue => Reads { jsValue =>
SerializerUtil.processJsStringOpt(ConnectionDirection.fromStringOpt)( SerializerUtil.processJsStringOpt(ConnectionDirection.fromStringOpt)(
jsValue) jsValue
)
} }
} }
@ -1568,7 +1643,8 @@ object JsonReaders {
implicit val closedChannelTypeReads: Reads[ClosedChannelType] = { implicit val closedChannelTypeReads: Reads[ClosedChannelType] = {
Reads { jsValue => Reads { jsValue =>
SerializerUtil.processJsStringOpt(ClosedChannelType.fromStringOpt)( SerializerUtil.processJsStringOpt(ClosedChannelType.fromStringOpt)(
jsValue) jsValue
)
} }
} }

View File

@ -136,8 +136,8 @@ object JsonSerializers {
.readNullable[Vector[BitcoinAddress]] and .readNullable[Vector[BitcoinAddress]] and
(__ \ "address").readNullable[BitcoinAddress])(RpcScriptPubKeyPostV22) (__ \ "address").readNullable[BitcoinAddress])(RpcScriptPubKeyPostV22)
implicit val rpcTransactionOutputPreV22Reads: Reads[ implicit val rpcTransactionOutputPreV22Reads
RpcTransactionOutputPreV22] = : Reads[RpcTransactionOutputPreV22] =
Json.reads[RpcTransactionOutputPreV22] Json.reads[RpcTransactionOutputPreV22]
implicit val rpcTransactionOutputV22Reads: Reads[RpcTransactionOutputV22] = implicit val rpcTransactionOutputV22Reads: Reads[RpcTransactionOutputV22] =
@ -154,18 +154,19 @@ object JsonSerializers {
implicit val fundRawTransactionResultReads: Reads[FundRawTransactionResult] = implicit val fundRawTransactionResultReads: Reads[FundRawTransactionResult] =
Json.reads[FundRawTransactionResult] Json.reads[FundRawTransactionResult]
implicit val signRawTransactionWithWalletResultReads: Reads[ implicit val signRawTransactionWithWalletResultReads
SignRawTransactionWithWalletResult] = : Reads[SignRawTransactionWithWalletResult] =
Json.reads[SignRawTransactionWithWalletResult] Json.reads[SignRawTransactionWithWalletResult]
implicit val getRawTransactionScriptSigReads: Reads[ implicit val getRawTransactionScriptSigReads
GetRawTransactionScriptSig] = Json.reads[GetRawTransactionScriptSig] : Reads[GetRawTransactionScriptSig] =
Json.reads[GetRawTransactionScriptSig]
implicit val getRawTransactionVinReads: Reads[GetRawTransactionVin] = implicit val getRawTransactionVinReads: Reads[GetRawTransactionVin] =
Json.reads[GetRawTransactionVin] Json.reads[GetRawTransactionVin]
implicit val getRawTransactionResultV22Reads: Reads[ implicit val getRawTransactionResultV22Reads
GetRawTransactionResultV22] = : Reads[GetRawTransactionResultV22] =
Json.reads[GetRawTransactionResultV22] Json.reads[GetRawTransactionResultV22]
implicit val signRawTransactionErrorReads: Reads[SignRawTransactionError] = implicit val signRawTransactionErrorReads: Reads[SignRawTransactionError] =
@ -258,8 +259,8 @@ object JsonSerializers {
implicit val getBlockResultReads: Reads[GetBlockResult] = implicit val getBlockResultReads: Reads[GetBlockResult] =
Json.reads[GetBlockResult] Json.reads[GetBlockResult]
implicit val getBlockWithTransactionsResultV22Reads: Reads[ implicit val getBlockWithTransactionsResultV22Reads
GetBlockWithTransactionsResultV22] = : Reads[GetBlockWithTransactionsResultV22] =
Json.reads[GetBlockWithTransactionsResultV22] Json.reads[GetBlockWithTransactionsResultV22]
implicit val softforkProgressPreV19Reads: Reads[SoftforkProgressPreV19] = implicit val softforkProgressPreV19Reads: Reads[SoftforkProgressPreV19] =
@ -271,8 +272,8 @@ object JsonSerializers {
implicit val bip9SoftforkPreV19Reads: Reads[Bip9SoftforkPreV19] = implicit val bip9SoftforkPreV19Reads: Reads[Bip9SoftforkPreV19] =
Json.reads[Bip9SoftforkPreV19] Json.reads[Bip9SoftforkPreV19]
implicit val getBlockChainInfoResultPreV19Reads: Reads[ implicit val getBlockChainInfoResultPreV19Reads
GetBlockChainInfoResultPreV19] = : Reads[GetBlockChainInfoResultPreV19] =
Json.reads[GetBlockChainInfoResultPreV19] Json.reads[GetBlockChainInfoResultPreV19]
implicit val bip9SoftforkDetailsReads: Reads[Bip9SoftforkDetails] = implicit val bip9SoftforkDetailsReads: Reads[Bip9SoftforkDetails] =
@ -286,11 +287,13 @@ object JsonSerializers {
} }
} }
implicit val getBlockChainInfoResultPostV19Reads: Reads[ implicit val getBlockChainInfoResultPostV19Reads
GetBlockChainInfoResultPostV19] = Json.reads[GetBlockChainInfoResultPostV19] : Reads[GetBlockChainInfoResultPostV19] =
Json.reads[GetBlockChainInfoResultPostV19]
implicit val getBlockChainInfoResultPostV23Reads: Reads[ implicit val getBlockChainInfoResultPostV23Reads
GetBlockChainInfoResultPostV23] = Json.reads[GetBlockChainInfoResultPostV23] : Reads[GetBlockChainInfoResultPostV23] =
Json.reads[GetBlockChainInfoResultPostV23]
implicit val blockHeaderFormattedReads: Reads[GetBlockHeaderResult] = implicit val blockHeaderFormattedReads: Reads[GetBlockHeaderResult] =
Json.reads[GetBlockHeaderResult] Json.reads[GetBlockHeaderResult]
@ -311,16 +314,16 @@ object JsonSerializers {
implicit val getMemPoolResultPostV23Reads: Reads[GetMemPoolResultPostV23] = implicit val getMemPoolResultPostV23Reads: Reads[GetMemPoolResultPostV23] =
Json.reads[GetMemPoolResultPostV23] Json.reads[GetMemPoolResultPostV23]
implicit val getMemPoolEntryResultPreV19Reads: Reads[ implicit val getMemPoolEntryResultPreV19Reads
GetMemPoolEntryResultPreV19] = : Reads[GetMemPoolEntryResultPreV19] =
Json.reads[GetMemPoolEntryResultPreV19] Json.reads[GetMemPoolEntryResultPreV19]
implicit val getMemPoolEntryResultPostV19Reads: Reads[ implicit val getMemPoolEntryResultPostV19Reads
GetMemPoolEntryResultPostV19] = : Reads[GetMemPoolEntryResultPostV19] =
Json.reads[GetMemPoolEntryResultPostV19] Json.reads[GetMemPoolEntryResultPostV19]
implicit val getMemPoolEntryResultPostV23Reads: Reads[ implicit val getMemPoolEntryResultPostV23Reads
GetMemPoolEntryResultPostV23] = : Reads[GetMemPoolEntryResultPostV23] =
Json.reads[GetMemPoolEntryResultPostV23] Json.reads[GetMemPoolEntryResultPostV23]
implicit val getMemPoolInfoResultReads: Reads[GetMemPoolInfoResult] = implicit val getMemPoolInfoResultReads: Reads[GetMemPoolInfoResult] =
@ -332,12 +335,12 @@ object JsonSerializers {
implicit val getTxOutSetInfoResultReads: Reads[GetTxOutSetInfoResult] = implicit val getTxOutSetInfoResultReads: Reads[GetTxOutSetInfoResult] =
Json.reads[GetTxOutSetInfoResult] Json.reads[GetTxOutSetInfoResult]
implicit val GetTxSpendingPrevOutResultReads: Reads[ implicit val GetTxSpendingPrevOutResultReads
GetTxSpendingPrevOutResult] = : Reads[GetTxSpendingPrevOutResult] =
Json.reads[GetTxSpendingPrevOutResult] Json.reads[GetTxSpendingPrevOutResult]
implicit val SimulateRawTransactionResultReads: Reads[ implicit val SimulateRawTransactionResultReads
SimulateRawTransactionResult] = : Reads[SimulateRawTransactionResult] =
Json.reads[SimulateRawTransactionResult] Json.reads[SimulateRawTransactionResult]
implicit object Bip32PathFormats extends Format[BIP32Path] { implicit object Bip32PathFormats extends Format[BIP32Path] {
@ -383,8 +386,8 @@ object JsonSerializers {
(__ \ "details").read[Vector[TransactionDetails]] and (__ \ "details").read[Vector[TransactionDetails]] and
(__ \ "hex").read[Transaction])(GetTransactionResult) (__ \ "hex").read[Transaction])(GetTransactionResult)
implicit val getWalletInfoResultReadsPostV22: Reads[ implicit val getWalletInfoResultReadsPostV22
GetWalletInfoResultPostV22] = : Reads[GetWalletInfoResultPostV22] =
Json.reads[GetWalletInfoResultPostV22] Json.reads[GetWalletInfoResultPostV22]
implicit val importMultiErrorReads: Reads[ImportMultiError] = implicit val importMultiErrorReads: Reads[ImportMultiError] =
@ -530,8 +533,8 @@ object JsonSerializers {
implicit val addressInfoResultPreV18Reads: Reads[AddressInfoResultPreV18] = implicit val addressInfoResultPreV18Reads: Reads[AddressInfoResultPreV18] =
Json.reads[AddressInfoResultPreV18] Json.reads[AddressInfoResultPreV18]
implicit val addressInfoResultPostV18Reads: Reads[ implicit val addressInfoResultPostV18Reads
AddressInfoResultPostV18] = { : Reads[AddressInfoResultPostV18] = {
Reads[AddressInfoResultPostV18] { json => Reads[AddressInfoResultPostV18] { json =>
for { for {
isProps <- isProps <-
@ -539,7 +542,8 @@ object JsonSerializers {
infoWithoutProps <- infoWithoutProps <-
Json Json
.reads[ .reads[
AddressInfoResultPostV18.AddressInfoResultPostV18WithoutIsProps] AddressInfoResultPostV18.AddressInfoResultPostV18WithoutIsProps
]
.reads(json) .reads(json)
} yield { } yield {
AddressInfoResultPostV18(infoWithoutProps, isProps) AddressInfoResultPostV18(infoWithoutProps, isProps)
@ -547,8 +551,8 @@ object JsonSerializers {
} }
} }
implicit val addressInfoResultPostV21Reads: Reads[ implicit val addressInfoResultPostV21Reads
AddressInfoResultPostV21] = { : Reads[AddressInfoResultPostV21] = {
Reads[AddressInfoResultPostV21] { json => Reads[AddressInfoResultPostV21] { json =>
for { for {
isProps <- isProps <-
@ -556,7 +560,8 @@ object JsonSerializers {
infoWithoutProps <- infoWithoutProps <-
Json Json
.reads[ .reads[
AddressInfoResultPostV21.AddressInfoResultPostV21WithoutIsProps] AddressInfoResultPostV21.AddressInfoResultPostV21WithoutIsProps
]
.reads(json) .reads(json)
} yield { } yield {
AddressInfoResultPostV21(infoWithoutProps, isProps) AddressInfoResultPostV21(infoWithoutProps, isProps)
@ -591,8 +596,8 @@ object JsonSerializers {
implicit val psbtWitnessUtxoInputReads: Reads[PsbtWitnessUtxoInput] = implicit val psbtWitnessUtxoInputReads: Reads[PsbtWitnessUtxoInput] =
Json.reads[PsbtWitnessUtxoInput] Json.reads[PsbtWitnessUtxoInput]
implicit val mapPubKeySignatureReads: Reads[ implicit val mapPubKeySignatureReads
Map[ECPublicKey, ECDigitalSignature]] = MapPubKeySignatureReads : Reads[Map[ECPublicKey, ECDigitalSignature]] = MapPubKeySignatureReads
implicit val rpcPsbtInputV22Reads: Reads[RpcPsbtInputV22] = implicit val rpcPsbtInputV22Reads: Reads[RpcPsbtInputV22] =
RpcPsbtInputV22Reads RpcPsbtInputV22Reads
@ -609,8 +614,8 @@ object JsonSerializers {
implicit val analyzePsbtResultReads: Reads[AnalyzePsbtResult] = implicit val analyzePsbtResultReads: Reads[AnalyzePsbtResult] =
Json.reads[AnalyzePsbtResult] Json.reads[AnalyzePsbtResult]
implicit val getNodeAddressesPostV22Reads: Reads[ implicit val getNodeAddressesPostV22Reads
GetNodeAddressesResultPostV22] = : Reads[GetNodeAddressesResultPostV22] =
Reads[GetNodeAddressesResultPostV22] { js => Reads[GetNodeAddressesResultPostV22] { js =>
for { for {
time <- (js \ "time").validate[Long].map(_.seconds) time <- (js \ "time").validate[Long].map(_.seconds)
@ -618,11 +623,13 @@ object JsonSerializers {
address <- (js \ "address").validate[URI] address <- (js \ "address").validate[URI]
port <- (js \ "port").validate[Int] port <- (js \ "port").validate[Int]
network <- (js \ "network").validate[String] network <- (js \ "network").validate[String]
} yield GetNodeAddressesResultPostV22(time, } yield GetNodeAddressesResultPostV22(
services, time,
address, services,
port, address,
network) port,
network
)
} }
implicit val rgetpcCommandsReads: Reads[RpcCommands] = Reads[RpcCommands] { implicit val rgetpcCommandsReads: Reads[RpcCommands] = Reads[RpcCommands] {
@ -648,8 +655,8 @@ object JsonSerializers {
implicit val getDescriptorInfoResultReads: Reads[GetDescriptorInfoResult] = implicit val getDescriptorInfoResultReads: Reads[GetDescriptorInfoResult] =
Json.reads[GetDescriptorInfoResult] Json.reads[GetDescriptorInfoResult]
implicit val walletCreateFundedPsbtResultReads: Reads[ implicit val walletCreateFundedPsbtResultReads
WalletCreateFundedPsbtResult] = : Reads[WalletCreateFundedPsbtResult] =
Json.reads[WalletCreateFundedPsbtResult] Json.reads[WalletCreateFundedPsbtResult]
implicit val scriptTypeReads: Reads[ScriptType] = ScriptTypeReads implicit val scriptTypeReads: Reads[ScriptType] = ScriptTypeReads
@ -659,8 +666,9 @@ object JsonSerializers {
implicit val FeeInfoTwoReads: Reads[FeeInfoTwo] = Json.reads[FeeInfoTwo] implicit val FeeInfoTwoReads: Reads[FeeInfoTwo] = Json.reads[FeeInfoTwo]
implicit val testMempoolAcceptResultReadsPostV22: Reads[ implicit val testMempoolAcceptResultReadsPostV22
TestMempoolAcceptResultPostV22] = Json.reads[TestMempoolAcceptResultPostV22] : Reads[TestMempoolAcceptResultPostV22] =
Json.reads[TestMempoolAcceptResultPostV22]
implicit val indexInfoResultReads: Reads[IndexInfoResult] = implicit val indexInfoResultReads: Reads[IndexInfoResult] =
Json.reads[IndexInfoResult] Json.reads[IndexInfoResult]
@ -719,12 +727,12 @@ object JsonSerializers {
implicit val cLightningInvoiceResultReads: Reads[CLightningInvoiceResult] = implicit val cLightningInvoiceResultReads: Reads[CLightningInvoiceResult] =
Json.reads[CLightningInvoiceResult] Json.reads[CLightningInvoiceResult]
implicit val cLightningLookupInvoiceResultReads: Reads[ implicit val cLightningLookupInvoiceResultReads
CLightningLookupInvoiceResult] = : Reads[CLightningLookupInvoiceResult] =
Json.reads[CLightningLookupInvoiceResult] Json.reads[CLightningLookupInvoiceResult]
implicit val cLightningListInvoicesResultReads: Reads[ implicit val cLightningListInvoicesResultReads
CLightningListInvoicesResult] = : Reads[CLightningListInvoicesResult] =
Json.reads[CLightningListInvoicesResult] Json.reads[CLightningListInvoicesResult]
implicit val cLightningPayResultReads: Reads[CLightningPayResult] = implicit val cLightningPayResultReads: Reads[CLightningPayResult] =
@ -745,23 +753,23 @@ object JsonSerializers {
implicit val cLightningWithdrawResultReads: Reads[WithdrawResult] = implicit val cLightningWithdrawResultReads: Reads[WithdrawResult] =
Json.reads[WithdrawResult] Json.reads[WithdrawResult]
implicit val cLightningFundChannelStartResultReads: Reads[ implicit val cLightningFundChannelStartResultReads
FundChannelStartResult] = : Reads[FundChannelStartResult] =
Json.reads[FundChannelStartResult] Json.reads[FundChannelStartResult]
implicit val cLightningFundChannelCompleteResultReads: Reads[ implicit val cLightningFundChannelCompleteResultReads
FundChannelCompleteResult] = : Reads[FundChannelCompleteResult] =
Json.reads[FundChannelCompleteResult] Json.reads[FundChannelCompleteResult]
implicit val cLightningFundChannelCancelResultReads: Reads[ implicit val cLightningFundChannelCancelResultReads
FundChannelCancelResult] = : Reads[FundChannelCancelResult] =
Json.reads[FundChannelCancelResult] Json.reads[FundChannelCancelResult]
implicit val CLightningTransactionReads: Reads[CLightningTransaction] = implicit val CLightningTransactionReads: Reads[CLightningTransaction] =
Json.reads[CLightningTransaction] Json.reads[CLightningTransaction]
implicit val CLightningListTransactionsResultsReads: Reads[ implicit val CLightningListTransactionsResultsReads
ListTransactionsResults] = : Reads[ListTransactionsResults] =
Json.reads[ListTransactionsResults] Json.reads[ListTransactionsResults]
implicit val SendCustomMessageResultReads: Reads[SendCustomMessageResult] = implicit val SendCustomMessageResultReads: Reads[SendCustomMessageResult] =
@ -788,14 +796,17 @@ object JsonSerializers {
implicit val int32Writes: Writes[Int32] = implicit val int32Writes: Writes[Int32] =
Writes[Int32](num => JsNumber(num.toLong)) Writes[Int32](num => JsNumber(num.toLong))
implicit val serializedTransactionWitnessWrites: Writes[ implicit val serializedTransactionWitnessWrites
SerializedTransactionWitness] = Json.writes[SerializedTransactionWitness] : Writes[SerializedTransactionWitness] =
Json.writes[SerializedTransactionWitness]
implicit val serializedTransactionInputWrites: Writes[ implicit val serializedTransactionInputWrites
SerializedTransactionInput] = Json.writes[SerializedTransactionInput] : Writes[SerializedTransactionInput] =
Json.writes[SerializedTransactionInput]
implicit val serializedTransactionOutputWrites: Writes[ implicit val serializedTransactionOutputWrites
SerializedTransactionOutput] = Json.writes[SerializedTransactionOutput] : Writes[SerializedTransactionOutput] =
Json.writes[SerializedTransactionOutput]
implicit val serializedTransactionWrites: Writes[SerializedTransaction] = implicit val serializedTransactionWrites: Writes[SerializedTransaction] =
Json.writes[SerializedTransaction] Json.writes[SerializedTransaction]
@ -822,46 +833,46 @@ object JsonSerializers {
Json.writes[SerializedPSBT] Json.writes[SerializedPSBT]
// Map stuff // Map stuff
implicit def mapDoubleSha256DigestReadsPreV19: Reads[ implicit def mapDoubleSha256DigestReadsPreV19
Map[DoubleSha256Digest, GetMemPoolResultPreV19]] = : Reads[Map[DoubleSha256Digest, GetMemPoolResultPreV19]] =
Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPreV19](s => Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPreV19](s =>
JsSuccess(DoubleSha256Digest.fromHex(s))) JsSuccess(DoubleSha256Digest.fromHex(s)))
implicit def mapDoubleSha256DigestReadsPostV19: Reads[ implicit def mapDoubleSha256DigestReadsPostV19
Map[DoubleSha256Digest, GetMemPoolResultPostV19]] = : Reads[Map[DoubleSha256Digest, GetMemPoolResultPostV19]] =
Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPostV19](s => Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPostV19](s =>
JsSuccess(DoubleSha256Digest.fromHex(s))) JsSuccess(DoubleSha256Digest.fromHex(s)))
implicit def mapDoubleSha256DigestReadsPostV23: Reads[ implicit def mapDoubleSha256DigestReadsPostV23
Map[DoubleSha256Digest, GetMemPoolResultPostV23]] = : Reads[Map[DoubleSha256Digest, GetMemPoolResultPostV23]] =
Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPostV23](s => Reads.mapReads[DoubleSha256Digest, GetMemPoolResultPostV23](s =>
JsSuccess(DoubleSha256Digest.fromHex(s))) JsSuccess(DoubleSha256Digest.fromHex(s)))
implicit def mapDoubleSha256DigestBEReadsPreV19: Reads[ implicit def mapDoubleSha256DigestBEReadsPreV19
Map[DoubleSha256DigestBE, GetMemPoolResultPreV19]] = : Reads[Map[DoubleSha256DigestBE, GetMemPoolResultPreV19]] =
Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPreV19](s => Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPreV19](s =>
JsSuccess(DoubleSha256DigestBE.fromHex(s))) JsSuccess(DoubleSha256DigestBE.fromHex(s)))
implicit def mapDoubleSha256DigestBEReadsPostV19: Reads[ implicit def mapDoubleSha256DigestBEReadsPostV19
Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]] = : Reads[Map[DoubleSha256DigestBE, GetMemPoolResultPostV19]] =
Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPostV19](s => Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPostV19](s =>
JsSuccess(DoubleSha256DigestBE.fromHex(s))) JsSuccess(DoubleSha256DigestBE.fromHex(s)))
implicit def mapDoubleSha256DigestBEReadsPostV23: Reads[ implicit def mapDoubleSha256DigestBEReadsPostV23
Map[DoubleSha256DigestBE, GetMemPoolResultPostV23]] = : Reads[Map[DoubleSha256DigestBE, GetMemPoolResultPostV23]] =
Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPostV23](s => Reads.mapReads[DoubleSha256DigestBE, GetMemPoolResultPostV23](s =>
JsSuccess(DoubleSha256DigestBE.fromHex(s))) JsSuccess(DoubleSha256DigestBE.fromHex(s)))
implicit def mapAddressesByLabelReads: Reads[ implicit def mapAddressesByLabelReads
Map[BitcoinAddress, LabelResult]] = : Reads[Map[BitcoinAddress, LabelResult]] =
Reads.mapReads[BitcoinAddress, LabelResult](s => Reads.mapReads[BitcoinAddress, LabelResult](s =>
JsSuccess(BitcoinAddress.fromString(s))) JsSuccess(BitcoinAddress.fromString(s)))
implicit def mapSatsPerKByteByIntReads: Reads[Map[Int, SatoshisPerKiloByte]] = implicit def mapSatsPerKByteByIntReads: Reads[Map[Int, SatoshisPerKiloByte]] =
Reads.mapReads[Int, SatoshisPerKiloByte](s => JsSuccess(s.toInt)) Reads.mapReads[Int, SatoshisPerKiloByte](s => JsSuccess(s.toInt))
implicit def mapBitcoinerLiveEstimateReads: Reads[ implicit def mapBitcoinerLiveEstimateReads
Map[Int, BitcoinerLiveEstimate]] = : Reads[Map[Int, BitcoinerLiveEstimate]] =
Reads.mapReads[Int, BitcoinerLiveEstimate](s => JsSuccess(s.toInt)) Reads.mapReads[Int, BitcoinerLiveEstimate](s => JsSuccess(s.toInt))
implicit val outputMapWrites: Writes[Map[BitcoinAddress, Bitcoins]] = implicit val outputMapWrites: Writes[Map[BitcoinAddress, Bitcoins]] =

View File

@ -40,7 +40,8 @@ object JsonWriters {
case _: SIGHASH_SINGLE_ANYONECANPAY => JsString("SINGLE|ANYONECANPAY") case _: SIGHASH_SINGLE_ANYONECANPAY => JsString("SINGLE|ANYONECANPAY")
case _: SIGHASH_ANYONECANPAY => case _: SIGHASH_ANYONECANPAY =>
throw new IllegalArgumentException( throw new IllegalArgumentException(
"SIGHHASH_ANYONECANPAY is not supported by the bitcoind RPC interface") "SIGHHASH_ANYONECANPAY is not supported by the bitcoind RPC interface"
)
} }
} }
@ -122,17 +123,22 @@ object JsonWriters {
override def writes(o: TransactionInput): JsValue = override def writes(o: TransactionInput): JsValue =
JsObject( JsObject(
Seq(("txid", JsString(o.previousOutput.txIdBE.hex)), Seq(
("vout", JsNumber(o.previousOutput.vout.toLong)), ("txid", JsString(o.previousOutput.txIdBE.hex)),
("sequence", JsNumber(o.sequence.toLong)))) ("vout", JsNumber(o.previousOutput.vout.toLong)),
("sequence", JsNumber(o.sequence.toLong))
)
)
} }
implicit object TransactionOutPointWrites implicit object TransactionOutPointWrites
extends OWrites[TransactionOutPoint] { extends OWrites[TransactionOutPoint] {
override def writes(o: TransactionOutPoint): JsObject = { override def writes(o: TransactionOutPoint): JsObject = {
Json.obj(PicklerKeys.txIdKey -> o.txIdBE.hex, Json.obj(
PicklerKeys.voutKey -> o.vout.toLong) PicklerKeys.txIdKey -> o.txIdBE.hex,
PicklerKeys.voutKey -> o.vout.toLong
)
} }
} }
@ -152,8 +158,9 @@ object JsonWriters {
override def writes(o: PSBT): JsValue = JsString(o.base64) override def writes(o: PSBT): JsValue = JsString(o.base64)
} }
implicit def mapWrites[K, V](keyString: K => String)(implicit implicit def mapWrites[K, V](
vWrites: Writes[V]): Writes[Map[K, V]] = keyString: K => String
)(implicit vWrites: Writes[V]): Writes[Map[K, V]] =
new Writes[Map[K, V]] { new Writes[Map[K, V]] {
override def writes(o: Map[K, V]): JsValue = override def writes(o: Map[K, V]): JsValue =
@ -180,7 +187,8 @@ object JsonWriters {
implicit object LnInvoiceWrites extends Writes[LnInvoice] { implicit object LnInvoiceWrites extends Writes[LnInvoice] {
override def writes(invoice: LnInvoice): JsValue = JsString( override def writes(invoice: LnInvoice): JsValue = JsString(
invoice.toString) invoice.toString
)
} }
implicit object WalletCreateFundedPsbtOptionsWrites implicit object WalletCreateFundedPsbtOptionsWrites
@ -195,7 +203,8 @@ object JsonWriters {
) )
def addToMapIfDefined[T](key: String, opt: Option[T])(implicit def addToMapIfDefined[T](key: String, opt: Option[T])(implicit
writes: Writes[T]): Unit = writes: Writes[T]
): Unit =
opt.foreach(o => jsOpts += (key -> Json.toJson(o))) opt.foreach(o => jsOpts += (key -> Json.toJson(o)))
addToMapIfDefined("changeAddress", opts.changeAddress) addToMapIfDefined("changeAddress", opts.changeAddress)
@ -214,7 +223,8 @@ object JsonWriters {
override def writes(o: GlobalPSBTRecord.Unknown): JsValue = override def writes(o: GlobalPSBTRecord.Unknown): JsValue =
JsObject( JsObject(
Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex)))) Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex)))
)
} }
implicit object InputPSBTRecordUnknownWrites implicit object InputPSBTRecordUnknownWrites
@ -222,7 +232,8 @@ object JsonWriters {
override def writes(o: InputPSBTRecord.Unknown): JsValue = override def writes(o: InputPSBTRecord.Unknown): JsValue =
JsObject( JsObject(
Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex)))) Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex)))
)
} }
implicit object OutputPSBTRecordUnknownWrites implicit object OutputPSBTRecordUnknownWrites
@ -230,7 +241,8 @@ object JsonWriters {
override def writes(o: OutputPSBTRecord.Unknown): JsValue = override def writes(o: OutputPSBTRecord.Unknown): JsValue =
JsObject( JsObject(
Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex)))) Seq(("key", JsString(o.key.toHex)), ("value", JsString(o.value.toHex)))
)
} }
implicit object PartialSignatureWrites implicit object PartialSignatureWrites
@ -238,8 +250,11 @@ object JsonWriters {
override def writes(o: InputPSBTRecord.PartialSignature): JsValue = override def writes(o: InputPSBTRecord.PartialSignature): JsValue =
JsObject( JsObject(
Seq(("pubkey", JsString(o.pubKey.hex)), Seq(
("signature", JsString(o.signature.hex)))) ("pubkey", JsString(o.pubKey.hex)),
("signature", JsString(o.signature.hex))
)
)
} }
implicit object InputBIP32PathWrites implicit object InputBIP32PathWrites
@ -247,9 +262,12 @@ object JsonWriters {
override def writes(o: InputPSBTRecord.BIP32DerivationPath): JsValue = override def writes(o: InputPSBTRecord.BIP32DerivationPath): JsValue =
JsObject( JsObject(
Seq(("pubkey", JsString(o.pubKey.hex)), Seq(
("master_fingerprint", JsString(o.masterFingerprint.toHex)), ("pubkey", JsString(o.pubKey.hex)),
("path", JsString(o.path.toString)))) ("master_fingerprint", JsString(o.masterFingerprint.toHex)),
("path", JsString(o.path.toString))
)
)
} }
implicit object OutputBIP32PathWrites implicit object OutputBIP32PathWrites
@ -257,8 +275,11 @@ object JsonWriters {
override def writes(o: OutputPSBTRecord.BIP32DerivationPath): JsValue = override def writes(o: OutputPSBTRecord.BIP32DerivationPath): JsValue =
JsObject( JsObject(
Seq(("pubkey", JsString(o.pubKey.hex)), Seq(
("master_fingerprint", JsString(o.masterFingerprint.toHex)), ("pubkey", JsString(o.pubKey.hex)),
("path", JsString(o.path.toString)))) ("master_fingerprint", JsString(o.masterFingerprint.toHex)),
("path", JsString(o.path.toString))
)
)
} }
} }

View File

@ -62,7 +62,8 @@ object Picklers {
str => { str => {
val uri = new URI("tcp://" + str) val uri = new URI("tcp://" + str)
InetSocketAddress.createUnresolved(uri.getHost, uri.getPort) InetSocketAddress.createUnresolved(uri.getHost, uri.getPort)
}) }
)
implicit val byteVectorPickler: ReadWriter[ByteVector] = implicit val byteVectorPickler: ReadWriter[ByteVector] =
readwriter[String].bimap(_.toHex, str => ByteVector.fromValidHex(str)) readwriter[String].bimap(_.toHex, str => ByteVector.fromValidHex(str))
@ -80,14 +81,16 @@ object Picklers {
implicit val schnorrNoncePickler: ReadWriter[SchnorrNonce] = implicit val schnorrNoncePickler: ReadWriter[SchnorrNonce] =
readwriter[String].bimap(_.hex, SchnorrNonce.fromHex) readwriter[String].bimap(_.hex, SchnorrNonce.fromHex)
implicit val enumEventDescriptorPickler: ReadWriter[ implicit val enumEventDescriptorPickler
EnumEventDescriptorV0TLV] = : ReadWriter[EnumEventDescriptorV0TLV] =
readwriter[String].bimap(_.hex, EnumEventDescriptorV0TLV.fromHex) readwriter[String].bimap(_.hex, EnumEventDescriptorV0TLV.fromHex)
implicit val digitDecompEventDescriptorPickler: ReadWriter[ implicit val digitDecompEventDescriptorPickler
DigitDecompositionEventDescriptorV0TLV] = : ReadWriter[DigitDecompositionEventDescriptorV0TLV] =
readwriter[String].bimap(_.hex, readwriter[String].bimap(
DigitDecompositionEventDescriptorV0TLV.fromHex) _.hex,
DigitDecompositionEventDescriptorV0TLV.fromHex
)
implicit val eventDescriptorPickler: ReadWriter[EventDescriptorTLV] = implicit val eventDescriptorPickler: ReadWriter[EventDescriptorTLV] =
readwriter[String].bimap(_.hex, EventDescriptorTLV.fromHex) readwriter[String].bimap(_.hex, EventDescriptorTLV.fromHex)
@ -116,8 +119,8 @@ object Picklers {
implicit val uInt32Pickler: ReadWriter[UInt32] = implicit val uInt32Pickler: ReadWriter[UInt32] =
readwriter[Long].bimap(_.toLong, long => UInt32(long)) readwriter[Long].bimap(_.toLong, long => UInt32(long))
implicit val satoshisPerVirtualBytePickler: ReadWriter[ implicit val satoshisPerVirtualBytePickler
SatoshisPerVirtualByte] = : ReadWriter[SatoshisPerVirtualByte] =
readwriter[Long] readwriter[Long]
.bimap(_.toLong, long => SatoshisPerVirtualByte(Satoshis(long))) .bimap(_.toLong, long => SatoshisPerVirtualByte(Satoshis(long)))
@ -133,8 +136,8 @@ object Picklers {
implicit val contractInfoTLVPickler: ReadWriter[ContractInfoV0TLV] = implicit val contractInfoTLVPickler: ReadWriter[ContractInfoV0TLV] =
readwriter[String].bimap(_.hex, ContractInfoV0TLV.fromHex) readwriter[String].bimap(_.hex, ContractInfoV0TLV.fromHex)
implicit val schnorrDigitalSignaturePickler: ReadWriter[ implicit val schnorrDigitalSignaturePickler
SchnorrDigitalSignature] = : ReadWriter[SchnorrDigitalSignature] =
readwriter[String].bimap(_.hex, SchnorrDigitalSignature.fromHex) readwriter[String].bimap(_.hex, SchnorrDigitalSignature.fromHex)
implicit val partialSignaturePickler: ReadWriter[PartialSignature] = implicit val partialSignaturePickler: ReadWriter[PartialSignature] =
@ -223,14 +226,16 @@ object Picklers {
upickle.default.read[TransactionOutput](obj(PicklerKeys.outputKey)) upickle.default.read[TransactionOutput](obj(PicklerKeys.outputKey))
val hdPath = upickle.default.read[HDPath](obj(PicklerKeys.hdPathKey)) val hdPath = upickle.default.read[HDPath](obj(PicklerKeys.hdPathKey))
val redeemScript = upickle.default.read[Option[ScriptPubKey]]( val redeemScript = upickle.default.read[Option[ScriptPubKey]](
obj(PicklerKeys.redeemScriptKey)) obj(PicklerKeys.redeemScriptKey)
)
val scriptWitness = val scriptWitness =
upickle.default.read[Option[ScriptWitness]](obj(PicklerKeys.witnessKey)) upickle.default.read[Option[ScriptWitness]](obj(PicklerKeys.witnessKey))
val state = upickle.default.read[TxoState](obj(PicklerKeys.stateKey)) val state = upickle.default.read[TxoState](obj(PicklerKeys.stateKey))
val txId = val txId =
upickle.default.read[DoubleSha256DigestBE](obj(PicklerKeys.txIdKey)) upickle.default.read[DoubleSha256DigestBE](obj(PicklerKeys.txIdKey))
val spendingTxId = upickle.default.read[Option[DoubleSha256DigestBE]]( val spendingTxId = upickle.default.read[Option[DoubleSha256DigestBE]](
obj(PicklerKeys.spendingTxIdKey)) obj(PicklerKeys.spendingTxIdKey)
)
SpendingInfoDb( SpendingInfoDb(
id = id, id = id,
outpoint = outpoint, outpoint = outpoint,
@ -293,7 +298,8 @@ object Picklers {
} }
private def parseAdaptorSignatures( private def parseAdaptorSignatures(
arr: ujson.Arr): Vector[ECAdaptorSignature] = { arr: ujson.Arr
): Vector[ECAdaptorSignature] = {
arr.value.toVector.map { arr.value.toVector.map {
case obj: ujson.Obj => case obj: ujson.Obj =>
ECAdaptorSignature.fromHex(obj(PicklerKeys.signatureKey).str) ECAdaptorSignature.fromHex(obj(PicklerKeys.signatureKey).str)
@ -303,19 +309,22 @@ object Picklers {
} }
private def writeAdaptorSignatures( private def writeAdaptorSignatures(
sigs: Vector[ECAdaptorSignature]): Vector[ujson.Obj] = { sigs: Vector[ECAdaptorSignature]
): Vector[ujson.Obj] = {
sigs.map { sig => sigs.map { sig =>
ujson.Obj(PicklerKeys.signatureKey -> Str(sig.hex)) ujson.Obj(PicklerKeys.signatureKey -> Str(sig.hex))
} }
} }
private def writeCetAdaptorSigs( private def writeCetAdaptorSigs(
cetSignaturesTLV: CETSignaturesTLV): ujson.Obj = { cetSignaturesTLV: CETSignaturesTLV
): ujson.Obj = {
cetSignaturesTLV match { cetSignaturesTLV match {
case v0: CETSignaturesV0TLV => case v0: CETSignaturesV0TLV =>
val sigsVec = writeAdaptorSignatures(v0.sigs) val sigsVec = writeAdaptorSignatures(v0.sigs)
ujson.Obj( ujson.Obj(
PicklerKeys.ecdsaAdaptorSignaturesKey -> ujson.Arr.from(sigsVec)) PicklerKeys.ecdsaAdaptorSignaturesKey -> ujson.Arr.from(sigsVec)
)
} }
} }
@ -323,17 +332,20 @@ object Picklers {
val tempContractId = val tempContractId =
Sha256Digest.fromHex(obj(PicklerKeys.tempContractIdKey).str) Sha256Digest.fromHex(obj(PicklerKeys.tempContractIdKey).str)
val acceptCollateral = Satoshis( val acceptCollateral = Satoshis(
obj(PicklerKeys.acceptCollateralKey).num.toLong) obj(PicklerKeys.acceptCollateralKey).num.toLong
)
val fundingPubKey = val fundingPubKey =
ECPublicKey.fromHex(obj(PicklerKeys.fundingPubKeyKey).str) ECPublicKey.fromHex(obj(PicklerKeys.fundingPubKeyKey).str)
val payoutSpk = ScriptPubKey.fromAsmHex(obj(PicklerKeys.payoutSpkKey).str) val payoutSpk = ScriptPubKey.fromAsmHex(obj(PicklerKeys.payoutSpkKey).str)
val payoutSerialId = parseU64(obj(PicklerKeys.payoutSerialIdKey).str) val payoutSerialId = parseU64(obj(PicklerKeys.payoutSerialIdKey).str)
val fundingInputs = parseFundingInputs( val fundingInputs = parseFundingInputs(
obj(PicklerKeys.fundingInputsKey).arr) obj(PicklerKeys.fundingInputsKey).arr
)
val changeSpk = ScriptPubKey.fromAsmHex(obj(PicklerKeys.changeSpkKey).str) val changeSpk = ScriptPubKey.fromAsmHex(obj(PicklerKeys.changeSpkKey).str)
val changeSerialId = parseU64(obj(PicklerKeys.changeSerialIdKey).str) val changeSerialId = parseU64(obj(PicklerKeys.changeSerialIdKey).str)
val cetAdaptorSigs = parseCetAdaptorSignatures( val cetAdaptorSigs = parseCetAdaptorSignatures(
obj(PicklerKeys.cetAdaptorSignaturesKey).obj) obj(PicklerKeys.cetAdaptorSignaturesKey).obj
)
val refundSignature = val refundSignature =
ECDigitalSignature.fromHex(obj(PicklerKeys.refundSignatureKey).str) ECDigitalSignature.fromHex(obj(PicklerKeys.refundSignatureKey).str)
val negotiationFields = { val negotiationFields = {
@ -365,17 +377,21 @@ object Picklers {
Obj( Obj(
PicklerKeys.tempContractIdKey -> Str(accept.tempContractId.hex), PicklerKeys.tempContractIdKey -> Str(accept.tempContractId.hex),
PicklerKeys.acceptCollateralKey -> Num( PicklerKeys.acceptCollateralKey -> Num(
accept.acceptCollateralSatoshis.toLong.toDouble), accept.acceptCollateralSatoshis.toLong.toDouble
),
PicklerKeys.fundingPubKeyKey -> Str(accept.fundingPubKey.hex), PicklerKeys.fundingPubKeyKey -> Str(accept.fundingPubKey.hex),
PicklerKeys.payoutSpkKey -> Str(accept.payoutSPK.asmHex), PicklerKeys.payoutSpkKey -> Str(accept.payoutSPK.asmHex),
PicklerKeys.payoutSerialIdKey -> Str( PicklerKeys.payoutSerialIdKey -> Str(
accept.payoutSerialId.toBigInt.toString()), accept.payoutSerialId.toBigInt.toString()
),
PicklerKeys.fundingInputsKey -> writeJs(accept.fundingInputs), PicklerKeys.fundingInputsKey -> writeJs(accept.fundingInputs),
PicklerKeys.changeSpkKey -> Str(accept.changeSPK.asmHex), PicklerKeys.changeSpkKey -> Str(accept.changeSPK.asmHex),
PicklerKeys.changeSerialIdKey -> Str( PicklerKeys.changeSerialIdKey -> Str(
accept.changeSerialId.toBigInt.toString()), accept.changeSerialId.toBigInt.toString()
),
PicklerKeys.cetAdaptorSignaturesKey -> writeCetAdaptorSigs( PicklerKeys.cetAdaptorSignaturesKey -> writeCetAdaptorSigs(
accept.cetSignatures), accept.cetSignatures
),
PicklerKeys.refundSignatureKey -> Str(accept.refundSignature.hex), PicklerKeys.refundSignatureKey -> Str(accept.refundSignature.hex),
PicklerKeys.negotiationFieldsKey -> ujson.Null PicklerKeys.negotiationFieldsKey -> ujson.Null
) )
@ -383,13 +399,15 @@ object Picklers {
private def parseFundingSignatures(obj: ujson.Obj): FundingSignaturesTLV = { private def parseFundingSignatures(obj: ujson.Obj): FundingSignaturesTLV = {
val fundingSignatures: Vector[ujson.Value] = obj( val fundingSignatures: Vector[ujson.Value] = obj(
PicklerKeys.fundingSignaturesKey).arr.toVector PicklerKeys.fundingSignaturesKey
).arr.toVector
val witV0 = paresFundingSignaturesArr(fundingSignatures) val witV0 = paresFundingSignaturesArr(fundingSignatures)
FundingSignaturesV0TLV(witV0) FundingSignaturesV0TLV(witV0)
} }
private def paresFundingSignaturesArr( private def paresFundingSignaturesArr(
arr: Vector[ujson.Value]): Vector[ScriptWitnessV0] = { arr: Vector[ujson.Value]
): Vector[ScriptWitnessV0] = {
arr.map { arr.map {
case obj: ujson.Obj => case obj: ujson.Obj =>
val witnessElementsArr = obj(PicklerKeys.witnessElementsKey).arr val witnessElementsArr = obj(PicklerKeys.witnessElementsKey).arr
@ -405,7 +423,8 @@ object Picklers {
} }
private def writeFundingSignatures( private def writeFundingSignatures(
fundingSigs: FundingSignaturesTLV): ujson.Obj = { fundingSigs: FundingSignaturesTLV
): ujson.Obj = {
val sigs: Vector[ujson.Obj] = fundingSigs match { val sigs: Vector[ujson.Obj] = fundingSigs match {
case v0: FundingSignaturesV0TLV => case v0: FundingSignaturesV0TLV =>
val witnessJson: Vector[Obj] = { val witnessJson: Vector[Obj] = {
@ -424,11 +443,13 @@ object Picklers {
private def readSignTLV(obj: ujson.Obj): DLCSignTLV = { private def readSignTLV(obj: ujson.Obj): DLCSignTLV = {
val contractId = ByteVector.fromValidHex(obj(PicklerKeys.contractIdKey).str) val contractId = ByteVector.fromValidHex(obj(PicklerKeys.contractIdKey).str)
val adaptorSigs = parseCetAdaptorSignatures( val adaptorSigs = parseCetAdaptorSignatures(
obj(PicklerKeys.cetAdaptorSignaturesKey).obj) obj(PicklerKeys.cetAdaptorSignaturesKey).obj
)
val refundSignature = val refundSignature =
ECDigitalSignature.fromHex(obj(PicklerKeys.refundSignatureKey).str) ECDigitalSignature.fromHex(obj(PicklerKeys.refundSignatureKey).str)
val fundingSignatures = parseFundingSignatures( val fundingSignatures = parseFundingSignatures(
obj(PicklerKeys.fundingSignaturesKey).obj) obj(PicklerKeys.fundingSignaturesKey).obj
)
val signTLV = val signTLV =
DLCSignTLV(contractId, adaptorSigs, refundSignature, fundingSignatures) DLCSignTLV(contractId, adaptorSigs, refundSignature, fundingSignatures)
@ -441,7 +462,8 @@ object Picklers {
ujson.Obj( ujson.Obj(
PicklerKeys.contractIdKey -> sign.contractId.toHex, PicklerKeys.contractIdKey -> sign.contractId.toHex,
PicklerKeys.cetAdaptorSignaturesKey -> writeCetAdaptorSigs( PicklerKeys.cetAdaptorSignaturesKey -> writeCetAdaptorSigs(
sign.cetSignatures), sign.cetSignatures
),
PicklerKeys.refundSignatureKey -> ujson.Str(sign.refundSignature.hex), PicklerKeys.refundSignatureKey -> ujson.Str(sign.refundSignature.hex),
PicklerKeys.fundingSignaturesKey -> PicklerKeys.fundingSignaturesKey ->
writeFundingSignatures(sign.fundingSignatures) writeFundingSignatures(sign.fundingSignatures)
@ -456,8 +478,8 @@ object Picklers {
readwriter[ujson.Obj].bimap(writeSignTLV, readSignTLV) readwriter[ujson.Obj].bimap(writeSignTLV, readSignTLV)
} }
implicit val lnMessageDLCAcceptTLVPickler: ReadWriter[ implicit val lnMessageDLCAcceptTLVPickler
LnMessage[DLCAcceptTLV]] = : ReadWriter[LnMessage[DLCAcceptTLV]] =
readwriter[String].bimap(_.hex, LnMessageFactory(DLCAcceptTLV).fromHex) readwriter[String].bimap(_.hex, LnMessageFactory(DLCAcceptTLV).fromHex)
implicit val lnMessageDLCSignTLVPickler: ReadWriter[LnMessage[DLCSignTLV]] = implicit val lnMessageDLCSignTLVPickler: ReadWriter[LnMessage[DLCSignTLV]] =
@ -488,8 +510,8 @@ object Picklers {
implicit val addressLabelTagPickler: ReadWriter[AddressLabelTag] = implicit val addressLabelTagPickler: ReadWriter[AddressLabelTag] =
readwriter[String].bimap(_.name, AddressLabelTag) readwriter[String].bimap(_.name, AddressLabelTag)
implicit val lockUnspentOutputParameterPickler: ReadWriter[ implicit val lockUnspentOutputParameterPickler
LockUnspentOutputParameter] = : ReadWriter[LockUnspentOutputParameter] =
readwriter[Value].bimap(_.toJson, LockUnspentOutputParameter.fromJson) readwriter[Value].bimap(_.toJson, LockUnspentOutputParameter.fromJson)
// can't make implicit because it will overlap with ones needed for cli // can't make implicit because it will overlap with ones needed for cli
@ -501,8 +523,10 @@ object Picklers {
val descriptorJson = announcement.eventTLV.eventDescriptor match { val descriptorJson = announcement.eventTLV.eventDescriptor match {
case EnumEventDescriptorV0TLV(outcomes) => case EnumEventDescriptorV0TLV(outcomes) =>
Obj("outcomes" -> outcomes.map(Str(_)), Obj(
"hex" -> announcement.eventTLV.eventDescriptor.hex) "outcomes" -> outcomes.map(Str(_)),
"hex" -> announcement.eventTLV.eventDescriptor.hex
)
case numeric: NumericEventDescriptorTLV => case numeric: NumericEventDescriptorTLV =>
Obj( Obj(
"base" -> Num(numeric.base.toLong.toDouble), "base" -> Num(numeric.base.toLong.toDouble),
@ -516,10 +540,12 @@ object Picklers {
val maturityStr = val maturityStr =
TimeUtil.iso8601ToString(Date.from(announcement.eventTLV.maturation)) TimeUtil.iso8601ToString(Date.from(announcement.eventTLV.maturation))
val eventJson = Obj("nonces" -> noncesJson, val eventJson = Obj(
"maturity" -> Str(maturityStr), "nonces" -> noncesJson,
"descriptor" -> descriptorJson, "maturity" -> Str(maturityStr),
"eventId" -> Str(announcement.eventTLV.eventId)) "descriptor" -> descriptorJson,
"eventId" -> Str(announcement.eventTLV.eventId)
)
Obj( Obj(
"announcementSignature" -> Str(announcement.announcementSignature.hex), "announcementSignature" -> Str(announcement.announcementSignature.hex),
@ -541,10 +567,12 @@ object Picklers {
val sigsJson = attestments.sigs.map(sig => Str(sig.hex)) val sigsJson = attestments.sigs.map(sig => Str(sig.hex))
val valuesJson = attestments.outcomes.map(Str(_)) val valuesJson = attestments.outcomes.map(Str(_))
Obj("eventId" -> Str(attestments.eventId), Obj(
"signatures" -> sigsJson, "eventId" -> Str(attestments.eventId),
"values" -> valuesJson, "signatures" -> sigsJson,
"hex" -> attestments.hex) "values" -> valuesJson,
"hex" -> attestments.hex
)
} }
implicit val fundingInputV0Writer: Writer[FundingInputTLV] = implicit val fundingInputV0Writer: Writer[FundingInputTLV] =
@ -591,15 +619,17 @@ object Picklers {
} }
} }
implicit val hyperbolaPayoutCurvePieceTLVWriter: Writer[ implicit val hyperbolaPayoutCurvePieceTLVWriter
HyperbolaPayoutCurvePieceTLV] = { : Writer[HyperbolaPayoutCurvePieceTLV] = {
writer[Obj].comap { piece => writer[Obj].comap { piece =>
Obj( Obj(
PicklerKeys.usePositivePiece -> Bool(piece.usePositivePiece), PicklerKeys.usePositivePiece -> Bool(piece.usePositivePiece),
PicklerKeys.translateOutcome -> Num( PicklerKeys.translateOutcome -> Num(
piece.translateOutcome.toBigDecimal.toDouble), piece.translateOutcome.toBigDecimal.toDouble
),
PicklerKeys.translatePayout -> Num( PicklerKeys.translatePayout -> Num(
piece.translatePayout.toBigDecimal.toDouble), piece.translatePayout.toBigDecimal.toDouble
),
PicklerKeys.a -> Num(piece.a.toBigDecimal.toDouble), PicklerKeys.a -> Num(piece.a.toBigDecimal.toDouble),
PicklerKeys.b -> Num(piece.b.toBigDecimal.toDouble), PicklerKeys.b -> Num(piece.b.toBigDecimal.toDouble),
PicklerKeys.c -> Num(piece.c.toBigDecimal.toDouble), PicklerKeys.c -> Num(piece.c.toBigDecimal.toDouble),
@ -612,7 +642,7 @@ object Picklers {
implicit val payoutFunctionV0TLVWriter: Writer[PayoutFunctionV0TLV] = { implicit val payoutFunctionV0TLVWriter: Writer[PayoutFunctionV0TLV] = {
def endpoint(json: Value, isEndpoint: Boolean): Value = json match { def endpoint(json: Value, isEndpoint: Boolean): Value = json match {
case obj: Obj => case obj: Obj =>
//drop old value on the floor if there is one // drop old value on the floor if there is one
obj.value.put(PicklerKeys.isEndpointKey, Bool(isEndpoint)) obj.value.put(PicklerKeys.isEndpointKey, Bool(isEndpoint))
Obj(obj.value) Obj(obj.value)
case v: Value => v case v: Value => v
@ -644,7 +674,8 @@ object Picklers {
val points: Vector[TLVPoint] = pointsArr.map { val points: Vector[TLVPoint] = pointsArr.map {
case x @ (_: Arr | _: Num | Null | _: Bool | _: Str) => case x @ (_: Arr | _: Num | Null | _: Bool | _: Str) =>
sys.error( sys.error(
s"Cannot have $x when parsing payout curve points, expected json object") s"Cannot have $x when parsing payout curve points, expected json object"
)
case obj: Obj => case obj: Obj =>
upickle.default.read[TLVPoint](obj) upickle.default.read[TLVPoint](obj)
}.toVector }.toVector
@ -660,8 +691,10 @@ object Picklers {
import roundingIntervals._ import roundingIntervals._
val intervalsJs = intervalStarts.map { i => val intervalsJs = intervalStarts.map { i =>
Obj("beginInterval" -> Num(i._1.toDouble), Obj(
"roundingMod" -> Num(i._2.toLong.toDouble)) "beginInterval" -> Num(i._1.toDouble),
"roundingMod" -> Num(i._2.toLong.toDouble)
)
} }
Obj("intervals" -> intervalsJs) Obj("intervals" -> intervalsJs)
@ -686,10 +719,12 @@ object Picklers {
writer[Obj].comap { v1 => writer[Obj].comap { v1 =>
import v1._ import v1._
Obj("numDigits" -> Num(numDigits.toDouble), Obj(
"payoutFunction" -> writeJs(payoutFunction), "numDigits" -> Num(numDigits.toDouble),
"roundingIntervals" -> writeJs(roundingIntervals), "payoutFunction" -> writeJs(payoutFunction),
"hex" -> v1.hex) "roundingIntervals" -> writeJs(roundingIntervals),
"hex" -> v1.hex
)
} }
implicit val contractDescriptorWriter: Writer[ContractDescriptorTLV] = implicit val contractDescriptorWriter: Writer[ContractDescriptorTLV] =
@ -704,23 +739,29 @@ object Picklers {
writer[Obj].comap { oracleInfo => writer[Obj].comap { oracleInfo =>
Obj( Obj(
"announcement" -> writeJs(oracleInfo.announcement)( "announcement" -> writeJs(oracleInfo.announcement)(
oracleAnnouncementTLVJsonWriter)) oracleAnnouncementTLVJsonWriter
)
)
} }
implicit val oracleInfoV1TLVWriter: Writer[OracleInfoV1TLV] = implicit val oracleInfoV1TLVWriter: Writer[OracleInfoV1TLV] =
writer[Obj].comap { oracleInfo => writer[Obj].comap { oracleInfo =>
import oracleInfo._ import oracleInfo._
Obj("threshold" -> Num(threshold.toDouble), Obj(
"announcements" -> oracles.map(o => "threshold" -> Num(threshold.toDouble),
writeJs(o)(oracleAnnouncementTLVJsonWriter))) "announcements" -> oracles.map(o =>
writeJs(o)(oracleAnnouncementTLVJsonWriter))
)
} }
implicit val oracleParamsV0TLVWriter: Writer[OracleParamsV0TLV] = implicit val oracleParamsV0TLVWriter: Writer[OracleParamsV0TLV] =
writer[Obj].comap { params => writer[Obj].comap { params =>
import params._ import params._
Obj("maxErrorExp" -> Num(maxErrorExp.toDouble), Obj(
"minFailExp" -> Num(minFailExp.toDouble), "maxErrorExp" -> Num(maxErrorExp.toDouble),
"maximizeCoverage" -> Bool(maximizeCoverage)) "minFailExp" -> Num(minFailExp.toDouble),
"maximizeCoverage" -> Bool(maximizeCoverage)
)
} }
implicit val oracleParamsTLVWriter: Writer[OracleParamsTLV] = implicit val oracleParamsTLVWriter: Writer[OracleParamsTLV] =
@ -731,10 +772,12 @@ object Picklers {
implicit val oracleInfoV2TLVWriter: Writer[OracleInfoV2TLV] = implicit val oracleInfoV2TLVWriter: Writer[OracleInfoV2TLV] =
writer[Obj].comap { oracleInfo => writer[Obj].comap { oracleInfo =>
import oracleInfo._ import oracleInfo._
Obj("threshold" -> Num(threshold.toDouble), Obj(
"announcements" -> oracles.map(o => "threshold" -> Num(threshold.toDouble),
writeJs(o)(oracleAnnouncementTLVJsonWriter)), "announcements" -> oracles.map(o =>
"params" -> writeJs(params)) writeJs(o)(oracleAnnouncementTLVJsonWriter)),
"params" -> writeJs(params)
)
} }
implicit val oracleInfoTLVWriter: Writer[OracleInfoTLV] = implicit val oracleInfoTLVWriter: Writer[OracleInfoTLV] =
@ -762,15 +805,18 @@ object Picklers {
case (c, o) => case (c, o) =>
val contractDescriptorJson = writeJs(c) val contractDescriptorJson = writeJs(c)
val oracleInfoJson = writeJs(o) val oracleInfoJson = writeJs(o)
ujson.Obj(PicklerKeys.contractDescriptorKey -> contractDescriptorJson, ujson.Obj(
PicklerKeys.oracleInfoKey -> oracleInfoJson) PicklerKeys.contractDescriptorKey -> contractDescriptorJson,
PicklerKeys.oracleInfoKey -> oracleInfoJson
)
} }
val arrayJson = ujson.Arr.from(arrayVec) val arrayJson = ujson.Arr.from(arrayVec)
Obj( Obj(
PicklerKeys.totalCollateralKey -> Num( PicklerKeys.totalCollateralKey -> Num(
contractInfo.totalCollateral.toLong.toDouble), contractInfo.totalCollateral.toLong.toDouble
),
PicklerKeys.pairsKey -> arrayJson PicklerKeys.pairsKey -> arrayJson
) )
} }
@ -840,9 +886,11 @@ object Picklers {
PicklerKeys.tempContractIdKey -> Str(tempContractId.hex), PicklerKeys.tempContractIdKey -> Str(tempContractId.hex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -852,8 +900,8 @@ object Picklers {
) )
} }
implicit val acceptedComputingAdaptorSigsW: Writer[ implicit val acceptedComputingAdaptorSigsW
AcceptedComputingAdaptorSigs] = writer[Obj].comap { accepted => : Writer[AcceptedComputingAdaptorSigs] = writer[Obj].comap { accepted =>
import accepted._ import accepted._
Obj( Obj(
"state" -> Str(statusString), "state" -> Str(statusString),
@ -864,9 +912,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -887,9 +937,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -911,9 +963,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -935,9 +989,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -960,9 +1016,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -985,9 +1043,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -1004,11 +1064,15 @@ object Picklers {
import claimed._ import claimed._
val (oraclesJs, outcomesJs) = oracleOutcome match { val (oraclesJs, outcomesJs) = oracleOutcome match {
case EnumOracleOutcome(oracles, outcome) => case EnumOracleOutcome(oracles, outcome) =>
(Arr.from(oracles.map(o => Str(o.announcement.hex))), (
Str(outcome.outcome)) Arr.from(oracles.map(o => Str(o.announcement.hex))),
Str(outcome.outcome)
)
case numeric: NumericOracleOutcome => case numeric: NumericOracleOutcome =>
(Arr.from(numeric.oracles.map(_.announcement.hex)), (
Arr.from(numeric.outcomes.map(o => Arr.from(o.digits)))) Arr.from(numeric.oracles.map(_.announcement.hex)),
Arr.from(numeric.outcomes.map(o => Arr.from(o.digits)))
)
} }
Obj( Obj(
@ -1020,9 +1084,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -1034,7 +1100,8 @@ object Picklers {
"oracles" -> oraclesJs, "oracles" -> oraclesJs,
PicklerKeys.myPayout -> Num(claimed.myPayout.satoshis.toLong.toDouble), PicklerKeys.myPayout -> Num(claimed.myPayout.satoshis.toLong.toDouble),
counterPartyPayoutKey -> Num( counterPartyPayoutKey -> Num(
claimed.counterPartyPayout.satoshis.toLong.toDouble), claimed.counterPartyPayout.satoshis.toLong.toDouble
),
PicklerKeys.pnl -> Num(claimed.pnl.satoshis.toLong.toDouble), PicklerKeys.pnl -> Num(claimed.pnl.satoshis.toLong.toDouble),
PicklerKeys.rateOfReturn -> Num(claimed.rateOfReturn.toDouble), PicklerKeys.rateOfReturn -> Num(claimed.rateOfReturn.toDouble),
"payoutAddress" -> writeJs(payoutAddress), "payoutAddress" -> writeJs(payoutAddress),
@ -1047,11 +1114,15 @@ object Picklers {
import remoteClaimed._ import remoteClaimed._
val (oraclesJs, outcomesJs) = oracleOutcome match { val (oraclesJs, outcomesJs) = oracleOutcome match {
case EnumOracleOutcome(oracles, outcome) => case EnumOracleOutcome(oracles, outcome) =>
(Arr.from(oracles.map(o => Str(o.announcement.hex))), (
Str(outcome.outcome)) Arr.from(oracles.map(o => Str(o.announcement.hex))),
Str(outcome.outcome)
)
case numeric: NumericOracleOutcome => case numeric: NumericOracleOutcome =>
(Arr.from(numeric.oracles.map(_.announcement.hex)), (
Arr.from(numeric.outcomes.map(o => Arr.from(o.digits)))) Arr.from(numeric.oracles.map(_.announcement.hex)),
Arr.from(numeric.outcomes.map(o => Arr.from(o.digits)))
)
} }
Obj( Obj(
@ -1063,9 +1134,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -1076,9 +1149,11 @@ object Picklers {
"outcomes" -> outcomesJs, "outcomes" -> outcomesJs,
"oracles" -> oraclesJs, "oracles" -> oraclesJs,
PicklerKeys.myPayout -> Num( PicklerKeys.myPayout -> Num(
remoteClaimed.myPayout.satoshis.toLong.toDouble), remoteClaimed.myPayout.satoshis.toLong.toDouble
),
counterPartyPayoutKey -> Num( counterPartyPayoutKey -> Num(
remoteClaimed.counterPartyPayout.satoshis.toLong.toDouble), remoteClaimed.counterPartyPayout.satoshis.toLong.toDouble
),
PicklerKeys.pnl -> Num(remoteClaimed.pnl.satoshis.toLong.toDouble), PicklerKeys.pnl -> Num(remoteClaimed.pnl.satoshis.toLong.toDouble),
PicklerKeys.rateOfReturn -> Num(remoteClaimed.rateOfReturn.toDouble), PicklerKeys.rateOfReturn -> Num(remoteClaimed.rateOfReturn.toDouble),
"payoutAddress" -> writeJs(payoutAddress), "payoutAddress" -> writeJs(payoutAddress),
@ -1097,9 +1172,11 @@ object Picklers {
"contractId" -> Str(contractId.toHex), "contractId" -> Str(contractId.toHex),
"contractInfo" -> Str(contractInfo.hex), "contractInfo" -> Str(contractInfo.hex),
"contractMaturity" -> Num( "contractMaturity" -> Num(
timeouts.contractMaturity.toUInt32.toLong.toDouble), timeouts.contractMaturity.toUInt32.toLong.toDouble
),
"contractTimeout" -> Num( "contractTimeout" -> Num(
timeouts.contractTimeout.toUInt32.toLong.toDouble), timeouts.contractTimeout.toUInt32.toLong.toDouble
),
"feeRate" -> Num(feeRate.toLong.toDouble), "feeRate" -> Num(feeRate.toLong.toDouble),
"totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble), "totalCollateral" -> Num(totalCollateral.satoshis.toLong.toDouble),
"localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble), "localCollateral" -> Num(localCollateral.satoshis.toLong.toDouble),
@ -1108,7 +1185,8 @@ object Picklers {
"closingTxId" -> Str(closingTxId.hex), "closingTxId" -> Str(closingTxId.hex),
PicklerKeys.myPayout -> Num(refunded.myPayout.satoshis.toLong.toDouble), PicklerKeys.myPayout -> Num(refunded.myPayout.satoshis.toLong.toDouble),
counterPartyPayoutKey -> Num( counterPartyPayoutKey -> Num(
refunded.counterPartyPayout.satoshis.toLong.toDouble), refunded.counterPartyPayout.satoshis.toLong.toDouble
),
PicklerKeys.pnl -> Num(refunded.pnl.satoshis.toLong.toDouble), PicklerKeys.pnl -> Num(refunded.pnl.satoshis.toLong.toDouble),
PicklerKeys.rateOfReturn -> Num(refunded.rateOfReturn.toDouble), PicklerKeys.rateOfReturn -> Num(refunded.rateOfReturn.toDouble),
"payoutAddress" -> writeJs(payoutAddress), "payoutAddress" -> writeJs(payoutAddress),
@ -1161,11 +1239,13 @@ object Picklers {
val message = Try(obj("message").str).toOption val message = Try(obj("message").str).toOption
val receivedAt = Instant.ofEpochSecond(obj("receivedAt").num.toLong) val receivedAt = Instant.ofEpochSecond(obj("receivedAt").num.toLong)
val offerTLV = DLCOfferTLV.fromHex(obj("offerTLV").str) val offerTLV = DLCOfferTLV.fromHex(obj("offerTLV").str)
IncomingDLCOfferDb(hash = hash, IncomingDLCOfferDb(
peer = peer, hash = hash,
message = message, peer = peer,
receivedAt = receivedAt, message = message,
offerTLV = offerTLV) receivedAt = receivedAt,
offerTLV = offerTLV
)
} }
implicit val dlcOfferRemoveR: Reader[Sha256Digest] = implicit val dlcOfferRemoveR: Reader[Sha256Digest] =
@ -1202,8 +1282,10 @@ object Picklers {
lazy val payoutAddress: Option[PayoutAddress] = payoutAddressJs match { lazy val payoutAddress: Option[PayoutAddress] = payoutAddressJs match {
case json: Obj => case json: Obj =>
json("address").strOpt.map(a => json("address").strOpt.map(a =>
PayoutAddress(BitcoinAddress.fromString(a), PayoutAddress(
json("isExternal").boolOpt.getOrElse(false))) BitcoinAddress.fromString(a),
json("isExternal").boolOpt.getOrElse(false)
))
case Null => None case Null => None
case v: Value => case v: Value =>
throw new IllegalArgumentException(s"Unexpected payout address $v") throw new IllegalArgumentException(s"Unexpected payout address $v")
@ -1228,8 +1310,10 @@ object Picklers {
lazy val oracleOutcome = outcomes.head match { lazy val oracleOutcome = outcomes.head match {
case outcome: EnumOutcome => case outcome: EnumOutcome =>
EnumOracleOutcome(oracles.asInstanceOf[Vector[EnumSingleOracleInfo]], EnumOracleOutcome(
outcome) oracles.asInstanceOf[Vector[EnumSingleOracleInfo]],
outcome
)
case UnsignedNumericOutcome(_) => case UnsignedNumericOutcome(_) =>
val numericOutcomes = val numericOutcomes =
outcomes.map(_.asInstanceOf[UnsignedNumericOutcome]) outcomes.map(_.asInstanceOf[UnsignedNumericOutcome])
@ -1377,8 +1461,10 @@ object Picklers {
peerOpt peerOpt
) )
case DLCState.RemoteClaimed => case DLCState.RemoteClaimed =>
require(oracleSigs.size == 1, require(
"Remote claimed should only have one oracle sig") oracleSigs.size == 1,
"Remote claimed should only have one oracle sig"
)
RemoteClaimed( RemoteClaimed(
dlcId, dlcId,
isInitiator, isInitiator,
@ -1425,13 +1511,17 @@ object Picklers {
writer[Obj].comap { walletAccounting: DLCWalletAccounting => writer[Obj].comap { walletAccounting: DLCWalletAccounting =>
Obj( Obj(
PicklerKeys.myCollateral -> Num( PicklerKeys.myCollateral -> Num(
walletAccounting.myCollateral.satoshis.toLong.toDouble), walletAccounting.myCollateral.satoshis.toLong.toDouble
),
PicklerKeys.theirCollateral -> Num( PicklerKeys.theirCollateral -> Num(
walletAccounting.theirCollateral.satoshis.toLong.toDouble), walletAccounting.theirCollateral.satoshis.toLong.toDouble
),
PicklerKeys.myPayout -> Num( PicklerKeys.myPayout -> Num(
walletAccounting.myPayout.satoshis.toLong.toDouble), walletAccounting.myPayout.satoshis.toLong.toDouble
),
PicklerKeys.theirPayout -> Num( PicklerKeys.theirPayout -> Num(
walletAccounting.theirPayout.satoshis.toLong.toDouble), walletAccounting.theirPayout.satoshis.toLong.toDouble
),
PicklerKeys.pnl -> Num(walletAccounting.pnl.satoshis.toLong.toDouble), PicklerKeys.pnl -> Num(walletAccounting.pnl.satoshis.toLong.toDouble),
PicklerKeys.rateOfReturn -> Num(walletAccounting.rateOfReturn.toDouble) PicklerKeys.rateOfReturn -> Num(walletAccounting.rateOfReturn.toDouble)
) )
@ -1441,7 +1531,8 @@ object Picklers {
implicit val mnemonicCodePickler: ReadWriter[MnemonicCode] = implicit val mnemonicCodePickler: ReadWriter[MnemonicCode] =
readwriter[String].bimap( readwriter[String].bimap(
_.words.mkString(" "), _.words.mkString(" "),
str => MnemonicCode.fromWords(str.split(' ').toVector)) str => MnemonicCode.fromWords(str.split(' ').toVector)
)
implicit val extPrivateKeyPickler: ReadWriter[ExtPrivateKey] = implicit val extPrivateKeyPickler: ReadWriter[ExtPrivateKey] =
readwriter[String].bimap(ExtKey.toString, ExtPrivateKey.fromString) readwriter[String].bimap(ExtKey.toString, ExtPrivateKey.fromString)
@ -1559,13 +1650,16 @@ object Picklers {
} }
private def writeCompactFilterDb( private def writeCompactFilterDb(
compactFilterDb: CompactFilterDb): ujson.Obj = { compactFilterDb: CompactFilterDb
): ujson.Obj = {
ujson.Obj( ujson.Obj(
PicklerKeys.hashKey -> ujson.Str(compactFilterDb.hashBE.hex), PicklerKeys.hashKey -> ujson.Str(compactFilterDb.hashBE.hex),
PicklerKeys.filterTypeKey -> ujson.Str( PicklerKeys.filterTypeKey -> ujson.Str(
compactFilterDb.filterType.toString), compactFilterDb.filterType.toString
),
PicklerKeys.compactFilterBytesKey -> ujson.Str( PicklerKeys.compactFilterBytesKey -> ujson.Str(
compactFilterDb.bytes.toHex), compactFilterDb.bytes.toHex
),
PicklerKeys.heightKey -> ujson.Num(compactFilterDb.height), PicklerKeys.heightKey -> ujson.Num(compactFilterDb.height),
PicklerKeys.blockHashKey -> ujson.Str(compactFilterDb.blockHashBE.hex) PicklerKeys.blockHashKey -> ujson.Str(compactFilterDb.blockHashBE.hex)
) )
@ -1590,7 +1684,8 @@ object Picklers {
} }
private def writeCompactFilterHeaderDb( private def writeCompactFilterHeaderDb(
filterHeaderDb: CompactFilterHeaderDb): ujson.Obj = { filterHeaderDb: CompactFilterHeaderDb
): ujson.Obj = {
ujson.Obj( ujson.Obj(
PicklerKeys.hashKey -> ujson.Str(filterHeaderDb.hashBE.hex), PicklerKeys.hashKey -> ujson.Str(filterHeaderDb.hashBE.hex),
PicklerKeys.filterHashKey -> ujson.Str(filterHeaderDb.filterHashBE.hex), PicklerKeys.filterHashKey -> ujson.Str(filterHeaderDb.filterHashBE.hex),
@ -1602,7 +1697,8 @@ object Picklers {
} }
private def readCompactFilterHeaderDb( private def readCompactFilterHeaderDb(
obj: ujson.Obj): CompactFilterHeaderDb = { obj: ujson.Obj
): CompactFilterHeaderDb = {
val hash = DoubleSha256DigestBE.fromHex(obj(PicklerKeys.hashKey).str) val hash = DoubleSha256DigestBE.fromHex(obj(PicklerKeys.hashKey).str)
val filterHash = val filterHash =
DoubleSha256DigestBE.fromHex(obj(PicklerKeys.filterHashKey).str) DoubleSha256DigestBE.fromHex(obj(PicklerKeys.filterHashKey).str)
@ -1611,11 +1707,13 @@ object Picklers {
val blockHash = val blockHash =
DoubleSha256DigestBE.fromHex(obj(PicklerKeys.blockHashKey).str) DoubleSha256DigestBE.fromHex(obj(PicklerKeys.blockHashKey).str)
val height = obj(PicklerKeys.heightKey).num val height = obj(PicklerKeys.heightKey).num
CompactFilterHeaderDb(hashBE = hash, CompactFilterHeaderDb(
filterHashBE = filterHash, hashBE = hash,
previousFilterHeaderBE = previousFilterHeader, filterHashBE = filterHash,
blockHashBE = blockHash, previousFilterHeaderBE = previousFilterHeader,
height = height.toInt) blockHashBE = blockHash,
height = height.toInt
)
} }
private def writeContactDb(contact: DLCContactDb): ujson.Obj = { private def writeContactDb(contact: DLCContactDb): ujson.Obj = {

View File

@ -4,8 +4,9 @@ import play.api.libs.json._
sealed abstract class SerializerUtil { sealed abstract class SerializerUtil {
def processJsNumberBigInt[T](numFunc: BigInt => T)( def processJsNumberBigInt[T](
json: JsValue): JsResult[T] = numFunc: BigInt => T
)(json: JsValue): JsResult[T] =
json match { json match {
case JsNumber(nDecimal) => case JsNumber(nDecimal) =>
val nOpt = nDecimal.toBigIntExact val nOpt = nDecimal.toBigIntExact
@ -54,14 +55,15 @@ sealed abstract class SerializerUtil {
SerializerUtil.buildJsErrorMsg("jsstring", err) SerializerUtil.buildJsErrorMsg("jsstring", err)
} }
def processJsStringOpt[T](f: String => Option[T])( def processJsStringOpt[T](
jsValue: JsValue): JsResult[T] = { f: String => Option[T]
)(jsValue: JsValue): JsResult[T] = {
jsValue match { jsValue match {
case JsString(key) => case JsString(key) =>
val tOpt = f(key) val tOpt = f(key)
tOpt match { tOpt match {
case Some(t) => JsSuccess(t) case Some(t) => JsSuccess(t)
case None => SerializerUtil.buildErrorMsg("invalid jsstring", jsValue) case None => SerializerUtil.buildErrorMsg("invalid jsstring", jsValue)
} }
case err @ (_: JsNumber | _: JsObject | _: JsArray | JsNull | case err @ (_: JsNumber | _: JsObject | _: JsArray | JsNull |
_: JsBoolean) => _: JsBoolean) =>

View File

@ -69,13 +69,15 @@ object WsPicklers {
} }
private def writeChainNotification( private def writeChainNotification(
notification: ChainNotification[_]): ujson.Obj = { notification: ChainNotification[_]
): ujson.Obj = {
val payloadJson: ujson.Value = notification match { val payloadJson: ujson.Value = notification match {
case BlockProcessedNotification(block) => case BlockProcessedNotification(block) =>
upickle.default.writeJs(block)(Picklers.getBlockHeaderResultPickler) upickle.default.writeJs(block)(Picklers.getBlockHeaderResultPickler)
case CompactFilterHeaderProcessedNotification(filterHeader) => case CompactFilterHeaderProcessedNotification(filterHeader) =>
upickle.default.writeJs(filterHeader)( upickle.default.writeJs(filterHeader)(
Picklers.compactFilterHeaderPickler) Picklers.compactFilterHeaderPickler
)
case CompactFilterProcessedNotification(filter) => case CompactFilterProcessedNotification(filter) =>
upickle.default.writeJs(filter)(Picklers.compactFilterDbPickler) upickle.default.writeJs(filter)(Picklers.compactFilterDbPickler)
case SyncFlagChangedNotification(syncing) => case SyncFlagChangedNotification(syncing) =>
@ -115,7 +117,8 @@ object WsPicklers {
} }
private def writeWalletNotification( private def writeWalletNotification(
notification: WalletNotification[_]): ujson.Obj = { notification: WalletNotification[_]
): ujson.Obj = {
val payloadJson: ujson.Value = notification match { val payloadJson: ujson.Value = notification match {
case TxBroadcastNotification(tx) => case TxBroadcastNotification(tx) =>
upickle.default.writeJs(tx)(Picklers.transactionPickler) upickle.default.writeJs(tx)(Picklers.transactionPickler)
@ -184,7 +187,8 @@ object WsPicklers {
} }
private def writeTorNotification( private def writeTorNotification(
notification: TorNotification[_]): ujson.Obj = { notification: TorNotification[_]
): ujson.Obj = {
val payloadJson = notification.`type` match { val payloadJson = notification.`type` match {
case TorWsType.TorStarted => case TorWsType.TorStarted =>
ujson.Null ujson.Null
@ -206,12 +210,15 @@ object WsPicklers {
} }
private def writeDLCNodeNotification( private def writeDLCNodeNotification(
notification: DLCNodeNotification[_]): ujson.Obj = { notification: DLCNodeNotification[_]
): ujson.Obj = {
def addr2str(address: InetSocketAddress) = def addr2str(address: InetSocketAddress) =
address.getHostName + ":" + address.getPort address.getHostName + ":" + address.getPort
def failure2obj(payload: (Sha256Digest, String)): ujson.Obj = { def failure2obj(payload: (Sha256Digest, String)): ujson.Obj = {
ujson.Obj(PicklerKeys.idKey -> writeJs(payload._1.hex), ujson.Obj(
PicklerKeys.errorKey -> writeJs(payload._2)) PicklerKeys.idKey -> writeJs(payload._1.hex),
PicklerKeys.errorKey -> writeJs(payload._2)
)
} }
val payloadJson: ujson.Value = notification match { val payloadJson: ujson.Value = notification match {
case DLCNodeConnectionInitiated(address) => case DLCNodeConnectionInitiated(address) =>
@ -238,13 +245,16 @@ object WsPicklers {
} }
private def readDLCNodeNotification( private def readDLCNodeNotification(
obj: ujson.Obj): DLCNodeNotification[_] = { obj: ujson.Obj
): DLCNodeNotification[_] = {
val typeObj = read[DLCNodeWsType](obj(PicklerKeys.typeKey)) val typeObj = read[DLCNodeWsType](obj(PicklerKeys.typeKey))
val payloadObj = obj(PicklerKeys.payloadKey) val payloadObj = obj(PicklerKeys.payloadKey)
def obj2failure(payload: ujson.Value): (Sha256Digest, String) = { def obj2failure(payload: ujson.Value): (Sha256Digest, String) = {
(Sha256Digest.fromHex(payload.obj(PicklerKeys.idKey).str), (
payload.obj(PicklerKeys.errorKey).str) Sha256Digest.fromHex(payload.obj(PicklerKeys.idKey).str),
payload.obj(PicklerKeys.errorKey).str
)
} }
typeObj match { typeObj match {
@ -278,25 +288,29 @@ object WsPicklers {
implicit val newAddressPickler: ReadWriter[NewAddressNotification] = { implicit val newAddressPickler: ReadWriter[NewAddressNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeWalletNotification(_), writeWalletNotification(_),
readWalletNotification(_).asInstanceOf[NewAddressNotification]) readWalletNotification(_).asInstanceOf[NewAddressNotification]
)
} }
implicit val txProcessedPickler: ReadWriter[TxProcessedNotification] = { implicit val txProcessedPickler: ReadWriter[TxProcessedNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeWalletNotification(_), writeWalletNotification(_),
readWalletNotification(_).asInstanceOf[TxProcessedNotification]) readWalletNotification(_).asInstanceOf[TxProcessedNotification]
)
} }
implicit val txBroadcastPickler: ReadWriter[TxBroadcastNotification] = { implicit val txBroadcastPickler: ReadWriter[TxBroadcastNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeWalletNotification(_), writeWalletNotification(_),
readWalletNotification(_).asInstanceOf[TxBroadcastNotification]) readWalletNotification(_).asInstanceOf[TxBroadcastNotification]
)
} }
implicit val reservedUtxosPickler: ReadWriter[ReservedUtxosNotification] = { implicit val reservedUtxosPickler: ReadWriter[ReservedUtxosNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeWalletNotification(_), writeWalletNotification(_),
readWalletNotification(_).asInstanceOf[ReservedUtxosNotification]) readWalletNotification(_).asInstanceOf[ReservedUtxosNotification]
)
} }
implicit val rescanPickler: ReadWriter[RescanComplete] = { implicit val rescanPickler: ReadWriter[RescanComplete] = {
@ -313,8 +327,8 @@ object WsPicklers {
) )
} }
implicit val dlcNodeNotificationPickler: ReadWriter[ implicit val dlcNodeNotificationPickler
DLCNodeNotification[_]] = { : ReadWriter[DLCNodeNotification[_]] = {
readwriter[ujson.Obj] readwriter[ujson.Obj]
.bimap(writeDLCNodeNotification, readDLCNodeNotification) .bimap(writeDLCNodeNotification, readDLCNodeNotification)
} }
@ -334,8 +348,8 @@ object WsPicklers {
) )
} }
implicit val compactFilterHeaderProcessedPickler: ReadWriter[ implicit val compactFilterHeaderProcessedPickler
CompactFilterHeaderProcessedNotification] = { : ReadWriter[CompactFilterHeaderProcessedNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeChainNotification(_), writeChainNotification(_),
readChainNotification(_) readChainNotification(_)
@ -343,16 +357,16 @@ object WsPicklers {
) )
} }
implicit val compactFilterProcessedPickler: ReadWriter[ implicit val compactFilterProcessedPickler
CompactFilterProcessedNotification] = { : ReadWriter[CompactFilterProcessedNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeChainNotification(_), writeChainNotification(_),
readChainNotification(_).asInstanceOf[CompactFilterProcessedNotification] readChainNotification(_).asInstanceOf[CompactFilterProcessedNotification]
) )
} }
implicit val syncFlagChangedPickler: ReadWriter[ implicit val syncFlagChangedPickler
SyncFlagChangedNotification] = { : ReadWriter[SyncFlagChangedNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeChainNotification(_), writeChainNotification(_),
readChainNotification(_).asInstanceOf[SyncFlagChangedNotification] readChainNotification(_).asInstanceOf[SyncFlagChangedNotification]
@ -362,83 +376,96 @@ object WsPicklers {
implicit val dlcStateChangePickler: ReadWriter[DLCStateChangeNotification] = { implicit val dlcStateChangePickler: ReadWriter[DLCStateChangeNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeWalletNotification(_), writeWalletNotification(_),
readWalletNotification(_).asInstanceOf[DLCStateChangeNotification]) readWalletNotification(_).asInstanceOf[DLCStateChangeNotification]
)
} }
implicit val dlcOfferAddPickler: ReadWriter[DLCOfferAddNotification] = { implicit val dlcOfferAddPickler: ReadWriter[DLCOfferAddNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeWalletNotification(_), writeWalletNotification(_),
readWalletNotification(_).asInstanceOf[DLCOfferAddNotification]) readWalletNotification(_).asInstanceOf[DLCOfferAddNotification]
)
} }
implicit val dlcOfferRemovePickler: ReadWriter[DLCOfferRemoveNotification] = { implicit val dlcOfferRemovePickler: ReadWriter[DLCOfferRemoveNotification] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeWalletNotification(_), writeWalletNotification(_),
readWalletNotification(_).asInstanceOf[DLCOfferRemoveNotification]) readWalletNotification(_).asInstanceOf[DLCOfferRemoveNotification]
)
} }
implicit val torStartedPickler: ReadWriter[ implicit val torStartedPickler
TorNotification.TorStartedNotification.type] = { : ReadWriter[TorNotification.TorStartedNotification.type] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeTorNotification(_), writeTorNotification(_),
readTorNotification(_) readTorNotification(_)
.asInstanceOf[TorNotification.TorStartedNotification.type]) .asInstanceOf[TorNotification.TorStartedNotification.type]
)
} }
implicit val dlcNodeConnectionInitiatedPickler: ReadWriter[ implicit val dlcNodeConnectionInitiatedPickler
DLCNodeConnectionInitiated] = { : ReadWriter[DLCNodeConnectionInitiated] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCNodeConnectionInitiated]) readDLCNodeNotification(_).asInstanceOf[DLCNodeConnectionInitiated]
)
} }
implicit val dlcNodeConnectionFailedPickler: ReadWriter[ implicit val dlcNodeConnectionFailedPickler
DLCNodeConnectionFailed] = { : ReadWriter[DLCNodeConnectionFailed] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCNodeConnectionFailed]) readDLCNodeNotification(_).asInstanceOf[DLCNodeConnectionFailed]
)
} }
implicit val dlcNodeConnectionEstablishedPickler: ReadWriter[ implicit val dlcNodeConnectionEstablishedPickler
DLCNodeConnectionEstablished] = { : ReadWriter[DLCNodeConnectionEstablished] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCNodeConnectionEstablished]) readDLCNodeNotification(_).asInstanceOf[DLCNodeConnectionEstablished]
)
} }
implicit val dlcAcceptSucceedPickler: ReadWriter[DLCAcceptSucceed] = { implicit val dlcAcceptSucceedPickler: ReadWriter[DLCAcceptSucceed] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCAcceptSucceed]) readDLCNodeNotification(_).asInstanceOf[DLCAcceptSucceed]
)
} }
implicit val dlcAcceptFailedPickler: ReadWriter[DLCAcceptFailed] = { implicit val dlcAcceptFailedPickler: ReadWriter[DLCAcceptFailed] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCAcceptFailed]) readDLCNodeNotification(_).asInstanceOf[DLCAcceptFailed]
)
} }
implicit val dlcSignSucceedPickler: ReadWriter[DLCSignSucceed] = { implicit val dlcSignSucceedPickler: ReadWriter[DLCSignSucceed] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCSignSucceed]) readDLCNodeNotification(_).asInstanceOf[DLCSignSucceed]
)
} }
implicit val dlcSignFailedPickler: ReadWriter[DLCSignFailed] = { implicit val dlcSignFailedPickler: ReadWriter[DLCSignFailed] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCSignFailed]) readDLCNodeNotification(_).asInstanceOf[DLCSignFailed]
)
} }
implicit val dlcOfferSendSucceedPickler: ReadWriter[DLCOfferSendSucceed] = { implicit val dlcOfferSendSucceedPickler: ReadWriter[DLCOfferSendSucceed] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCOfferSendSucceed]) readDLCNodeNotification(_).asInstanceOf[DLCOfferSendSucceed]
)
} }
implicit val dlcOfferSendFailedPickler: ReadWriter[DLCOfferSendFailed] = { implicit val dlcOfferSendFailedPickler: ReadWriter[DLCOfferSendFailed] = {
readwriter[ujson.Obj].bimap( readwriter[ujson.Obj].bimap(
writeDLCNodeNotification(_), writeDLCNodeNotification(_),
readDLCNodeNotification(_).asInstanceOf[DLCOfferSendFailed]) readDLCNodeNotification(_).asInstanceOf[DLCOfferSendFailed]
)
} }
} }

View File

@ -5,14 +5,15 @@ import org.bitcoins.commons.config.AppConfig
import java.nio.file.{Path, Paths} import java.nio.file.{Path, Paths}
/** Parses the correct datadir given the possible input sources for datadir config /** Parses the correct datadir given the possible input sources for datadir
* 1. The --datadir command line flag * config
* 2. Inferring the datadir based on the bitcoin network configured * 1. The --datadir command line flag 2. Inferring the datadir based on the
* 3. ??? Anything else i'm forgetting ???? * bitcoin network configured 3. ??? Anything else i'm forgetting ????
*/ */
case class DatadirParser( case class DatadirParser(
serverArgs: ServerArgParser, serverArgs: ServerArgParser,
customFinalDirOpt: Option[String]) { customFinalDirOpt: Option[String]
) {
/** Sets the default data dir, overridden by the --datadir option */ /** Sets the default data dir, overridden by the --datadir option */
private lazy val datadirPath: Path = serverArgs.datadirOpt match { private lazy val datadirPath: Path = serverArgs.datadirOpt match {
@ -22,7 +23,8 @@ case class DatadirParser(
lazy val datadirConfig: Config = lazy val datadirConfig: Config =
ConfigFactory.parseString( ConfigFactory.parseString(
s"bitcoin-s.datadir = ${AppConfig.safePathToString(datadirPath)}") s"bitcoin-s.datadir = ${AppConfig.safePathToString(datadirPath)}"
)
lazy val networkConfig: Config = serverArgs.networkOpt match { lazy val networkConfig: Config = serverArgs.networkOpt match {
case Some(network) => case Some(network) =>
@ -35,9 +37,11 @@ case class DatadirParser(
serverArgs.configOpt match { serverArgs.configOpt match {
case None => case None =>
AppConfig AppConfig
.getBaseConfig(datadirPath, .getBaseConfig(
AppConfig.DEFAULT_BITCOIN_S_CONF_FILE, datadirPath,
Vector(networkConfig)) AppConfig.DEFAULT_BITCOIN_S_CONF_FILE,
Vector(networkConfig)
)
.withFallback(datadirConfig) .withFallback(datadirConfig)
.resolve() .resolve()
case Some(config) => case Some(config) =>
@ -55,11 +59,8 @@ case class DatadirParser(
lazy val datadir: Path = lazy val datadir: Path =
Paths.get(baseConfig.getString("bitcoin-s.datadir")) Paths.get(baseConfig.getString("bitcoin-s.datadir"))
/** Directory specific for current network or custom dir /** Directory specific for current network or custom dir Examples are
* Examples are * HOME/.bitcoin-s/mainnet HOME/.bitcoin-s/testnet3 HOME/.bitcoin-s/oracle
* HOME/.bitcoin-s/mainnet
* HOME/.bitcoin-s/testnet3
* HOME/.bitcoin-s/oracle
*/ */
def networkDir: Path = def networkDir: Path =
DatadirUtil.getFinalDatadir(datadir, baseConfig, customFinalDirOpt) DatadirUtil.getFinalDatadir(datadir, baseConfig, customFinalDirOpt)

View File

@ -30,15 +30,15 @@ object DatadirUtil {
} }
} }
/** Sets the final datadir for our applicatoin. /** Sets the final datadir for our applicatoin. We allow useres to pass in a
* We allow useres to pass in a --datadir command line * --datadir command line flag that needs to be used instead of the
* flag that needs to be used instead of the [[datadir]] * [[datadir]] specified in bitcoin-s.conf
* specified in bitcoin-s.conf
*/ */
def getFinalDatadir( def getFinalDatadir(
datadir: Path, datadir: Path,
baseConfig: Config, baseConfig: Config,
customFinalDirOpt: Option[String] = None): Path = { customFinalDirOpt: Option[String] = None
): Path = {
// $HOME is not set for windows, need to manually set it // $HOME is not set for windows, need to manually set it
if (Properties.isWin) { if (Properties.isWin) {

View File

@ -30,7 +30,7 @@ trait NativeProcessFactory extends BitcoinSLogger {
def startBinary(): Future[Unit] = FutureUtil.makeAsync { () => def startBinary(): Future[Unit] = FutureUtil.makeAsync { () =>
processOpt match { processOpt match {
case Some(p) => case Some(p) =>
//don't do anything as it is already started // don't do anything as it is already started
logger.info(s"Binary was already started! process=$p") logger.info(s"Binary was already started! process=$p")
() ()
case None => case None =>
@ -46,8 +46,8 @@ trait NativeProcessFactory extends BitcoinSLogger {
/** Stops the binary by destroying the underlying operating system process /** Stops the binary by destroying the underlying operating system process
* *
* If the client is a remote client (not started on the host operating system) * If the client is a remote client (not started on the host operating
* this method is a no-op * system) this method is a no-op
*/ */
def stopBinary(): Future[Unit] = FutureUtil.makeAsync { () => def stopBinary(): Future[Unit] = FutureUtil.makeAsync { () =>
processOpt match { processOpt match {
@ -58,7 +58,7 @@ trait NativeProcessFactory extends BitcoinSLogger {
processOpt = None processOpt = None
case None => case None =>
logger.info(s"No process found, binary wasn't started!") logger.info(s"No process found, binary wasn't started!")
//no process running, nothing to do // no process running, nothing to do
() ()
} }
} }

View File

@ -7,8 +7,9 @@ import org.bitcoins.core.config._
import java.nio.file.{Path, Paths} import java.nio.file.{Path, Paths}
import scala.util.Properties import scala.util.Properties
/** Parses arguments passed to the bitcoin-s app server as command line arguments /** Parses arguments passed to the bitcoin-s app server as command line
* This does NOT consider things that exist in reference.conf or application.conf files * arguments This does NOT consider things that exist in reference.conf or
* application.conf files
*/ */
case class ServerArgParser(commandLineArgs: Vector[String]) { case class ServerArgParser(commandLineArgs: Vector[String]) {
@ -70,10 +71,10 @@ case class ServerArgParser(commandLineArgs: Vector[String]) {
/** The datadir passed in as a command line arg using --datadir */ /** The datadir passed in as a command line arg using --datadir */
lazy val datadirOpt: Option[Path] = dataDirIndexOpt.map { case (_, idx) => lazy val datadirOpt: Option[Path] = dataDirIndexOpt.map { case (_, idx) =>
val str = commandLineArgs(idx + 1) val str = commandLineArgs(idx + 1)
//we only want the replace ~ if it is first in the file path // we only want the replace ~ if it is first in the file path
//otherwise windows gets mangled as it can have parts of the file path containing ~ // otherwise windows gets mangled as it can have parts of the file path containing ~
//https://stackoverflow.com/a/7163455/967713 // https://stackoverflow.com/a/7163455/967713
//C:\Users\RUNNER~1\AppData\Local\Temp\bitcoin-s-13391384540028797275 // C:\Users\RUNNER~1\AppData\Local\Temp\bitcoin-s-13391384540028797275
val usableStr = str.replaceFirst("^~", Properties.userHome) val usableStr = str.replaceFirst("^~", Properties.userHome)
Paths.get(usableStr) Paths.get(usableStr)
} }
@ -91,9 +92,9 @@ case class ServerArgParser(commandLineArgs: Vector[String]) {
} }
} }
/** Converts the given command line args into a Config object. /** Converts the given command line args into a Config object. There is one
* There is one exclusion to this, we cannot write the --conf * exclusion to this, we cannot write the --conf flag to the config file as
* flag to the config file as that is self referential * that is self referential
*/ */
def toConfig: Config = { def toConfig: Config = {
val rpcPortString = rpcPortOpt match { val rpcPortString = rpcPortOpt match {
@ -126,7 +127,7 @@ case class ServerArgParser(commandLineArgs: Vector[String]) {
case None => "" case None => ""
} }
//omitting configOpt as i don't know if we can do anything with that? // omitting configOpt as i don't know if we can do anything with that?
val all = val all =
rpcPortString + rpcPortString +

View File

@ -18,7 +18,8 @@ object Cli extends App {
} catch { } catch {
case _: ConnectException => case _: ConnectException =>
printerr( printerr(
"Connection refused! Check that the server is running and configured correctly.") "Connection refused! Check that the server is running and configured correctly."
)
sys.exit(1) sys.exit(1)
} }
} }

View File

@ -103,8 +103,8 @@ object CliReaders {
EnumEventDescriptorV0TLV.fromHex EnumEventDescriptorV0TLV.fromHex
} }
implicit val digitDecompEventDescriptorReads: Read[ implicit val digitDecompEventDescriptorReads
DigitDecompositionEventDescriptorV0TLV] = : Read[DigitDecompositionEventDescriptorV0TLV] =
new Read[DigitDecompositionEventDescriptorV0TLV] { new Read[DigitDecompositionEventDescriptorV0TLV] {
override def arity: Int = 1 override def arity: Int = 1
@ -124,7 +124,8 @@ object CliReaders {
override def arity: Int = 1 override def arity: Int = 1
override def reads: String => ContractDescriptorTLV = { str => override def reads: String => ContractDescriptorTLV = { str =>
upickle.default.read[ContractDescriptorV0TLV](str)( upickle.default.read[ContractDescriptorV0TLV](str)(
Picklers.contractDescriptorV0) Picklers.contractDescriptorV0
)
} }
} }
} }
@ -239,14 +240,16 @@ object CliReaders {
val reads: String => BlockStamp = { val reads: String => BlockStamp = {
case dateRe(year, month, day) => case dateRe(year, month, day) =>
val time = ZonedDateTime.of(year.toInt, val time = ZonedDateTime.of(
month.toInt, year.toInt,
day.toInt, month.toInt,
0, day.toInt,
0, 0,
0, 0,
0, 0,
ZoneId.of("UTC")) 0,
ZoneId.of("UTC")
)
BlockTime(time) BlockTime(time)
case str => BlockStamp.fromString(str) case str => BlockStamp.fromString(str)
} }
@ -310,8 +313,8 @@ object CliReaders {
val reads: String => Sha256DigestBE = Sha256DigestBE.fromHex val reads: String => Sha256DigestBE = Sha256DigestBE.fromHex
} }
implicit val lockUnspentOutputParametersReads: Read[ implicit val lockUnspentOutputParametersReads
Vector[LockUnspentOutputParameter]] = : Read[Vector[LockUnspentOutputParameter]] =
new Read[Vector[LockUnspentOutputParameter]] { new Read[Vector[LockUnspentOutputParameter]] {
override val arity: Int = 1 override val arity: Int = 1

File diff suppressed because it is too large Load Diff

View File

@ -74,7 +74,8 @@ class OracleRoutesSpec
Get() ~> route ~> check { Get() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert( assert(
responseAs[String] == s"""{"result":"${key.hex}","error":null}""") responseAs[String] == s"""{"result":"${key.hex}","error":null}"""
)
} }
} }
@ -90,7 +91,8 @@ class OracleRoutesSpec
Get() ~> route ~> check { Get() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert( assert(
responseAs[String] == s"""{"result":"$testAddress","error":null}""") responseAs[String] == s"""{"result":"$testAddress","error":null}"""
)
} }
} }
@ -105,7 +107,10 @@ class OracleRoutesSpec
Get() ~> route ~> check { Get() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert( assert(
responseAs[String] == s"""{"result":["${dummyOracleEvent.eventName}"],"error":null}""") responseAs[
String
] == s"""{"result":["${dummyOracleEvent.eventName}"],"error":null}"""
)
} }
} }
@ -117,7 +122,8 @@ class OracleRoutesSpec
.returning(Future.successful(Some(dummyOracleEvent))) .returning(Future.successful(Some(dummyOracleEvent)))
val route = oracleRoutes.handleCommand( val route = oracleRoutes.handleCommand(
ServerCommand("getannouncement", Arr(eventName))) ServerCommand("getannouncement", Arr(eventName))
)
val expected = val expected =
s""" s"""
@ -141,7 +147,7 @@ class OracleRoutesSpec
| "error":null | "error":null
|} |}
|""".stripMargin |""".stripMargin
.replaceAll("\\s", "") //strip whitespace .replaceAll("\\s", "") // strip whitespace
val expectedJson: ujson.Value = ujson.read(Readable.fromString(expected)) val expectedJson: ujson.Value = ujson.read(Readable.fromString(expected))
Post() ~> route ~> check { Post() ~> route ~> check {
@ -160,15 +166,19 @@ class OracleRoutesSpec
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("createenumannouncement", ServerCommand(
Arr(Str("id"), "createenumannouncement",
Str("2021-02-04T00:00:00Z"), Arr(Str("id"), Str("2021-02-04T00:00:00Z"), Arr(Str("1"), Str("2")))
Arr(Str("1"), Str("2"))))) )
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}"""
)
} }
} }
@ -182,118 +192,159 @@ class OracleRoutesSpec
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand( ServerCommand(
"createenumannouncement", "createenumannouncement",
Arr(Str("id"), Str("2021-02-04"), Arr(Str("1"), Str("2"))))) Arr(Str("id"), Str("2021-02-04"), Arr(Str("1"), Str("2")))
)
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}"""
)
} }
} }
"create numeric announcement" in { "create numeric announcement" in {
(mockOracleApi (mockOracleApi
.createNewDigitDecompAnnouncement(_: String, .createNewDigitDecompAnnouncement(
_: Instant, _: String,
_: UInt16, _: Instant,
_: Boolean, _: UInt16,
_: Int, _: Boolean,
_: String, _: Int,
_: Int32)) _: String,
.expects("id", _: Int32
Instant.ofEpochSecond(1612396800), ))
UInt16(2), .expects(
false, "id",
17, Instant.ofEpochSecond(1612396800),
"units", UInt16(2),
Int32.zero) false,
17,
"units",
Int32.zero
)
.returning(Future.successful(OracleAnnouncementV0TLV.dummy)) .returning(Future.successful(OracleAnnouncementV0TLV.dummy))
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("createnumericannouncement", ServerCommand(
Arr(Str("id"), "createnumericannouncement",
Str("2021-02-04T00:00:00Z"), Arr(
Num(0), Str("id"),
Num(131000), Str("2021-02-04T00:00:00Z"),
Str("units"), Num(0),
Num(0)))) Num(131000),
Str("units"),
Num(0)
)
)
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}"""
)
} }
} }
"create numeric announcement with just date" in { "create numeric announcement with just date" in {
(mockOracleApi (mockOracleApi
.createNewDigitDecompAnnouncement(_: String, .createNewDigitDecompAnnouncement(
_: Instant, _: String,
_: UInt16, _: Instant,
_: Boolean, _: UInt16,
_: Int, _: Boolean,
_: String, _: Int,
_: Int32)) _: String,
.expects("id", _: Int32
Instant.ofEpochSecond(1612396800), ))
UInt16(2), .expects(
true, "id",
17, Instant.ofEpochSecond(1612396800),
"units", UInt16(2),
Int32.zero) true,
17,
"units",
Int32.zero
)
.returning(Future.successful(OracleAnnouncementV0TLV.dummy)) .returning(Future.successful(OracleAnnouncementV0TLV.dummy))
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("createnumericannouncement", ServerCommand(
Arr(Str("id"), "createnumericannouncement",
Str("2021-02-04"), Arr(
Num(-1), Str("id"),
Num(131000), Str("2021-02-04"),
Str("units"), Num(-1),
Num(0)))) Num(131000),
Str("units"),
Num(0)
)
)
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}"""
)
} }
} }
"create digit decomp announcement" in { "create digit decomp announcement" in {
(mockOracleApi (mockOracleApi
.createNewDigitDecompAnnouncement(_: String, .createNewDigitDecompAnnouncement(
_: Instant, _: String,
_: UInt16, _: Instant,
_: Boolean, _: UInt16,
_: Int, _: Boolean,
_: String, _: Int,
_: Int32)) _: String,
.expects("id", _: Int32
Instant.ofEpochSecond(1612396800), ))
UInt16(2), .expects(
true, "id",
17, Instant.ofEpochSecond(1612396800),
"units", UInt16(2),
Int32.zero) true,
17,
"units",
Int32.zero
)
.returning(Future.successful(OracleAnnouncementV0TLV.dummy)) .returning(Future.successful(OracleAnnouncementV0TLV.dummy))
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("createdigitdecompannouncement", ServerCommand(
Arr(Str("id"), "createdigitdecompannouncement",
Num(1612396800), Arr(
Num(2), Str("id"),
Bool(true), Num(1612396800),
Num(17), Num(2),
Str("units"), Bool(true),
Num(0)))) Num(17),
Str("units"),
Num(0)
)
)
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${OracleAnnouncementV0TLV.dummy.hex}","error":null}"""
)
} }
} }
@ -305,12 +356,16 @@ class OracleRoutesSpec
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("signenum", Arr(Str("id"), Str("outcome")))) ServerCommand("signenum", Arr(Str("id"), Str("outcome")))
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${dummyAttestmentTLV.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${dummyAttestmentTLV.hex}","error":null}"""
)
} }
} }
@ -322,12 +377,16 @@ class OracleRoutesSpec
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("signdigits", Arr(Str("id"), Num(123)))) ServerCommand("signdigits", Arr(Str("id"), Num(123)))
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${dummyAttestmentTLV.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${dummyAttestmentTLV.hex}","error":null}"""
)
} }
} }
@ -339,12 +398,16 @@ class OracleRoutesSpec
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("getsignatures", Arr(Str("id")))) ServerCommand("getsignatures", Arr(Str("id")))
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${dummyAttestmentTLV.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${dummyAttestmentTLV.hex}","error":null}"""
)
} }
} }
@ -356,12 +419,14 @@ class OracleRoutesSpec
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("signmessage", Arr(Str("message")))) ServerCommand("signmessage", Arr(Str("message")))
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert( assert(
responseAs[String] == s"""{"result":"${sig.hex}","error":null}""") responseAs[String] == s"""{"result":"${sig.hex}","error":null}"""
)
} }
} }
@ -376,8 +441,11 @@ class OracleRoutesSpec
val route = oracleRoutes.handleCommand(cmd) val route = oracleRoutes.handleCommand(cmd)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${dummyOracleEvent.announcementTLV.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${dummyOracleEvent.announcementTLV.hex}","error":null}"""
)
} }
} }
@ -393,8 +461,11 @@ class OracleRoutesSpec
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${dummyOracleEvent.announcementTLV.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${dummyOracleEvent.announcementTLV.hex}","error":null}"""
)
} }
} }
@ -409,7 +480,8 @@ class OracleRoutesSpec
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert( assert(
responseAs[String] == s"""{"result":"oracle name","error":null}""") responseAs[String] == s"""{"result":"oracle name","error":null}"""
)
} }
} }
@ -421,12 +493,14 @@ class OracleRoutesSpec
val route = val route =
oracleRoutes.handleCommand( oracleRoutes.handleCommand(
ServerCommand("setoraclename", Arr(Str("oracle name")))) ServerCommand("setoraclename", Arr(Str("oracle name")))
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert( assert(
responseAs[String] == s"""{"result":"oracle name","error":null}""") responseAs[String] == s"""{"result":"oracle name","error":null}"""
)
} }
} }
} }

View File

@ -19,8 +19,8 @@ import scala.util.{Failure, Success}
case class OracleRoutes(oracle: DLCOracleApi)(implicit case class OracleRoutes(oracle: DLCOracleApi)(implicit
system: ActorSystem, system: ActorSystem,
conf: DLCOracleAppConfig) conf: DLCOracleAppConfig
extends ServerRoute { ) extends ServerRoute {
import system.dispatcher import system.dispatcher
override def handleCommand: PartialFunction[ServerCommand, StandardRoute] = { override def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
@ -71,12 +71,15 @@ case class OracleRoutes(oracle: DLCOracleApi)(implicit
case Failure(exception) => case Failure(exception) =>
reject(ValidationRejection("failure", Some(exception))) reject(ValidationRejection("failure", Some(exception)))
case Success( case Success(
CreateNumericAnnouncement(eventName, CreateNumericAnnouncement(
maturationTime, eventName,
minValue, maturationTime,
maxValue, minValue,
unit, maxValue,
precision)) => unit,
precision
)
) =>
complete { complete {
val isSigned = minValue < 0 val isSigned = minValue < 0
@ -84,13 +87,15 @@ case class OracleRoutes(oracle: DLCOracleApi)(implicit
Math.ceil(Math.log(maxValue.toDouble) / Math.log(2)).toInt Math.ceil(Math.log(maxValue.toDouble) / Math.log(2)).toInt
oracle oracle
.createNewDigitDecompAnnouncement(eventName, .createNewDigitDecompAnnouncement(
maturationTime, eventName,
UInt16(2), maturationTime,
isSigned, UInt16(2),
numDigits, isSigned,
unit, numDigits,
Int32(precision)) unit,
Int32(precision)
)
.map { announcementTLV => .map { announcementTLV =>
Server.httpSuccess(announcementTLV.hex) Server.httpSuccess(announcementTLV.hex)
} }
@ -105,22 +110,27 @@ case class OracleRoutes(oracle: DLCOracleApi)(implicit
case Failure(exception) => case Failure(exception) =>
reject(ValidationRejection("failure", Some(exception))) reject(ValidationRejection("failure", Some(exception)))
case Success( case Success(
CreateDigitDecompAnnouncement(eventName, CreateDigitDecompAnnouncement(
maturationTime, eventName,
base, maturationTime,
isSigned, base,
numDigits, isSigned,
unit, numDigits,
precision)) => unit,
precision
)
) =>
complete { complete {
oracle oracle
.createNewDigitDecompAnnouncement(eventName, .createNewDigitDecompAnnouncement(
maturationTime, eventName,
UInt16(base), maturationTime,
isSigned, UInt16(base),
numDigits, isSigned,
unit, numDigits,
Int32(precision)) unit,
Int32(precision)
)
.map { announcementTLV => .map { announcementTLV =>
Server.httpSuccess(announcementTLV.hex) Server.httpSuccess(announcementTLV.hex)
} }
@ -179,9 +189,11 @@ case class OracleRoutes(oracle: DLCOracleApi)(implicit
"signingVersion" -> Str(event.signingVersion.toString), "signingVersion" -> Str(event.signingVersion.toString),
"maturationTime" -> Str(event.maturationTime.toString), "maturationTime" -> Str(event.maturationTime.toString),
"maturationTimeEpoch" -> Num( "maturationTimeEpoch" -> Num(
event.maturationTime.getEpochSecond.toDouble), event.maturationTime.getEpochSecond.toDouble
),
"announcementSignature" -> Str( "announcementSignature" -> Str(
event.announcementSignature.hex), event.announcementSignature.hex
),
"eventDescriptorTLV" -> Str(event.eventDescriptorTLV.hex), "eventDescriptorTLV" -> Str(event.eventDescriptorTLV.hex),
"eventTLV" -> Str(event.eventTLV.hex), "eventTLV" -> Str(event.eventTLV.hex),
"announcementTLV" -> Str(event.announcementTLV.hex), "announcementTLV" -> Str(event.announcementTLV.hex),
@ -269,9 +281,11 @@ case class OracleRoutes(oracle: DLCOracleApi)(implicit
case Success(KeyManagerPassphraseChange(oldPassword, newPassword)) => case Success(KeyManagerPassphraseChange(oldPassword, newPassword)) =>
complete { complete {
val path = conf.seedPath val path = conf.seedPath
WalletStorage.changeAesPassword(path, WalletStorage.changeAesPassword(
Some(oldPassword), path,
Some(newPassword)) Some(oldPassword),
Some(newPassword)
)
Server.httpSuccess(ujson.Null) Server.httpSuccess(ujson.Null)
} }

View File

@ -13,8 +13,8 @@ import scala.concurrent.{Await, Future}
class OracleServerMain(override val serverArgParser: ServerArgParser)(implicit class OracleServerMain(override val serverArgParser: ServerArgParser)(implicit
override val system: ActorSystem, override val system: ActorSystem,
conf: DLCOracleAppConfig) conf: DLCOracleAppConfig
extends BitcoinSServerRunner[Unit] { ) extends BitcoinSServerRunner[Unit] {
override def start(): Future[Unit] = { override def start(): Future[Unit] = {
@ -31,21 +31,25 @@ class OracleServerMain(override val serverArgParser: ServerArgParser)(implicit
routes = Seq(OracleRoutes(oracle), commonRoutes).map(Future.successful) routes = Seq(OracleRoutes(oracle), commonRoutes).map(Future.successful)
server = serverArgParser.rpcPortOpt match { server = serverArgParser.rpcPortOpt match {
case Some(rpcport) => case Some(rpcport) =>
Server(conf = conf, Server(
handlersF = routes, conf = conf,
rpcbindOpt = bindConfOpt, handlersF = routes,
rpcport = rpcport, rpcbindOpt = bindConfOpt,
rpcPassword = conf.rpcPassword, rpcport = rpcport,
None, rpcPassword = conf.rpcPassword,
Source.empty) None,
Source.empty
)
case None => case None =>
Server(conf = conf, Server(
handlersF = routes, conf = conf,
rpcbindOpt = bindConfOpt, handlersF = routes,
rpcport = conf.rpcPort, rpcbindOpt = bindConfOpt,
rpcPassword = conf.rpcPassword, rpcport = conf.rpcPort,
None, rpcPassword = conf.rpcPassword,
Source.empty) None,
Source.empty
)
} }
bindings <- server.start() bindings <- server.start()
@ -88,14 +92,16 @@ object OracleServerMain extends BitcoinSAppScalaDaemon {
implicit lazy val conf: DLCOracleAppConfig = implicit lazy val conf: DLCOracleAppConfig =
DLCOracleAppConfig(datadirParser.datadir, Vector(datadirParser.baseConfig))( DLCOracleAppConfig(datadirParser.datadir, Vector(datadirParser.baseConfig))(
system.dispatcher) system.dispatcher
)
val m = new OracleServerMain(serverCmdLineArgs) val m = new OracleServerMain(serverCmdLineArgs)
m.run() m.run()
sys.addShutdownHook { sys.addShutdownHook {
logger.info( logger.info(
s"@@@@@@@@@@@@@@@@@@@@@ Shutting down ${getClass.getSimpleName} @@@@@@@@@@@@@@@@@@@@@") s"@@@@@@@@@@@@@@@@@@@@@ Shutting down ${getClass.getSimpleName} @@@@@@@@@@@@@@@@@@@@@"
)
Await.result(m.stop(), 10.seconds) Await.result(m.stop(), 10.seconds)
} }
} }

View File

@ -30,7 +30,9 @@ object SignMessage extends ServerJsonModels {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 1")) s"Bad number of arguments: ${other.length}. Expected: 1"
)
)
} }
} }
} }
@ -38,7 +40,8 @@ object SignMessage extends ServerJsonModels {
case class CreateAnnouncement( case class CreateAnnouncement(
label: String, label: String,
maturationTime: Instant, maturationTime: Instant,
outcomes: Vector[String]) outcomes: Vector[String]
)
object CreateAnnouncement extends ServerJsonModels { object CreateAnnouncement extends ServerJsonModels {
@ -54,11 +57,14 @@ object CreateAnnouncement extends ServerJsonModels {
} }
case Nil => case Nil =>
Failure( Failure(
new IllegalArgumentException("Missing label and outcome arguments")) new IllegalArgumentException("Missing label and outcome arguments")
)
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 2")) s"Bad number of arguments: ${other.length}. Expected: 2"
)
)
} }
} }
} }
@ -69,7 +75,8 @@ case class CreateNumericAnnouncement(
minValue: Long, minValue: Long,
maxValue: Long, maxValue: Long,
unit: String, unit: String,
precision: Int) precision: Int
)
object CreateNumericAnnouncement extends ServerJsonModels { object CreateNumericAnnouncement extends ServerJsonModels {
@ -84,20 +91,27 @@ object CreateNumericAnnouncement extends ServerJsonModels {
val unit = unitJs.str val unit = unitJs.str
val precision = precisionJs.num.toInt val precision = precisionJs.num.toInt
CreateNumericAnnouncement(label, CreateNumericAnnouncement(
maturationTime, label,
minValue, maturationTime,
maxValue, minValue,
unit, maxValue,
precision) unit,
precision
)
} }
case Nil => case Nil =>
Failure(new IllegalArgumentException( Failure(
"Missing label, maturationTime, minValue, maxValue, units, and precision arguments")) new IllegalArgumentException(
"Missing label, maturationTime, minValue, maxValue, units, and precision arguments"
)
)
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 6")) s"Bad number of arguments: ${other.length}. Expected: 6"
)
)
} }
} }
} }
@ -109,7 +123,8 @@ case class CreateDigitDecompAnnouncement(
isSigned: Boolean, isSigned: Boolean,
numDigits: Int, numDigits: Int,
unit: String, unit: String,
precision: Int) precision: Int
)
object CreateDigitDecompAnnouncement extends ServerJsonModels { object CreateDigitDecompAnnouncement extends ServerJsonModels {
@ -126,21 +141,28 @@ object CreateDigitDecompAnnouncement extends ServerJsonModels {
val unit = unitJs.str val unit = unitJs.str
val precision = precisionJs.num.toInt val precision = precisionJs.num.toInt
CreateDigitDecompAnnouncement(label, CreateDigitDecompAnnouncement(
maturationTime, label,
base, maturationTime,
isSigned, base,
numDigits, isSigned,
unit, numDigits,
precision) unit,
precision
)
} }
case Nil => case Nil =>
Failure(new IllegalArgumentException( Failure(
"Missing label, maturationTime, base, isSigned, numDigits, units, and precision arguments")) new IllegalArgumentException(
"Missing label, maturationTime, base, isSigned, numDigits, units, and precision arguments"
)
)
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 7")) s"Bad number of arguments: ${other.length}. Expected: 7"
)
)
} }
} }
} }
@ -160,11 +182,15 @@ object SignEnum extends ServerJsonModels {
case Nil => case Nil =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
"Missing oracle event tlv and outcome arguments")) "Missing oracle event tlv and outcome arguments"
)
)
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 2")) s"Bad number of arguments: ${other.length}. Expected: 2"
)
)
} }
} }
} }
@ -182,7 +208,8 @@ object SignDigits extends ServerJsonModels {
case str: Str => str.value.toDouble case str: Str => str.value.toDouble
case _: Value => case _: Value =>
throw new IllegalArgumentException( throw new IllegalArgumentException(
s"Unable to parse $numJs as a number") s"Unable to parse $numJs as a number"
)
} }
SignDigits(nameJs.str, num.toLong) SignDigits(nameJs.str, num.toLong)
@ -190,11 +217,15 @@ object SignDigits extends ServerJsonModels {
case Nil => case Nil =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
"Missing oracle event tlv and num arguments")) "Missing oracle event tlv and num arguments"
)
)
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 2")) s"Bad number of arguments: ${other.length}. Expected: 2"
)
)
} }
} }
} }
@ -204,8 +235,10 @@ case class GetAnnouncement(eventName: String)
object GetAnnouncement extends ServerJsonModels { object GetAnnouncement extends ServerJsonModels {
def fromJsArr(jsArr: ujson.Arr): Try[GetAnnouncement] = { def fromJsArr(jsArr: ujson.Arr): Try[GetAnnouncement] = {
require(jsArr.arr.size == 1, require(
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1") jsArr.arr.size == 1,
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1"
)
Try { Try {
GetAnnouncement(jsArr.arr.head.str) GetAnnouncement(jsArr.arr.head.str)
} }
@ -214,7 +247,8 @@ object GetAnnouncement extends ServerJsonModels {
case class KeyManagerPassphraseChange( case class KeyManagerPassphraseChange(
oldPassword: AesPassword, oldPassword: AesPassword,
newPassword: AesPassword) newPassword: AesPassword
)
object KeyManagerPassphraseChange extends ServerJsonModels { object KeyManagerPassphraseChange extends ServerJsonModels {
@ -230,11 +264,15 @@ object KeyManagerPassphraseChange extends ServerJsonModels {
case Nil => case Nil =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
"Missing old password and new password arguments")) "Missing old password and new password arguments"
)
)
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 2")) s"Bad number of arguments: ${other.length}. Expected: 2"
)
)
} }
} }
} }
@ -256,7 +294,9 @@ object KeyManagerPassphraseSet extends ServerJsonModels {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 1")) s"Bad number of arguments: ${other.length}. Expected: 1"
)
)
} }
} }
} }
@ -276,8 +316,11 @@ object DeleteAnnouncement
case Vector() => case Vector() =>
Failure(new IllegalArgumentException(s"Missing event name argument")) Failure(new IllegalArgumentException(s"Missing event name argument"))
case other => case other =>
Failure(new IllegalArgumentException( Failure(
s"Bad number of arguments to deleteannouncement, got=${other.length} expected: 1")) new IllegalArgumentException(
s"Bad number of arguments to deleteannouncement, got=${other.length} expected: 1"
)
)
} }
} }
@ -303,7 +346,9 @@ object DeleteAttestation
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 1")) s"Bad number of arguments: ${other.length}. Expected: 1"
)
)
} }
} }
@ -341,7 +386,8 @@ trait ServerJsonModels {
LockUnspentOutputParameter.fromJson(js) LockUnspentOutputParameter.fromJson(js)
def jsToLockUnspentOutputParameters( def jsToLockUnspentOutputParameters(
js: Value): Seq[LockUnspentOutputParameter] = { js: Value
): Seq[LockUnspentOutputParameter] = {
js.arr.foldLeft(Seq.empty[LockUnspentOutputParameter])((seq, outPoint) => js.arr.foldLeft(Seq.empty[LockUnspentOutputParameter])((seq, outPoint) =>
seq :+ jsToLockUnspentOutputParameter(outPoint)) seq :+ jsToLockUnspentOutputParameter(outPoint))
} }

View File

@ -16,15 +16,16 @@ import org.bitcoins.server.util.BitcoinSAppScalaDaemon
import java.time.Instant import java.time.Instant
import scala.concurrent.Future import scala.concurrent.Future
/** Useful script for scanning bitcoind /** Useful script for scanning bitcoind This file assumes you have
* This file assumes you have pre-configured the connection * pre-configured the connection between bitcoin-s and bitcoind inside of
* between bitcoin-s and bitcoind inside of bitcoin-s.conf * bitcoin-s.conf
* @see https://bitcoin-s.org/docs/config/configuration#example-configuration-file * @see
* https://bitcoin-s.org/docs/config/configuration#example-configuration-file
*/ */
class ScanBitcoind()(implicit class ScanBitcoind()(implicit
override val system: ActorSystem, override val system: ActorSystem,
rpcAppConfig: BitcoindRpcAppConfig) rpcAppConfig: BitcoindRpcAppConfig
extends BitcoinSRunner[Unit] { ) extends BitcoinSRunner[Unit] {
override def start(): Future[Unit] = { override def start(): Future[Unit] = {
@ -36,7 +37,7 @@ class ScanBitcoind()(implicit
val f = for { val f = for {
bitcoind <- bitcoindF bitcoind <- bitcoindF
endHeight <- endHeightF endHeight <- endHeightF
//_ <- countWitV1MempoolTxs(bitcoind) // _ <- countWitV1MempoolTxs(bitcoind)
_ <- countTaprootTxsInBlocks(endHeight, 50000, bitcoind) _ <- countTaprootTxsInBlocks(endHeight, 50000, bitcoind)
} yield () } yield ()
f.failed.foreach(err => f.failed.foreach(err =>
@ -50,13 +51,15 @@ class ScanBitcoind()(implicit
.map(_ => ()) .map(_ => ())
} }
/** Searches a given Source[Int] that represents block heights applying f to them and returning a Seq[T] with the results */ /** Searches a given Source[Int] that represents block heights applying f to
* them and returning a Seq[T] with the results
*/
def searchBlocks[T]( def searchBlocks[T](
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
source: Source[Int, NotUsed], source: Source[Int, NotUsed],
f: Block => T, f: Block => T,
numParallelism: Int = Runtime.getRuntime.availableProcessors()): Future[ numParallelism: Int = Runtime.getRuntime.availableProcessors()
Seq[T]] = { ): Future[Seq[T]] = {
source source
.mapAsync(parallelism = numParallelism) { height => .mapAsync(parallelism = numParallelism) { height =>
bitcoind bitcoind
@ -66,7 +69,8 @@ class ScanBitcoind()(implicit
} }
.mapAsync(numParallelism) { case (block, height) => .mapAsync(numParallelism) { case (block, height) =>
logger.info( logger.info(
s"Searching block at height=$height hashBE=${block.blockHeader.hashBE.hex}") s"Searching block at height=$height hashBE=${block.blockHeader.hashBE.hex}"
)
FutureUtil.makeAsync { () => FutureUtil.makeAsync { () =>
f(block) f(block)
} }
@ -78,11 +82,12 @@ class ScanBitcoind()(implicit
def countSegwitTxs( def countSegwitTxs(
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
startHeight: Int, startHeight: Int,
endHeight: Int): Future[Unit] = { endHeight: Int
): Future[Unit] = {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val source: Source[Int, NotUsed] = Source(startHeight.to(endHeight)) val source: Source[Int, NotUsed] = Source(startHeight.to(endHeight))
//in this simple example, we are going to count the number of witness transactions // in this simple example, we are going to count the number of witness transactions
val countSegwitTxs: Block => Int = { block: Block => val countSegwitTxs: Block => Int = { block: Block =>
block.transactions.count(_.isInstanceOf[WitnessTransaction]) block.transactions.count(_.isInstanceOf[WitnessTransaction])
} }
@ -96,14 +101,16 @@ class ScanBitcoind()(implicit
count <- countF count <- countF
endTime = System.currentTimeMillis() endTime = System.currentTimeMillis()
_ = logger.info( _ = logger.info(
s"Count of segwit txs from height=${startHeight} to endHeight=${endHeight} is ${count}. It took ${endTime - startTime}ms ") s"Count of segwit txs from height=${startHeight} to endHeight=${endHeight} is ${count}. It took ${endTime - startTime}ms "
)
} yield () } yield ()
} }
def countTaprootTxsInBlocks( def countTaprootTxsInBlocks(
endHeight: Int, endHeight: Int,
lastBlocks: Int, lastBlocks: Int,
bitcoind: BitcoindRpcClient): Future[Int] = { bitcoind: BitcoindRpcClient
): Future[Int] = {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val startHeight = endHeight - lastBlocks val startHeight = endHeight - lastBlocks
val source: Source[Int, NotUsed] = Source(startHeight.to(endHeight)) val source: Source[Int, NotUsed] = Source(startHeight.to(endHeight))
@ -124,7 +131,8 @@ class ScanBitcoind()(implicit
count <- countF count <- countF
endTime = System.currentTimeMillis() endTime = System.currentTimeMillis()
_ = logger.info( _ = logger.info(
s"Count of taproot outputs from height=${startHeight} to endHeight=${endHeight} is ${count}. It took ${endTime - startTime}ms ") s"Count of taproot outputs from height=${startHeight} to endHeight=${endHeight} is ${count}. It took ${endTime - startTime}ms "
)
} yield count } yield count
} }
@ -135,12 +143,14 @@ class ScanBitcoind()(implicit
}) })
countF.foreach(c => countF.foreach(c =>
logger.info( logger.info(
s"Found $c mempool transactions with witness v1 outputs at ${Instant.now}")) s"Found $c mempool transactions with witness v1 outputs at ${Instant.now}"
))
countF countF
} }
def getMemPoolSource( def getMemPoolSource(
bitcoind: BitcoindRpcClient): Future[Source[Transaction, NotUsed]] = { bitcoind: BitcoindRpcClient
): Future[Source[Transaction, NotUsed]] = {
val mempoolF = bitcoind.getRawMemPool val mempoolF = bitcoind.getRawMemPool
val sourceF: Future[Source[DoubleSha256DigestBE, NotUsed]] = val sourceF: Future[Source[DoubleSha256DigestBE, NotUsed]] =
mempoolF.map(Source(_)) mempoolF.map(Source(_))

View File

@ -10,15 +10,17 @@ import org.bitcoins.server.util.BitcoinSAppScalaDaemon
import java.nio.file.Paths import java.nio.file.Paths
import scala.concurrent.Future import scala.concurrent.Future
/** This script zips your $HOME/.bitcoin-s/ directory to a specified path, excluding chaindb.sqlite */ /** This script zips your $HOME/.bitcoin-s/ directory to a specified path,
* excluding chaindb.sqlite
*/
class ZipDatadir(override val serverArgParser: ServerArgParser)(implicit class ZipDatadir(override val serverArgParser: ServerArgParser)(implicit
override val system: ActorSystem, override val system: ActorSystem,
conf: BitcoinSAppConfig) conf: BitcoinSAppConfig
extends BitcoinSServerRunner[Unit] { ) extends BitcoinSServerRunner[Unit] {
override def start(): Future[Unit] = { override def start(): Future[Unit] = {
//replace the line below with where you want to zip too // replace the line below with where you want to zip too
val path = Paths.get("/tmp", "bitcoin-s.zip") val path = Paths.get("/tmp", "bitcoin-s.zip")
val target = DatadirUtil.zipDatadir(conf.baseDatadir, path) val target = DatadirUtil.zipDatadir(conf.baseDatadir, path)
logger.info(s"Done zipping to $target!") logger.info(s"Done zipping to $target!")
@ -46,7 +48,8 @@ object Zip extends BitcoinSAppScalaDaemon {
implicit lazy val conf: BitcoinSAppConfig = implicit lazy val conf: BitcoinSAppConfig =
BitcoinSAppConfig(datadirParser.datadir, Vector(datadirParser.baseConfig))( BitcoinSAppConfig(datadirParser.datadir, Vector(datadirParser.baseConfig))(
system) system
)
new ZipDatadir(serverCmdLineArgs).run() new ZipDatadir(serverCmdLineArgs).run()
} }

View File

@ -31,11 +31,13 @@ import upickle.default.{read, write, Reader, Writer}
import scala.collection.immutable.Seq import scala.collection.immutable.Seq
/** Automatic to and from JSON marshalling/unmarshalling using *upickle* protocol. /** Automatic to and from JSON marshalling/unmarshalling using *upickle*
* protocol.
*/ */
object UpickleSupport extends UpickleSupport object UpickleSupport extends UpickleSupport
/** Automatic to and from JSON marshalling/unmarshalling using *upickle* protocol. /** Automatic to and from JSON marshalling/unmarshalling using *upickle*
* protocol.
*/ */
trait UpickleSupport { trait UpickleSupport {
@ -58,16 +60,20 @@ trait UpickleSupport {
/** HTTP entity => `A` /** HTTP entity => `A`
* *
* @tparam A type to decode * @tparam A
* @return unmarshaller for `A` * type to decode
* @return
* unmarshaller for `A`
*/ */
implicit def unmarshaller[A: Reader]: FromEntityUnmarshaller[A] = implicit def unmarshaller[A: Reader]: FromEntityUnmarshaller[A] =
jsonStringUnmarshaller.map(read(_)) jsonStringUnmarshaller.map(read(_))
/** `A` => HTTP entity /** `A` => HTTP entity
* *
* @tparam A type to encode * @tparam A
* @return marshaller for any `A` value * type to encode
* @return
* marshaller for any `A` value
*/ */
implicit def marshaller[A: Writer]: ToEntityMarshaller[A] = implicit def marshaller[A: Writer]: ToEntityMarshaller[A] =
jsonStringMarshaller.compose(write(_)) jsonStringMarshaller.compose(write(_))

View File

@ -15,16 +15,17 @@ trait BitcoinSRunner[T] extends StartStopAsync[T] with BitcoinSLogger {
// start everything! // start everything!
final def run(): Future[T] = { final def run(): Future[T] = {
//We need to set the system property before any logger instances // We need to set the system property before any logger instances
//are in instantiated. If we don't do this, we will not log to // are in instantiated. If we don't do this, we will not log to
//the correct location // the correct location
//see: https://github.com/bitcoin-s/bitcoin-s/issues/2496 // see: https://github.com/bitcoin-s/bitcoin-s/issues/2496
//System.setProperty("bitcoins.log.location", usedDir.toAbsolutePath.toString) // System.setProperty("bitcoins.log.location", usedDir.toAbsolutePath.toString)
logger.info( logger.info(
s"version=${EnvUtil.getVersion} jdkVersion=${EnvUtil.getJdkVersion}") s"version=${EnvUtil.getVersion} jdkVersion=${EnvUtil.getJdkVersion}"
)
//logger.info(s"using directory ${usedDir.toAbsolutePath.toString}") // logger.info(s"using directory ${usedDir.toAbsolutePath.toString}")
val runner: Future[T] = start() val runner: Future[T] = start()
runner.failed.foreach { err => runner.failed.foreach { err =>
logger.error(s"Failed to startup server!", err) logger.error(s"Failed to startup server!", err)

View File

@ -8,28 +8,33 @@ import org.apache.pekko.http.scaladsl.server.Directives._
import org.apache.pekko.http.scaladsl.server.Route import org.apache.pekko.http.scaladsl.server.Route
/** Add CORS handling for accessing backend over localhost from modern browsers /** Add CORS handling for accessing backend over localhost from modern browsers
* @see dzone.com/articles/handling-cors-in-akka-http * @see
* dzone.com/articles/handling-cors-in-akka-http
*/ */
trait CORSHandler { trait CORSHandler {
private val corsResponseHeaders = List( private val corsResponseHeaders = List(
`Access-Control-Allow-Origin`.*, `Access-Control-Allow-Origin`.*,
`Access-Control-Allow-Credentials`(true), `Access-Control-Allow-Credentials`(true),
`Access-Control-Allow-Headers`("Authorization", `Access-Control-Allow-Headers`(
"Content-Type", "Authorization",
"X-Requested-With") "Content-Type",
"X-Requested-With"
)
) )
//this directive adds access control headers to normal responses // this directive adds access control headers to normal responses
private def addAccessControlHeaders: Directive0 = { private def addAccessControlHeaders: Directive0 = {
respondWithHeaders(corsResponseHeaders) respondWithHeaders(corsResponseHeaders)
} }
//this handles preflight OPTIONS requests. // this handles preflight OPTIONS requests.
private def preflightRequestHandler: Route = options { private def preflightRequestHandler: Route = options {
complete( complete(
HttpResponse(StatusCodes.OK).withHeaders( HttpResponse(StatusCodes.OK).withHeaders(
`Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE))) `Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE)
)
)
} }
// Wrap the Route with this method to enable adding of CORS headers // Wrap the Route with this method to enable adding of CORS headers

View File

@ -51,7 +51,9 @@ object ZipDataDir {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 1")) s"Bad number of arguments: ${other.length}. Expected: 1"
)
)
} }
} }
} }

View File

@ -1,8 +1,7 @@
package org.bitcoins.server.routes package org.bitcoins.server.routes
/** HTTP errors our server knows how to handle. /** HTTP errors our server knows how to handle. These gets picked up by the
* These gets picked up by the exceptions handler * exceptions handler in Main
* in Main
*/ */
sealed abstract class HttpError extends Error sealed abstract class HttpError extends Error

View File

@ -52,7 +52,8 @@ case class Server(
rpcport: Int, rpcport: Int,
rpcPassword: String, rpcPassword: String,
wsConfigOpt: Option[WsServerConfig], wsConfigOpt: Option[WsServerConfig],
wsSource: Source[WsNotification[_], NotUsed])(implicit system: ActorSystem) wsSource: Source[WsNotification[_], NotUsed]
)(implicit system: ActorSystem)
extends HttpLogger { extends HttpLogger {
import system.dispatcher import system.dispatcher
@ -61,8 +62,10 @@ case class Server(
if (rpchost == "localhost" || rpchost == "127.0.0.1") { if (rpchost == "localhost" || rpchost == "127.0.0.1") {
logger.warn(s"RPC password is not set (rpchost=$rpchost)") logger.warn(s"RPC password is not set (rpchost=$rpchost)")
} else { } else {
require(rpcPassword.nonEmpty, require(
s"RPC password must be set (rpchost=$rpchost)") rpcPassword.nonEmpty,
s"RPC password must be set (rpchost=$rpchost)"
)
} }
} }
@ -81,7 +84,8 @@ case class Server(
complete { complete {
Server.httpError( Server.httpError(
"""Resource not found. Hint: all RPC calls are made against root ('/')""", """Resource not found. Hint: all RPC calls are made against root ('/')""",
StatusCodes.BadRequest) StatusCodes.BadRequest
)
} }
} }
.result() .result()
@ -89,8 +93,11 @@ case class Server(
val exceptionHandler = ExceptionHandler { val exceptionHandler = ExceptionHandler {
case HttpError.MethodNotFound(method) => case HttpError.MethodNotFound(method) =>
complete( complete(
Server.httpError(s"'$method' is not a valid method", Server.httpError(
StatusCodes.BadRequest)) s"'$method' is not a valid method",
StatusCodes.BadRequest
)
)
case err: Throwable => case err: Throwable =>
logger.error(s"Unhandled error in server:", err) logger.error(s"Unhandled error in server:", err)
complete(Server.httpError(err.getMessage)) complete(Server.httpError(err.getMessage))
@ -173,7 +180,8 @@ case class Server(
} }
DebuggingDirectives.logRequestResult( DebuggingDirectives.logRequestResult(
("http-rpc-server", Logging.DebugLevel)) { ("http-rpc-server", Logging.DebugLevel)
) {
authenticatedRoute authenticatedRoute
} }
} }
@ -250,7 +258,8 @@ object Server extends BitcoinSLogger {
// TODO id parameter // TODO id parameter
case class Response( case class Response(
result: Option[ujson.Value] = None, result: Option[ujson.Value] = None,
error: Option[String] = None) { error: Option[String] = None
) {
def toJsonMap: Map[String, ujson.Value] = { def toJsonMap: Map[String, ujson.Value] = {
Map( Map(
@ -267,8 +276,9 @@ object Server extends BitcoinSLogger {
} }
/** Creates a HTTP response with the given body as a JSON response */ /** Creates a HTTP response with the given body as a JSON response */
def httpSuccess[T](body: T)(implicit def httpSuccess[T](
writer: up.Writer[T]): HttpEntity.Strict = { body: T
)(implicit writer: up.Writer[T]): HttpEntity.Strict = {
val response = Response(result = Some(up.writeJs(body))) val response = Response(result = Some(up.writeJs(body)))
HttpEntity( HttpEntity(
ContentTypes.`application/json`, ContentTypes.`application/json`,
@ -276,8 +286,9 @@ object Server extends BitcoinSLogger {
) )
} }
def httpSuccessOption[T](bodyOpt: Option[T])(implicit def httpSuccessOption[T](
writer: up.Writer[T]): HttpEntity.Strict = { bodyOpt: Option[T]
)(implicit writer: up.Writer[T]): HttpEntity.Strict = {
val response = Response(result = bodyOpt.map(body => up.writeJs(body))) val response = Response(result = bodyOpt.map(body => up.writeJs(body)))
HttpEntity( HttpEntity(
ContentTypes.`application/json`, ContentTypes.`application/json`,

View File

@ -2,13 +2,17 @@ package org.bitcoins.server.util
import scala.concurrent.Future import scala.concurrent.Future
/** A trait used to indicated when different parts of [[BitcoinSAppConfig]] are started */ /** A trait used to indicated when different parts of [[BitcoinSAppConfig]] are
* started
*/
sealed trait AppConfigMarker sealed trait AppConfigMarker
/** This class represents when BitcoinSAppConfig modules are started /** This class represents when BitcoinSAppConfig modules are started
* @param torStartedF this future is completed when all tor dependent modules are fully started * @param torStartedF
* the reason this is needed is because tor startup time is so variable * this future is completed when all tor dependent modules are fully started
* @see https://github.com/bitcoin-s/bitcoin-s/issues/4210 * the reason this is needed is because tor startup time is so variable
* @see
* https://github.com/bitcoin-s/bitcoin-s/issues/4210
*/ */
case class StartedBitcoinSAppConfig(torStartedF: Future[Unit]) case class StartedBitcoinSAppConfig(torStartedF: Future[Unit])
extends AppConfigMarker extends AppConfigMarker

View File

@ -10,7 +10,9 @@ trait BitcoinSApp {
def commandLineArgs: Array[String] def commandLineArgs: Array[String]
/** Useful for projects like the oracle server to specify a custom directory inside of ~./bitcoin-s */ /** Useful for projects like the oracle server to specify a custom directory
* inside of ~./bitcoin-s
*/
def customFinalDirOpt: Option[String] def customFinalDirOpt: Option[String]
} }

View File

@ -8,8 +8,8 @@ import scala.concurrent.{ExecutionContext, Future}
case class ServerBindings( case class ServerBindings(
httpServer: Http.ServerBinding, httpServer: Http.ServerBinding,
webSocketServerOpt: Option[Http.ServerBinding]) webSocketServerOpt: Option[Http.ServerBinding]
extends BitcoinSLogger { ) extends BitcoinSLogger {
private val terminateTimeout = 5.seconds private val terminateTimeout = 5.seconds

View File

@ -27,8 +27,10 @@ class BitcoinSServerMainBitcoindTest
config: BitcoinSAppConfig => config: BitcoinSAppConfig =>
val server = new BitcoinSServerMain(ServerArgParser.empty)(system, config) val server = new BitcoinSServerMain(ServerArgParser.empty)(system, config)
val cliConfig = Config(rpcPortOpt = Some(config.rpcPort), val cliConfig = Config(
rpcPassword = config.rpcPassword) rpcPortOpt = Some(config.rpcPort),
rpcPassword = config.rpcPassword
)
for { for {
_ <- server.start() _ <- server.start()
@ -38,7 +40,7 @@ class BitcoinSServerMainBitcoindTest
addr = exec(GetNewAddress(labelOpt = None), cliConfig) addr = exec(GetNewAddress(labelOpt = None), cliConfig)
blockHash = ConsoleCli.exec(CliCommand.GetBestBlockHash, cliConfig) blockHash = ConsoleCli.exec(CliCommand.GetBestBlockHash, cliConfig)
_ <- AsyncUtil.nonBlockingSleep(1.second) _ <- AsyncUtil.nonBlockingSleep(1.second)
_ <- server.stop() //stop to free all resources _ <- server.stop() // stop to free all resources
} yield { } yield {
assert(info.isSuccess) assert(info.isSuccess)
assert(balance.isSuccess) assert(balance.isSuccess)
@ -54,8 +56,10 @@ class BitcoinSServerMainBitcoindTest
val server = new BitcoinSServerMain(ServerArgParser.empty)(system, config) val server = new BitcoinSServerMain(ServerArgParser.empty)(system, config)
val cliConfig = Config(rpcPortOpt = Some(config.rpcPort), val cliConfig = Config(
rpcPassword = config.rpcPassword) rpcPortOpt = Some(config.rpcPort),
rpcPassword = config.rpcPassword
)
val mnemonic = val mnemonic =
MnemonicCode.fromEntropy(ECPrivateKey.freshPrivateKey.bytes.toBitVector) MnemonicCode.fromEntropy(ECPrivateKey.freshPrivateKey.bytes.toBitVector)
@ -136,7 +140,8 @@ class BitcoinSServerMainBitcoindTest
assert(infoT.isFailure) assert(infoT.isFailure)
assert( assert(
infoT.failed.get.getMessage infoT.failed.get.getMessage
.contains("The supplied authentication is invalid")) .contains("The supplied authentication is invalid")
)
} }
failF failF

View File

@ -21,8 +21,10 @@ class BitcoinSServerMainBitcoindTorTest
config: BitcoinSAppConfig => config: BitcoinSAppConfig =>
val server = new BitcoinSServerMain(ServerArgParser.empty)(system, config) val server = new BitcoinSServerMain(ServerArgParser.empty)(system, config)
val cliConfig = Config(rpcPortOpt = Some(config.rpcPort), val cliConfig = Config(
rpcPassword = config.rpcPassword) rpcPortOpt = Some(config.rpcPort),
rpcPassword = config.rpcPassword
)
for { for {
_ <- torF _ <- torF
@ -33,7 +35,7 @@ class BitcoinSServerMainBitcoindTorTest
addr = exec(GetNewAddress(labelOpt = None), cliConfig) addr = exec(GetNewAddress(labelOpt = None), cliConfig)
blockHash = ConsoleCli.exec(CliCommand.GetBestBlockHash, cliConfig) blockHash = ConsoleCli.exec(CliCommand.GetBestBlockHash, cliConfig)
_ <- AsyncUtil.nonBlockingSleep(1.second) _ <- AsyncUtil.nonBlockingSleep(1.second)
_ <- server.stop() //stop to free all resources _ <- server.stop() // stop to free all resources
} yield { } yield {
assert(info.isSuccess) assert(info.isSuccess)
assert(balance.isSuccess) assert(balance.isSuccess)

View File

@ -30,7 +30,8 @@ class BitcoindRpcAppConfigTest extends BitcoinSAsyncTest {
assert(withOther.rpcPort == 5555) assert(withOther.rpcPort == 5555)
val mainnetConf = ConfigFactory.parseString( val mainnetConf = ConfigFactory.parseString(
s"bitcoin-s.bitcoind-rpc.rpcport = ${MainNet.rpcPort}") s"bitcoin-s.bitcoind-rpc.rpcport = ${MainNet.rpcPort}"
)
val mainnet = withOther.withOverrides(mainnetConf) val mainnet = withOther.withOverrides(mainnetConf)
assert(mainnet.rpcPort == MainNet.rpcPort) assert(mainnet.rpcPort == MainNet.rpcPort)
} }

View File

@ -51,8 +51,8 @@ class CallBackUtilTest extends BitcoinSWalletTest {
_ <- callbacks _ <- callbacks
.executeOnTxReceivedCallbacks(tx2) .executeOnTxReceivedCallbacks(tx2)
.recoverWith { case _: StreamDetachedException => .recoverWith { case _: StreamDetachedException =>
//expect the stream to be detatched because we stopped // expect the stream to be detatched because we stopped
//the stream with callbacks.stop() // the stream with callbacks.stop()
Future.unit Future.unit
} }
balance3 <- wallet.getBalance() balance3 <- wallet.getBalance()
@ -92,8 +92,8 @@ class CallBackUtilTest extends BitcoinSWalletTest {
_ <- callbacks _ <- callbacks
.executeOnTxReceivedCallbacks(tx2) .executeOnTxReceivedCallbacks(tx2)
.recoverWith { case _: StreamDetachedException => .recoverWith { case _: StreamDetachedException =>
//expect the stream to be detatched because we stopped // expect the stream to be detatched because we stopped
//the stream with callbacks.stop() // the stream with callbacks.stop()
Future.unit Future.unit
} }
balance3 <- wallet.getBalance() balance3 <- wallet.getBalance()

View File

@ -54,7 +54,8 @@ class CommonRoutesSpec
val route = val route =
commonRoutes.handleCommand( commonRoutes.handleCommand(
ServerCommand("zipdatadir", ujson.Arr(target.toString))) ServerCommand("zipdatadir", ujson.Arr(target.toString))
)
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == ContentTypes.`application/json`) assert(contentType == ContentTypes.`application/json`)

View File

@ -32,20 +32,22 @@ class DLCRoutesSpec
val dlcRoutes = DLCRoutes(mockNodeApi) val dlcRoutes = DLCRoutes(mockNodeApi)
//https://test.oracle.suredbits.com/announcement/8863cd80e1d37f668e27b84cbfed48541d671b4fed1462b86d547e7f13b5a9e4 // https://test.oracle.suredbits.com/announcement/8863cd80e1d37f668e27b84cbfed48541d671b4fed1462b86d547e7f13b5a9e4
val announcement = OracleAnnouncementTLV.fromHex( val announcement = OracleAnnouncementTLV.fromHex(
"fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e") "fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e"
)
//https://test.oracle.suredbits.com/announcement/362ae482860fc93bac5cbcca3f1f0e49b3c94eac92224a008bd81ef81292f43a // https://test.oracle.suredbits.com/announcement/362ae482860fc93bac5cbcca3f1f0e49b3c94eac92224a008bd81ef81292f43a
val numericAnnouncement = OracleAnnouncementTLV.fromHex( val numericAnnouncement = OracleAnnouncementTLV.fromHex(
"fdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65" "fdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65"
) )
//https://test.oracle.suredbits.com/contract/enum/5fbc1d037bacd9ece32ff4b591143bce7fa1c22e0aec2fa8437cc336feb95138 // https://test.oracle.suredbits.com/contract/enum/5fbc1d037bacd9ece32ff4b591143bce7fa1c22e0aec2fa8437cc336feb95138
val expectedContractInfo = ContractInfo.fromHex( val expectedContractInfo = ContractInfo.fromHex(
"fdd82efd01120000000005f5e100fda7103b030e52657075626c6963616e5f77696e00000000000000000c44656d6f637261745f77696e0000000005f5e100056f746865720000000003938700fda712c7fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e") "fdd82efd01120000000005f5e100fda7103b030e52657075626c6963616e5f77696e00000000000000000c44656d6f637261745f77696e0000000005f5e100056f746865720000000003938700fda712c7fdd824c3988fabec9820690f366271c9ceac00fbec1412075f9b319bb0db1f86460519dd9c61478949f2c00c35aeb8e53a1507616072cb802891e2c189a9fa65a0493de5d3b04a6d7b90c9c43c09ebe5250d583e1c3fc423219b26f6a02ec394a130000afdd8225f0001ae3e30df5a203ad10ee89a909df0c8ccea4836e94e0a5d34c3cdab758fcaee1460189600fdd8062400030e52657075626c6963616e5f77696e0c44656d6f637261745f77696e056f7468657210323032302d75732d656c656374696f6e"
)
//https://test.oracle.suredbits.com/contract/numeric/d4d4df2892fb2cfd2e8f030f0e69a568e19668b5d355e7713f69853db09a4c33 // https://test.oracle.suredbits.com/contract/numeric/d4d4df2892fb2cfd2e8f030f0e69a568e19668b5d355e7713f69853db09a4c33
val expectedNumericContractInfo = ContractInfo.fromHex( val expectedNumericContractInfo = ContractInfo.fromHex(
"fdd82efd032500000000000186a0fda720540011fda72648000501000000000000000000000001fd9c400000000000000000000001fda604000000000000c350000001fdafc800000000000186a0000001fe0001ffff00000000000186a00000fda724020000fda712fd02bffdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65" "fdd82efd032500000000000186a0fda720540011fda72648000501000000000000000000000001fd9c400000000000000000000001fda604000000000000c350000001fdafc800000000000186a0000001fe0001ffff00000000000186a00000fda724020000fda712fd02bffdd824fd02b9659e890eef1b223ba45c9993f88c7997859302fd5510ac23f4cac0d4ee8232a77ecbdf50c07f093794370e6a506a836f6b0fb54b45f1fb662e1307166d2e57030574f77305826939fa9124d19bfa8a8b2f00f000586b8c58c79ee8b77969a949fdd822fd025300114762c188048a953803f0edeeeb68c69e6cdc1d371ba8d517003accfe05afc4d6588c3ea326512bc66c26a841adffa68330b8c723da442792e731fb19fda94274a7766bb48e520f118c100bbe62dc3806a8d05a63d92e23683a04b0b8c24148cd166585a6b33b995b3d6c083523a8435b156c05100d88f449f4754310d5574d5e88aad09af1b8ba942cfd305e728044ec6360d847254453ec05b1b518a36660e2238360e02f3a004663a7f3a3534973d8b66a2646c1386779aa820672b6361b88a8696395c0add87840b460dfd8a8c0d520017efc6bf58267d4c9d2a225c5d0e5719068a7dda5d630d7432239b6c9d921d5f3842b584503460ca52612ac2e64337d299513690372e8f4770eb8a28080e8d7c29920ca32af470d65d6f916ee81e3ac15ce02684ba6d2522a9ffea1de7e202b4b699ef7ec4f089dda07f3de5b7d1f853b2c56471999be4efca82674a651c80f047ba3a2b9e6f9999f0cd4062c533d1ae29cab2a5e33cbe98728b7b4271c67f7c5cd6e12e39128b9971e08496cbd84cfa99c77c88867d33e73acef37022ba4422a5221776991d45416db71fb54bc6c104f6a8e50e8905161709215104a7e7b97e866f32cf43233ffd615cab66699832ec607cf59c85a7f56fa957aa5f5d7ec9f46d84d5d4b777122d41ad76c6f4968aeedca243f2030d4f502e58f4181130e9afb75309ac21637bcfd0717528bfb82ffe1b6c9fadee6ba70357210990539184bcc913a0ec65837a736733a2fb6172d601b3900fdd80a11000200074254432f55534400000000001117626974636f696e2d732d70726963652d6578616d706c65"
) )
@ -84,8 +86,11 @@ class DLCRoutesSpec
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == ContentTypes.`application/json`) assert(contentType == ContentTypes.`application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${expectedContractInfo.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${expectedContractInfo.hex}","error":null}"""
)
} }
} }
@ -147,8 +152,11 @@ class DLCRoutesSpec
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == ContentTypes.`application/json`) assert(contentType == ContentTypes.`application/json`)
assert(responseAs[ assert(
String] == s"""{"result":"${expectedNumericContractInfo.hex}","error":null}""") responseAs[
String
] == s"""{"result":"${expectedNumericContractInfo.hex}","error":null}"""
)
} }
} }
@ -222,7 +230,8 @@ class DLCRoutesSpec
Post() ~> route ~> check { Post() ~> route ~> check {
assert(contentType == ContentTypes.`application/json`) assert(contentType == ContentTypes.`application/json`)
assert( assert(
responseAs[String] == s"""{"result":{"dlcId":"0000000000000000000000000000000000000000000000000000000000000000","contactId":"i3bfhauurptgypgnolqmj7xxv7pcs5c6udtzthbgojc7eaxx6zbbjyad.onion:2862"},"error":null}""") responseAs[String] == s"""{"result":{"dlcId":"0000000000000000000000000000000000000000000000000000000000000000","contactId":"i3bfhauurptgypgnolqmj7xxv7pcs5c6udtzthbgojc7eaxx6zbbjyad.onion:2862"},"error":null}"""
)
} }
} }

View File

@ -34,38 +34,41 @@ class DLCWalletBitcoindBackendLoaderTest extends WalletLoaderFixtures {
it must "track rescan state accurately" in { walletHolderWithLoader => it must "track rescan state accurately" in { walletHolderWithLoader =>
val loader = walletHolderWithLoader.loaderApi val loader = walletHolderWithLoader.loaderApi
val bitcoind = walletHolderWithLoader.bitcoind val bitcoind = walletHolderWithLoader.bitcoind
//need some blocks to make rescans last longer for the test case // need some blocks to make rescans last longer for the test case
val blocksF = bitcoind.generate(250) val blocksF = bitcoind.generate(250)
val loadedWalletF = loader.load(walletNameOpt = None, aesPasswordOpt = None) val loadedWalletF = loader.load(walletNameOpt = None, aesPasswordOpt = None)
val walletConfigF = loadedWalletF.map(_._2) val walletConfigF = loadedWalletF.map(_._2)
//as a hack, set rescanning to true, so next time we load it starts a rescan // as a hack, set rescanning to true, so next time we load it starts a rescan
val setRescanF = for { val setRescanF = for {
_ <- blocksF _ <- blocksF
walletConfig <- walletConfigF walletConfig <- walletConfigF
descriptorDAO = WalletStateDescriptorDAO()(system.dispatcher, descriptorDAO = WalletStateDescriptorDAO()(
walletConfig) system.dispatcher,
walletConfig
)
set <- descriptorDAO.compareAndSetRescanning(false, true) set <- descriptorDAO.compareAndSetRescanning(false, true)
} yield assert(set) } yield assert(set)
//now that we have set rescanning, we should see a rescan next time we load wallet // now that we have set rescanning, we should see a rescan next time we load wallet
for { for {
_ <- setRescanF _ <- setRescanF
(loadWallet2, _, _) <- loader.load( (loadWallet2, _, _) <- loader.load(
walletNameOpt = None, walletNameOpt = None,
aesPasswordOpt = None aesPasswordOpt = None
) //load wallet again ) // load wallet again
isRescanning <- loadWallet2.isRescanning() isRescanning <- loadWallet2.isRescanning()
_ = assert(isRescanning) _ = assert(isRescanning)
_ = assert(loader.isRescanStateDefined) _ = assert(loader.isRescanStateDefined)
//wait until rescanning is done // wait until rescanning is done
_ <- AsyncUtil.retryUntilSatisfiedF( _ <- AsyncUtil.retryUntilSatisfiedF(
{ () => { () =>
loadWallet2.isRescanning().map(isRescanning => isRescanning == false) loadWallet2.isRescanning().map(isRescanning => isRescanning == false)
}, },
1.second) 1.second
)
} yield { } yield {
assert(loader.isRescanStateEmpty) assert(loader.isRescanStateEmpty)
} }

View File

@ -18,8 +18,8 @@ class ServerRunTest extends BitcoinSAsyncTest {
// Note: on this test passing it will output a stack trace // Note: on this test passing it will output a stack trace
// because runMain calls err.printStackTrace() on failure // because runMain calls err.printStackTrace() on failure
it must "throw errors" in { it must "throw errors" in {
//custom configuration to make peers empty // custom configuration to make peers empty
//this should cause an exception in startBitcoinSBackend() // this should cause an exception in startBitcoinSBackend()
val noPeersConfig = val noPeersConfig =
ConfigFactory.parseString(s"""bitcoin-s.node.peers=[]""") ConfigFactory.parseString(s"""bitcoin-s.node.peers=[]""")
implicit val config = implicit val config =
@ -27,10 +27,12 @@ class ServerRunTest extends BitcoinSAsyncTest {
val datadir = config.chainConf.datadir val datadir = config.chainConf.datadir
val invalidPort = -1 val invalidPort = -1
val args = Vector("--datadir", val args = Vector(
datadir.toAbsolutePath.toString, "--datadir",
"--rpcport", datadir.toAbsolutePath.toString,
invalidPort.toString) "--rpcport",
invalidPort.toString
)
val serverArgParser = ServerArgParser(args) val serverArgParser = ServerArgParser(args)
val main = new BitcoinSServerMain(serverArgParser) val main = new BitcoinSServerMain(serverArgParser)

View File

@ -40,10 +40,12 @@ class WalletRoutesSpec
val feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one) val feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one)
val walletLoader: DLCWalletNeutrinoBackendLoader = val walletLoader: DLCWalletNeutrinoBackendLoader =
DLCWalletNeutrinoBackendLoader(walletHolder, DLCWalletNeutrinoBackendLoader(
mockChainApi, walletHolder,
mockNode, mockChainApi,
feeRateApi) mockNode,
feeRateApi
)
val walletRoutes: WalletRoutes = val walletRoutes: WalletRoutes =
WalletRoutes(walletLoader)(system, conf.walletConf) WalletRoutes(walletLoader)(system, conf.walletConf)
@ -101,7 +103,8 @@ class WalletRoutesSpec
) )
(mockWalletApi.findDLCByTemporaryContractId: Sha256Digest => Future[ (mockWalletApi.findDLCByTemporaryContractId: Sha256Digest => Future[
Option[DLCStatus]]) Option[DLCStatus]
])
.expects(tempContractId) .expects(tempContractId)
.returning(Future.successful(Some(status))) .returning(Future.successful(Some(status)))
(mockWalletApi.getDLCOffer: Sha256Digest => Future[Option[DLCOffer]]) (mockWalletApi.getDLCOffer: Sha256Digest => Future[Option[DLCOffer]])
@ -110,7 +113,8 @@ class WalletRoutesSpec
val route = val route =
walletRoutes.handleCommand( walletRoutes.handleCommand(
ServerCommand("getdlcoffer", ujson.Arr(tempContractId.hex))) ServerCommand("getdlcoffer", ujson.Arr(tempContractId.hex))
)
Get() ~> route ~> check { Get() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
@ -127,23 +131,28 @@ class WalletRoutesSpec
LnMessage(TLVGen.dlcAcceptTLV(offer.toTLV).sampleSome) LnMessage(TLVGen.dlcAcceptTLV(offer.toTLV).sampleSome)
val acceptTLV = DLCAccept.fromTLV(dummyAcceptLnMsg.tlv, offer) val acceptTLV = DLCAccept.fromTLV(dummyAcceptLnMsg.tlv, offer)
(mockWalletApi (mockWalletApi
.acceptDLCOffer(_: DLCOfferTLV, .acceptDLCOffer(
_: Option[InetSocketAddress], _: DLCOfferTLV,
_: Option[BitcoinAddress], _: Option[InetSocketAddress],
_: Option[BitcoinAddress])) _: Option[BitcoinAddress],
_: Option[BitcoinAddress]
))
.expects(expectedTlv, None, None, None) .expects(expectedTlv, None, None, None)
.returning(Future.successful(acceptTLV)) .returning(Future.successful(acceptTLV))
val cmd = ServerCommand( val cmd = ServerCommand(
"acceptdlcoffer", "acceptdlcoffer",
ujson.Arr(ujson.Str(tlv), ujson.Null, ujson.Null, ujson.Null)) ujson.Arr(ujson.Str(tlv), ujson.Null, ujson.Null, ujson.Null)
)
val route = walletRoutes.handleCommand(cmd) val route = walletRoutes.handleCommand(cmd)
Get() ~> route ~> check { Get() ~> route ~> check {
assert(contentType == `application/json`) assert(contentType == `application/json`)
assert( assert(
responseAs[ responseAs[
String] == s"""{"result":"${dummyAcceptLnMsg.hex}","error":null}""") String
] == s"""{"result":"${dummyAcceptLnMsg.hex}","error":null}"""
)
} }
} }
} }

View File

@ -56,19 +56,26 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
val sink: Sink[Message, Future[Seq[WsNotification[_]]]] = Flow[Message] val sink: Sink[Message, Future[Seq[WsNotification[_]]]] = Flow[Message]
.map { .map {
case message: TextMessage.Strict => case message: TextMessage.Strict =>
//we should be able to parse the address message // we should be able to parse the address message
val text = message.text val text = message.text
val dlcNodeNotificationOpt: Option[DLCNodeNotification[_]] = Try( val dlcNodeNotificationOpt: Option[DLCNodeNotification[_]] = Try(
upickle.default.read[DLCNodeNotification[_]](text)( upickle.default.read[DLCNodeNotification[_]](text)(
WsPicklers.dlcNodeNotificationPickler)).toOption WsPicklers.dlcNodeNotificationPickler
)
).toOption
val walletNotificationOpt: Option[WalletNotification[_]] = Try( val walletNotificationOpt: Option[WalletNotification[_]] = Try(
upickle.default.read[WalletNotification[_]](text)( upickle.default.read[WalletNotification[_]](text)(
WsPicklers.walletNotificationPickler)).toOption WsPicklers.walletNotificationPickler
)
).toOption
val chainNotificationOpt: Option[ChainNotification[_]] = Try( val chainNotificationOpt: Option[ChainNotification[_]] = Try(
upickle.default.read[ChainNotification[_]](text)( upickle.default.read[ChainNotification[_]](text)(
WsPicklers.chainNotificationPickler)).toOption WsPicklers.chainNotificationPickler
)
).toOption
walletNotificationOpt.getOrElse( walletNotificationOpt.getOrElse(
chainNotificationOpt.getOrElse(dlcNodeNotificationOpt.get)) chainNotificationOpt.getOrElse(dlcNodeNotificationOpt.get)
)
case msg => case msg =>
fail(s"Unexpected msg type received in the sink, msg=$msg") fail(s"Unexpected msg type received in the sink, msg=$msg")
} }
@ -76,19 +83,27 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
def buildReq( def buildReq(
conf: BitcoinSAppConfig, conf: BitcoinSAppConfig,
rpcPassword: Option[String] = None): WebSocketRequest = { rpcPassword: Option[String] = None
): WebSocketRequest = {
val headers: Vector[HttpHeader] = Vector( val headers: Vector[HttpHeader] = Vector(
Authorization( Authorization(
BasicHttpCredentials("bitcoins", BasicHttpCredentials(
rpcPassword.getOrElse(conf.rpcPassword)))) "bitcoins",
WebSocketRequest(s"ws://localhost:${conf.wsPort}/events", rpcPassword.getOrElse(conf.rpcPassword)
extraHeaders = headers) )
)
)
WebSocketRequest(
s"ws://localhost:${conf.wsPort}/events",
extraHeaders = headers
)
} }
val websocketFlow: Flow[ val websocketFlow: Flow[
Message, Message,
Message, Message,
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])] = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
] = {
Flow Flow
.fromSinkAndSourceCoupledMat(sink, Source.maybe[Message])(Keep.both) .fromSinkAndSourceCoupledMat(sink, Source.maybe[Message])(Keep.both)
} }
@ -133,26 +148,32 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
} yield { } yield {
assert(response.response.status == StatusCodes.Unauthorized) assert(response.response.status == StatusCodes.Unauthorized)
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = "wrong password") rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = "wrong password"
)
val cliResponse = exec(GetNewAddress(labelOpt = None), cliConfig) val cliResponse = exec(GetNewAddress(labelOpt = None), cliConfig)
assert(cliResponse.isFailure) assert(cliResponse.isFailure)
assert( assert(
cliResponse.failed.get.getMessage == "The supplied authentication is invalid") cliResponse.failed.get.getMessage == "The supplied authentication is invalid"
)
} }
} }
it must "receive updates when an address is generated" in { it must "receive updates when an address is generated" in {
serverWithBitcoind => serverWithBitcoind =>
val ServerWithBitcoind(_, server) = serverWithBitcoind val ServerWithBitcoind(_, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val notificationsF: ( val notificationsF: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -171,20 +192,24 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
notifications <- walletNotificationsF notifications <- walletNotificationsF
} yield { } yield {
assert( assert(
notifications.exists(_ == NewAddressNotification(expectedAddress))) notifications.exists(_ == NewAddressNotification(expectedAddress))
)
} }
} }
it must "receive updates when a transaction is broadcast" in { it must "receive updates when a transaction is broadcast" in {
serverWithBitcoind => serverWithBitcoind =>
val ServerWithBitcoind(bitcoind, server) = serverWithBitcoind val ServerWithBitcoind(bitcoind, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val tuple: ( val tuple: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -196,11 +221,12 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
for { for {
address <- addressF address <- addressF
cmd = SendToAddress(destination = address, cmd = SendToAddress(
amount = Bitcoins.one, destination = address,
satoshisPerVirtualByte = amount = Bitcoins.one,
Some(SatoshisPerVirtualByte.one), satoshisPerVirtualByte = Some(SatoshisPerVirtualByte.one),
noBroadcast = false) noBroadcast = false
)
txIdStr = ConsoleCli.exec(cmd, cliConfig) txIdStr = ConsoleCli.exec(cmd, cliConfig)
expectedTxId = DoubleSha256DigestBE.fromHex(txIdStr.get) expectedTxId = DoubleSha256DigestBE.fromHex(txIdStr.get)
getTxCmd = GetTransaction(expectedTxId) getTxCmd = GetTransaction(expectedTxId)
@ -217,13 +243,16 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
it must "receive updates when a transaction is processed" in { it must "receive updates when a transaction is processed" in {
serverWithBitcoind => serverWithBitcoind =>
val ServerWithBitcoind(bitcoind, server) = serverWithBitcoind val ServerWithBitcoind(bitcoind, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val tuple: ( val tuple: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -235,11 +264,12 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
for { for {
address <- addressF address <- addressF
cmd = SendToAddress(destination = address, cmd = SendToAddress(
amount = Bitcoins.one, destination = address,
satoshisPerVirtualByte = amount = Bitcoins.one,
Some(SatoshisPerVirtualByte.one), satoshisPerVirtualByte = Some(SatoshisPerVirtualByte.one),
noBroadcast = false) noBroadcast = false
)
txIdStr = ConsoleCli.exec(cmd, cliConfig) txIdStr = ConsoleCli.exec(cmd, cliConfig)
expectedTxId = DoubleSha256DigestBE.fromHex(txIdStr.get) expectedTxId = DoubleSha256DigestBE.fromHex(txIdStr.get)
getTxCmd = GetTransaction(expectedTxId) getTxCmd = GetTransaction(expectedTxId)
@ -255,13 +285,16 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
it must "receive updates when a block is processed" in { serverWithBitcoind => it must "receive updates when a block is processed" in { serverWithBitcoind =>
val ServerWithBitcoind(bitcoind, server) = serverWithBitcoind val ServerWithBitcoind(bitcoind, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val tuple: ( val tuple: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -271,20 +304,22 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
val addressF = bitcoind.getNewAddress val addressF = bitcoind.getNewAddress
val timeout = val timeout =
15.seconds //any way we can remove this timeout and just check? 15.seconds // any way we can remove this timeout and just check?
for { for {
address <- addressF address <- addressF
hashes <- bitcoind.generateToAddress(1, address) hashes <- bitcoind.generateToAddress(1, address)
cmd = GetBlockHeader(hash = hashes.head) cmd = GetBlockHeader(hash = hashes.head)
getBlockHeaderResultStr = ConsoleCli.exec(cmd, cliConfig) getBlockHeaderResultStr = ConsoleCli.exec(cmd, cliConfig)
getBlockHeaderResult = upickle.default.read(getBlockHeaderResultStr.get)( getBlockHeaderResult = upickle.default.read(getBlockHeaderResultStr.get)(
Picklers.getBlockHeaderResultPickler) Picklers.getBlockHeaderResultPickler
)
_ <- PekkoUtil.nonBlockingSleep(timeout) _ <- PekkoUtil.nonBlockingSleep(timeout)
_ = promise.success(None) _ = promise.success(None)
notifications <- notificationsF notifications <- notificationsF
} yield { } yield {
val count = notifications.count( val count = notifications.count(
_ == BlockProcessedNotification(getBlockHeaderResult)) _ == BlockProcessedNotification(getBlockHeaderResult)
)
assert(count == 1, s"count=$count") assert(count == 1, s"count=$count")
} }
} }
@ -292,13 +327,16 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
it must "get notifications for reserving and unreserving utxos" in { it must "get notifications for reserving and unreserving utxos" in {
serverWithBitcoind => serverWithBitcoind =>
val ServerWithBitcoind(_, server) = serverWithBitcoind val ServerWithBitcoind(_, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val tuple: ( val tuple: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -306,11 +344,11 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
val notificationsF: Future[Seq[WsNotification[_]]] = tuple._2._1 val notificationsF: Future[Seq[WsNotification[_]]] = tuple._2._1
val promise = tuple._2._2 val promise = tuple._2._2
//lock all utxos // lock all utxos
val lockCmd = LockUnspent(unlock = false, Vector.empty) val lockCmd = LockUnspent(unlock = false, Vector.empty)
ConsoleCli.exec(lockCmd, cliConfig) ConsoleCli.exec(lockCmd, cliConfig)
//unlock all utxos // unlock all utxos
val unlockCmd = LockUnspent(unlock = true, Vector.empty) val unlockCmd = LockUnspent(unlock = true, Vector.empty)
ConsoleCli.exec(unlockCmd, cliConfig) ConsoleCli.exec(unlockCmd, cliConfig)
@ -319,7 +357,7 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
_ = promise.success(None) _ = promise.success(None)
notifications <- notificationsF notifications <- notificationsF
} yield { } yield {
//should have two notifications for locking and then unlocking the utxos // should have two notifications for locking and then unlocking the utxos
assert(notifications.count(_.`type` == WalletWsType.ReservedUtxos) == 2) assert(notifications.count(_.`type` == WalletWsType.ReservedUtxos) == 2)
} }
} }
@ -327,13 +365,16 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
it must "receive updates when an offer is added and removed" in { it must "receive updates when an offer is added and removed" in {
serverWithBitcoind => serverWithBitcoind =>
val ServerWithBitcoind(_, server) = serverWithBitcoind val ServerWithBitcoind(_, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val notificationsF: ( val notificationsF: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -348,7 +389,8 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
ConsoleCli ConsoleCli
.exec( .exec(
CliCommand.AddDLCOffer(offer = offer, message = "msg", peer = "uri"), CliCommand.AddDLCOffer(offer = offer, message = "msg", peer = "uri"),
cliConfig) cliConfig
)
.get .get
ConsoleCli ConsoleCli
@ -376,23 +418,28 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
it must "receive updates when a rescan is complete" in { serverWithBitcoind => it must "receive updates when a rescan is complete" in { serverWithBitcoind =>
val ServerWithBitcoind(_, server) = serverWithBitcoind val ServerWithBitcoind(_, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val tuple: ( val tuple: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
val notificationsF = tuple._2._1 val notificationsF = tuple._2._1
val promise = tuple._2._2 val promise = tuple._2._2
val cmd = Rescan(batchSize = None, val cmd = Rescan(
startBlock = None, batchSize = None,
endBlock = None, startBlock = None,
force = true, endBlock = None,
ignoreCreationTime = false) force = true,
ignoreCreationTime = false
)
val _ = ConsoleCli.exec(cmd, cliConfig) val _ = ConsoleCli.exec(cmd, cliConfig)
for { for {
_ <- PekkoUtil.nonBlockingSleep(10.second) _ <- PekkoUtil.nonBlockingSleep(10.second)
@ -410,7 +457,8 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
val req = buildReq(server.conf) val req = buildReq(server.conf)
val tuple: ( val tuple: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -434,7 +482,8 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
val req = buildReq(server.conf) val req = buildReq(server.conf)
val tuple: ( val tuple: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -453,13 +502,16 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
it must "receive dlc node updates" in { serverWithBitcoind => it must "receive dlc node updates" in { serverWithBitcoind =>
val ServerWithBitcoind(_, server) = serverWithBitcoind val ServerWithBitcoind(_, server) = serverWithBitcoind
val cliConfig = Config(rpcPortOpt = Some(server.conf.rpcPort), val cliConfig = Config(
rpcPassword = server.conf.rpcPassword) rpcPortOpt = Some(server.conf.rpcPort),
rpcPassword = server.conf.rpcPassword
)
val req = buildReq(server.conf) val req = buildReq(server.conf)
val notificationsF: ( val notificationsF: (
Future[WebSocketUpgradeResponse], Future[WebSocketUpgradeResponse],
(Future[Seq[WsNotification[_]]], Promise[Option[Message]])) = { (Future[Seq[WsNotification[_]]], Promise[Option[Message]])
) = {
Http() Http()
.singleWebSocketRequest(req, websocketFlow) .singleWebSocketRequest(req, websocketFlow)
} }
@ -474,10 +526,12 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
InetSocketAddress.createUnresolved("127.0.0.1", NetworkUtil.randomPort()) InetSocketAddress.createUnresolved("127.0.0.1", NetworkUtil.randomPort())
val acceptMsg = { val acceptMsg = {
AcceptDLC(offer = offer, AcceptDLC(
externalPayoutAddressOpt = None, offer = offer,
externalChangeAddressOpt = None, externalPayoutAddressOpt = None,
peerAddr = peerAddr) externalChangeAddressOpt = None,
peerAddr = peerAddr
)
} }
for { for {
_ <- setupF _ <- setupF
@ -488,13 +542,16 @@ class WebsocketTests extends BitcoinSServerMainBitcoindFixture {
} yield { } yield {
assert(notifications.exists(_ == DLCNodeConnectionInitiated(peerAddr))) assert(notifications.exists(_ == DLCNodeConnectionInitiated(peerAddr)))
assert(notifications.exists(_ == DLCNodeConnectionFailed(peerAddr))) assert(notifications.exists(_ == DLCNodeConnectionFailed(peerAddr)))
assert(notifications.exists(n => assert(
n match { notifications.exists(n =>
case DLCAcceptFailed((id, error)) => n match {
id == offer.tlv.tempContractId && error.startsWith( case DLCAcceptFailed((id, error)) =>
"Connection refused") id == offer.tlv.tempContractId && error.startsWith(
case _ => false "Connection refused"
})) )
case _ => false
})
)
} }
} }

View File

@ -7,8 +7,8 @@ import org.bitcoins.core.protocol.BitcoinAddress
import scala.concurrent.Future import scala.concurrent.Future
/** ScalaMock cannot stub traits with protected methods, /** ScalaMock cannot stub traits with protected methods, so we need to stub them
* so we need to stub them manually. * manually.
*/ */
abstract class MockWalletApi extends DLCNeutrinoHDWalletApi { abstract class MockWalletApi extends DLCNeutrinoHDWalletApi {
@ -18,7 +18,8 @@ abstract class MockWalletApi extends DLCNeutrinoHDWalletApi {
override def getDefaultAccount(): Future[AccountDb] = stub override def getDefaultAccount(): Future[AccountDb] = stub
override def getDefaultAccountForType( override def getDefaultAccountForType(
addressType: AddressType): Future[AccountDb] = stub addressType: AddressType
): Future[AccountDb] = stub
private def stub[T] = private def stub[T] =
Future.failed[T](new RuntimeException("Not implemented")) Future.failed[T](new RuntimeException("Not implemented"))

View File

@ -27,18 +27,20 @@ import java.util.concurrent.TimeUnit
import scala.concurrent.Future import scala.concurrent.Future
import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.concurrent.duration.{DurationInt, FiniteDuration}
/** A unified config class for all submodules of Bitcoin-S /** A unified config class for all submodules of Bitcoin-S that accepts
* that accepts configuration. Thanks to implicit definitions * configuration. Thanks to implicit definitions in this case class' companion
* in this case class' companion object an instance * object an instance of this class can be passed in anywhere a wallet, chain
* of this class can be passed in anywhere a wallet, * or node config is required.
* chain or node config is required.
* *
* @param directory The data directory of this app configuration * @param directory
* @param confs A sequence of optional configuration overrides * The data directory of this app configuration
* @param confs
* A sequence of optional configuration overrides
*/ */
case class BitcoinSAppConfig( case class BitcoinSAppConfig(
baseDatadir: Path, baseDatadir: Path,
configOverrides: Vector[Config])(implicit system: ActorSystem) configOverrides: Vector[Config]
)(implicit system: ActorSystem)
extends StartStopAsync[AppConfigMarker] extends StartStopAsync[AppConfigMarker]
with BitcoinSLogger { with BitcoinSLogger {
import system.dispatcher import system.dispatcher
@ -73,8 +75,8 @@ case class BitcoinSAppConfig(
/** Initializes the wallet, node and chain projects */ /** Initializes the wallet, node and chain projects */
override def start(): Future[StartedBitcoinSAppConfig] = { override def start(): Future[StartedBitcoinSAppConfig] = {
val start = TimeUtil.currentEpochMs val start = TimeUtil.currentEpochMs
//configurations that don't depend on tor startup // configurations that don't depend on tor startup
//start these in parallel as an optimization // start these in parallel as an optimization
val nonTorConfigs = Vector(kmConf, chainConf, walletConf, dlcConf) val nonTorConfigs = Vector(kmConf, chainConf, walletConf, dlcConf)
val torConfig = torConf.start() val torConfig = torConf.start()
@ -84,10 +86,10 @@ case class BitcoinSAppConfig(
val dbConfigsDependentOnTor: Vector[DbManagement] = val dbConfigsDependentOnTor: Vector[DbManagement] =
Vector(nodeConf) Vector(nodeConf)
//run migrations here to avoid issues like: https://github.com/bitcoin-s/bitcoin-s/issues/4606 // run migrations here to avoid issues like: https://github.com/bitcoin-s/bitcoin-s/issues/4606
//since we don't require tor dependent configs // since we don't require tor dependent configs
//to be fully started before completing the Future returned by this // to be fully started before completing the Future returned by this
//method, we need to run them on their own // method, we need to run them on their own
val migrateTorDependentDbConfigsF = val migrateTorDependentDbConfigsF =
Future.traverse(dbConfigsDependentOnTor)(dbConfig => Future.traverse(dbConfigsDependentOnTor)(dbConfig =>
Future(dbConfig.migrate())) Future(dbConfig.migrate()))
@ -109,7 +111,8 @@ case class BitcoinSAppConfig(
_ <- startedNonTorConfigsF _ <- startedNonTorConfigsF
} yield { } yield {
logger.info( logger.info(
s"Done starting BitcoinSAppConfig, it took=${TimeUtil.currentEpochMs - start}ms") s"Done starting BitcoinSAppConfig, it took=${TimeUtil.currentEpochMs - start}ms"
)
StartedBitcoinSAppConfig(startedTorDependentConfigsF.map(_ => ())) StartedBitcoinSAppConfig(startedTorDependentConfigsF.map(_ => ()))
} }
} }
@ -132,9 +135,11 @@ case class BitcoinSAppConfig(
/** The underlying config the result of our fields derive from */ /** The underlying config the result of our fields derive from */
lazy val config: Config = { lazy val config: Config = {
val finalConfig = val finalConfig =
AppConfig.getBaseConfig(baseDatadir = baseDatadir, AppConfig.getBaseConfig(
DEFAULT_BITCOIN_S_CONF_FILE, baseDatadir = baseDatadir,
configOverrides) DEFAULT_BITCOIN_S_CONF_FILE,
configOverrides
)
val resolved = finalConfig.resolve() val resolved = finalConfig.resolve()
resolved.checkValid(ConfigFactory.defaultReference(), "bitcoin-s") resolved.checkValid(ConfigFactory.defaultReference(), "bitcoin-s")
@ -146,8 +151,8 @@ case class BitcoinSAppConfig(
def wsPort: Int = config.getIntOrElse("bitcoin-s.server.wsport", 19999) def wsPort: Int = config.getIntOrElse("bitcoin-s.server.wsport", 19999)
/** How long until we forcefully terminate connections to the server /** How long until we forcefully terminate connections to the server when
* when shutting down the server * shutting down the server
*/ */
def terminationDeadline: FiniteDuration = { def terminationDeadline: FiniteDuration = {
val opt = config.getDurationOpt("bitcoin-s.server.termination-deadline") val opt = config.getDurationOpt("bitcoin-s.server.termination-deadline")
@ -157,9 +162,10 @@ case class BitcoinSAppConfig(
new FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS) new FiniteDuration(duration.toNanos, TimeUnit.NANOSECONDS)
} else { } else {
sys.error( sys.error(
s"Can only have a finite duration for termination deadline, got=$duration") s"Can only have a finite duration for termination deadline, got=$duration"
)
} }
case None => 5.seconds //5 seconds by default case None => 5.seconds // 5 seconds by default
} }
} }
@ -190,13 +196,14 @@ case class BitcoinSAppConfig(
} }
} }
/** Implicit conversions that allow a unified configuration /** Implicit conversions that allow a unified configuration to be passed in
* to be passed in wherever a specializes one is required * wherever a specializes one is required
*/ */
object BitcoinSAppConfig extends BitcoinSLogger { object BitcoinSAppConfig extends BitcoinSLogger {
def fromConfig(config: Config)(implicit def fromConfig(
system: ActorSystem): BitcoinSAppConfig = { config: Config
)(implicit system: ActorSystem): BitcoinSAppConfig = {
val configDataDir: Path = Paths.get(config.getString("bitcoin-s.datadir")) val configDataDir: Path = Paths.get(config.getString("bitcoin-s.datadir"))
BitcoinSAppConfig(configDataDir, Vector(config)) BitcoinSAppConfig(configDataDir, Vector(config))
} }
@ -206,33 +213,36 @@ object BitcoinSAppConfig extends BitcoinSLogger {
} }
def fromDatadir(datadir: Path, confs: Config*)(implicit def fromDatadir(datadir: Path, confs: Config*)(implicit
system: ActorSystem): BitcoinSAppConfig = { system: ActorSystem
): BitcoinSAppConfig = {
BitcoinSAppConfig(datadir, confs.toVector) BitcoinSAppConfig(datadir, confs.toVector)
} }
def fromDatadirWithServerArgs( def fromDatadirWithServerArgs(
datadir: Path, datadir: Path,
serverArgsParser: ServerArgParser)(implicit serverArgsParser: ServerArgParser
system: ActorSystem): BitcoinSAppConfig = { )(implicit system: ActorSystem): BitcoinSAppConfig = {
fromDatadir(datadir, serverArgsParser.toConfig) fromDatadir(datadir, serverArgsParser.toConfig)
} }
/** Constructs an app configuration from the default Bitcoin-S /** Constructs an app configuration from the default Bitcoin-S data directory
* data directory and given list of configuration overrides. * and given list of configuration overrides.
*/ */
def fromDefaultDatadir(confs: Config*)(implicit def fromDefaultDatadir(confs: Config*)(implicit
system: ActorSystem): BitcoinSAppConfig = system: ActorSystem
): BitcoinSAppConfig =
BitcoinSAppConfig(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs.toVector) BitcoinSAppConfig(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs.toVector)
def fromDefaultDatadirWithBundleConf(confs: Vector[Config] = Vector.empty)( def fromDefaultDatadirWithBundleConf(
implicit system: ActorSystem): BitcoinSAppConfig = { confs: Vector[Config] = Vector.empty
)(implicit system: ActorSystem): BitcoinSAppConfig = {
fromDatadirWithBundleConf(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs) fromDatadirWithBundleConf(AppConfig.DEFAULT_BITCOIN_S_DATADIR, confs)
} }
def fromDatadirWithBundleConf( def fromDatadirWithBundleConf(
datadir: Path, datadir: Path,
confs: Vector[Config] = Vector.empty)(implicit confs: Vector[Config] = Vector.empty
system: ActorSystem): BitcoinSAppConfig = { )(implicit system: ActorSystem): BitcoinSAppConfig = {
val baseConf: BitcoinSAppConfig = val baseConf: BitcoinSAppConfig =
fromDatadir(datadir, confs: _*) fromDatadir(datadir, confs: _*)
@ -250,22 +260,20 @@ object BitcoinSAppConfig extends BitcoinSLogger {
} }
/** Resolve BitcoinSAppConfig in the following order of precedence /** Resolve BitcoinSAppConfig in the following order of precedence
* 1. Server args * 1. Server args 2. bitcoin-s-bundle.conf 3. bitcoin-s.conf 4.
* 2. bitcoin-s-bundle.conf * application.conf 5. reference.conf
* 3. bitcoin-s.conf
* 4. application.conf
* 5. reference.conf
*/ */
def fromDatadirWithBundleConfWithServerArgs( def fromDatadirWithBundleConfWithServerArgs(
datadir: Path, datadir: Path,
serverArgParser: ServerArgParser)(implicit serverArgParser: ServerArgParser
system: ActorSystem): BitcoinSAppConfig = { )(implicit system: ActorSystem): BitcoinSAppConfig = {
fromDatadirWithBundleConf(datadir, Vector(serverArgParser.toConfig)) fromDatadirWithBundleConf(datadir, Vector(serverArgParser.toConfig))
} }
/** Creates a BitcoinSAppConfig the the given daemon args to a server */ /** Creates a BitcoinSAppConfig the the given daemon args to a server */
def fromDefaultDatadirWithServerArgs(serverArgParser: ServerArgParser)( def fromDefaultDatadirWithServerArgs(
implicit system: ActorSystem): BitcoinSAppConfig = { serverArgParser: ServerArgParser
)(implicit system: ActorSystem): BitcoinSAppConfig = {
val config = serverArgParser.toConfig val config = serverArgParser.toConfig
fromConfig(config) fromConfig(config)
} }

View File

@ -51,8 +51,8 @@ import scala.concurrent.{Await, Future, Promise}
class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
override val system: ActorSystem, override val system: ActorSystem,
val conf: BitcoinSAppConfig) val conf: BitcoinSAppConfig
extends BitcoinSServerRunner[WalletHolder] { ) extends BitcoinSServerRunner[WalletHolder] {
implicit lazy val nodeConf: NodeAppConfig = conf.nodeConf implicit lazy val nodeConf: NodeAppConfig = conf.nodeConf
implicit lazy val chainConf: ChainAppConfig = conf.chainConf implicit lazy val chainConf: ChainAppConfig = conf.chainConf
@ -69,16 +69,18 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
logger.info(s"Start on network $network") logger.info(s"Start on network $network")
startedConfigF.failed.foreach { err => startedConfigF.failed.foreach { err =>
logger.error(s"Failed to initialize configuration for BitcoinServerMain", logger.error(
err) s"Failed to initialize configuration for BitcoinServerMain",
err
)
} }
for { for {
startedConfig <- startedConfigF startedConfig <- startedConfigF
chainApi = ChainHandler.fromDatabase() chainApi = ChainHandler.fromDatabase()
nodeType = nodeConf.nodeType nodeType = nodeConf.nodeType
//on server startup we assume we are out of sync with the bitcoin network // on server startup we assume we are out of sync with the bitcoin network
//so we set this flag to true. // so we set this flag to true.
_ <- initializeChainState(chainApi, nodeType) _ <- initializeChainState(chainApi, nodeType)
start <- { start <- {
nodeType match { nodeType match {
@ -90,21 +92,23 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
} }
} yield { } yield {
logger.info( logger.info(
s"Done start BitcoinSServerMain, it took=${TimeUtil.currentEpochMs - startTime}ms") s"Done start BitcoinSServerMain, it took=${TimeUtil.currentEpochMs - startTime}ms"
)
start start
} }
} }
private def initializeChainState( private def initializeChainState(
chainHandler: ChainHandler, chainHandler: ChainHandler,
nodeType: NodeType): Future[Unit] = { nodeType: NodeType
): Future[Unit] = {
val syncF = chainHandler.setSyncing(true) val syncF = chainHandler.setSyncing(true)
val blockCountF = chainHandler.getBlockCount() val blockCountF = chainHandler.getBlockCount()
nodeType match { nodeType match {
case NodeType.NeutrinoNode => case NodeType.NeutrinoNode =>
blockCountF.flatMap { blockCount => blockCountF.flatMap { blockCount =>
if (blockCount == 0) { if (blockCount == 0) {
//means we are starting a fresh node, set IBD to true // means we are starting a fresh node, set IBD to true
chainHandler chainHandler
.setIBD(true) .setIBD(true)
.map(_ => ()) .map(_ => ())
@ -113,11 +117,12 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
} }
} }
case NodeType.BitcoindBackend => case NodeType.BitcoindBackend =>
//don't need to do anything as we outsource chain management to bitcoind // don't need to do anything as we outsource chain management to bitcoind
syncF.map(_ => ()) syncF.map(_ => ())
case NodeType.FullNode => case NodeType.FullNode =>
sys.error( sys.error(
s"Full not is not implemented, not sure what to do with chainstate") s"Full not is not implemented, not sure what to do with chainstate"
)
} }
} }
@ -147,7 +152,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
_ = logger.info(s"Stopped ${nodeConf.nodeType.shortName} node") _ = logger.info(s"Stopped ${nodeConf.nodeType.shortName} node")
} yield { } yield {
resetState() resetState()
//return empty wallet holder // return empty wallet holder
WalletHolder.empty WalletHolder.empty
} }
} }
@ -160,21 +165,24 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
} }
/** Start the bitcoin-s wallet server with a neutrino backend /** Start the bitcoin-s wallet server with a neutrino backend
* @param startedTorConfigF a future that is completed when tor is fully started * @param startedTorConfigF
* a future that is completed when tor is fully started
* @return * @return
*/ */
def startBitcoinSBackend( def startBitcoinSBackend(
startedTorConfigF: Future[Unit]): Future[WalletHolder] = { startedTorConfigF: Future[Unit]
): Future[WalletHolder] = {
logger.info(s"startBitcoinSBackend()") logger.info(s"startBitcoinSBackend()")
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val chainApi = ChainHandler.fromDatabase() val chainApi = ChainHandler.fromDatabase()
val creationTime: Instant = conf.walletConf.creationTime val creationTime: Instant = conf.walletConf.creationTime
//get a node that isn't started // get a node that isn't started
val nodeF = nodeConf.createNode( val nodeF = nodeConf.createNode(
peers = nodeConf.peers, peers = nodeConf.peers,
walletCreationTimeOpt = Some(creationTime))(chainConf, system) walletCreationTimeOpt = Some(creationTime)
)(chainConf, system)
val defaultApi = val defaultApi =
MempoolSpaceProvider(HourFeeTarget, network, torConf.socks5ProxyParams) MempoolSpaceProvider(HourFeeTarget, network, torConf.socks5ProxyParams)
@ -183,17 +191,20 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
conf.walletConf.feeProviderNameOpt, conf.walletConf.feeProviderNameOpt,
conf.walletConf.feeProviderTargetOpt, conf.walletConf.feeProviderTargetOpt,
torConf.socks5ProxyParams, torConf.socks5ProxyParams,
network) network
//get our wallet )
// get our wallet
val walletHolder = WalletHolder.empty val walletHolder = WalletHolder.empty
val neutrinoWalletLoaderF = { val neutrinoWalletLoaderF = {
for { for {
node <- nodeF node <- nodeF
} yield { } yield {
val l = DLCWalletNeutrinoBackendLoader(walletHolder, val l = DLCWalletNeutrinoBackendLoader(
chainApi, walletHolder,
nodeApi = node, chainApi,
feeRateApi = feeProvider) nodeApi = node,
feeRateApi = feeProvider
)
walletLoaderApiOpt = Some(l) walletLoaderApiOpt = Some(l)
l l
} }
@ -208,13 +219,13 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
torConf.addCallbacks(torCallbacks) torConf.addCallbacks(torCallbacks)
val isTorStartedF = if (torConf.torProvided) { val isTorStartedF = if (torConf.torProvided) {
//if tor is provided we need to execute the tor started callback immediately // if tor is provided we need to execute the tor started callback immediately
torConf.callBacks.executeOnTorStarted() torConf.callBacks.executeOnTorStarted()
} else { } else {
Future.unit Future.unit
} }
val startedNodeF = { val startedNodeF = {
//can't start connecting to peers until tor is done starting // can't start connecting to peers until tor is done starting
for { for {
_ <- startedTorConfigF _ <- startedTorConfigF
_ <- isTorStartedF _ <- isTorStartedF
@ -229,7 +240,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
_ <- startedNodeF _ <- startedNodeF
walletWithConfigs <- neutrinoWalletLoader.load( walletWithConfigs <- neutrinoWalletLoader.load(
walletNameOpt = walletNameOpt, walletNameOpt = walletNameOpt,
aesPasswordOpt = conf.walletConf.aesPasswordOpt) aesPasswordOpt = conf.walletConf.aesPasswordOpt
)
} yield { } yield {
walletWithConfigs walletWithConfigs
} }
@ -250,7 +262,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
} yield dlcNode } yield dlcNode
} }
//start our http server now that we are synced // start our http server now that we are synced
val startedF = for { val startedF = for {
_ <- configuredWalletF _ <- configuredWalletF
_ <- startHttpServer( _ <- startHttpServer(
@ -265,16 +277,18 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
_ = { _ = {
logger.info( logger.info(
s"Starting ${nodeConf.nodeType.shortName} node sync, it took=${System s"Starting ${nodeConf.nodeType.shortName} node sync, it took=${System
.currentTimeMillis() - start}ms") .currentTimeMillis() - start}ms"
)
} }
//make sure callbacks are registered before we start sync // make sure callbacks are registered before we start sync
_ <- callbacksF _ <- callbacksF
node <- startedNodeF node <- startedNodeF
_ <- startedTorConfigF _ <- startedTorConfigF
} yield { } yield {
nodeOpt = Some(node) nodeOpt = Some(node)
logger.info( logger.info(
s"Done starting Main! It took ${System.currentTimeMillis() - start}ms") s"Done starting Main! It took ${System.currentTimeMillis() - start}ms"
)
() ()
} }
@ -288,7 +302,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
wsQueue: SourceQueueWithComplete[WsNotification[_]], wsQueue: SourceQueueWithComplete[WsNotification[_]],
chainApi: ChainApi, chainApi: ChainApi,
walletConf: WalletAppConfig, walletConf: WalletAppConfig,
dlcConf: DLCAppConfig): Unit = { dlcConf: DLCAppConfig
): Unit = {
val chainCallbacks = WebsocketUtil.buildChainCallbacks(wsQueue, chainApi) val chainCallbacks = WebsocketUtil.buildChainCallbacks(wsQueue, chainApi)
chainConf.addCallbacks(chainCallbacks) chainConf.addCallbacks(chainCallbacks)
@ -308,10 +323,11 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
() ()
} }
/** Returns blockchain info, in case of [[InWarmUp]] exception it retries. /** Returns blockchain info, in case of [[InWarmUp]] exception it retries.
*/ */
private def getBlockChainInfo( private def getBlockChainInfo(
client: BitcoindRpcClient): Future[GetBlockChainInfoResult] = { client: BitcoindRpcClient
): Future[GetBlockChainInfoResult] = {
val promise = Promise[GetBlockChainInfoResult]() val promise = Promise[GetBlockChainInfoResult]()
val interval = 1.second val interval = 1.second
val maxTries = 12 val maxTries = 12
@ -346,11 +362,13 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
private[this] var nodeOpt: Option[Node] = None private[this] var nodeOpt: Option[Node] = None
/** Start the bitcoin-s wallet server with a bitcoind backend /** Start the bitcoin-s wallet server with a bitcoind backend
* @param startedTorConfigF a future that is completed when tor is fully started * @param startedTorConfigF
* a future that is completed when tor is fully started
* @return * @return
*/ */
private def startBitcoindBackend( private def startBitcoindBackend(
startedTorConfigF: Future[Unit]): Future[WalletHolder] = { startedTorConfigF: Future[Unit]
): Future[WalletHolder] = {
logger.info(s"startBitcoindBackend()") logger.info(s"startBitcoindBackend()")
val bitcoindF = for { val bitcoindF = for {
client <- bitcoindRpcConf.clientF client <- bitcoindRpcConf.clientF
@ -365,7 +383,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
val torCallbacks = WebsocketUtil.buildTorCallbacks(wsQueue) val torCallbacks = WebsocketUtil.buildTorCallbacks(wsQueue)
val _ = torConf.addCallbacks(torCallbacks) val _ = torConf.addCallbacks(torCallbacks)
val isTorStartedF = if (torConf.torProvided) { val isTorStartedF = if (torConf.torProvided) {
//if tor is provided we need to emit a tor started event immediately // if tor is provided we need to emit a tor started event immediately
torConf.callBacks.executeOnTorStarted() torConf.callBacks.executeOnTorStarted()
} else { } else {
Future.unit Future.unit
@ -373,7 +391,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
val walletNameF = for { val walletNameF = for {
lastLoadedWallet <- getLastLoadedWalletName() lastLoadedWallet <- getLastLoadedWalletName()
walletName = lastLoadedWallet.getOrElse( walletName = lastLoadedWallet.getOrElse(
WalletAppConfig.DEFAULT_WALLET_NAME) WalletAppConfig.DEFAULT_WALLET_NAME
)
} yield walletName } yield walletName
val walletHolder = WalletHolder.empty val walletHolder = WalletHolder.empty
@ -388,7 +407,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
} yield BitcoindRpcBackendUtil.buildBitcoindNodeApi( } yield BitcoindRpcBackendUtil.buildBitcoindNodeApi(
bitcoind, bitcoind,
Future.successful(walletHolder), Future.successful(walletHolder),
Some(chainCallbacks)) Some(chainCallbacks)
)
val feeProviderF = bitcoindF.map { bitcoind => val feeProviderF = bitcoindF.map { bitcoind =>
FeeProviderFactory.getFeeProviderOrElse( FeeProviderFactory.getFeeProviderOrElse(
@ -406,10 +426,12 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
nodeApi <- nodeApiF nodeApi <- nodeApiF
feeProvider <- feeProviderF feeProvider <- feeProviderF
} yield { } yield {
val l = DLCWalletBitcoindBackendLoader(walletHolder = walletHolder, val l = DLCWalletBitcoindBackendLoader(
bitcoind = bitcoind, walletHolder = walletHolder,
nodeApi = nodeApi, bitcoind = bitcoind,
feeProvider = feeProvider) nodeApi = nodeApi,
feeProvider = feeProvider
)
walletLoaderApiOpt = Some(l) walletLoaderApiOpt = Some(l)
l l
@ -421,8 +443,10 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
_ <- isTorStartedF _ <- isTorStartedF
loadWalletApi <- loadWalletApiF loadWalletApi <- loadWalletApiF
walletName <- walletNameF walletName <- walletNameF
result <- loadWalletApi.load(Some(walletName), result <- loadWalletApi.load(
conf.walletConf.aesPasswordOpt) Some(walletName),
conf.walletConf.aesPasswordOpt
)
} yield result } yield result
} }
@ -442,7 +466,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
bitcoindNetwork = blockchainInfo.chain bitcoindNetwork = blockchainInfo.chain
_ = require( _ = require(
bitcoindNetwork == network, bitcoindNetwork == network,
s"bitcoind ($bitcoindNetwork) on different network than wallet ($network)") s"bitcoind ($bitcoindNetwork) on different network than wallet ($network)"
)
_ <- startHttpServer( _ <- startHttpServer(
nodeApiF = Future.successful(bitcoind), nodeApiF = Future.successful(bitcoind),
chainApi = bitcoind, chainApi = bitcoind,
@ -453,8 +478,10 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
wsSource = wsSource wsSource = wsSource
) )
walletName <- walletNameF walletName <- walletNameF
walletCallbacks = WebsocketUtil.buildWalletCallbacks(wsQueue, walletCallbacks = WebsocketUtil.buildWalletCallbacks(
walletName) wsQueue,
walletName
)
chainCallbacks <- chainCallbacksF chainCallbacks <- chainCallbacksF
(wallet, walletConfig, dlcConfig) <- walletF (wallet, walletConfig, dlcConfig) <- walletF
_ = walletConfig.addCallbacks(walletCallbacks) _ = walletConfig.addCallbacks(walletCallbacks)
@ -462,7 +489,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
bitcoindSyncState <- syncWalletWithBitcoindAndStartPolling( bitcoindSyncState <- syncWalletWithBitcoindAndStartPolling(
bitcoind, bitcoind,
wallet, wallet,
Some(chainCallbacks)) Some(chainCallbacks)
)
_ = { _ = {
bitcoindSyncStateOpt = Some(bitcoindSyncState) bitcoindSyncStateOpt = Some(bitcoindSyncState)
} }
@ -477,9 +505,9 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
} }
} }
//don't return the Future that represents the full syncing of the wallet with bitcoind // don't return the Future that represents the full syncing of the wallet with bitcoind
for { for {
_ <- bitcoindSyncStateF //drop nested Future here _ <- bitcoindSyncStateF // drop nested Future here
walletHolder <- walletF.map(_._1) walletHolder <- walletF.map(_._1)
} yield walletHolder } yield walletHolder
} }
@ -493,9 +521,8 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
dlcNodeF: Future[DLCNode], dlcNodeF: Future[DLCNode],
torConfStarted: Future[Unit], torConfStarted: Future[Unit],
serverCmdLineArgs: ServerArgParser, serverCmdLineArgs: ServerArgParser,
wsSource: Source[WsNotification[_], NotUsed])(implicit wsSource: Source[WsNotification[_], NotUsed]
system: ActorSystem, )(implicit system: ActorSystem, conf: BitcoinSAppConfig): Future[Server] = {
conf: BitcoinSAppConfig): Future[Server] = {
implicit val nodeConf: NodeAppConfig = conf.nodeConf implicit val nodeConf: NodeAppConfig = conf.nodeConf
implicit val walletConf: WalletAppConfig = conf.walletConf implicit val walletConf: WalletAppConfig = conf.walletConf
@ -516,7 +543,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
Future.successful(commonRoutes), Future.successful(commonRoutes),
Future.successful(coreRoutes), Future.successful(coreRoutes),
Future.successful(chainRoutes), Future.successful(chainRoutes),
//dependent on tor, slow start up // dependent on tor, slow start up
walletRoutesF, walletRoutesF,
nodeRoutesF, nodeRoutesF,
dlcRoutesF dlcRoutesF
@ -543,13 +570,15 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
val server = { val server = {
serverCmdLineArgs.rpcPortOpt match { serverCmdLineArgs.rpcPortOpt match {
case Some(rpcport) => case Some(rpcport) =>
Server(conf = nodeConf, Server(
handlersF = handlers, conf = nodeConf,
rpcbindOpt = rpcBindConfOpt, handlersF = handlers,
rpcport = rpcport, rpcbindOpt = rpcBindConfOpt,
rpcPassword = conf.rpcPassword, rpcport = rpcport,
wsConfigOpt = Some(wsServerConfig), rpcPassword = conf.rpcPassword,
wsSource) wsConfigOpt = Some(wsServerConfig),
wsSource
)
case None => case None =>
Server( Server(
conf = nodeConf, conf = nodeConf,
@ -570,29 +599,33 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
} }
} }
/** Syncs the bitcoin-s wallet against bitcoind and then /** Syncs the bitcoin-s wallet against bitcoind and then starts rpc polling if
* starts rpc polling if zmq isn't enabled, otherwise it starts zmq polling. * zmq isn't enabled, otherwise it starts zmq polling.
* *
* The key thing this helper method does is it logs errors based on the * The key thing this helper method does is it logs errors based on the
* future returned by this method. This is needed because we don't want * future returned by this method. This is needed because we don't want to
* to block the rest of the application from starting if we have to * block the rest of the application from starting if we have to do a ton of
* do a ton of syncing. However, we don't want to swallow * syncing. However, we don't want to swallow exceptions thrown by this
* exceptions thrown by this method. * method.
* @return the [[Cancellable]] representing the schedule job that polls the mempool. You can call .cancel() to stop this * @return
* the [[Cancellable]] representing the schedule job that polls the
* mempool. You can call .cancel() to stop this
*/ */
private def syncWalletWithBitcoindAndStartPolling( private def syncWalletWithBitcoindAndStartPolling(
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
wallet: NeutrinoHDWalletApi, wallet: NeutrinoHDWalletApi,
chainCallbacksOpt: Option[ChainCallbacks]): Future[BitcoindSyncState] = { chainCallbacksOpt: Option[ChainCallbacks]
): Future[BitcoindSyncState] = {
val f = for { val f = for {
_ <- handlePotentialBitcoindLostBlock(bitcoind, wallet) _ <- handlePotentialBitcoindLostBlock(bitcoind, wallet)
syncF = BitcoindRpcBackendUtil.syncWalletToBitcoind( syncF = BitcoindRpcBackendUtil.syncWalletToBitcoind(
bitcoind, bitcoind,
wallet, wallet,
chainCallbacksOpt)(system) chainCallbacksOpt
)(system)
_ = syncF.map(_ => wallet.updateUtxoPendingStates()) _ = syncF.map(_ => wallet.updateUtxoPendingStates())
//don't start polling until initial sync is done // don't start polling until initial sync is done
pollingCancellable <- syncF.flatMap { _ => pollingCancellable <- syncF.flatMap { _ =>
if (bitcoindRpcConf.zmqConfig == ZmqConfig.empty) { if (bitcoindRpcConf.zmqConfig == ZmqConfig.empty) {
val blockingPollingCancellable = BitcoindRpcBackendUtil val blockingPollingCancellable = BitcoindRpcBackendUtil
@ -603,15 +636,18 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
.executeOnTxReceivedCallbacks(tx) .executeOnTxReceivedCallbacks(tx)
} }
val combinedCancellable = val combinedCancellable =
BitcoindPollingCancellable(blockingPollingCancellable, BitcoindPollingCancellable(
mempoolCancellable) blockingPollingCancellable,
mempoolCancellable
)
Future.successful(combinedCancellable) Future.successful(combinedCancellable)
} else { } else {
Future { Future {
BitcoindRpcBackendUtil.startZMQWalletCallbacks( BitcoindRpcBackendUtil.startZMQWalletCallbacks(
wallet, wallet,
bitcoindRpcConf.zmqConfig) bitcoindRpcConf.zmqConfig
)
BitcoindPollingCancellabe.none BitcoindPollingCancellabe.none
} }
} }
@ -624,13 +660,15 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
f f
} }
/** Surprisingly on some OSes like umbrel bitcoind can lose blocks during the shutdown process /** Surprisingly on some OSes like umbrel bitcoind can lose blocks during the
* This means next time we boot up, our wallet will have more blocks than bitcoind! * shutdown process This means next time we boot up, our wallet will have
* Eventually bitcoind will synchrnoize with the network. This waits until bitcoind is synced * more blocks than bitcoind! Eventually bitcoind will synchrnoize with the
* network. This waits until bitcoind is synced
*/ */
private def handlePotentialBitcoindLostBlock( private def handlePotentialBitcoindLostBlock(
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
wallet: WalletApi): Future[Unit] = { wallet: WalletApi
): Future[Unit] = {
AsyncUtil.retryUntilSatisfiedF( AsyncUtil.retryUntilSatisfiedF(
conditionF = { () => conditionF = { () =>
for { for {
@ -645,21 +683,22 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
) )
} }
/** Builds a websocket queue that you can feed elements to. /** Builds a websocket queue that you can feed elements to. The Source can be
* The Source can be wired up with Directives.handleWebSocketMessages * wired up with Directives.handleWebSocketMessages to create a flow that
* to create a flow that emits websocket messages * emits websocket messages
*/ */
private def buildWsSource: ( private def buildWsSource: (
SourceQueueWithComplete[WsNotification[_]], SourceQueueWithComplete[WsNotification[_]],
Source[WsNotification[_], NotUsed]) = { Source[WsNotification[_], NotUsed]
) = {
val maxBufferSize: Int = 25 val maxBufferSize: Int = 25
/** This will queue [[maxBufferSize]] elements in the queue. Once the buffer size is reached, /** This will queue [[maxBufferSize]] elements in the queue. Once the buffer
* we will drop the first element in the buffer * size is reached, we will drop the first element in the buffer
*/ */
val tuple = { val tuple = {
//from: https://github.com/akka/akka-http/issues/3039#issuecomment-610263181 // from: https://github.com/akka/akka-http/issues/3039#issuecomment-610263181
//the BroadcastHub.sink is needed to avoid these errors // the BroadcastHub.sink is needed to avoid these errors
// 'Websocket handler failed with Processor actor' // 'Websocket handler failed with Processor actor'
Source Source
.queue[WsNotification[_]](maxBufferSize, OverflowStrategy.dropHead) .queue[WsNotification[_]](maxBufferSize, OverflowStrategy.dropHead)
@ -667,7 +706,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
.run() .run()
} }
//need to drain the websocket queue if no one is connected // need to drain the websocket queue if no one is connected
val _: Future[Done] = tuple._2.runWith(Sink.ignore) val _: Future[Done] = tuple._2.runWith(Sink.ignore)
tuple tuple
@ -703,7 +742,8 @@ object BitcoinSServerMain extends BitcoinSAppScalaDaemon {
implicit lazy val conf: BitcoinSAppConfig = implicit lazy val conf: BitcoinSAppConfig =
BitcoinSAppConfig( BitcoinSAppConfig(
datadirParser.datadir, datadirParser.datadir,
Vector(datadirParser.baseConfig, serverCmdLineArgs.toConfig))(system) Vector(datadirParser.baseConfig, serverCmdLineArgs.toConfig)
)(system)
val m = new BitcoinSServerMain(serverCmdLineArgs) val m = new BitcoinSServerMain(serverCmdLineArgs)
@ -711,7 +751,8 @@ object BitcoinSServerMain extends BitcoinSAppScalaDaemon {
sys.addShutdownHook { sys.addShutdownHook {
logger.info( logger.info(
s"@@@@@@@@@@@@@@@@@@@@@ Shutting down ${getClass.getSimpleName} @@@@@@@@@@@@@@@@@@@@@") s"@@@@@@@@@@@@@@@@@@@@@ Shutting down ${getClass.getSimpleName} @@@@@@@@@@@@@@@@@@@@@"
)
Await.result(m.stop(), 10.seconds) Await.result(m.stop(), 10.seconds)
() ()
} }

View File

@ -32,15 +32,19 @@ import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}
import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.concurrent.{ExecutionContext, Future, Promise} import scala.concurrent.{ExecutionContext, Future, Promise}
/** Useful utilities to use in the wallet project for syncing things against bitcoind */ /** Useful utilities to use in the wallet project for syncing things against
* bitcoind
*/
object BitcoindRpcBackendUtil extends BitcoinSLogger { object BitcoindRpcBackendUtil extends BitcoinSLogger {
/** Has the wallet process all the blocks it has not seen up until bitcoind's chain tip */ /** Has the wallet process all the blocks it has not seen up until bitcoind's
* chain tip
*/
def syncWalletToBitcoind( def syncWalletToBitcoind(
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
wallet: NeutrinoHDWalletApi, wallet: NeutrinoHDWalletApi,
chainCallbacksOpt: Option[ChainCallbacks])(implicit chainCallbacksOpt: Option[ChainCallbacks]
system: ActorSystem): Future[Unit] = { )(implicit system: ActorSystem): Future[Unit] = {
logger.info("Syncing wallet to bitcoind") logger.info("Syncing wallet to bitcoind")
import system.dispatcher import system.dispatcher
@ -59,24 +63,26 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
} }
_ = logger.info( _ = logger.info(
s"Syncing from bitcoind with bitcoindHeight=$bitcoindHeight walletHeight=${heightRange.start}") s"Syncing from bitcoind with bitcoindHeight=$bitcoindHeight walletHeight=${heightRange.start}"
)
syncFlow <- buildBitcoindSyncSink(bitcoind, wallet) syncFlow <- buildBitcoindSyncSink(bitcoind, wallet)
stream = Source(heightRange).toMat(syncFlow)(Keep.right) stream = Source(heightRange).toMat(syncFlow)(Keep.right)
} yield stream } yield stream
//run the stream // run the stream
val res = streamF.flatMap(_.run()) val res = streamF.flatMap(_.run())
res.onComplete { case _ => res.onComplete { case _ =>
val isBitcoindInSyncF = BitcoindRpcBackendUtil.isBitcoindInSync(bitcoind) val isBitcoindInSyncF = BitcoindRpcBackendUtil.isBitcoindInSync(bitcoind)
isBitcoindInSyncF.flatMap { isBitcoindInSync => isBitcoindInSyncF.flatMap { isBitcoindInSync =>
if (isBitcoindInSync) { if (isBitcoindInSync) {
//if bitcoind is in sync, and we are in sync with bitcoind, set the syncing flag to false // if bitcoind is in sync, and we are in sync with bitcoind, set the syncing flag to false
setSyncingFlag(false, bitcoind, chainCallbacksOpt) setSyncingFlag(false, bitcoind, chainCallbacksOpt)
} else { } else {
//if bitcoind is not in sync, we cannot be done syncing. Keep the syncing flag to true // if bitcoind is not in sync, we cannot be done syncing. Keep the syncing flag to true
//so do nothing in this case // so do nothing in this case
logger.warn( logger.warn(
s"We synced against bitcoind, but bitcoind is not in sync with the network.") s"We synced against bitcoind, but bitcoind is not in sync with the network."
)
Future.unit Future.unit
} }
} }
@ -85,14 +91,15 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
res.map(_ => ()) res.map(_ => ())
} }
/** Gets the height range for syncing against bitcoind when we don't have a [[org.bitcoins.core.api.wallet.WalletStateDescriptor]] /** Gets the height range for syncing against bitcoind when we don't have a
* to read the sync height from. * [[org.bitcoins.core.api.wallet.WalletStateDescriptor]] to read the sync
* height from.
*/ */
private def getHeightRangeNoWalletState( private def getHeightRangeNoWalletState(
wallet: NeutrinoHDWalletApi, wallet: NeutrinoHDWalletApi,
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
bitcoindHeight: Int)(implicit bitcoindHeight: Int
ex: ExecutionContext): Future[Range.Inclusive] = { )(implicit ex: ExecutionContext): Future[Range.Inclusive] = {
for { for {
txDbs <- wallet.listTransactions() txDbs <- wallet.listTransactions()
lastConfirmedOpt = txDbs lastConfirmedOpt = txDbs
@ -108,7 +115,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
range <- heightOpt match { range <- heightOpt match {
case Some(height) => case Some(height) =>
logger.info( logger.info(
s"Last tx occurred at block $height, syncing from there") s"Last tx occurred at block $height, syncing from there"
)
val range = height.to(bitcoindHeight) val range = height.to(bitcoindHeight)
Future.successful(range) Future.successful(range)
case None => case None =>
@ -123,8 +131,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
private def setSyncingFlag( private def setSyncingFlag(
syncing: Boolean, syncing: Boolean,
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
chainCallbacksOpt: Option[ChainCallbacks])(implicit chainCallbacksOpt: Option[ChainCallbacks]
ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
val oldSyncingFlagF = bitcoind.isSyncing() val oldSyncingFlagF = bitcoind.isSyncing()
for { for {
oldFlag <- oldSyncingFlagF oldFlag <- oldSyncingFlagF
@ -146,14 +154,16 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
} }
/** Helper method to sync the wallet until the bitcoind height. /** Helper method to sync the wallet until the bitcoind height. This method
* This method returns a Sink that you can give block heights too and * returns a Sink that you can give block heights too and the sink will
* the sink will synchronize our bitcoin-s wallet against bitcoind * synchronize our bitcoin-s wallet against bitcoind
*/ */
private def buildBitcoindSyncSink( private def buildBitcoindSyncSink(
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
wallet: NeutrinoHDWalletApi)(implicit wallet: NeutrinoHDWalletApi
system: ActorSystem): Future[Sink[Int, Future[NeutrinoHDWalletApi]]] = { )(implicit
system: ActorSystem
): Future[Sink[Int, Future[NeutrinoHDWalletApi]]] = {
import system.dispatcher import system.dispatcher
val hasFiltersF = bitcoind val hasFiltersF = bitcoind
@ -163,10 +173,10 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
.recover { case _: Throwable => false } .recover { case _: Throwable => false }
val numParallelism = FutureUtil.getParallelism val numParallelism = FutureUtil.getParallelism
//feeding blockchain hashes into this sync // feeding blockchain hashes into this sync
//will sync our wallet with those blockchain hashes // will sync our wallet with those blockchain hashes
val syncWalletSinkF: Future[ val syncWalletSinkF
Sink[DoubleSha256DigestBE, Future[NeutrinoHDWalletApi]]] = { : Future[Sink[DoubleSha256DigestBE, Future[NeutrinoHDWalletApi]]] = {
for { for {
hasFilters <- hasFiltersF hasFilters <- hasFiltersF
@ -197,8 +207,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
def createWalletWithBitcoindCallbacks( def createWalletWithBitcoindCallbacks(
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
wallet: Wallet, wallet: Wallet,
chainCallbacksOpt: Option[ChainCallbacks])(implicit chainCallbacksOpt: Option[ChainCallbacks]
system: ActorSystem): Wallet = { )(implicit system: ActorSystem): Wallet = {
// We need to create a promise so we can inject the wallet with the callback // We need to create a promise so we can inject the wallet with the callback
// after we have created it into SyncUtil.getNodeApiWalletCallback // after we have created it into SyncUtil.getNodeApiWalletCallback
// so we don't lose the internal state of the wallet // so we don't lose the internal state of the wallet
@ -207,7 +217,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
val nodeApi = BitcoindRpcBackendUtil.buildBitcoindNodeApi( val nodeApi = BitcoindRpcBackendUtil.buildBitcoindNodeApi(
bitcoind, bitcoind,
walletCallbackP.future, walletCallbackP.future,
chainCallbacksOpt) chainCallbacksOpt
)
val pairedWallet = Wallet( val pairedWallet = Wallet(
nodeApi = nodeApi, nodeApi = nodeApi,
chainQueryApi = bitcoind, chainQueryApi = bitcoind,
@ -221,10 +232,12 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
def startZMQWalletCallbacks( def startZMQWalletCallbacks(
wallet: NeutrinoHDWalletApi, wallet: NeutrinoHDWalletApi,
zmqConfig: ZmqConfig)(implicit zmqConfig: ZmqConfig
ec: ExecutionContext): WalletZmqSubscribers = { )(implicit ec: ExecutionContext): WalletZmqSubscribers = {
require(zmqConfig != ZmqConfig.empty, require(
"Must have the zmq raw configs defined to setup ZMQ callbacks") zmqConfig != ZmqConfig.empty,
"Must have the zmq raw configs defined to setup ZMQ callbacks"
)
val rawTxSub = zmqConfig.rawTx.map { zmq => val rawTxSub = zmqConfig.rawTx.map { zmq =>
val rawTxListener: Option[Transaction => Unit] = Some { val rawTxListener: Option[Transaction => Unit] = Some {
@ -238,18 +251,21 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
} }
new ZMQSubscriber(socket = zmq, new ZMQSubscriber(
hashTxListener = None, socket = zmq,
hashBlockListener = None, hashTxListener = None,
rawTxListener = rawTxListener, hashBlockListener = None,
rawBlockListener = None) rawTxListener = rawTxListener,
rawBlockListener = None
)
} }
val rawBlockSub = zmqConfig.rawBlock.map { zmq => val rawBlockSub = zmqConfig.rawBlock.map { zmq =>
val rawBlockListener: Option[Block => Unit] = Some { val rawBlockListener: Option[Block => Unit] = Some {
{ block: Block => { block: Block =>
logger.info( logger.info(
s"Received block ${block.blockHeader.hashBE.hex}, processing") s"Received block ${block.blockHeader.hashBE.hex}, processing"
)
val f = wallet.processBlock(block) val f = wallet.processBlock(block)
f.failed.foreach { err => f.failed.foreach { err =>
logger.error("failed to process raw block zmq message", err) logger.error("failed to process raw block zmq message", err)
@ -258,11 +274,13 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
} }
new ZMQSubscriber(socket = zmq, new ZMQSubscriber(
hashTxListener = None, socket = zmq,
hashBlockListener = None, hashTxListener = None,
rawTxListener = None, hashBlockListener = None,
rawBlockListener = rawBlockListener) rawTxListener = None,
rawBlockListener = rawBlockListener
)
} }
val subs = WalletZmqSubscribers(rawTxSub, rawBlockSub) val subs = WalletZmqSubscribers(rawTxSub, rawBlockSub)
@ -273,18 +291,19 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
def createDLCWalletWithBitcoindCallbacks( def createDLCWalletWithBitcoindCallbacks(
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
wallet: DLCWallet, wallet: DLCWallet,
chainCallbacksOpt: Option[ChainCallbacks])(implicit chainCallbacksOpt: Option[ChainCallbacks]
system: ActorSystem): DLCWallet = { )(implicit system: ActorSystem): DLCWallet = {
// We need to create a promise so we can inject the wallet with the callback // We need to create a promise so we can inject the wallet with the callback
// after we have created it into SyncUtil.getNodeApiWalletCallback // after we have created it into SyncUtil.getNodeApiWalletCallback
// so we don't lose the internal state of the wallet // so we don't lose the internal state of the wallet
val walletCallbackP = Promise[DLCWallet]() val walletCallbackP = Promise[DLCWallet]()
val pairedWallet = DLCWallet( val pairedWallet = DLCWallet(
nodeApi = nodeApi = BitcoindRpcBackendUtil.buildBitcoindNodeApi(
BitcoindRpcBackendUtil.buildBitcoindNodeApi(bitcoind, bitcoind,
walletCallbackP.future, walletCallbackP.future,
chainCallbacksOpt), chainCallbacksOpt
),
chainQueryApi = bitcoind, chainQueryApi = bitcoind,
feeRateApi = wallet.feeRateApi feeRateApi = wallet.feeRateApi
)(wallet.walletConfig, wallet.dlcConfig) )(wallet.walletConfig, wallet.dlcConfig)
@ -296,9 +315,10 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
private def filterSyncSink( private def filterSyncSink(
bitcoindRpcClient: BlockchainRpc, bitcoindRpcClient: BlockchainRpc,
wallet: NeutrinoHDWalletApi)(implicit ec: ExecutionContext): Sink[ wallet: NeutrinoHDWalletApi
DoubleSha256DigestBE, )(implicit
Future[NeutrinoHDWalletApi]] = { ec: ExecutionContext
): Sink[DoubleSha256DigestBE, Future[NeutrinoHDWalletApi]] = {
val numParallelism = FutureUtil.getParallelism val numParallelism = FutureUtil.getParallelism
val sink: Sink[DoubleSha256DigestBE, Future[NeutrinoHDWalletApi]] = val sink: Sink[DoubleSha256DigestBE, Future[NeutrinoHDWalletApi]] =
@ -317,39 +337,46 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
sink sink
} }
/** Creates an anonymous [[NodeApi]] that downloads blocks using /** Creates an anonymous [[NodeApi]] that downloads blocks using akka streams
* akka streams from bitcoind, and then calls [[NeutrinoWalletApi.processBlock]] * from bitcoind, and then calls [[NeutrinoWalletApi.processBlock]]
*/ */
def buildBitcoindNodeApi( def buildBitcoindNodeApi(
bitcoindRpcClient: BitcoindRpcClient, bitcoindRpcClient: BitcoindRpcClient,
walletF: Future[WalletApi], walletF: Future[WalletApi],
chainCallbacksOpt: Option[ChainCallbacks])(implicit chainCallbacksOpt: Option[ChainCallbacks]
system: ActorSystem): NodeApi = { )(implicit system: ActorSystem): NodeApi = {
import system.dispatcher import system.dispatcher
new NodeApi { new NodeApi {
override def downloadBlocks( override def downloadBlocks(
blockHashes: Vector[DoubleSha256DigestBE]): Future[Unit] = { blockHashes: Vector[DoubleSha256DigestBE]
): Future[Unit] = {
logger.info(s"Fetching ${blockHashes.length} blocks from bitcoind") logger.info(s"Fetching ${blockHashes.length} blocks from bitcoind")
val numParallelism = FutureUtil.getParallelism val numParallelism = FutureUtil.getParallelism
val source = Source(blockHashes) val source = Source(blockHashes)
val fetchBlocksFlow = BitcoindStreamUtil.fetchBlocksBitcoind( val fetchBlocksFlow = BitcoindStreamUtil.fetchBlocksBitcoind(
bitcoindRpcClient = bitcoindRpcClient, bitcoindRpcClient = bitcoindRpcClient,
parallelism = numParallelism) parallelism = numParallelism
)
val sinkF: Future[ val sinkF
Sink[(Block, GetBlockHeaderResult), Future[WalletApi]]] = { : Future[Sink[(Block, GetBlockHeaderResult), Future[WalletApi]]] = {
walletF.map { initWallet => walletF.map { initWallet =>
Sink.foldAsync[WalletApi, (Block, GetBlockHeaderResult)]( Sink.foldAsync[WalletApi, (Block, GetBlockHeaderResult)](
initWallet) { initWallet
case (wallet: WalletApi, ) {
(block: Block, blockHeaderResult: GetBlockHeaderResult)) => case (
wallet: WalletApi,
(block: Block, blockHeaderResult: GetBlockHeaderResult)
) =>
val blockProcessedF = wallet.processBlock(block) val blockProcessedF = wallet.processBlock(block)
val executeCallbackF: Future[WalletApi] = { val executeCallbackF: Future[WalletApi] = {
for { for {
wallet <- blockProcessedF wallet <- blockProcessedF
_ <- handleChainCallbacks(chainCallbacksOpt, _ <- handleChainCallbacks(
blockHeaderResult) chainCallbacksOpt,
blockHeaderResult
)
} yield wallet } yield wallet
} }
@ -374,7 +401,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
/** Broadcasts the given transaction over the P2P network /** Broadcasts the given transaction over the P2P network
*/ */
override def broadcastTransactions( override def broadcastTransactions(
transactions: Vector[Transaction]): Future[Unit] = { transactions: Vector[Transaction]
): Future[Unit] = {
bitcoindRpcClient.broadcastTransactions(transactions) bitcoindRpcClient.broadcastTransactions(transactions)
} }
@ -386,12 +414,12 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
private def handleChainCallbacks( private def handleChainCallbacks(
chainCallbacksOpt: Option[ChainCallbacks], chainCallbacksOpt: Option[ChainCallbacks],
blockHeaderResult: GetBlockHeaderResult)(implicit blockHeaderResult: GetBlockHeaderResult
ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
chainCallbacksOpt match { chainCallbacksOpt match {
case None => Future.unit case None => Future.unit
case Some(callback) => case Some(callback) =>
//this can be slow as we aren't batching headers at all // this can be slow as we aren't batching headers at all
val headerWithHeights = val headerWithHeights =
Vector((blockHeaderResult.height, blockHeaderResult.blockHeader)) Vector((blockHeaderResult.height, blockHeaderResult.blockHeader))
val f = callback val f = callback
@ -400,19 +428,22 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
} }
/** Starts the [[ActorSystem]] to poll the [[BitcoindRpcClient]] for its block count, /** Starts the [[ActorSystem]] to poll the [[BitcoindRpcClient]] for its block
* if it has changed, it will then request those blocks to process them * count, if it has changed, it will then request those blocks to process
* them
* *
* @param startCount The starting block height of the wallet * @param startCount
* @param interval The amount of time between polls, this should not be too aggressive * The starting block height of the wallet
* as the wallet will need to process the new blocks * @param interval
* The amount of time between polls, this should not be too aggressive as
* the wallet will need to process the new blocks
*/ */
def startBitcoindBlockPolling( def startBitcoindBlockPolling(
wallet: WalletApi, wallet: WalletApi,
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
chainCallbacksOpt: Option[ChainCallbacks], chainCallbacksOpt: Option[ChainCallbacks],
interval: FiniteDuration = 10.seconds)(implicit interval: FiniteDuration = 10.seconds
system: ActorSystem): Cancellable = { )(implicit system: ActorSystem): Cancellable = {
import system.dispatcher import system.dispatcher
val processingBitcoindBlocks = new AtomicBoolean(false) val processingBitcoindBlocks = new AtomicBoolean(false)
@ -431,10 +462,12 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
res <- res <-
if (!rescanning) { if (!rescanning) {
val pollFOptF = val pollFOptF =
pollBitcoind(wallet = wallet, pollBitcoind(
bitcoind = bitcoind, wallet = wallet,
chainCallbacksOpt = chainCallbacksOpt, bitcoind = bitcoind,
prevCount = walletSyncState.height) chainCallbacksOpt = chainCallbacksOpt,
prevCount = walletSyncState.height
)
pollFOptF.flatMap { pollFOptF.flatMap {
case Some(pollF) => pollF case Some(pollF) => pollF
@ -442,17 +475,20 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
} else { } else {
logger.info( logger.info(
s"Skipping scanning the blockchain during wallet rescan") s"Skipping scanning the blockchain during wallet rescan"
)
Future.unit Future.unit
} }
} yield res } yield res
f.onComplete { _ => f.onComplete { _ =>
processingBitcoindBlocks.set(false) processingBitcoindBlocks.set(false)
BitcoindRpcBackendUtil.setSyncingFlag(false, BitcoindRpcBackendUtil.setSyncingFlag(
bitcoind, false,
chainCallbacksOpt) bitcoind,
} //reset polling variable chainCallbacksOpt
)
} // reset polling variable
f.failed.foreach(err => f.failed.foreach(err =>
logger.error(s"Failed to poll bitcoind", err)) logger.error(s"Failed to poll bitcoind", err))
} else { } else {
@ -465,14 +501,16 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
/** Polls bitcoind for syncing the blockchain /** Polls bitcoind for syncing the blockchain
* @return None if there was nothing to sync, else the Future[Done] that is completed when the sync is finished. * @return
* None if there was nothing to sync, else the Future[Done] that is
* completed when the sync is finished.
*/ */
private def pollBitcoind( private def pollBitcoind(
wallet: WalletApi, wallet: WalletApi,
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
chainCallbacksOpt: Option[ChainCallbacks], chainCallbacksOpt: Option[ChainCallbacks],
prevCount: Int)(implicit prevCount: Int
system: ActorSystem): Future[Option[Future[Done]]] = { )(implicit system: ActorSystem): Future[Option[Future[Done]]] = {
import system.dispatcher import system.dispatcher
val atomicPrevCount = new AtomicInteger(prevCount) val atomicPrevCount = new AtomicInteger(prevCount)
val queueSource: Source[Int, SourceQueueWithComplete[Int]] = val queueSource: Source[Int, SourceQueueWithComplete[Int]] =
@ -492,7 +530,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
atomicPrevCount.set(prevCount) atomicPrevCount.set(prevCount)
logger.error( logger.error(
s"Processing blocks from bitcoind polling failed, range=[$prevCount, $failedCount]", s"Processing blocks from bitcoind polling failed, range=[$prevCount, $failedCount]",
err) err
)
} }
for { for {
@ -523,7 +562,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
retval <- { retval <- {
if (prevCount < count) { if (prevCount < count) {
logger.info( logger.info(
s"Bitcoind has new block(s), requesting... ${count - prevCount} blocks") s"Bitcoind has new block(s), requesting... ${count - prevCount} blocks"
)
val setSyncFlagF = setSyncingFlag(true, bitcoind, chainCallbacksOpt) val setSyncFlagF = setSyncingFlag(true, bitcoind, chainCallbacksOpt)
setSyncFlagF.map { _ => setSyncFlagF.map { _ =>
// use .tail so we don't process the previous block that we already did // use .tail so we don't process the previous block that we already did
@ -531,15 +571,18 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
range.foreach(r => queue.offer(r)) range.foreach(r => queue.offer(r))
} }
} else if (prevCount > count) { } else if (prevCount > count) {
Future.failed(new RuntimeException( Future.failed(
s"Bitcoind is at a block height ($count) before the wallet's ($prevCount)")) new RuntimeException(
s"Bitcoind is at a block height ($count) before the wallet's ($prevCount)"
)
)
} else { } else {
logger.debug(s"In sync $prevCount count=$count") logger.debug(s"In sync $prevCount count=$count")
Future.unit Future.unit
} }
} }
} yield { } yield {
queue.complete() //complete the stream after offering all heights we need to sync queue.complete() // complete the stream after offering all heights we need to sync
retval retval
} }
resF.map(_ => Some(doneF)) resF.map(_ => Some(doneF))
@ -548,15 +591,16 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
def startBitcoindMempoolPolling( def startBitcoindMempoolPolling(
wallet: WalletApi, wallet: WalletApi,
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
interval: FiniteDuration = 10.seconds)( interval: FiniteDuration = 10.seconds
processTx: Transaction => Future[Unit])(implicit )(
system: ActorSystem, processTx: Transaction => Future[Unit]
ec: ExecutionContext): Cancellable = { )(implicit system: ActorSystem, ec: ExecutionContext): Cancellable = {
@volatile var prevMempool: Set[DoubleSha256DigestBE] = @volatile var prevMempool: Set[DoubleSha256DigestBE] =
Set.empty[DoubleSha256DigestBE] Set.empty[DoubleSha256DigestBE]
def getDiffAndReplace( def getDiffAndReplace(
newMempool: Set[DoubleSha256DigestBE]): Set[DoubleSha256DigestBE] = newMempool: Set[DoubleSha256DigestBE]
): Set[DoubleSha256DigestBE] =
synchronized { synchronized {
val txids = newMempool.diff(prevMempool) val txids = newMempool.diff(prevMempool)
prevMempool = newMempool prevMempool = newMempool
@ -570,7 +614,7 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
logger.debug("Polling bitcoind for mempool") logger.debug("Polling bitcoind for mempool")
val numParallelism = FutureUtil.getParallelism val numParallelism = FutureUtil.getParallelism
//don't want to execute these in parallel // don't want to execute these in parallel
val processTxFlow = Sink.foreachAsync[Option[Transaction]](1) { val processTxFlow = Sink.foreachAsync[Option[Transaction]](1) {
case Some(tx) => processTx(tx) case Some(tx) => processTx(tx)
case None => Future.unit case None => Future.unit
@ -594,14 +638,16 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
.run() .run()
} yield { } yield {
logger.debug( logger.debug(
s"Done processing ${newTxIds.size} new mempool transactions") s"Done processing ${newTxIds.size} new mempool transactions"
)
() ()
} }
res.onComplete(_ => processingMempool.set(false)) res.onComplete(_ => processingMempool.set(false))
res res
} else { } else {
logger.info( logger.info(
s"Skipping scanning the mempool since a previously scheduled task is still running") s"Skipping scanning the mempool since a previously scheduled task is still running"
)
Future.unit Future.unit
} }
} }
@ -625,9 +671,12 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
} }
} }
/** Checks if bitcoind has all blocks for the headers it has seen on the network */ /** Checks if bitcoind has all blocks for the headers it has seen on the
private def isBitcoindInSync(bitcoind: BitcoindRpcClient)(implicit * network
ec: ExecutionContext): Future[Boolean] = { */
private def isBitcoindInSync(
bitcoind: BitcoindRpcClient
)(implicit ec: ExecutionContext): Future[Boolean] = {
for { for {
blockchainInfo <- bitcoind.getBlockChainInfo blockchainInfo <- bitcoind.getBlockChainInfo
} yield { } yield {

View File

@ -17,7 +17,8 @@ import scala.concurrent.Future
case class ChainRoutes( case class ChainRoutes(
chain: ChainApi, chain: ChainApi,
network: BitcoinNetwork, network: BitcoinNetwork,
startedTorConfigF: Future[Unit])(implicit system: ActorSystem) startedTorConfigF: Future[Unit]
)(implicit system: ActorSystem)
extends ServerRoute { extends ServerRoute {
import system.dispatcher import system.dispatcher
@ -59,8 +60,8 @@ case class ChainRoutes(
for { for {
results <- resultsF results <- resultsF
} yield { } yield {
val json = upickle.default.writeJs(results.head)( val json = upickle.default
Picklers.getBlockHeaderResultPickler) .writeJs(results.head)(Picklers.getBlockHeaderResultPickler)
Server.httpSuccess(json) Server.httpSuccess(json)
} }
} }

View File

@ -172,7 +172,8 @@ case class CoreRoutes()(implicit system: ActorSystem, config: BitcoinSAppConfig)
case DecodeContractInfo(contractInfo) => case DecodeContractInfo(contractInfo) =>
complete { complete {
Server.httpSuccess( Server.httpSuccess(
writeJs(contractInfo)(contractInfoV0TLVJsonWriter)) writeJs(contractInfo)(contractInfoV0TLVJsonWriter)
)
} }
} }
@ -181,7 +182,8 @@ case class CoreRoutes()(implicit system: ActorSystem, config: BitcoinSAppConfig)
case DecodeAnnouncement(announcement) => case DecodeAnnouncement(announcement) =>
complete { complete {
Server.httpSuccess( Server.httpSuccess(
writeJs(announcement)(oracleAnnouncementTLVJsonWriter)) writeJs(announcement)(oracleAnnouncementTLVJsonWriter)
)
} }
} }

View File

@ -42,10 +42,12 @@ case class DLCRoutes(dlcNode: DLCNodeApi)(implicit system: ActorSystem)
case AcceptDLC(offer, address, payoutAddressOpt, changeAddressOpt) => case AcceptDLC(offer, address, payoutAddressOpt, changeAddressOpt) =>
complete { complete {
dlcNode dlcNode
.acceptDLCOffer(address, .acceptDLCOffer(
offer, address,
payoutAddressOpt, offer,
changeAddressOpt) payoutAddressOpt,
changeAddressOpt
)
.map { accept => .map { accept =>
Server.httpSuccess(accept.toMessage.hex) Server.httpSuccess(accept.toMessage.hex)
} }
@ -63,9 +65,11 @@ case class DLCRoutes(dlcNode: DLCNodeApi)(implicit system: ActorSystem)
case _: EnumEventDescriptorV0TLV => case _: EnumEventDescriptorV0TLV =>
EnumSingleOracleInfo(create.announcementTLV) EnumSingleOracleInfo(create.announcementTLV)
} }
val contractInfo = SingleContractInfo(create.totalCollateral, val contractInfo = SingleContractInfo(
create.ContractDescriptorTLV, create.totalCollateral,
oracleInfo) create.ContractDescriptorTLV,
oracleInfo
)
Server.httpSuccess(contractInfo.hex) Server.httpSuccess(contractInfo.hex)
} }
} }
@ -91,9 +95,11 @@ case class DLCRoutes(dlcNode: DLCNodeApi)(implicit system: ActorSystem)
withValidServerCommand(OfferAdd.fromJsArr(arr)) { register => withValidServerCommand(OfferAdd.fromJsArr(arr)) { register =>
complete { complete {
dlcNode.wallet dlcNode.wallet
.registerIncomingDLCOffer(register.offerTLV, .registerIncomingDLCOffer(
register.peer, register.offerTLV,
register.message) register.peer,
register.message
)
.map { hash => .map { hash =>
Server.httpSuccess(hash.hex) Server.httpSuccess(hash.hex)
} }
@ -133,8 +139,8 @@ case class DLCRoutes(dlcNode: DLCNodeApi)(implicit system: ActorSystem)
case ServerCommand("contacts-list", _) => case ServerCommand("contacts-list", _) =>
complete { complete {
dlcNode.wallet.listDLCContacts().map { contacts => dlcNode.wallet.listDLCContacts().map { contacts =>
val json = contacts.map(c => val json = contacts
upickle.default.writeJs(c)(Picklers.contactDbPickler)) .map(c => upickle.default.writeJs(c)(Picklers.contactDbPickler))
Server.httpSuccess(json) Server.httpSuccess(json)
} }
} }
@ -171,7 +177,8 @@ case class DLCRoutes(dlcNode: DLCNodeApi)(implicit system: ActorSystem)
val contactId = val contactId =
dlcContactAdd.address.getHostName + ":" + dlcContactAdd.address.getPort dlcContactAdd.address.getHostName + ":" + dlcContactAdd.address.getPort
Server.httpSuccess( Server.httpSuccess(
ujson.Obj("dlcId" -> dlcId, "contactId" -> contactId)) ujson.Obj("dlcId" -> dlcId, "contactId" -> contactId)
)
} }
} }
} }

View File

@ -22,8 +22,9 @@ import org.bitcoins.wallet.config.WalletAppConfig
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
/** A trait used to help load a different load and discard the current wallet in memory /** A trait used to help load a different load and discard the current wallet in
* This trait encapsulates the heavy lifting done in the 'loadwallet' RPC command * memory This trait encapsulates the heavy lifting done in the 'loadwallet'
* RPC command
*/ */
sealed trait DLCWalletLoaderApi sealed trait DLCWalletLoaderApi
extends BitcoinSLogger extends BitcoinSLogger
@ -42,8 +43,8 @@ sealed trait DLCWalletLoaderApi
def load( def load(
walletNameOpt: Option[String], walletNameOpt: Option[String],
aesPasswordOpt: Option[AesPassword]): Future[ aesPasswordOpt: Option[AesPassword]
(WalletHolder, WalletAppConfig, DLCAppConfig)] ): Future[(WalletHolder, WalletAppConfig, DLCAppConfig)]
protected def loadWallet( protected def loadWallet(
walletHolder: WalletHolder, walletHolder: WalletHolder,
@ -51,17 +52,21 @@ sealed trait DLCWalletLoaderApi
nodeApi: NodeApi, nodeApi: NodeApi,
feeProviderApi: FeeRateApi, feeProviderApi: FeeRateApi,
walletNameOpt: Option[String], walletNameOpt: Option[String],
aesPasswordOpt: Option[AesPassword])(implicit aesPasswordOpt: Option[AesPassword]
ec: ExecutionContext): Future[ )(implicit
(DLCNeutrinoHDWalletApi, WalletAppConfig, DLCAppConfig)] = { ec: ExecutionContext
): Future[(DLCNeutrinoHDWalletApi, WalletAppConfig, DLCAppConfig)] = {
logger.info( logger.info(
s"Loading wallet with bitcoind backend, walletName=${walletNameOpt.getOrElse("DEFAULT")}") s"Loading wallet with bitcoind backend, walletName=${walletNameOpt.getOrElse("DEFAULT")}"
)
val walletName = val walletName =
walletNameOpt.getOrElse(WalletAppConfig.DEFAULT_WALLET_NAME) walletNameOpt.getOrElse(WalletAppConfig.DEFAULT_WALLET_NAME)
for { for {
(walletConfig, dlcConfig) <- updateWalletConfigs(walletName, (walletConfig, dlcConfig) <- updateWalletConfigs(
aesPasswordOpt) walletName,
aesPasswordOpt
)
_ <- { _ <- {
if (walletHolder.isInitialized) { if (walletHolder.isInitialized) {
walletHolder walletHolder
@ -83,8 +88,8 @@ sealed trait DLCWalletLoaderApi
protected def updateWalletConfigs( protected def updateWalletConfigs(
walletName: String, walletName: String,
aesPasswordOpt: Option[AesPassword])(implicit aesPasswordOpt: Option[AesPassword]
ec: ExecutionContext): Future[(WalletAppConfig, DLCAppConfig)] = { )(implicit ec: ExecutionContext): Future[(WalletAppConfig, DLCAppConfig)] = {
val walletNameArgOpt = ArgumentSource.RpcArgument(walletName) val walletNameArgOpt = ArgumentSource.RpcArgument(walletName)
val aesPasswordArgOpt = aesPasswordOpt match { val aesPasswordArgOpt = aesPasswordOpt match {
case None => case None =>
@ -93,8 +98,11 @@ sealed trait DLCWalletLoaderApi
Some(ArgumentSource.RpcArgument(pw)) Some(ArgumentSource.RpcArgument(pw))
} }
val kmConfigF = Future.successful( val kmConfigF = Future.successful(
conf.walletConf.kmConf.copy(walletNameOverride = Some(walletNameArgOpt), conf.walletConf.kmConf.copy(
aesPasswordOverride = aesPasswordArgOpt)) walletNameOverride = Some(walletNameArgOpt),
aesPasswordOverride = aesPasswordArgOpt
)
)
(for { (for {
kmConfig <- kmConfigF kmConfig <- kmConfigF
@ -107,46 +115,49 @@ sealed trait DLCWalletLoaderApi
} yield (walletConfig, dlcConfig)) } yield (walletConfig, dlcConfig))
} }
protected def updateWalletName(walletNameOpt: Option[String])(implicit protected def updateWalletName(
ec: ExecutionContext): Future[Unit] = { walletNameOpt: Option[String]
)(implicit ec: ExecutionContext): Future[Unit] = {
val nodeStateDAO: NodeStateDescriptorDAO = val nodeStateDAO: NodeStateDescriptorDAO =
NodeStateDescriptorDAO()(ec, conf.nodeConf) NodeStateDescriptorDAO()(ec, conf.nodeConf)
nodeStateDAO.updateWalletName(walletNameOpt) nodeStateDAO.updateWalletName(walletNameOpt)
} }
protected def restartRescanIfNeeded(wallet: DLCNeutrinoHDWalletApi)(implicit protected def restartRescanIfNeeded(
ec: ExecutionContext): Future[RescanState] = { wallet: DLCNeutrinoHDWalletApi
)(implicit ec: ExecutionContext): Future[RescanState] = {
for { for {
isRescanning <- wallet.isRescanning() isRescanning <- wallet.isRescanning()
res <- res <-
if (isRescanning) if (isRescanning)
wallet.rescanNeutrinoWallet(startOpt = None, wallet.rescanNeutrinoWallet(
endOpt = None, startOpt = None,
addressBatchSize = endOpt = None,
wallet.discoveryBatchSize(), addressBatchSize = wallet.discoveryBatchSize(),
useCreationTime = true, useCreationTime = true,
force = true) force = true
)
else Future.successful(RescanState.RescanDone) else Future.successful(RescanState.RescanDone)
} yield res } yield res
} }
/** Store a rescan state for the wallet that is currently loaded /** Store a rescan state for the wallet that is currently loaded This is
* This is needed because we don't save rescan state anywhere else. * needed because we don't save rescan state anywhere else.
*/ */
@volatile private[this] var rescanStateOpt: Option[ @volatile private[this] var rescanStateOpt
RescanState.RescanStarted] = None : Option[RescanState.RescanStarted] = None
def setRescanState(rescanState: RescanState): Unit = { def setRescanState(rescanState: RescanState): Unit = {
rescanState match { rescanState match {
case RescanState.RescanAlreadyStarted => case RescanState.RescanAlreadyStarted =>
//do nothing in this case, we don't need to keep these states around // do nothing in this case, we don't need to keep these states around
//don't overwrite the existing reference to RescanStarted // don't overwrite the existing reference to RescanStarted
case RescanState.RescanDone | RescanState.RescanNotNeeded => case RescanState.RescanDone | RescanState.RescanNotNeeded =>
//rescan is done, reset state // rescan is done, reset state
rescanStateOpt = None rescanStateOpt = None
case started: RescanState.RescanStarted => case started: RescanState.RescanStarted =>
if (rescanStateOpt.isEmpty) { if (rescanStateOpt.isEmpty) {
//add callback to reset state when the rescan is done // add callback to reset state when the rescan is done
val resetStateCallbackF = started.entireRescanDoneF.map { _ => val resetStateCallbackF = started.entireRescanDoneF.map { _ =>
rescanStateOpt = None rescanStateOpt = None
} }
@ -156,20 +167,22 @@ sealed trait DLCWalletLoaderApi
case scala.util.control.NonFatal(exn) => case scala.util.control.NonFatal(exn) =>
logger.error( logger.error(
s"Failed to reset rescanState in wallet loader. Resetting rescan state", s"Failed to reset rescanState in wallet loader. Resetting rescan state",
exn) exn
)
rescanStateOpt = None rescanStateOpt = None
} }
rescanStateOpt = Some(started) rescanStateOpt = Some(started)
} else { } else {
sys.error( sys.error(
s"Cannot run multiple rescans at the same time, got=$started have=$rescanStateOpt") s"Cannot run multiple rescans at the same time, got=$started have=$rescanStateOpt"
)
} }
} }
} }
protected def stopRescan()(implicit ec: ExecutionContext): Future[Unit] = { protected def stopRescan()(implicit ec: ExecutionContext): Future[Unit] = {
rescanStateOpt match { rescanStateOpt match {
case Some(state) => state.stop().map(_ => ()) //stop the rescan case Some(state) => state.stop().map(_ => ()) // stop the rescan
case None => Future.unit case None => Future.unit
} }
} }
@ -188,17 +201,18 @@ sealed trait DLCWalletLoaderApi
() ()
} }
@volatile private[this] var currentWalletAppConfigOpt: Option[ @volatile private[this] var currentWalletAppConfigOpt
WalletAppConfig] = None : Option[WalletAppConfig] = None
@volatile private[this] var currentDLCAppConfigOpt: Option[DLCAppConfig] = @volatile private[this] var currentDLCAppConfigOpt: Option[DLCAppConfig] =
None None
protected def stopOldWalletAppConfig( protected def stopOldWalletAppConfig(
newWalletConfig: WalletAppConfig): Future[Unit] = { newWalletConfig: WalletAppConfig
): Future[Unit] = {
currentWalletAppConfigOpt match { currentWalletAppConfigOpt match {
case Some(current) => case Some(current) =>
//stop the old config // stop the old config
current current
.stop() .stop()
.map(_ => { .map(_ => {
@ -214,10 +228,11 @@ sealed trait DLCWalletLoaderApi
} }
protected def stopOldDLCAppConfig( protected def stopOldDLCAppConfig(
newDlcConfig: DLCAppConfig): Future[Unit] = { newDlcConfig: DLCAppConfig
): Future[Unit] = {
currentDLCAppConfigOpt match { currentDLCAppConfigOpt match {
case Some(current) => case Some(current) =>
//stop the old config // stop the old config
current current
.stop() .stop()
.map(_ => { .map(_ => {
@ -266,10 +281,11 @@ case class DLCWalletNeutrinoBackendLoader(
walletHolder: WalletHolder, walletHolder: WalletHolder,
chainQueryApi: ChainQueryApi, chainQueryApi: ChainQueryApi,
nodeApi: NodeApi, nodeApi: NodeApi,
feeRateApi: FeeRateApi)(implicit feeRateApi: FeeRateApi
)(implicit
override val conf: BitcoinSAppConfig, override val conf: BitcoinSAppConfig,
override val system: ActorSystem) override val system: ActorSystem
extends DLCWalletLoaderApi { ) extends DLCWalletLoaderApi {
import system.dispatcher import system.dispatcher
implicit private val nodeConf: NodeAppConfig = conf.nodeConf implicit private val nodeConf: NodeAppConfig = conf.nodeConf
@ -278,8 +294,8 @@ case class DLCWalletNeutrinoBackendLoader(
override def load( override def load(
walletNameOpt: Option[String], walletNameOpt: Option[String],
aesPasswordOpt: Option[AesPassword]): Future[ aesPasswordOpt: Option[AesPassword]
(WalletHolder, WalletAppConfig, DLCAppConfig)] = { ): Future[(WalletHolder, WalletAppConfig, DLCAppConfig)] = {
val stopCallbackF = nodeConf.callBacks match { val stopCallbackF = nodeConf.callBacks match {
case stream: NodeCallbackStreamManager => case stream: NodeCallbackStreamManager =>
stream.stop() stream.stop()
@ -320,17 +336,18 @@ case class DLCWalletBitcoindBackendLoader(
walletHolder: WalletHolder, walletHolder: WalletHolder,
bitcoind: BitcoindRpcClient, bitcoind: BitcoindRpcClient,
nodeApi: NodeApi, nodeApi: NodeApi,
feeProvider: FeeRateApi)(implicit feeProvider: FeeRateApi
)(implicit
override val conf: BitcoinSAppConfig, override val conf: BitcoinSAppConfig,
override val system: ActorSystem) override val system: ActorSystem
extends DLCWalletLoaderApi { ) extends DLCWalletLoaderApi {
import system.dispatcher import system.dispatcher
implicit private val nodeConf: NodeAppConfig = conf.nodeConf implicit private val nodeConf: NodeAppConfig = conf.nodeConf
override def load( override def load(
walletNameOpt: Option[String], walletNameOpt: Option[String],
aesPasswordOpt: Option[AesPassword]): Future[ aesPasswordOpt: Option[AesPassword]
(WalletHolder, WalletAppConfig, DLCAppConfig)] = { ): Future[(WalletHolder, WalletAppConfig, DLCAppConfig)] = {
val stopCallbackF = nodeConf.callBacks match { val stopCallbackF = nodeConf.callBacks match {
case stream: NodeCallbackStreamManager => case stream: NodeCallbackStreamManager =>
stream.stop() stream.stop()
@ -347,15 +364,17 @@ case class DLCWalletBitcoindBackendLoader(
nodeApi = nodeApi, nodeApi = nodeApi,
feeProviderApi = feeProvider, feeProviderApi = feeProvider,
walletNameOpt = walletNameOpt, walletNameOpt = walletNameOpt,
aesPasswordOpt = aesPasswordOpt) aesPasswordOpt = aesPasswordOpt
)
_ <- stopOldWalletAppConfig(walletConfig) _ <- stopOldWalletAppConfig(walletConfig)
_ <- stopOldDLCAppConfig(dlcConfig) _ <- stopOldDLCAppConfig(dlcConfig)
nodeCallbacks <- CallbackUtil.createBitcoindNodeCallbacksForWallet( nodeCallbacks <- CallbackUtil.createBitcoindNodeCallbacksForWallet(
walletHolder) walletHolder
)
_ = nodeConf.replaceCallbacks(nodeCallbacks) _ = nodeConf.replaceCallbacks(nodeCallbacks)
_ <- walletHolder.replaceWallet(dlcWallet) _ <- walletHolder.replaceWallet(dlcWallet)
//do something with possible rescan? // do something with possible rescan?
rescanState <- restartRescanIfNeeded(walletHolder) rescanState <- restartRescanIfNeeded(walletHolder)
_ = setRescanState(rescanState) _ = setRescanState(rescanState)
} yield { } yield {

View File

@ -45,7 +45,9 @@ object DecodeAccept extends ServerJsonModels {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length} Expected: 1")) s"Bad number of arguments: ${other.length} Expected: 1"
)
)
} }
} }
} }
@ -75,7 +77,9 @@ object DecodeSign extends ServerJsonModels {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length} Expected: 1")) s"Bad number of arguments: ${other.length} Expected: 1"
)
)
} }
} }
} }
@ -97,7 +101,9 @@ object DecodeAttestations extends ServerJsonModels {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 1")) s"Bad number of arguments: ${other.length}. Expected: 1"
)
)
} }
} }
} }
@ -106,7 +112,8 @@ case class DLCDataFromFile(
path: Path, path: Path,
destinationOpt: Option[Path], destinationOpt: Option[Path],
externalPayoutAddressOpt: Option[BitcoinAddress], externalPayoutAddressOpt: Option[BitcoinAddress],
externalChangeAddressOpt: Option[BitcoinAddress]) externalChangeAddressOpt: Option[BitcoinAddress]
)
object DLCDataFromFile extends ServerJsonModels { object DLCDataFromFile extends ServerJsonModels {
@ -115,7 +122,8 @@ object DLCDataFromFile extends ServerJsonModels {
pathJs: Value, pathJs: Value,
destJs: Value, destJs: Value,
payoutAddressJs: Value, payoutAddressJs: Value,
changeAddressJs: Value) = Try { changeAddressJs: Value
) = Try {
val path = new File(pathJs.str).toPath val path = new File(pathJs.str).toPath
val destJsOpt = nullToOpt(destJs) val destJsOpt = nullToOpt(destJs)
val destOpt = destJsOpt.map(js => new File(js.str).toPath) val destOpt = destJsOpt.map(js => new File(js.str).toPath)
@ -143,7 +151,9 @@ object DLCDataFromFile extends ServerJsonModels {
case other => case other =>
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
s"Bad number of arguments: ${other.length}. Expected: 1")) s"Bad number of arguments: ${other.length}. Expected: 1"
)
)
} }
} }
} }
@ -151,7 +161,8 @@ object DLCDataFromFile extends ServerJsonModels {
case class OfferAdd( case class OfferAdd(
offerTLV: DLCOfferTLV, offerTLV: DLCOfferTLV,
peer: Option[String], peer: Option[String],
message: Option[String]) message: Option[String]
)
object OfferAdd extends ServerJsonModels { object OfferAdd extends ServerJsonModels {
@ -167,7 +178,8 @@ object OfferAdd extends ServerJsonModels {
} }
case other => case other =>
val exn = new IllegalArgumentException( val exn = new IllegalArgumentException(
s"Bad number or arguments to offer-add, got=${other.length} expected=3") s"Bad number or arguments to offer-add, got=${other.length} expected=3"
)
Failure(exn) Failure(exn)
} }
} }
@ -186,7 +198,8 @@ object OfferRemove {
} }
case other => case other =>
val exn = new IllegalArgumentException( val exn = new IllegalArgumentException(
s"Bad number or arguments to offer-remove, got=${other.length} expected=1") s"Bad number or arguments to offer-remove, got=${other.length} expected=1"
)
Failure(exn) Failure(exn)
} }
} }
@ -195,7 +208,8 @@ object OfferRemove {
case class OfferSend( case class OfferSend(
remoteAddress: InetSocketAddress, remoteAddress: InetSocketAddress,
message: String, message: String,
offerE: Either[DLCOfferTLV, Sha256Digest]) offerE: Either[DLCOfferTLV, Sha256Digest]
)
object OfferSend extends ServerJsonModels { object OfferSend extends ServerJsonModels {
@ -216,7 +230,8 @@ object OfferSend extends ServerJsonModels {
} }
case other => case other =>
val exn = new IllegalArgumentException( val exn = new IllegalArgumentException(
s"Bad number or arguments to offer-send, got=${other.length} expected=3") s"Bad number or arguments to offer-send, got=${other.length} expected=3"
)
Failure(exn) Failure(exn)
} }
} }
@ -236,7 +251,8 @@ object GetDLCOffer {
} }
case other => case other =>
val exn = new IllegalArgumentException( val exn = new IllegalArgumentException(
s"Bad number or arguments to offer-send, got=${other.length} expected=1") s"Bad number or arguments to offer-send, got=${other.length} expected=1"
)
Failure(exn) Failure(exn)
} }
} }
@ -251,7 +267,8 @@ trait ServerJsonModels {
case _: Value => case _: Value =>
throw Value.InvalidData( throw Value.InvalidData(
js, js,
"Expected an OracleAnnouncementTLV as a hex string") "Expected an OracleAnnouncementTLV as a hex string"
)
} }
def jsToContractInfoTLV(js: Value): ContractInfoV0TLV = def jsToContractInfoTLV(js: Value): ContractInfoV0TLV =
@ -311,7 +328,8 @@ trait ServerJsonModels {
LockUnspentOutputParameter.fromJson(js) LockUnspentOutputParameter.fromJson(js)
def jsToLockUnspentOutputParameters( def jsToLockUnspentOutputParameters(
js: Value): Seq[LockUnspentOutputParameter] = { js: Value
): Seq[LockUnspentOutputParameter] = {
js.arr.foldLeft(Seq.empty[LockUnspentOutputParameter])((seq, outPoint) => js.arr.foldLeft(Seq.empty[LockUnspentOutputParameter])((seq, outPoint) =>
seq :+ jsToLockUnspentOutputParameter(outPoint)) seq :+ jsToLockUnspentOutputParameter(outPoint))
} }
@ -337,11 +355,13 @@ trait ServerJsonModels {
case _: Value => case _: Value =>
throw Value.InvalidData( throw Value.InvalidData(
js, js,
"Expected a SchnorrDigitalSignature as a hex string") "Expected a SchnorrDigitalSignature as a hex string"
)
} }
def jsToSchnorrDigitalSignatureVec( def jsToSchnorrDigitalSignatureVec(
js: Value): Vector[SchnorrDigitalSignature] = { js: Value
): Vector[SchnorrDigitalSignature] = {
js.arr.foldLeft(Vector.empty[SchnorrDigitalSignature])((vec, sig) => js.arr.foldLeft(Vector.empty[SchnorrDigitalSignature])((vec, sig) =>
vec :+ jsToSchnorrDigitalSignature(sig)) vec :+ jsToSchnorrDigitalSignature(sig))
} }
@ -353,7 +373,8 @@ trait ServerJsonModels {
case _: Value => case _: Value =>
throw Value.InvalidData( throw Value.InvalidData(
js, js,
"Expected a OracleAttestmentTLV as a hex string") "Expected a OracleAttestmentTLV as a hex string"
)
} }
def jsToOracleAttestmentTLVVec(js: Value): Vector[OracleAttestmentTLV] = { def jsToOracleAttestmentTLVVec(js: Value): Vector[OracleAttestmentTLV] = {
@ -384,7 +405,8 @@ trait ServerJsonModels {
} }
def jsToWalletNameAndPassword( def jsToWalletNameAndPassword(
js: Value): (Option[String], Option[AesPassword]) = { js: Value
): (Option[String], Option[AesPassword]) = {
js match { js match {
case Arr(arr) => case Arr(arr) =>
if (arr.size >= 2) { if (arr.size >= 2) {
@ -403,14 +425,16 @@ trait ServerJsonModels {
case Arr(arr) => arr.map(_.str).toVector case Arr(arr) => arr.map(_.str).toVector
case Null | False | True | Num(_) | Obj(_) => case Null | False | True | Num(_) | Obj(_) =>
throw new IllegalArgumentException( throw new IllegalArgumentException(
"mnemonic must be a string or array of strings") "mnemonic must be a string or array of strings"
)
} }
MnemonicCode.fromWords(mnemonicWords) MnemonicCode.fromWords(mnemonicWords)
} }
def jsToInetSocketAddress( def jsToInetSocketAddress(
js: Value, js: Value,
defaultPort: Int = -1): InetSocketAddress = { defaultPort: Int = -1
): InetSocketAddress = {
js match { js match {
case str: Str => case str: Str =>
val uri = new URI("tcp://" + str.str) val uri = new URI("tcp://" + str.str)

View File

@ -37,8 +37,8 @@ import scala.util.{Failure, Success}
case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
system: ActorSystem, system: ActorSystem,
walletConf: WalletAppConfig) walletConf: WalletAppConfig
extends ServerRoute ) extends ServerRoute
with BitcoinSLogger { with BitcoinSLogger {
import system.dispatcher import system.dispatcher
@ -64,7 +64,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
private def handleBroadcastable( private def handleBroadcastable(
tx: Transaction, tx: Transaction,
noBroadcast: Boolean): Future[NetworkElement] = { noBroadcast: Boolean
): Future[NetworkElement] = {
if (noBroadcast) { if (noBroadcast) {
Future.successful(tx) Future.successful(tx)
} else { } else {
@ -154,11 +155,14 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
val json = Obj( val json = Obj(
"confirmed" -> Num( "confirmed" -> Num(
formatCurrencyUnit(confirmed, isSats).toDouble), formatCurrencyUnit(confirmed, isSats).toDouble
),
"unconfirmed" -> Num( "unconfirmed" -> Num(
formatCurrencyUnit(unconfirmed, isSats).toDouble), formatCurrencyUnit(unconfirmed, isSats).toDouble
),
"reserved" -> Num( "reserved" -> Num(
formatCurrencyUnit(reserved, isSats).toDouble), formatCurrencyUnit(reserved, isSats).toDouble
),
"total" -> Num(formatCurrencyUnit(total, isSats).toDouble) "total" -> Num(formatCurrencyUnit(total, isSats).toDouble)
) )
Server.httpSuccess(json) Server.httpSuccess(json)
@ -226,7 +230,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
complete { complete {
wallet.tagAddress(address, label).map { tagDb => wallet.tagAddress(address, label).map { tagDb =>
Server.httpSuccess( Server.httpSuccess(
s"Added label \'${tagDb.tagName.name}\' to ${tagDb.address.value}") s"Added label \'${tagDb.tagName.name}\' to ${tagDb.address.value}"
)
} }
} }
} }
@ -263,8 +268,10 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
val json: Vector[ujson.Obj] = grouped.map { case (address, labels) => val json: Vector[ujson.Obj] = grouped.map { case (address, labels) =>
val tagNames: Vector[ujson.Str] = val tagNames: Vector[ujson.Str] =
labels.map(l => ujson.Str(l.tagName.name)) labels.map(l => ujson.Str(l.tagName.name))
ujson.Obj(("address", address.toString), ujson.Obj(
("labels", ujson.Arr.from(tagNames))) ("address", address.toString),
("labels", ujson.Arr.from(tagNames))
)
}.toVector }.toVector
Server.httpSuccess(ujson.Arr.from(json)) Server.httpSuccess(ujson.Arr.from(json))
} }
@ -298,11 +305,15 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
dlcOpt <- wallet.findDLCByTemporaryContractId(tempContractId) dlcOpt <- wallet.findDLCByTemporaryContractId(tempContractId)
dlc = dlcOpt.getOrElse( dlc = dlcOpt.getOrElse(
throw new IllegalArgumentException( throw new IllegalArgumentException(
s"Cannot find a DLC with temp contact ID $tempContractId")) s"Cannot find a DLC with temp contact ID $tempContractId"
)
)
offerOpt <- wallet.getDLCOffer(dlc.dlcId) offerOpt <- wallet.getDLCOffer(dlc.dlcId)
offer = offerOpt.getOrElse( offer = offerOpt.getOrElse(
throw new IllegalArgumentException( throw new IllegalArgumentException(
s"Cannot find an offer with for DLC ID ${dlc.dlcId}")) s"Cannot find an offer with for DLC ID ${dlc.dlcId}"
)
)
} yield { } yield {
Server.httpSuccess(offer.toMessage.hex) Server.httpSuccess(offer.toMessage.hex)
} }
@ -358,14 +369,17 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success( case Success(
CreateDLCOffer(contractInfo, CreateDLCOffer(
collateral, contractInfo,
feeRateOpt, collateral,
locktimeOpt, feeRateOpt,
refundLT, locktimeOpt,
payoutAddressOpt, refundLT,
changeAddressOpt, payoutAddressOpt,
peerAddressOpt)) => changeAddressOpt,
peerAddressOpt
)
) =>
complete { complete {
val announcements = contractInfo.oracleInfo match { val announcements = contractInfo.oracleInfo match {
case OracleInfoV0TLV(announcement) => Vector(announcement) case OracleInfoV0TLV(announcement) => Vector(announcement)
@ -375,29 +389,34 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
if (!announcements.forall(_.validateSignature)) { if (!announcements.forall(_.validateSignature)) {
throw new RuntimeException( throw new RuntimeException(
s"Received Oracle announcement with invalid signature! ${announcements s"Received Oracle announcement with invalid signature! ${announcements
.map(_.hex)}") .map(_.hex)}"
)
} }
val offerF = locktimeOpt match { val offerF = locktimeOpt match {
case Some(locktime) => case Some(locktime) =>
wallet wallet
.createDLCOffer(contractInfo, .createDLCOffer(
collateral, contractInfo,
feeRateOpt, collateral,
locktime, feeRateOpt,
refundLT, locktime,
peerAddressOpt, refundLT,
payoutAddressOpt, peerAddressOpt,
changeAddressOpt) payoutAddressOpt,
changeAddressOpt
)
case None => case None =>
wallet wallet
.createDLCOffer(contractInfo, .createDLCOffer(
collateral, contractInfo,
feeRateOpt, collateral,
refundLT, feeRateOpt,
peerAddressOpt, refundLT,
payoutAddressOpt, peerAddressOpt,
changeAddressOpt) payoutAddressOpt,
changeAddressOpt
)
} }
offerF.map { offer => offerF.map { offer =>
@ -411,16 +430,21 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success( case Success(
AcceptDLCOffer(offer, AcceptDLCOffer(
payoutAddressOpt, offer,
changeAddressOpt, payoutAddressOpt,
peerAddressOpt)) => changeAddressOpt,
peerAddressOpt
)
) =>
complete { complete {
wallet wallet
.acceptDLCOffer(offer.tlv, .acceptDLCOffer(
peerAddressOpt, offer.tlv,
payoutAddressOpt, peerAddressOpt,
changeAddressOpt) payoutAddressOpt,
changeAddressOpt
)
.map { accept => .map { accept =>
Server.httpSuccess(accept.toMessage.hex) Server.httpSuccess(accept.toMessage.hex)
} }
@ -432,10 +456,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case Failure(exception) => case Failure(exception) =>
complete(Server.httpBadRequest(exception)) complete(Server.httpBadRequest(exception))
case Success( case Success(
DLCDataFromFile(path, DLCDataFromFile(path, destOpt, payoutAddressOpt, changeAddressOpt)
destOpt, ) =>
payoutAddressOpt,
changeAddressOpt)) =>
complete { complete {
val hex = Files.readAllLines(path).get(0) val hex = Files.readAllLines(path).get(0)
@ -445,10 +467,12 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
.getOrElse(LnMessage(DLCOfferTLV.fromHex(hex))) .getOrElse(LnMessage(DLCOfferTLV.fromHex(hex)))
wallet wallet
.acceptDLCOffer(offerMessage.tlv, .acceptDLCOffer(
None, offerMessage.tlv,
payoutAddressOpt, None,
changeAddressOpt) payoutAddressOpt,
changeAddressOpt
)
.map { accept => .map { accept =>
val ret = handleDestinationOpt(accept.toMessage.hex, destOpt) val ret = handleDestinationOpt(accept.toMessage.hex, destOpt)
Server.httpSuccess(ret) Server.httpSuccess(ret)
@ -500,7 +524,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
complete { complete {
wallet.addDLCSigs(sigs.tlv).map { db => wallet.addDLCSigs(sigs.tlv).map { db =>
Server.httpSuccess( Server.httpSuccess(
s"Successfully added sigs to DLC ${db.contractIdOpt.get.toHex}") s"Successfully added sigs to DLC ${db.contractIdOpt.get.toHex}"
)
} }
} }
} }
@ -520,7 +545,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
wallet.addDLCSigs(signMessage.tlv).map { db => wallet.addDLCSigs(signMessage.tlv).map { db =>
Server.httpSuccess( Server.httpSuccess(
s"Successfully added sigs to DLC ${db.contractIdOpt.get.toHex}") s"Successfully added sigs to DLC ${db.contractIdOpt.get.toHex}"
)
} }
} }
} }
@ -604,7 +630,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
Server.httpSuccess(ret) Server.httpSuccess(ret)
case None => case None =>
Server.httpError( Server.httpError(
s"Cannot execute DLC with contractId=${contractId.toHex}") s"Cannot execute DLC with contractId=${contractId.toHex}"
)
} }
} }
} }
@ -627,15 +654,19 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case ServerCommand("sendtoaddress", arr) => case ServerCommand("sendtoaddress", arr) =>
withValidServerCommand(SendToAddress.fromJsArr(arr)) { withValidServerCommand(SendToAddress.fromJsArr(arr)) {
case SendToAddress(address, case SendToAddress(
bitcoins, address,
satoshisPerVirtualByteOpt, bitcoins,
noBroadcast) => satoshisPerVirtualByteOpt,
noBroadcast
) =>
complete { complete {
for { for {
tx <- wallet.sendToAddress(address, tx <- wallet.sendToAddress(
bitcoins, address,
satoshisPerVirtualByteOpt) bitcoins,
satoshisPerVirtualByteOpt
)
retStr <- handleBroadcastable(tx, noBroadcast) retStr <- handleBroadcastable(tx, noBroadcast)
} yield { } yield {
Server.httpSuccess(retStr.hex) Server.httpSuccess(retStr.hex)
@ -644,16 +675,20 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
} }
case ServerCommand("sendfromoutpoints", arr) => case ServerCommand("sendfromoutpoints", arr) =>
withValidServerCommand(SendFromOutPoints.fromJsArr(arr)) { withValidServerCommand(SendFromOutPoints.fromJsArr(arr)) {
case SendFromOutPoints(outPoints, case SendFromOutPoints(
address, outPoints,
bitcoins, address,
satoshisPerVirtualByteOpt) => bitcoins,
satoshisPerVirtualByteOpt
) =>
complete { complete {
for { for {
tx <- wallet.sendFromOutPoints(outPoints, tx <- wallet.sendFromOutPoints(
address, outPoints,
bitcoins, address,
satoshisPerVirtualByteOpt) bitcoins,
satoshisPerVirtualByteOpt
)
_ <- wallet.broadcastTransaction(tx) _ <- wallet.broadcastTransaction(tx)
} yield Server.httpSuccess(tx.txIdBE) } yield Server.httpSuccess(tx.txIdBE)
} }
@ -675,10 +710,12 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case SendWithAlgo(address, bitcoins, satoshisPerVirtualByteOpt, algo) => case SendWithAlgo(address, bitcoins, satoshisPerVirtualByteOpt, algo) =>
complete { complete {
for { for {
tx <- wallet.sendWithAlgo(address, tx <- wallet.sendWithAlgo(
bitcoins, address,
satoshisPerVirtualByteOpt, bitcoins,
algo) satoshisPerVirtualByteOpt,
algo
)
_ <- wallet.broadcastTransaction(tx) _ <- wallet.broadcastTransaction(tx)
} yield Server.httpSuccess(tx.txIdBE) } yield Server.httpSuccess(tx.txIdBE)
} }
@ -698,9 +735,11 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case OpReturnCommit(message, hashMessage, satoshisPerVirtualByteOpt) => case OpReturnCommit(message, hashMessage, satoshisPerVirtualByteOpt) =>
complete { complete {
for { for {
tx <- wallet.makeOpReturnCommitment(message, tx <- wallet.makeOpReturnCommitment(
hashMessage, message,
satoshisPerVirtualByteOpt) hashMessage,
satoshisPerVirtualByteOpt
)
_ <- wallet.broadcastTransaction(tx) _ <- wallet.broadcastTransaction(tx)
} yield { } yield {
Server.httpSuccess(tx.txIdBE) Server.httpSuccess(tx.txIdBE)
@ -838,9 +877,11 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
case KeyManagerPassphraseChange(oldPassword, newPassword) => case KeyManagerPassphraseChange(oldPassword, newPassword) =>
complete { complete {
val path = walletConf.seedPath val path = walletConf.seedPath
WalletStorage.changeAesPassword(path, WalletStorage.changeAesPassword(
Some(oldPassword), path,
Some(newPassword)) Some(oldPassword),
Some(newPassword)
)
Server.httpSuccess(ujson.Null) Server.httpSuccess(ujson.Null)
} }
@ -867,16 +908,20 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
val mnemonicState = passwordOpt match { val mnemonicState = passwordOpt match {
case Some(pass) => case Some(pass) =>
DecryptedMnemonic(mnemonic, DecryptedMnemonic(
creationTime, mnemonic,
backupTimeOpt = None, creationTime,
imported = true) backupTimeOpt = None,
imported = true
)
.encrypt(pass) .encrypt(pass)
case None => case None =>
DecryptedMnemonic(mnemonic, DecryptedMnemonic(
creationTime, mnemonic,
backupTimeOpt = None, creationTime,
imported = true) backupTimeOpt = None,
imported = true
)
} }
WalletStorage.writeSeedToDisk(seedPath, mnemonicState) WalletStorage.writeSeedToDisk(seedPath, mnemonicState)
@ -894,7 +939,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
Server.httpSuccess( Server.httpSuccess(
WalletStorage WalletStorage
.readDecryptedSeedPhraseForBackup(seedPath, passwordOpt) .readDecryptedSeedPhraseForBackup(seedPath, passwordOpt)
.get) .get
)
} }
} }
@ -938,16 +984,20 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
val mnemonicState = passwordOpt match { val mnemonicState = passwordOpt match {
case Some(pass) => case Some(pass) =>
DecryptedExtPrivKey(xprv, DecryptedExtPrivKey(
creationTime, xprv,
backupTimeOpt = None, creationTime,
imported = true) backupTimeOpt = None,
imported = true
)
.encrypt(pass) .encrypt(pass)
case None => case None =>
DecryptedExtPrivKey(xprv, DecryptedExtPrivKey(
creationTime, xprv,
backupTimeOpt = None, creationTime,
imported = true) backupTimeOpt = None,
imported = true
)
} }
WalletStorage.writeSeedToDisk(seedPath, mnemonicState) WalletStorage.writeSeedToDisk(seedPath, mnemonicState)
@ -973,7 +1023,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
feeRateF.map { f => feeRateF.map { f =>
logger.info(s"Retrieved fee rate ${f.toSatsPerVByte}, it took ${System logger.info(s"Retrieved fee rate ${f.toSatsPerVByte}, it took ${System
.currentTimeMillis() - start}ms") .currentTimeMillis() - start}ms")
Server.httpSuccess(f.toSatsPerVByte) Server.httpSuccess(f.toSatsPerVByte)
} }
} }
@ -1020,7 +1070,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
.map(_ + "-") .map(_ + "-")
.getOrElse(WalletAppConfig.DEFAULT_WALLET_NAME) .getOrElse(WalletAppConfig.DEFAULT_WALLET_NAME)
kmConf.seedFolder.resolve( kmConf.seedFolder.resolve(
walletName + WalletStorage.ENCRYPTED_SEED_FILE_NAME) walletName + WalletStorage.ENCRYPTED_SEED_FILE_NAME
)
} }
/** Returns information about the state of our wallet */ /** Returns information about the state of our wallet */
@ -1048,7 +1099,8 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
private def formatCurrencyUnit( private def formatCurrencyUnit(
currencyUnit: CurrencyUnit, currencyUnit: CurrencyUnit,
isSats: Boolean): Double = { isSats: Boolean
): Double = {
if (isSats) { if (isSats) {
currencyUnit.satoshis.toBigDecimal.toDouble currencyUnit.satoshis.toBigDecimal.toDouble
} else { } else {
@ -1066,19 +1118,21 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
} }
} }
/** Gets the fee rate for the wallet with a timeout on the request of 1 second */ /** Gets the fee rate for the wallet with a timeout on the request of 1 second
*/
private def getFeeRate(): Future[FeeUnit] = { private def getFeeRate(): Future[FeeUnit] = {
val resultF = wallet val resultF = wallet
.getFeeRate() .getFeeRate()
.recover { case scala.util.control.NonFatal(exn) => .recover { case scala.util.control.NonFatal(exn) =>
logger.error( logger.error(
s"Failed to fetch fee rate from wallet, returning -1 sats/vbyte", s"Failed to fetch fee rate from wallet, returning -1 sats/vbyte",
exn) exn
)
SatoshisPerVirtualByte.negativeOne SatoshisPerVirtualByte.negativeOne
} }
//due to tor variability, we need to make sure we give a prompt response. // due to tor variability, we need to make sure we give a prompt response.
//timeout the fee rate request after 1 second // timeout the fee rate request after 1 second
//see: https://github.com/bitcoin-s/bitcoin-s/issues/4460#issuecomment-1182325014 // see: https://github.com/bitcoin-s/bitcoin-s/issues/4460#issuecomment-1182325014
try { try {
val result = Await.result(resultF, 1.second) val result = Await.result(resultF, 1.second)
Future.successful(result) Future.successful(result)
@ -1097,15 +1151,15 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
val stateF: Future[RescanState] = rescanState match { val stateF: Future[RescanState] = rescanState match {
case started: RescanState.RescanStarted => case started: RescanState.RescanStarted =>
if (started.isStopped) { if (started.isStopped) {
//means rescan is done, reset the variable // means rescan is done, reset the variable
rescanStateOpt = Some(RescanDone) rescanStateOpt = Some(RescanDone)
Future.successful(RescanDone) Future.successful(RescanDone)
} else { } else {
//do nothing, we don't want to reset/stop a rescan that is running // do nothing, we don't want to reset/stop a rescan that is running
Future.successful(started) Future.successful(started)
} }
case RescanState.RescanDone | RescanState.RescanNotNeeded => case RescanState.RescanDone | RescanState.RescanNotNeeded =>
//if the previous rescan is done, start another rescan // if the previous rescan is done, start another rescan
startRescan(rescan) startRescan(rescan)
case RescanState.RescanAlreadyStarted => case RescanState.RescanAlreadyStarted =>
Future.successful(RescanState.RescanAlreadyStarted) Future.successful(RescanState.RescanAlreadyStarted)
@ -1124,7 +1178,9 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
} else { } else {
Future.failed( Future.failed(
new IllegalArgumentException( new IllegalArgumentException(
s"Cannot rescan when a rescan is already ongoing for wallet")) s"Cannot rescan when a rescan is already ongoing for wallet"
)
)
} }
} }
@ -1149,7 +1205,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
} }
case RescanState.RescanAlreadyStarted | RescanState.RescanDone | case RescanState.RescanAlreadyStarted | RescanState.RescanDone |
RescanState.RescanNotNeeded => RescanState.RescanNotNeeded =>
//do nothing in these cases, no state needs to be reset // do nothing in these cases, no state needs to be reset
} }
stateF stateF

View File

@ -7,8 +7,8 @@ import java.util.concurrent.atomic.AtomicBoolean
case class WalletZmqSubscribers( case class WalletZmqSubscribers(
rawTxSubscriberOpt: Option[ZMQSubscriber], rawTxSubscriberOpt: Option[ZMQSubscriber],
rawBlockSubscriberOpt: Option[ZMQSubscriber]) rawBlockSubscriberOpt: Option[ZMQSubscriber]
extends StartStop[Unit] { ) extends StartStop[Unit] {
private val isStarted: AtomicBoolean = new AtomicBoolean(false) private val isStarted: AtomicBoolean = new AtomicBoolean(false)
override def start(): Unit = { override def start(): Unit = {

View File

@ -4,12 +4,17 @@ import org.bitcoins.server.util.{BitcoindPollingCancellable}
import scala.concurrent.Future import scala.concurrent.Future
/** @param syncF the future that will be completed when the synchronization with bitcoind is complete /** @param syncF
* @param pollingCancellable You can cancel bitcoind polling by calling [[BitcoindPollingCancellabe.cancel()]] * the future that will be completed when the synchronization with bitcoind
* is complete
* @param pollingCancellable
* You can cancel bitcoind polling by calling
* [[BitcoindPollingCancellabe.cancel()]]
*/ */
case class BitcoindSyncState( case class BitcoindSyncState(
syncF: Future[Unit], syncF: Future[Unit],
pollingCancellable: BitcoindPollingCancellable) { pollingCancellable: BitcoindPollingCancellable
) {
/** Stops syncing and polling bitcoind */ /** Stops syncing and polling bitcoind */
def stop(): Future[Unit] = { def stop(): Future[Unit] = {

View File

@ -5,8 +5,8 @@ import org.bitcoins.commons.util.BitcoinSLogger
case class BitcoindPollingCancellable( case class BitcoindPollingCancellable(
blockPollingCancellable: Cancellable, blockPollingCancellable: Cancellable,
mempoolPollingCancelable: Cancellable) mempoolPollingCancelable: Cancellable
extends Cancellable ) extends Cancellable
with BitcoinSLogger { with BitcoinSLogger {
override def cancel(): Boolean = { override def cancel(): Boolean = {
@ -22,5 +22,6 @@ object BitcoindPollingCancellabe {
val none: BitcoindPollingCancellable = BitcoindPollingCancellable( val none: BitcoindPollingCancellable = BitcoindPollingCancellable(
Cancellable.alreadyCancelled, Cancellable.alreadyCancelled,
Cancellable.alreadyCancelled) Cancellable.alreadyCancelled
)
} }

View File

@ -18,8 +18,8 @@ import scala.concurrent.Future
object CallbackUtil extends BitcoinSLogger { object CallbackUtil extends BitcoinSLogger {
def createNeutrinoNodeCallbacksForWallet( def createNeutrinoNodeCallbacksForWallet(
wallet: WalletApi with NeutrinoWalletApi)(implicit wallet: WalletApi with NeutrinoWalletApi
system: ActorSystem): Future[NodeCallbackStreamManager] = { )(implicit system: ActorSystem): Future[NodeCallbackStreamManager] = {
import system.dispatcher import system.dispatcher
val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction => val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction =>
logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback") logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback")
@ -32,7 +32,8 @@ object CallbackUtil extends BitcoinSLogger {
Sink.foreachAsync[Vector[(DoubleSha256DigestBE, GolombFilter)]](1) { Sink.foreachAsync[Vector[(DoubleSha256DigestBE, GolombFilter)]](1) {
case blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)] => case blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)] =>
logger.debug( logger.debug(
s"Executing onCompactFilters callback with filter count=${blockFilters.length}") s"Executing onCompactFilters callback with filter count=${blockFilters.length}"
)
wallet wallet
.processCompactFilters(blockFilters = blockFilters) .processCompactFilters(blockFilters = blockFilters)
.map(_ => ()) .map(_ => ())
@ -42,7 +43,8 @@ object CallbackUtil extends BitcoinSLogger {
val blockSink = { val blockSink = {
Sink.foreachAsync[Block](1) { case block: Block => Sink.foreachAsync[Block](1) { case block: Block =>
logger.debug( logger.debug(
s"Executing onBlock callback=${block.blockHeader.hashBE.hex}") s"Executing onBlock callback=${block.blockHeader.hashBE.hex}"
)
wallet.processBlock(block).map(_ => ()) wallet.processBlock(block).map(_ => ())
} }
} }
@ -50,7 +52,8 @@ object CallbackUtil extends BitcoinSLogger {
val onHeaderSink = { val onHeaderSink = {
Sink.foreachAsync(1) { headers: Vector[BlockHeader] => Sink.foreachAsync(1) { headers: Vector[BlockHeader] =>
logger.debug( logger.debug(
s"Executing block header with header count=${headers.length}") s"Executing block header with header count=${headers.length}"
)
if (headers.isEmpty) { if (headers.isEmpty) {
Future.unit Future.unit
} else { } else {
@ -85,18 +88,20 @@ object CallbackUtil extends BitcoinSLogger {
.map(_ => ()) .map(_ => ())
} }
val callbacks = NodeCallbacks(onTxReceived = Vector(onTx), val callbacks = NodeCallbacks(
onBlockReceived = Vector(onBlock), onTxReceived = Vector(onTx),
onCompactFiltersReceived = onBlockReceived = Vector(onBlock),
Vector(onCompactFilters), onCompactFiltersReceived = Vector(onCompactFilters),
onBlockHeadersReceived = Vector(onHeaders)) onBlockHeadersReceived = Vector(onHeaders)
)
val streamManager = NodeCallbackStreamManager(callbacks) val streamManager = NodeCallbackStreamManager(callbacks)
Future.successful(streamManager) Future.successful(streamManager)
} }
def createBitcoindNodeCallbacksForWallet(wallet: DLCNeutrinoHDWalletApi)( def createBitcoindNodeCallbacksForWallet(
implicit system: ActorSystem): Future[NodeCallbackStreamManager] = { wallet: DLCNeutrinoHDWalletApi
)(implicit system: ActorSystem): Future[NodeCallbackStreamManager] = {
import system.dispatcher import system.dispatcher
val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction => val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction =>
logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback") logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback")

View File

@ -12,8 +12,8 @@ object ChainUtil {
def getBlockHeaderResult( def getBlockHeaderResult(
hashes: Vector[DoubleSha256DigestBE], hashes: Vector[DoubleSha256DigestBE],
chain: ChainApi)(implicit chain: ChainApi
ec: ExecutionContext): Future[Vector[GetBlockHeaderResult]] = { )(implicit ec: ExecutionContext): Future[Vector[GetBlockHeaderResult]] = {
val headersF: Future[Vector[Option[BlockHeaderDb]]] = val headersF: Future[Vector[Option[BlockHeaderDb]]] =
chain.getHeaders(hashes) chain.getHeaders(hashes)
val bestHeightF = chain.getBestBlockHeader().map(_.height) val bestHeightF = chain.getBestBlockHeader().map(_.height)
@ -30,7 +30,8 @@ object ChainUtil {
headersWithConfs.map { headersWithConfs.map {
case None => case None =>
sys.error( sys.error(
s"Could not find block header or confirmations for the header ") s"Could not find block header or confirmations for the header "
)
case Some((header, confs)) => case Some((header, confs)) =>
val chainworkStr = { val chainworkStr = {
val bytes = ByteVector(header.chainWork.toByteArray) val bytes = ByteVector(header.chainWork.toByteArray)

View File

@ -15,12 +15,12 @@ sealed trait WalletHolderWithLoaderApi {
case class WalletHolderWithNeutrinoLoaderApi( case class WalletHolderWithNeutrinoLoaderApi(
walletHolder: WalletHolder, walletHolder: WalletHolder,
loaderApi: DLCWalletNeutrinoBackendLoader) loaderApi: DLCWalletNeutrinoBackendLoader
extends WalletHolderWithLoaderApi ) extends WalletHolderWithLoaderApi
case class WalletHolderWithBitcoindLoaderApi( case class WalletHolderWithBitcoindLoaderApi(
walletHolder: WalletHolder, walletHolder: WalletHolder,
loaderApi: DLCWalletBitcoindBackendLoader) loaderApi: DLCWalletBitcoindBackendLoader
extends WalletHolderWithLoaderApi { ) extends WalletHolderWithLoaderApi {
val bitcoind: BitcoindRpcClient = loaderApi.bitcoind val bitcoind: BitcoindRpcClient = loaderApi.bitcoind
} }

View File

@ -69,8 +69,8 @@ object WebsocketUtil extends BitcoinSLogger {
private def sendHeadersToWs( private def sendHeadersToWs(
notifications: Vector[ChainNotification[_]], notifications: Vector[ChainNotification[_]],
queue: SourceQueueWithComplete[WsNotification[_]])(implicit queue: SourceQueueWithComplete[WsNotification[_]]
ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
for { for {
_ <- FutureUtil.sequentially(notifications) { case msg => _ <- FutureUtil.sequentially(notifications) { case msg =>
val x: Future[Unit] = queue val x: Future[Unit] = queue
@ -83,9 +83,11 @@ object WebsocketUtil extends BitcoinSLogger {
def buildChainCallbacks( def buildChainCallbacks(
queue: SourceQueueWithComplete[WsNotification[_]], queue: SourceQueueWithComplete[WsNotification[_]],
chainApi: ChainApi)(implicit chainApi: ChainApi
)(implicit
ec: ExecutionContext, ec: ExecutionContext,
chainAppConfig: ChainAppConfig): ChainCallbacks = { chainAppConfig: ChainAppConfig
): ChainCallbacks = {
val onBlockProcessed: OnBlockHeaderConnected = { val onBlockProcessed: OnBlockHeaderConnected = {
case headersWithHeight: Vector[(Int, BlockHeader)] => case headersWithHeight: Vector[(Int, BlockHeader)] =>
val hashes: Vector[DoubleSha256DigestBE] = val hashes: Vector[DoubleSha256DigestBE] =
@ -98,7 +100,7 @@ object WebsocketUtil extends BitcoinSLogger {
chainAppConfig.ibdBlockProcessedEvents chainAppConfig.ibdBlockProcessedEvents
isIBDF.flatMap { isIBD => isIBDF.flatMap { isIBD =>
if (isIBD && !emitBlockProccessedWhileIBDOnGoing) { if (isIBD && !emitBlockProccessedWhileIBDOnGoing) {
//only emit the last header so that we don't overwhelm the UI // only emit the last header so that we don't overwhelm the UI
for { for {
results <- resultsF results <- resultsF
notification = BlockProcessedNotification(results.last) notification = BlockProcessedNotification(results.last)
@ -166,15 +168,16 @@ object WebsocketUtil extends BitcoinSLogger {
ChainCallbacks.onBlockHeaderConnected(onBlockProcessed) + ChainCallbacks.onBlockHeaderConnected(onBlockProcessed) +
ChainCallbacks.onOnSyncFlagChanged(onSyncFlagChanged) + ChainCallbacks.onOnSyncFlagChanged(onSyncFlagChanged) +
ChainCallbacks.onCompactFilterHeaderConnected( ChainCallbacks.onCompactFilterHeaderConnected(
onCompactFilterHeaderProcessed) + onCompactFilterHeaderProcessed
) +
ChainCallbacks.onCompactFilterConnected(onCompactFilterProcessed) ChainCallbacks.onCompactFilterConnected(onCompactFilterProcessed)
} }
/** Builds websocket callbacks for the wallet */ /** Builds websocket callbacks for the wallet */
def buildWalletCallbacks( def buildWalletCallbacks(
walletQueue: SourceQueueWithComplete[WsNotification[_]], walletQueue: SourceQueueWithComplete[WsNotification[_]],
walletName: String)(implicit walletName: String
system: ActorSystem): WalletCallbackStreamManager = { )(implicit system: ActorSystem): WalletCallbackStreamManager = {
import system.dispatcher import system.dispatcher
val onAddressCreated: OnNewAddressGenerated = { addr => val onAddressCreated: OnNewAddressGenerated = { addr =>
val notification = WalletNotification.NewAddressNotification(addr) val notification = WalletNotification.NewAddressNotification(addr)
@ -183,15 +186,19 @@ object WebsocketUtil extends BitcoinSLogger {
} }
val onTxProcessed: OnTransactionProcessed = { tx => val onTxProcessed: OnTransactionProcessed = { tx =>
buildTxNotification(wsType = WalletWsType.TxProcessed, buildTxNotification(
tx = tx, wsType = WalletWsType.TxProcessed,
walletQueue = walletQueue) tx = tx,
walletQueue = walletQueue
)
} }
val onTxBroadcast: OnTransactionBroadcast = { tx => val onTxBroadcast: OnTransactionBroadcast = { tx =>
buildTxNotification(wsType = WalletWsType.TxBroadcast, buildTxNotification(
tx = tx, wsType = WalletWsType.TxBroadcast,
walletQueue = walletQueue) tx = tx,
walletQueue = walletQueue
)
} }
val onReservedUtxo: OnReservedUtxos = { utxos => val onReservedUtxo: OnReservedUtxos = { utxos =>
@ -226,8 +233,9 @@ object WebsocketUtil extends BitcoinSLogger {
WalletCallbackStreamManager(callbacks = callbacks) WalletCallbackStreamManager(callbacks = callbacks)
} }
def buildTorCallbacks(queue: SourceQueueWithComplete[WsNotification[_]])( def buildTorCallbacks(
implicit ec: ExecutionContext): TorCallbacks = { queue: SourceQueueWithComplete[WsNotification[_]]
)(implicit ec: ExecutionContext): TorCallbacks = {
val onTorStarted: OnTorStarted = { _ => val onTorStarted: OnTorStarted = { _ =>
val notification = TorStartedNotification val notification = TorStartedNotification
val offerF = queue.offer(notification) val offerF = queue.offer(notification)
@ -240,8 +248,8 @@ object WebsocketUtil extends BitcoinSLogger {
private def buildTxNotification( private def buildTxNotification(
wsType: WalletWsType, wsType: WalletWsType,
tx: Transaction, tx: Transaction,
walletQueue: SourceQueueWithComplete[WsNotification[_]])(implicit walletQueue: SourceQueueWithComplete[WsNotification[_]]
ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
val notification = wsType match { val notification = wsType match {
case WalletWsType.TxProcessed => case WalletWsType.TxProcessed =>
WalletNotification.TxProcessedNotification(tx) WalletNotification.TxProcessedNotification(tx)
@ -259,8 +267,8 @@ object WebsocketUtil extends BitcoinSLogger {
} }
def buildDLCWalletCallbacks( def buildDLCWalletCallbacks(
walletQueue: SourceQueueWithComplete[WsNotification[_]])(implicit walletQueue: SourceQueueWithComplete[WsNotification[_]]
system: ActorSystem): DLCWalletCallbackStreamManager = { )(implicit system: ActorSystem): DLCWalletCallbackStreamManager = {
import system.dispatcher import system.dispatcher
val onStateChange: OnDLCStateChange = { status: DLCStatus => val onStateChange: OnDLCStateChange = { status: DLCStatus =>
val notification = WalletNotification.DLCStateChangeNotification(status) val notification = WalletNotification.DLCStateChangeNotification(status)
@ -284,14 +292,15 @@ object WebsocketUtil extends BitcoinSLogger {
import DLCWalletCallbacks._ import DLCWalletCallbacks._
val callbacks = onDLCStateChange(onStateChange) + onDLCOfferAdd( val callbacks = onDLCStateChange(onStateChange) + onDLCOfferAdd(
onOfferAdd) + onDLCOfferRemove(onOfferRemove) onOfferAdd
) + onDLCOfferRemove(onOfferRemove)
DLCWalletCallbackStreamManager(callbacks) DLCWalletCallbackStreamManager(callbacks)
} }
def buildDLCNodeCallbacks( def buildDLCNodeCallbacks(
walletQueue: SourceQueueWithComplete[WsNotification[_]])(implicit walletQueue: SourceQueueWithComplete[WsNotification[_]]
ec: ExecutionContext): DLCNodeCallbacks = { )(implicit ec: ExecutionContext): DLCNodeCallbacks = {
val onConnectionInitiated: OnPeerConnectionInitiated = { payload => val onConnectionInitiated: OnPeerConnectionInitiated = { payload =>
val notification = val notification =

View File

@ -12,7 +12,8 @@ abstract class AsyncUtil extends AsyncUtilApi {
private def retryRunnable( private def retryRunnable(
condition: => Boolean, condition: => Boolean,
p: Promise[Boolean]): Runnable = p: Promise[Boolean]
): Runnable =
new Runnable { new Runnable {
override def run(): Unit = { override def run(): Unit = {
@ -24,8 +25,8 @@ abstract class AsyncUtil extends AsyncUtilApi {
def retryUntilSatisfied( def retryUntilSatisfied(
condition: => Boolean, condition: => Boolean,
interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL, interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL,
maxTries: Int = DEFAULT_MAX_TRIES)(implicit maxTries: Int = DEFAULT_MAX_TRIES
ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
val f = () => Future(condition) val f = () => Future(condition)
retryUntilSatisfiedF(f, interval, maxTries) retryUntilSatisfiedF(f, interval, maxTries)
} }
@ -35,32 +36,43 @@ abstract class AsyncUtil extends AsyncUtilApi {
case object Exponential extends RetryMode case object Exponential extends RetryMode
/** The returned Future completes when condition becomes true /** The returned Future completes when condition becomes true
* @param conditionF The condition being waited on * @param conditionF
* @param duration The interval between calls to check condition * The condition being waited on
* @param maxTries If condition is tried this many times, the Future fails * @param duration
* @param system An ActorSystem to schedule calls to condition * The interval between calls to check condition
* @return A Future[Unit] that succeeds if condition becomes true and fails otherwise * @param maxTries
* If condition is tried this many times, the Future fails
* @param system
* An ActorSystem to schedule calls to condition
* @return
* A Future[Unit] that succeeds if condition becomes true and fails
* otherwise
*/ */
def retryUntilSatisfiedF( def retryUntilSatisfiedF(
conditionF: () => Future[Boolean], conditionF: () => Future[Boolean],
interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL, interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL,
maxTries: Int = DEFAULT_MAX_TRIES, maxTries: Int = DEFAULT_MAX_TRIES,
mode: RetryMode = Linear)(implicit ec: ExecutionContext): Future[Unit] = { mode: RetryMode = Linear
)(implicit ec: ExecutionContext): Future[Unit] = {
if (mode == Exponential) { if (mode == Exponential) {
val millis = interval.toMillis val millis = interval.toMillis
if (millis > 0) { if (millis > 0) {
require((millis << maxTries) > 0, require(
s"Too many tries for retryUntilSatisfied(): $maxTries") (millis << maxTries) > 0,
s"Too many tries for retryUntilSatisfied(): $maxTries"
)
} }
} }
val stackTrace: Array[StackTraceElement] = val stackTrace: Array[StackTraceElement] =
Thread.currentThread().getStackTrace Thread.currentThread().getStackTrace
retryUntilSatisfiedWithCounter(conditionF = conditionF, retryUntilSatisfiedWithCounter(
interval = interval, conditionF = conditionF,
maxTries = maxTries, interval = interval,
stackTrace = stackTrace, maxTries = maxTries,
mode = mode) stackTrace = stackTrace,
mode = mode
)
} }
// Has a different name so that default values are permitted // Has a different name so that default values are permitted
@ -70,14 +82,18 @@ abstract class AsyncUtil extends AsyncUtilApi {
counter: Int = 0, counter: Int = 0,
maxTries: Int, maxTries: Int,
stackTrace: Array[StackTraceElement], stackTrace: Array[StackTraceElement],
mode: RetryMode)(implicit ec: ExecutionContext): Future[Unit] = { mode: RetryMode
)(implicit ec: ExecutionContext): Future[Unit] = {
conditionF().flatMap { condition => conditionF().flatMap { condition =>
if (condition) { if (condition) {
Future.unit Future.unit
} else if (counter == maxTries) { } else if (counter == maxTries) {
Future.failed(AsyncUtil.RpcRetryException( Future.failed(
s"Condition timed out after $maxTries attempts with interval=$interval waiting periods", AsyncUtil.RpcRetryException(
stackTrace)) s"Condition timed out after $maxTries attempts with interval=$interval waiting periods",
stackTrace
)
)
} else { } else {
val p = Promise[Boolean]() val p = Promise[Boolean]()
val runnable = retryRunnable(condition, p) val runnable = retryRunnable(condition, p)
@ -92,32 +108,38 @@ abstract class AsyncUtil extends AsyncUtilApi {
p.future.flatMap { p.future.flatMap {
case true => Future.unit case true => Future.unit
case false => case false =>
retryUntilSatisfiedWithCounter(conditionF = conditionF, retryUntilSatisfiedWithCounter(
interval = interval, conditionF = conditionF,
counter = counter + 1, interval = interval,
maxTries = maxTries, counter = counter + 1,
stackTrace = stackTrace, maxTries = maxTries,
mode = mode) stackTrace = stackTrace,
mode = mode
)
} }
} }
} }
} }
/** Returns a future that resolved when the condition becomes true, the condition /** Returns a future that resolved when the condition becomes true, the
* is checked maxTries times, or overallTimeout is reached * condition is checked maxTries times, or overallTimeout is reached
* @param condition The blocking condition * @param condition
* @param duration The interval between calls to check condition * The blocking condition
* @param maxTries If condition is tried this many times, an exception is thrown * @param duration
* @param system An ActorSystem to schedule calls to condition * The interval between calls to check condition
* @param maxTries
* If condition is tried this many times, an exception is thrown
* @param system
* An ActorSystem to schedule calls to condition
*/ */
def awaitCondition( def awaitCondition(
condition: () => Boolean, condition: () => Boolean,
interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL, interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL,
maxTries: Int = DEFAULT_MAX_TRIES)(implicit maxTries: Int = DEFAULT_MAX_TRIES
ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
//type hackery here to go from () => Boolean to () => Future[Boolean] // type hackery here to go from () => Boolean to () => Future[Boolean]
//to make sure we re-evaluate every time retryUntilSatisfied is called // to make sure we re-evaluate every time retryUntilSatisfied is called
def conditionDef: Boolean = condition() def conditionDef: Boolean = condition()
val conditionF: () => Future[Boolean] = () => Future(conditionDef) val conditionF: () => Future[Boolean] = () => Future(conditionDef)
@ -127,21 +149,25 @@ abstract class AsyncUtil extends AsyncUtilApi {
def awaitConditionF( def awaitConditionF(
conditionF: () => Future[Boolean], conditionF: () => Future[Boolean],
interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL, interval: FiniteDuration = AsyncUtil.DEFAULT_INTERVAL,
maxTries: Int = DEFAULT_MAX_TRIES)(implicit maxTries: Int = DEFAULT_MAX_TRIES
ec: ExecutionContext): Future[Unit] = { )(implicit ec: ExecutionContext): Future[Unit] = {
retryUntilSatisfiedF(conditionF = conditionF, retryUntilSatisfiedF(
interval = interval, conditionF = conditionF,
maxTries = maxTries) interval = interval,
maxTries = maxTries
)
} }
override def nonBlockingSleep(duration: FiniteDuration): Future[Unit] = { override def nonBlockingSleep(duration: FiniteDuration): Future[Unit] = {
val p = Promise[Unit]() val p = Promise[Unit]()
val r: Runnable = () => p.success(()) val r: Runnable = () => p.success(())
AsyncUtil.scheduler.scheduleOnce(duration.toMillis, AsyncUtil.scheduler.scheduleOnce(
TimeUnit.MILLISECONDS, duration.toMillis,
r) TimeUnit.MILLISECONDS,
r
)
p.future p.future
} }
} }
@ -150,8 +176,8 @@ object AsyncUtil extends AsyncUtil {
case class RpcRetryException( case class RpcRetryException(
message: String, message: String,
caller: Array[StackTraceElement]) caller: Array[StackTraceElement]
extends Exception(message) { ) extends Exception(message) {
/* /*
Someone who calls a method in this class will be interested Someone who calls a method in this class will be interested
@ -161,10 +187,12 @@ object AsyncUtil extends AsyncUtil {
* *
* This trims the top of the stack trace to exclude these internal calls. * This trims the top of the stack trace to exclude these internal calls.
*/ */
val internalFiles: Vector[String] = Vector("AsyncUtil.scala", val internalFiles: Vector[String] = Vector(
"RpcUtil.scala", "AsyncUtil.scala",
"TestAsyncUtil.scala", "RpcUtil.scala",
"TestRpcUtil.scala") "TestAsyncUtil.scala",
"TestRpcUtil.scala"
)
private val relevantStackTrace = private val relevantStackTrace =
caller.tail.dropWhile(elem => internalFiles.contains(elem.getFileName)) caller.tail.dropWhile(elem => internalFiles.contains(elem.getFileName))
@ -182,7 +210,9 @@ object AsyncUtil extends AsyncUtil {
*/ */
private[bitcoins] val DEFAULT_MAX_TRIES: Int = 50 private[bitcoins] val DEFAULT_MAX_TRIES: Int = 50
/** Gives you a thread factory with the given prefix with a counter appended to the name */ /** Gives you a thread factory with the given prefix with a counter appended
* to the name
*/
def getNewThreadFactory(prefix: String): ThreadFactory = { def getNewThreadFactory(prefix: String): ThreadFactory = {
new ThreadFactory { new ThreadFactory {
private val atomicInteger = new AtomicInteger(0) private val atomicInteger = new AtomicInteger(0)

View File

@ -35,5 +35,5 @@ object BlockBench extends App {
0.until(10).foreach(_ => bench1()) 0.until(10).foreach(_ => bench1())
//bench2() // bench2()
} }

View File

@ -13,13 +13,15 @@ import scala.concurrent.Future
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
/** This test spins up one test node and [[NetworkSize]] sender nodes, which open channels with the test one. /** This test spins up one test node and [[NetworkSize]] sender nodes, which
* Then each sender node sends [[PaymentCount]] payments to the test node one by one. For each payment the * open channels with the test one. Then each sender node sends
* test node generates an invoice and the send node pays it using `sendtonode` API call. * [[PaymentCount]] payments to the test node one by one. For each payment the
* test node generates an invoice and the send node pays it using `sendtonode`
* API call.
* *
* The test keeps track of times when a payment was initiated, when the payment ID was received, * The test keeps track of times when a payment was initiated, when the payment
* and when the corresponding web socket event was received. It writes all results into [[OutputFileName]] * ID was received, and when the corresponding web socket event was received.
* in CSV format. * It writes all results into [[OutputFileName]] in CSV format.
*/ */
object EclairBench extends App with EclairRpcTestUtil { object EclairBench extends App with EclairRpcTestUtil {
@ -72,7 +74,8 @@ object EclairBench extends App with EclairRpcTestUtil {
def sendPayments( def sendPayments(
network: EclairNetwork, network: EclairNetwork,
amount: MilliSatoshis, amount: MilliSatoshis,
count: Int): Future[Vector[PaymentId]] = count: Int
): Future[Vector[PaymentId]] =
for { for {
_ <- network.testEclairNode.getInfo _ <- network.testEclairNode.getInfo
paymentIds <- Future.sequence(network.networkEclairNodes.map { node => paymentIds <- Future.sequence(network.networkEclairNodes.map { node =>
@ -104,22 +107,26 @@ object EclairBench extends App with EclairRpcTestUtil {
val _ = logEvent(event) val _ = logEvent(event)
} }
_ = println( _ = println(
s"Set up $NetworkSize nodes, that will send $PaymentCount payments to the test node each") s"Set up $NetworkSize nodes, that will send $PaymentCount payments to the test node each"
)
_ = println( _ = println(
s"Test node data directory: ${network.testEclairNode.instance.authCredentials.datadir s"Test node data directory: ${network.testEclairNode.instance.authCredentials.datadir
.getOrElse("")}") .getOrElse("")}"
)
_ = println("Testing...") _ = println("Testing...")
_ <- sendPayments(network, PaymentAmount, PaymentCount) _ <- sendPayments(network, PaymentAmount, PaymentCount)
_ <- TestAsyncUtil.retryUntilSatisfied( _ <- TestAsyncUtil.retryUntilSatisfied(
condition = paymentLog.size() == NetworkSize * PaymentCount, condition = paymentLog.size() == NetworkSize * PaymentCount,
interval = 1.second, interval = 1.second,
maxTries = 100) maxTries = 100
)
_ <- _ <-
TestAsyncUtil TestAsyncUtil
.retryUntilSatisfied( .retryUntilSatisfied(
condition = EclairBenchUtil.paymentLogValues().forall(_.completed), condition = EclairBenchUtil.paymentLogValues().forall(_.completed),
interval = 1.second, interval = 1.second,
maxTries = 100) maxTries = 100
)
.recover { case ex: Throwable => ex.printStackTrace() } .recover { case ex: Throwable => ex.printStackTrace() }
_ = println("\nDone!") _ = println("\nDone!")
} yield { } yield {
@ -128,13 +135,15 @@ object EclairBench extends App with EclairRpcTestUtil {
} }
val res: Future[Unit] = for { val res: Future[Unit] = for {
network <- EclairNetwork.start(TestEclairVersion, network <- EclairNetwork.start(
TestEclairCommit, TestEclairVersion,
SenderEclairVersion, TestEclairCommit,
SenderEclairCommit, SenderEclairVersion,
NetworkSize, SenderEclairCommit,
ChannelAmount, NetworkSize,
LogbackXml) ChannelAmount,
LogbackXml
)
log <- runTests(network).recoverWith { case e: Throwable => log <- runTests(network).recoverWith { case e: Throwable =>
e.printStackTrace() e.printStackTrace()
Future.successful(Vector.empty[PaymentLogEntry]) Future.successful(Vector.empty[PaymentLogEntry])
@ -145,17 +154,20 @@ object EclairBench extends App with EclairRpcTestUtil {
val first = log.head val first = log.head
val csv = val csv =
Vector( Vector(
"time,number_of_payments,payment_hash,payment_id,event,payment_sent_at,payment_id_received_at,event_received_at,received_in,completed_in") ++ "time,number_of_payments,payment_hash,payment_id,event,payment_sent_at,payment_id_received_at,event_received_at,received_in,completed_in"
) ++
log.zipWithIndex log.zipWithIndex
.map { case (x, i) => .map { case (x, i) =>
s"${x.paymentSentAt - first.paymentSentAt},${i + 1},${x.toCSV}" s"${x.paymentSentAt - first.paymentSentAt},${i + 1},${x.toCSV}"
} }
val outputFile = new File(OutputFileName) val outputFile = new File(OutputFileName)
Files.write(outputFile.toPath, Files.write(
EclairBenchUtil.convertStrings(csv), outputFile.toPath,
StandardOpenOption.CREATE, EclairBenchUtil.convertStrings(csv),
StandardOpenOption.WRITE, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING) StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING
)
println(s"The test results was written in ${outputFile.getAbsolutePath}") println(s"The test results was written in ${outputFile.getAbsolutePath}")
} }
} }

View File

@ -22,11 +22,14 @@ object PaymentLog {
event: Option[WebSocketEvent] = None, event: Option[WebSocketEvent] = None,
paymentSentAt: Long, paymentSentAt: Long,
paymentIdReceivedAt: Long = 0, paymentIdReceivedAt: Long = 0,
eventReceivedAt: Long = 0) { eventReceivedAt: Long = 0
) {
def withPaymentHash(paymentHash: Sha256Digest): PaymentLogEntry = def withPaymentHash(paymentHash: Sha256Digest): PaymentLogEntry =
copy(paymentHash = Some(paymentHash), copy(
paymentSentAt = System.currentTimeMillis()) paymentHash = Some(paymentHash),
paymentSentAt = System.currentTimeMillis()
)
def withPaymentId(id: PaymentId): PaymentLogEntry = def withPaymentId(id: PaymentId): PaymentLogEntry =
copy(id = Some(id), paymentIdReceivedAt = System.currentTimeMillis()) copy(id = Some(id), paymentIdReceivedAt = System.currentTimeMillis())
@ -47,7 +50,8 @@ object PaymentLog {
part.timestamp.toEpochMilli part.timestamp.toEpochMilli
case None => case None =>
throw new RuntimeException( throw new RuntimeException(
s"PaymentReceived but with no parts, got $e") s"PaymentReceived but with no parts, got $e"
)
} }
case PaymentFailed(_, _, _, timestamp) => timestamp.toEpochMilli case PaymentFailed(_, _, _, timestamp) => timestamp.toEpochMilli
case _: WebSocketEvent => case _: WebSocketEvent =>
@ -57,18 +61,21 @@ object PaymentLog {
def toCSV: String = def toCSV: String =
s"""${paymentHash s"""${paymentHash
.map(_.hex) .map(_.hex)
.getOrElse("")},${id.map(_.toString).getOrElse("")},${event .getOrElse("")},${id.map(_.toString).getOrElse("")},${event
.map(_.getClass.getName.split('$').last) .map(_.getClass.getName.split('$').last)
.getOrElse( .getOrElse(
"")},$paymentSentAt,$paymentIdReceivedAt,$eventReceivedAt,${paymentIdReceivedAt - paymentSentAt},${eventReceivedAt - paymentIdReceivedAt}""" ""
)},$paymentSentAt,$paymentIdReceivedAt,$eventReceivedAt,${paymentIdReceivedAt - paymentSentAt},${eventReceivedAt - paymentIdReceivedAt}"""
} }
object PaymentLogEntry { object PaymentLogEntry {
def apply(paymentHash: Sha256Digest): PaymentLogEntry = { def apply(paymentHash: Sha256Digest): PaymentLogEntry = {
PaymentLogEntry(paymentSentAt = System.currentTimeMillis(), PaymentLogEntry(
paymentHash = Some(paymentHash)) paymentSentAt = System.currentTimeMillis(),
paymentHash = Some(paymentHash)
)
} }
} }
@ -87,13 +94,15 @@ object PaymentLog {
def logPaymentId( def logPaymentId(
paymentHash: Sha256Digest, paymentHash: Sha256Digest,
paymentId: PaymentId): PaymentLogEntry = { paymentId: PaymentId
): PaymentLogEntry = {
paymentLog.compute( paymentLog.compute(
paymentHash, paymentHash,
new BiFunction[Sha256Digest, PaymentLogEntry, PaymentLogEntry] { new BiFunction[Sha256Digest, PaymentLogEntry, PaymentLogEntry] {
override def apply( override def apply(
hash: Sha256Digest, hash: Sha256Digest,
old: PaymentLogEntry): PaymentLogEntry = { old: PaymentLogEntry
): PaymentLogEntry = {
val log = if (old == null) { val log = if (old == null) {
PaymentLogEntry(paymentSentAt = 0, paymentHash = Some(hash)) PaymentLogEntry(paymentSentAt = 0, paymentHash = Some(hash))
} else { } else {
@ -121,7 +130,8 @@ object PaymentLog {
new BiFunction[Sha256Digest, PaymentLogEntry, PaymentLogEntry] { new BiFunction[Sha256Digest, PaymentLogEntry, PaymentLogEntry] {
override def apply( override def apply(
hash: Sha256Digest, hash: Sha256Digest,
old: PaymentLogEntry): PaymentLogEntry = { old: PaymentLogEntry
): PaymentLogEntry = {
val log = if (old == null) { val log = if (old == null) {
PaymentLogEntry(paymentSentAt = 0, paymentHash = Some(hash)) PaymentLogEntry(paymentSentAt = 0, paymentHash = Some(hash))
} else { } else {
@ -136,7 +146,8 @@ object PaymentLog {
new BiFunction[Sha256Digest, Promise[Unit], Promise[Unit]] { new BiFunction[Sha256Digest, Promise[Unit], Promise[Unit]] {
override def apply( override def apply(
hash: Sha256Digest, hash: Sha256Digest,
old: Promise[Unit]): Promise[Unit] = { old: Promise[Unit]
): Promise[Unit] = {
val promise = if (old == null) { val promise = if (old == null) {
Promise[Unit]() Promise[Unit]()
} else { } else {

View File

@ -39,8 +39,8 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
pw.close() pw.close()
} }
/** Tests that the client can call the isStartedF method /** Tests that the client can call the isStartedF method without throwing and
* without throwing and then start * then start
*/ */
private def testClientStart(client: BitcoindRpcClient): Future[Assertion] = private def testClientStart(client: BitcoindRpcClient): Future[Assertion] =
synchronized { synchronized {
@ -71,7 +71,8 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
val instance = BitcoindInstanceLocal.fromConfig(conf, newestBitcoindBinary) val instance = BitcoindInstanceLocal.fromConfig(conf, newestBitcoindBinary)
assert( assert(
instance.authCredentials instance.authCredentials
.isInstanceOf[BitcoindAuthCredentials.CookieBased]) .isInstanceOf[BitcoindAuthCredentials.CookieBased]
)
val cli = BitcoindRpcClient.withActorSystem(instance) val cli = BitcoindRpcClient.withActorSystem(instance)
testClientStart(cli) testClientStart(cli)
@ -91,7 +92,8 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
val instance = BitcoindInstanceLocal.fromConfig(conf, newestBitcoindBinary) val instance = BitcoindInstanceLocal.fromConfig(conf, newestBitcoindBinary)
assert( assert(
instance.authCredentials instance.authCredentials
.isInstanceOf[BitcoindAuthCredentials.PasswordBased]) .isInstanceOf[BitcoindAuthCredentials.PasswordBased]
)
testClientStart(BitcoindRpcClient.withActorSystem(instance)) testClientStart(BitcoindRpcClient.withActorSystem(instance))
} }
@ -116,8 +118,10 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
val conf = BitcoindConfig(confStr, FileUtil.tmpDir()) val conf = BitcoindConfig(confStr, FileUtil.tmpDir())
val authCredentials = val authCredentials =
BitcoindAuthCredentials.PasswordBased(username = "bitcoin-s", BitcoindAuthCredentials.PasswordBased(
password = "strong_password") username = "bitcoin-s",
password = "strong_password"
)
val instance = val instance =
BitcoindInstanceLocal( BitcoindInstanceLocal(
network = RegTest, network = RegTest,
@ -167,8 +171,10 @@ class BitcoindInstanceTest extends BitcoindRpcTest {
val conf = BitcoindConfig(confStr, FileUtil.tmpDir()) val conf = BitcoindConfig(confStr, FileUtil.tmpDir())
val authCredentials = val authCredentials =
BitcoindAuthCredentials.PasswordBased(username = "bitcoin-s", BitcoindAuthCredentials.PasswordBased(
password = "strong_password") username = "bitcoin-s",
password = "strong_password"
)
val instance = val instance =
BitcoindInstanceLocal( BitcoindInstanceLocal(
network = RegTest, network = RegTest,

View File

@ -35,7 +35,8 @@ class TestRpcUtilTest extends BitcoindFixturesCachedPairNewest {
assert(dir.isDirectory) assert(dir.isDirectory)
assert(dir.getPath().startsWith(scala.util.Properties.tmpDir)) assert(dir.getPath().startsWith(scala.util.Properties.tmpDir))
assert( assert(
dir.listFiles.contains(new File(dir.getAbsolutePath + "/bitcoin.conf"))) dir.listFiles.contains(new File(dir.getAbsolutePath + "/bitcoin.conf"))
)
FileUtil.deleteTmpDir(dir) FileUtil.deleteTmpDir(dir)
assert(!dir.exists) assert(!dir.exists)
} }
@ -63,15 +64,18 @@ class TestRpcUtilTest extends BitcoindFixturesCachedPairNewest {
val allClients = nodes.toVector val allClients = nodes.toVector
for { for {
heightPreGeneration <- first.getBlockCount() heightPreGeneration <- first.getBlockCount()
_ <- BitcoindRpcTestUtil.generateAllAndSync(allClients, _ <- BitcoindRpcTestUtil.generateAllAndSync(
blocks = blocksToGenerate) allClients,
blocks = blocksToGenerate
)
firstHash <- first.getBestBlockHash() firstHash <- first.getBestBlockHash()
secondHash <- second.getBestBlockHash() secondHash <- second.getBestBlockHash()
heightPostGeneration <- first.getBlockCount() heightPostGeneration <- first.getBlockCount()
} yield { } yield {
assert(firstHash == secondHash) assert(firstHash == secondHash)
assert( assert(
heightPostGeneration - heightPreGeneration == blocksToGenerate * allClients.length) heightPostGeneration - heightPreGeneration == blocksToGenerate * allClients.length
)
} }
} }
@ -82,10 +86,12 @@ class TestRpcUtilTest extends BitcoindFixturesCachedPairNewest {
address <- second.getNewAddress address <- second.getNewAddress
txid <- first.sendToAddress(address, Bitcoins.one) txid <- first.sendToAddress(address, Bitcoins.one)
hashes <- BitcoindRpcTestUtil.generateAndSync(Vector(first, second)) hashes <- BitcoindRpcTestUtil.generateAndSync(Vector(first, second))
vout <- BitcoindRpcTestUtil.findOutput(first, vout <- BitcoindRpcTestUtil.findOutput(
txid, first,
Bitcoins.one, txid,
Some(hashes.head)) Bitcoins.one,
Some(hashes.head)
)
tx <- first.getRawTransaction(txid, Some(hashes.head)) tx <- first.getRawTransaction(txid, Some(hashes.head))
} yield { } yield {
assert(tx.vout(vout.toInt).value == Bitcoins.one) assert(tx.vout(vout.toInt).value == Bitcoins.one)

View File

@ -230,10 +230,13 @@ class BlockchainRpcTest extends BitcoindFixturesCachedPairNewest {
txs <- Future.sequence( txs <- Future.sequence(
block.transactions block.transactions
.filterNot(_.isCoinbase) .filterNot(_.isCoinbase)
.map(tx => client.getTransaction(tx.txIdBE))) .map(tx => client.getTransaction(tx.txIdBE))
)
prevFilter <- client.getBlockFilter(block.blockHeader.previousBlockHashBE, prevFilter <- client.getBlockFilter(
FilterType.Basic) block.blockHeader.previousBlockHashBE,
FilterType.Basic
)
} yield { } yield {
val pubKeys = txs.flatMap(_.hex.outputs.map(_.scriptPubKey)).toVector val pubKeys = txs.flatMap(_.hex.outputs.map(_.scriptPubKey)).toVector
val filter = BlockFilter(block, pubKeys) val filter = BlockFilter(block, pubKeys)
@ -242,7 +245,8 @@ class BlockchainRpcTest extends BitcoindFixturesCachedPairNewest {
blockFilter.header == filter blockFilter.header == filter
.getHeader(prevFilter.header.flip) .getHeader(prevFilter.header.flip)
.hash .hash
.flip) .flip
)
} }
} }
} }

View File

@ -132,17 +132,21 @@ class MempoolRpcTest extends BitcoindFixturesCachedPairNewest {
for { for {
_ <- client.generate(1) _ <- client.generate(1)
address1 <- client.getNewAddress address1 <- client.getNewAddress
txid1 <- BitcoindRpcTestUtil.fundMemPoolTransaction(client, txid1 <- BitcoindRpcTestUtil.fundMemPoolTransaction(
address1, client,
Bitcoins(2)) address1,
Bitcoins(2)
)
mempool <- client.getRawMemPool mempool <- client.getRawMemPool
address2 <- client.getNewAddress address2 <- client.getNewAddress
createdTx <- { createdTx <- {
val input: TransactionInput = val input: TransactionInput =
TransactionInput(TransactionOutPoint(txid1.flip, UInt32.zero), TransactionInput(
ScriptSignature.empty, TransactionOutPoint(txid1.flip, UInt32.zero),
UInt32.max - UInt32.one) ScriptSignature.empty,
UInt32.max - UInt32.one
)
client client
.createRawTransaction(Vector(input), Map(address2 -> Bitcoins.one)) .createRawTransaction(Vector(input), Map(address2 -> Bitcoins.one))
} }

View File

@ -37,8 +37,10 @@ class MessageRpcTest extends BitcoindRpcTest {
for { for {
client <- clientF client <- clientF
signature <- client.signMessageWithPrivKey(privKey.toPrivateKeyBytes(), signature <- client.signMessageWithPrivKey(
message) privKey.toPrivateKeyBytes(),
message
)
validity <- client.verifyMessage(address, signature, message) validity <- client.verifyMessage(address, signature, message)
} yield assert(validity) } yield assert(validity)
} }

View File

@ -56,12 +56,15 @@ class MiningRpcTest extends BitcoindRpcTest {
TransactionInput( TransactionInput(
TransactionOutPoint(output.txid.flip, UInt32(output.vout)), TransactionOutPoint(output.txid.flip, UInt32(output.vout)),
ScriptSignature.empty, ScriptSignature.empty,
UInt32.max - UInt32(2)) UInt32.max - UInt32(2)
)
val inputs = Vector(input) val inputs = Vector(input)
val outputs = val outputs =
Map(address -> Bitcoins(0.5), Map(
changeAddress -> Bitcoins(output.amount.toBigDecimal - 0.55)) address -> Bitcoins(0.5),
changeAddress -> Bitcoins(output.amount.toBigDecimal - 0.55)
)
client.createRawTransaction(inputs, outputs) client.createRawTransaction(inputs, outputs)
} }
@ -97,7 +100,9 @@ class MiningRpcTest extends BitcoindRpcTest {
foundBlocks.foreach { case found: GetBlockWithTransactionsResultV22 => foundBlocks.foreach { case found: GetBlockWithTransactionsResultV22 =>
assert( assert(
found.tx.exists( found.tx.exists(
_.vout.exists(_.scriptPubKey.address == Some(address)))) _.vout.exists(_.scriptPubKey.address == Some(address))
)
)
} }
succeed succeed
} }

View File

@ -23,7 +23,9 @@ import java.io.File
import scala.concurrent.Future import scala.concurrent.Future
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
/** These tests are all copied over from WalletRpcTest and changed to be for multi-wallet */ /** These tests are all copied over from WalletRpcTest and changed to be for
* multi-wallet
*/
class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest { class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
val walletName = "other" val walletName = "other"
@ -43,9 +45,12 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
clientsF.flatMap(setupWalletClient) clientsF.flatMap(setupWalletClient)
} }
/** We need to test bitcoin core's wallet specific features, so we need to set that up */ /** We need to test bitcoin core's wallet specific features, so we need to set
private def setupWalletClient(pair: NodePair[BitcoindRpcClient]): Future[ * that up
NodePair[BitcoindRpcClient]] = { */
private def setupWalletClient(
pair: NodePair[BitcoindRpcClient]
): Future[NodePair[BitcoindRpcClient]] = {
val NodePair(client: BitcoindRpcClient, walletClient: BitcoindRpcClient) = val NodePair(client: BitcoindRpcClient, walletClient: BitcoindRpcClient) =
pair pair
for { for {
@ -228,9 +233,11 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
for { for {
address <- otherClient.getNewAddress(Some(walletName)) address <- otherClient.getNewAddress(Some(walletName))
_ <- client.walletPassphrase(password, 1000, Some(walletName)) _ <- client.walletPassphrase(password, 1000, Some(walletName))
txid <- client.sendToAddress(address, txid <- client.sendToAddress(
Bitcoins(1), address,
walletNameOpt = Some(walletName)) Bitcoins(1),
walletNameOpt = Some(walletName)
)
transaction <- transaction <-
client.getTransaction(txid, walletNameOpt = Some(walletName)) client.getTransaction(txid, walletNameOpt = Some(walletName))
} yield { } yield {
@ -248,8 +255,10 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
_ <- client.walletPassphrase(password, 1000, Some(walletName)) _ <- client.walletPassphrase(password, 1000, Some(walletName))
txid <- txid <-
client client
.sendMany(Map(address1 -> Bitcoins(1), address2 -> Bitcoins(2)), .sendMany(
walletNameOpt = Some(walletName)) Map(address1 -> Bitcoins(1), address2 -> Bitcoins(2)),
walletNameOpt = Some(walletName)
)
transaction <- transaction <-
client.getTransaction(txid, walletNameOpt = Some(walletName)) client.getTransaction(txid, walletNameOpt = Some(walletName))
} yield { } yield {
@ -286,20 +295,27 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
for { for {
firstResult <- firstResult <-
client client
.createMultiSig(2, .createMultiSig(
Vector(privKey1.publicKey, privKey2.publicKey), 2,
AddressType.Bech32, Vector(privKey1.publicKey, privKey2.publicKey),
walletNameOpt = Some(walletName)) AddressType.Bech32,
walletNameOpt = Some(walletName)
)
address2 = firstResult.address address2 = firstResult.address
secondResult <- secondResult <-
client client
.importMulti( .importMulti(
Vector( Vector(
RpcOpts.ImportMultiRequest(RpcOpts.ImportMultiAddress(address1), RpcOpts.ImportMultiRequest(
UInt32(0)), RpcOpts.ImportMultiAddress(address1),
RpcOpts.ImportMultiRequest(RpcOpts.ImportMultiAddress(address2), UInt32(0)
UInt32(0))), ),
RpcOpts.ImportMultiRequest(
RpcOpts.ImportMultiAddress(address2),
UInt32(0)
)
),
rescan = false, rescan = false,
walletNameOpt = Some(walletName) walletNameOpt = Some(walletName)
) )
@ -343,7 +359,9 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairNewest {
assert(transaction.inputs.length == 1) assert(transaction.inputs.length == 1)
assert( assert(
transaction.outputs.contains( transaction.outputs.contains(
TransactionOutput(Bitcoins(1), address.scriptPubKey))) TransactionOutput(Bitcoins(1), address.scriptPubKey)
)
)
} }
} }

View File

@ -15,8 +15,10 @@ class MultisigRpcTest extends BitcoindRpcTest {
BitcoindRpcTestUtil.instance(versionOpt = Some(BitcoindVersion.newest)) BitcoindRpcTestUtil.instance(versionOpt = Some(BitcoindVersion.newest))
lazy val clientF: Future[BitcoindRpcClient] = lazy val clientF: Future[BitcoindRpcClient] =
BitcoindRpcTestUtil.startedBitcoindRpcClient(instanceOpt = Some(instance), BitcoindRpcTestUtil.startedBitcoindRpcClient(
clientAccum = clientAccum) instanceOpt = Some(instance),
clientAccum = clientAccum
)
behavior of "MultisigRpc" behavior of "MultisigRpc"
@ -29,9 +31,11 @@ class MultisigRpcTest extends BitcoindRpcTest {
for { for {
client <- clientF client <- clientF
_ <- client.createMultiSig(2, _ <- client.createMultiSig(
Vector(pubKey1, pubKey2), 2,
AddressType.Bech32) Vector(pubKey1, pubKey2),
AddressType.Bech32
)
} yield succeed } yield succeed
} }

View File

@ -98,14 +98,20 @@ class RawTransactionRpcTest extends BitcoindRpcTest {
address <- otherClient.getNewAddress address <- otherClient.getNewAddress
input0 = TransactionOutPoint(transaction0.txid.flip, input0 = TransactionOutPoint(
UInt32(transaction0.blockindex.get)) transaction0.txid.flip,
input1 = TransactionOutPoint(transaction1.txid.flip, UInt32(transaction0.blockindex.get)
UInt32(transaction1.blockindex.get)) )
input1 = TransactionOutPoint(
transaction1.txid.flip,
UInt32(transaction1.blockindex.get)
)
transaction <- { transaction <- {
val sig: ScriptSignature = ScriptSignature.empty val sig: ScriptSignature = ScriptSignature.empty
val inputs = Vector(TransactionInput(input0, sig, UInt32(1)), val inputs = Vector(
TransactionInput(input1, sig, UInt32(2))) TransactionInput(input0, sig, UInt32(1)),
TransactionInput(input1, sig, UInt32(2))
)
val outputs = Map(address -> Bitcoins(1)) val outputs = Map(address -> Bitcoins(1))
client.createRawTransaction(inputs, outputs) client.createRawTransaction(inputs, outputs)
} }
@ -150,12 +156,16 @@ class RawTransactionRpcTest extends BitcoindRpcTest {
newAddress <- client.getNewAddress newAddress <- client.getNewAddress
rawCreatedTx <- { rawCreatedTx <- {
val input = val input =
TransactionInput(TransactionOutPoint(txid.flip, UInt32(output.n)), TransactionInput(
EmptyScriptSignature, TransactionOutPoint(txid.flip, UInt32(output.n)),
UInt32.max - UInt32.one) EmptyScriptSignature,
UInt32.max - UInt32.one
)
client client
.createRawTransaction(Vector(input), .createRawTransaction(
Map(newAddress -> Bitcoins(sendAmt.satoshis))) Vector(input),
Map(newAddress -> Bitcoins(sendAmt.satoshis))
)
} }
result <- { result <- {
@ -166,7 +176,8 @@ class RawTransactionRpcTest extends BitcoindRpcTest {
scriptPubKey = ScriptPubKey.fromAsmHex(output.scriptPubKey.hex), scriptPubKey = ScriptPubKey.fromAsmHex(output.scriptPubKey.hex),
redeemScript = None, redeemScript = None,
amount = Some(fundAmt) amount = Some(fundAmt)
)) )
)
BitcoindRpcTestUtil.signRawTransaction( BitcoindRpcTestUtil.signRawTransaction(
client, client,
rawCreatedTx, rawCreatedTx,
@ -183,7 +194,8 @@ class RawTransactionRpcTest extends BitcoindRpcTest {
.createRawTransaction(Vector(), Map(address -> Bitcoins(1))) .createRawTransaction(Vector(), Map(address -> Bitcoins(1)))
.flatMap { tx => .flatMap { tx =>
recoverToSucceededIf[InvalidAddressOrKey]( recoverToSucceededIf[InvalidAddressOrKey](
client.abandonTransaction(tx.txId)) client.abandonTransaction(tx.txId)
)
} }
} }
} }

View File

@ -35,8 +35,10 @@ class UTXORpcTest extends BitcoindRpcTest {
val vout1 = unspent(0).vout val vout1 = unspent(0).vout
val vout2 = unspent(1).vout val vout2 = unspent(1).vout
Vector(RpcOpts.LockUnspentOutputParameter(txid1, vout1), Vector(
RpcOpts.LockUnspentOutputParameter(txid2, vout2)) RpcOpts.LockUnspentOutputParameter(txid1, vout1),
RpcOpts.LockUnspentOutputParameter(txid2, vout2)
)
} }
firstSuccess <- client.lockUnspent(unlock = false, param) firstSuccess <- client.lockUnspent(unlock = false, param)
locked <- client.listLockUnspent locked <- client.listLockUnspent

View File

@ -52,7 +52,8 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
lazy val walletClientF: Future[BitcoindRpcClient] = clientsF.flatMap { _ => lazy val walletClientF: Future[BitcoindRpcClient] = clientsF.flatMap { _ =>
val walletClient = val walletClient =
BitcoindRpcClient.withActorSystem( BitcoindRpcClient.withActorSystem(
BitcoindRpcTestUtil.instance(versionOpt = Some(BitcoindVersion.newest))) BitcoindRpcTestUtil.instance(versionOpt = Some(BitcoindVersion.newest))
)
for { for {
_ <- startClient(walletClient) _ <- startClient(walletClient)
@ -126,10 +127,12 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
for { for {
_ <- { _ <- {
val addrFuts = val addrFuts =
List(client.getNewAddress, List(
client.getNewAddress(AddressType.Bech32), client.getNewAddress,
client.getNewAddress(AddressType.P2SHSegwit), client.getNewAddress(AddressType.Bech32),
client.getNewAddress(AddressType.Legacy)) client.getNewAddress(AddressType.P2SHSegwit),
client.getNewAddress(AddressType.Legacy)
)
Future.sequence(addrFuts) Future.sequence(addrFuts)
} }
} yield succeed } yield succeed
@ -166,8 +169,10 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
val client = nodePair.node1 val client = nodePair.node1
for { for {
balance <- client.getUnconfirmedBalance balance <- client.getUnconfirmedBalance
transaction <- BitcoindRpcTestUtil.sendCoinbaseTransaction(client, transaction <- BitcoindRpcTestUtil.sendCoinbaseTransaction(
client) client,
client
)
newBalance <- client.getUnconfirmedBalance newBalance <- client.getUnconfirmedBalance
} yield { } yield {
assert(balance == Bitcoins(0)) assert(balance == Bitcoins(0))
@ -188,7 +193,7 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
} }
it should "be able to refill the keypool" ignore { nodePair: FixtureParam => it should "be able to refill the keypool" ignore { nodePair: FixtureParam =>
//ignore until: https://github.com/bitcoin/bitcoin/issues/29924 // ignore until: https://github.com/bitcoin/bitcoin/issues/29924
val client = nodePair.node1 val client = nodePair.node1
for { for {
info <- client.getWalletInfo info <- client.getWalletInfo
@ -228,15 +233,18 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
def getChangeAddressAndAmount( def getChangeAddressAndAmount(
client: BitcoindRpcClient, client: BitcoindRpcClient,
address: BitcoinAddress, address: BitcoinAddress,
txid: DoubleSha256DigestBE): Future[(BitcoinAddress, CurrencyUnit)] = { txid: DoubleSha256DigestBE
): Future[(BitcoinAddress, CurrencyUnit)] = {
for { for {
rawTx <- client.getRawTransactionRaw(txid) rawTx <- client.getRawTransactionRaw(txid)
} yield { } yield {
val outs = rawTx.outputs.filterNot(_.value == amount) val outs = rawTx.outputs.filterNot(_.value == amount)
val changeAddresses = outs val changeAddresses = outs
.map(out => .map(out =>
(BitcoinAddress.fromScriptPubKey(out.scriptPubKey, networkParam), (
out.value)) BitcoinAddress.fromScriptPubKey(out.scriptPubKey, networkParam),
out.value
))
assert(changeAddresses.size == 1) assert(changeAddresses.size == 1)
assert(changeAddresses.head._1 != address) assert(changeAddresses.head._1 != address)
(changeAddresses.head._1, changeAddresses.head._2) (changeAddresses.head._1, changeAddresses.head._2)
@ -248,10 +256,12 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
address <- client.getNewAddress address <- client.getNewAddress
txid <- BitcoindRpcTestUtil.fundBlockChainTransaction(client, txid <- BitcoindRpcTestUtil.fundBlockChainTransaction(
otherClient, client,
address, otherClient,
amount) address,
amount
)
(changeAddress, changeAmount) <- (changeAddress, changeAmount) <-
getChangeAddressAndAmount(client, address, txid) getChangeAddressAndAmount(client, address, txid)
@ -269,7 +279,8 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
// the change address should be added to an exiting address grouping // the change address should be added to an exiting address grouping
assert( assert(
!groupingsBefore.exists(vec => vec.exists(_.address == changeAddress))) !groupingsBefore.exists(vec => vec.exists(_.address == changeAddress))
)
val changeGroupingOpt = val changeGroupingOpt =
groupingsAfter.find(vec => vec.exists(_.address == changeAddress)) groupingsAfter.find(vec => vec.exists(_.address == changeAddress))
@ -324,10 +335,12 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
address <- otherClient.getNewAddress address <- otherClient.getNewAddress
txid <- txid <-
BitcoindRpcTestUtil BitcoindRpcTestUtil
.fundBlockChainTransaction(client, .fundBlockChainTransaction(
otherClient, client,
address, otherClient,
Bitcoins(1.5)) address,
Bitcoins(1.5)
)
receivedList <- otherClient.listReceivedByAddress() receivedList <- otherClient.listReceivedByAddress()
} yield { } yield {
val entryList = val entryList =
@ -348,10 +361,12 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
address <- otherClient.getNewAddress address <- otherClient.getNewAddress
txid <- txid <-
BitcoindRpcTestUtil BitcoindRpcTestUtil
.fundBlockChainTransaction(client, .fundBlockChainTransaction(
otherClient, client,
address, otherClient,
Bitcoins(1.5)) address,
Bitcoins(1.5)
)
txs <- otherClient.listTransactions() txs <- otherClient.listTransactions()
} yield { } yield {
assert(txs.nonEmpty) assert(txs.nonEmpty)
@ -419,12 +434,15 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
TransactionInput( TransactionInput(
TransactionOutPoint(output.txid.flip, UInt32(output.vout)), TransactionOutPoint(output.txid.flip, UInt32(output.vout)),
ScriptSignature.empty, ScriptSignature.empty,
UInt32.max - UInt32(2)) UInt32.max - UInt32(2)
)
val inputs = Vector(input) val inputs = Vector(input)
val outputs = val outputs =
Map(address -> Bitcoins(0.5), Map(
changeAddress -> Bitcoins(output.amount.toBigDecimal - 0.55)) address -> Bitcoins(0.5),
changeAddress -> Bitcoins(output.amount.toBigDecimal - 0.55)
)
client.createRawTransaction(inputs, outputs) client.createRawTransaction(inputs, outputs)
} }
@ -453,7 +471,9 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
} yield { } yield {
assert( assert(
transaction.outputs.contains( transaction.outputs.contains(
TransactionOutput(Bitcoins(1), address.scriptPubKey))) TransactionOutput(Bitcoins(1), address.scriptPubKey)
)
)
} }
} }
@ -467,23 +487,31 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
val spk = P2WPKHWitnessSPKV0(privKey.publicKey) val spk = P2WPKHWitnessSPKV0(privKey.publicKey)
val importedAddress = Bech32Address.fromScriptPubKey(spk, np) val importedAddress = Bech32Address.fromScriptPubKey(spk, np)
for { for {
fundingTxId <- otherClient.sendToAddress(importedAddress, fundingTxId <- otherClient.sendToAddress(
Bitcoins(1.01)) importedAddress,
Bitcoins(1.01)
)
_ <- otherClient.generate(1) _ <- otherClient.generate(1)
vout <- otherClient vout <- otherClient
.getRawTransactionRaw(fundingTxId) .getRawTransactionRaw(fundingTxId)
.map(_.outputs.zipWithIndex.find( .map(
_._1.scriptPubKey == descriptor.scriptPubKey)) _.outputs.zipWithIndex
.find(_._1.scriptPubKey == descriptor.scriptPubKey)
)
.map(_.get._2) .map(_.get._2)
fundingPrevOut = TransactionOutPoint(fundingTxId, vout) fundingPrevOut = TransactionOutPoint(fundingTxId, vout)
fundingInput = TransactionInput(fundingPrevOut, fundingInput = TransactionInput(
ScriptSignature.empty, fundingPrevOut,
TransactionConstants.sequence) ScriptSignature.empty,
TransactionConstants.sequence
)
address <- otherClient.getNewAddress address <- otherClient.getNewAddress
transaction <- transaction <-
client client
.createRawTransaction(inputs = Vector(fundingInput), .createRawTransaction(
outputs = Map(address -> Bitcoins.one)) inputs = Vector(fundingInput),
outputs = Map(address -> Bitcoins.one)
)
signedTx <- client signedTx <- client
.signRawTransactionWithKey(transaction, Vector(privKey)) .signRawTransactionWithKey(transaction, Vector(privKey))
.map(_.hex) .map(_.hex)
@ -499,7 +527,8 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
P2WPKHV0InputInfo(outPoint, output.value, privKey.publicKey), P2WPKHV0InputInfo(outPoint, output.value, privKey.publicKey),
prevTx, prevTx,
privKey, privKey,
HashType.sigHashAll), HashType.sigHashAll
),
transaction, transaction,
isDummySignature = false isDummySignature = false
) )
@ -507,7 +536,8 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
signedTx match { signedTx match {
case btx: NonWitnessTransaction => case btx: NonWitnessTransaction =>
assert( assert(
btx.inputs.head.scriptSignature.signatures.head == partialSig.signature) btx.inputs.head.scriptSignature.signatures.head == partialSig.signature
)
case wtx: WitnessTransaction => case wtx: WitnessTransaction =>
wtx.witness.head match { wtx.witness.head match {
case p2wpkh: P2WPKHWitnessV0 => case p2wpkh: P2WPKHWitnessV0 =>
@ -552,7 +582,8 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
val psbt = val psbt =
PSBT.fromBase64( PSBT.fromBase64(
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==") "cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA=="
)
for { for {
result <- client.utxoUpdatePsbt(psbt, Seq(descriptor)) result <- client.utxoUpdatePsbt(psbt, Seq(descriptor))
@ -578,15 +609,19 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
val pubKey2 = ECPublicKey.freshPublicKey val pubKey2 = ECPublicKey.freshPublicKey
for { for {
multiSigResult <- client.createMultiSig(2, multiSigResult <- client.createMultiSig(
Vector(pubKey1, pubKey2), 2,
AddressType.Bech32) Vector(pubKey1, pubKey2),
AddressType.Bech32
)
} yield { } yield {
// just validate we are able to receive a sane descriptor // just validate we are able to receive a sane descriptor
// no need to check checksum // no need to check checksum
assert( assert(
multiSigResult.descriptor.startsWith( multiSigResult.descriptor.startsWith(
s"wsh(multi(2,${pubKey1.hex},${pubKey2.hex}))#")) s"wsh(multi(2,${pubKey1.hex},${pubKey2.hex}))#"
)
)
} }
} }

View File

@ -17,10 +17,12 @@ class BitcoindConfigTest extends BitcoinSUnitTest {
} }
it must "parse networks" in { it must "parse networks" in {
val conf = BitcoindConfig(""" val conf = BitcoindConfig(
"""
|regtest=1 |regtest=1
""".stripMargin, """.stripMargin,
tmpDir) tmpDir
)
assert(conf.network == RegTest) assert(conf.network == RegTest)
} }

View File

@ -6,7 +6,8 @@ import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.testkit.rpc.BitcoindFixturesFundedCachedNewest import org.bitcoins.testkit.rpc.BitcoindFixturesFundedCachedNewest
/** Tests for PSBT for RPC calls specific to V18 new PSBT calls /** Tests for PSBT for RPC calls specific to V18 new PSBT calls
* @see https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors * @see
* https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors
*/ */
class PsbtRpcTest extends BitcoindFixturesFundedCachedNewest { class PsbtRpcTest extends BitcoindFixturesFundedCachedNewest {
@ -14,7 +15,7 @@ class PsbtRpcTest extends BitcoindFixturesFundedCachedNewest {
it should "return something when analyzePsbt is called" in { it should "return something when analyzePsbt is called" in {
client: BitcoindRpcClient => client: BitcoindRpcClient =>
//PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. // PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled.
val psbt = val psbt =
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="
@ -26,7 +27,7 @@ class PsbtRpcTest extends BitcoindFixturesFundedCachedNewest {
} }
it should "analyze a PSBT and return a non-empty result" in { it should "analyze a PSBT and return a non-empty result" in {
client: BitcoindRpcClient => client: BitcoindRpcClient =>
//PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. // PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled.
val psbt = val psbt =
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="
@ -47,7 +48,7 @@ class PsbtRpcTest extends BitcoindFixturesFundedCachedNewest {
} }
it should "correctly analyze a psbt " in { client: BitcoindRpcClient => it should "correctly analyze a psbt " in { client: BitcoindRpcClient =>
val psbt = val psbt =
//PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. // PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled.
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="
val analyzedF = client.analyzePsbt(PSBT.fromBase64(psbt)) val analyzedF = client.analyzePsbt(PSBT.fromBase64(psbt))
val expectedfee = Bitcoins(0.00090341) val expectedfee = Bitcoins(0.00090341)
@ -70,12 +71,13 @@ class PsbtRpcTest extends BitcoindFixturesFundedCachedNewest {
} }
} }
//Todo: figure out how to implement a test here // Todo: figure out how to implement a test here
it should "check to see if the utxoUpdate input has been updated" in { it should "check to see if the utxoUpdate input has been updated" in {
client: BitcoindRpcClient => client: BitcoindRpcClient =>
val psbt = val psbt =
PSBT.fromBase64( PSBT.fromBase64(
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA==") "cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA=="
)
val updatedF = client.utxoUpdatePsbt(psbt) val updatedF = client.utxoUpdatePsbt(psbt)
updatedF.map { result => updatedF.map { result =>
@ -83,16 +85,18 @@ class PsbtRpcTest extends BitcoindFixturesFundedCachedNewest {
} }
} }
/** Join psbt looks at the characteristics of a vector of PSBTs and converts them into a singular PSBT. /** Join psbt looks at the characteristics of a vector of PSBTs and converts
* This test takes test vectors from BIP 157 each missing some characteristic covered by the other. When joined * them into a singular PSBT. This test takes test vectors from BIP 157 each
* together the resulting PSBT represented as a string is very different so we can't just search for parts of either * missing some characteristic covered by the other. When joined together the
* PSBT. * resulting PSBT represented as a string is very different so we can't just
* search for parts of either PSBT.
*/ */
it should "joinpsbts" in { client: BitcoindRpcClient => it should "joinpsbts" in { client: BitcoindRpcClient =>
val seqofpsbts = Vector( val seqofpsbts = Vector(
PSBT.fromBase64( PSBT.fromBase64(
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA"), "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA"
//PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled. ),
// PSBT with one P2PKH input and one P2SH-P2WPKH input both with non-final scriptSigs. P2SH-P2WPKH input's redeemScript is available. Outputs filled.
PSBT.fromBase64( PSBT.fromBase64(
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=" "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="
) )

Some files were not shown because too many files have changed in this diff Show More