diff --git a/.gitattributes b/.gitattributes index 3e20865f7058..4f6cf94a1b55 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,4 +17,4 @@ statements_gettextgen.po linguist-generated=true cln-grpc/proto/node.proto -text -diff linguist-generated=true cln-grpc/src/convert.rs -text -diff linguist-generated=true cln-rpc/src/model.rs -text -diff linguist-generated=true -contrib/pyln-testing/pyln/testing/node_pb2.py -text -diff linguist-generated=true \ No newline at end of file +contrib/pyln-testing/pyln/testing/node_pb2.py linguist-generated=true \ No newline at end of file diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index e1a18acc937b..1037cc97d8aa 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -54,14 +54,14 @@ then export STRIP="$TARGET_HOST"-strip export CONFIGURATION_WRAPPER=qemu-"${TARGET_HOST%%-*}"-static - wget -q https://zlib.net/zlib-1.2.12.tar.gz - tar xf zlib-1.2.12.tar.gz - cd zlib-1.2.12 || exit 1 + wget -q https://zlib.net/fossils/zlib-1.2.13.tar.gz + tar xf zlib-1.2.13.tar.gz + cd zlib-1.2.13 || exit 1 ./configure --prefix="$QEMU_LD_PREFIX" make sudo make install cd .. || exit 1 - rm zlib-1.2.12.tar.gz && rm -rf zlib-1.2.12 + rm zlib-1.2.13.tar.gz && rm -rf zlib-1.2.13 wget -q https://www.sqlite.org/2018/sqlite-src-3260000.zip unzip -q sqlite-src-3260000.zip diff --git a/.github/scripts/setup.sh b/.github/scripts/setup.sh index 83dd289d024b..b1b4f252f138 100755 --- a/.github/scripts/setup.sh +++ b/.github/scripts/setup.sh @@ -69,7 +69,17 @@ sudo chmod 0440 /etc/sudoers.d/tester elements-$ELEMENTS_VERSION ) -if [ "$RUST" == "1" ]; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \ - -y --default-toolchain ${RUST_VERSION} -fi +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \ + -y --default-toolchain ${RUST_VERSION} + +# We also need a relatively recent protobuf-compiler, at least 3.12.0, +# in order to support the experimental `optional` flag. +PROTOC_VERSION=3.15.8 +PB_REL="https://github.com/protocolbuffers/protobuf/releases" +curl -LO $PB_REL/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip +sudo unzip protoc-3.15.8-linux-x86_64.zip -d /usr/local/ +sudo chmod a+x /usr/local/bin/protoc +export PROTOC=/usr/local/bin/protoc +export PATH=$PATH:/usr/local/bin +env +ls -lha /usr/local/bin \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e568e936d182..4b274e016424 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,6 +5,7 @@ on: branches: - "master" pull_request: + jobs: smoke-test: name: Smoke Test ${{ matrix.cfg }} @@ -61,6 +62,8 @@ jobs: TEST_GROUP_COUNT: ${{ matrix.TEST_GROUP_COUNT }} TEST_GROUP: ${{ matrix.TEST_GROUP }} run: | + echo $PROTOC + which protoc bash -x .github/scripts/build.sh - name: Upload Unit Test Results @@ -97,44 +100,44 @@ jobs: ./configure make check-doc - proto-test: - name: Protocol Test Config - runs-on: ubuntu-22.04 - timeout-minutes: 300 - needs: [smoke-test] - strategy: - fail-fast: true - matrix: - include: - - {compiler: clang, db: sqlite3} - - {compiler: gcc, db: postgres} - steps: - - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build and run - run: | - docker build -f contrib/docker/Dockerfile.ubuntu -t cln-ci-ubuntu . - docker run -e ARCH=${{ matrix.arch }} \ - -e COMPILER=${{ matrix.compiler }} \ - -e DB=${{ matrix.db }} \ - -e NETWORK=${{ matrix.network }} \ - -e TARGET_HOST=${{ matrix.TARGET_HOST }} \ - -e VALGRIND=${{ matrix.valgrind }} \ - -e DEVELOPER=1 \ - -e EXPERIMENTAL_FEATURES=1 \ - -e COMPAT=0 \ - -e PYTEST_PAR=2 \ - -e PYTEST_OPTS="--timeout=300" \ - -e TEST_CMD="make check-protos" \ - -e TEST_GROUP=1 \ - -e TEST_GROUP_COUNT=1 \ - cln-ci-ubuntu - - name: Upload Unit Test Results - if: always() - uses: actions/upload-artifact@v2 - with: - name: Junit Report ${{ github.run_number }}.{{ matrix.cfg }} - path: report.* + # proto-test: + # name: Protocol Test Config + # runs-on: ubuntu-22.04 + # timeout-minutes: 300 + # needs: [smoke-test] + # strategy: + # fail-fast: true + # matrix: + # include: + # - {compiler: clang, db: sqlite3} + # - {compiler: gcc, db: postgres} + # steps: + # - name: Checkout + # uses: actions/checkout@v2.0.0 + # - name: Build and run + # run: | + # docker build -f contrib/docker/Dockerfile.ubuntu -t cln-ci-ubuntu . + # docker run -e ARCH=${{ matrix.arch }} \ + # -e COMPILER=${{ matrix.compiler }} \ + # -e DB=${{ matrix.db }} \ + # -e NETWORK=${{ matrix.network }} \ + # -e TARGET_HOST=${{ matrix.TARGET_HOST }} \ + # -e VALGRIND=${{ matrix.valgrind }} \ + # -e DEVELOPER=1 \ + # -e EXPERIMENTAL_FEATURES=1 \ + # -e COMPAT=0 \ + # -e PYTEST_PAR=2 \ + # -e PYTEST_OPTS="--timeout=300" \ + # -e TEST_CMD="make check-protos" \ + # -e TEST_GROUP=1 \ + # -e TEST_GROUP_COUNT=1 \ + # cln-ci-ubuntu + # - name: Upload Unit Test Results + # if: always() + # uses: actions/upload-artifact@v2 + # with: + # name: Junit Report ${{ github.run_number }}.{{ matrix.cfg }} + # path: report.* normal-test: name: Normal Test Config ${{ matrix.cfg }} diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index 58ba7bae5997..bb8157bcfcac 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -20,8 +20,9 @@ jobs: - name: Install dependencies run: | export PATH="/usr/local/opt:/Users/runner/.local/bin:/Users/runner/Library/Python/3.10/bin:$PATH" + export BITCOIN_VERSION=0.20.1 - brew install wget python autoconf automake libtool python3 gmp gnu-sed gettext libsodium + brew install wget autoconf automake libtool python@3.10 gmp gnu-sed gettext libsodium ( cd /tmp/ @@ -30,12 +31,11 @@ jobs: sudo mv bitcoin-$BITCOIN_VERSION/bin/* /usr/local/bin ) - pip3 install --user poetry - poetry config virtualenvs.create false --local - poetry install + python3.10 -m pip install -U --user poetry wheel pip + python3.10 -m poetry install + python3.10 -m pip install -U --user mako ln -s /usr/local/Cellar/gettext/0.20.1/bin/xgettext /usr/local/opt - export PATH="/usr/local/opt:$PATH" - name: Build env: @@ -53,7 +53,7 @@ jobs: TEST_GROUP_COUNT: ${{ matrix.TEST_GROUP_COUNT }} TEST_GROUP: ${{ matrix.TEST_GROUP }} run: | - export PATH="/usr/local/opt:/Users/runner/.local/bin:/Users/runner/Library/Python/3.10/bin:$PATH" + export PATH="/usr/local/opt:/Users/runner/.local/bin:/Users/runner/Library/Python/3.10/bin:/usr/local/opt:$PATH" export LDFLAGS="-L/usr/local/opt/sqlite/lib" export CPPFLAGS="-I/usr/local/opt/sqlite/include" @@ -64,5 +64,5 @@ jobs: slow_test: marks tests as slow (deselect with '-m "not slow_test"') EOF - ./configure - make + python3.10 -m poetry run ./configure + python3.10 -m poetry run make diff --git a/.gitignore b/.gitignore index 8ddca96722c1..4c75b538d175 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ tests/plugins/test_selfdisable_after_getmanifest # Ignore generated files devtools/features doc/lightning*.[1578] +doc/reckless*.[1578] *_sqlgen.[ch] *_wiregen.[ch] *_printgen.[ch] @@ -70,6 +71,7 @@ tests/primitives_pb2_grpc.py # Ignore unrelated stuff .DS_Store .gdb_history +.python-version # Rust targets target diff --git a/.gitmodules b/.gitmodules index 100691cdbde8..743f3a4c4375 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,7 +16,7 @@ url = https://github.com/valyala/gheap [submodule "external/lnprototest"] path = external/lnprototest - url = https://github.com/niftynei/lnprototest.git + url = https://github.com/rustyrussell/lnprototest.git branch = nifty/ripemd160-fallback [submodule "external/lowdown"] path = external/lowdown diff --git a/.msggen.json b/.msggen.json index d8298fc29a66..4bb7a19d7f1e 100644 --- a/.msggen.json +++ b/.msggen.json @@ -76,6 +76,7 @@ }, "ListfundsOutputsStatus": { "confirmed": 1, + "immature": 3, "spent": 2, "unconfirmed": 0 }, @@ -271,6 +272,7 @@ "CreateInvoice.bolt12": 3, "CreateInvoice.description": 7, "CreateInvoice.expires_at": 8, + "CreateInvoice.invreq_payer_note": 15, "CreateInvoice.label": 1, "CreateInvoice.local_offer_id": 13, "CreateInvoice.paid_at": 11, @@ -335,6 +337,7 @@ "DelInvoice.bolt12": 3, "DelInvoice.description": 5, "DelInvoice.expires_at": 8, + "DelInvoice.invreq_payer_note": 11, "DelInvoice.label": 1, "DelInvoice.local_offer_id": 9, "DelInvoice.payer_note": 10, @@ -629,6 +632,7 @@ "ListInvoices.invoices[].bolt12": 8, "ListInvoices.invoices[].description": 2, "ListInvoices.invoices[].expires_at": 5, + "ListInvoices.invoices[].invreq_payer_note": 15, "ListInvoices.invoices[].label": 1, "ListInvoices.invoices[].local_offer_id": 9, "ListInvoices.invoices[].paid_at": 13, @@ -870,6 +874,7 @@ "Pay.exclude": 10, "Pay.exemptfee": 7, "Pay.label": 3, + "Pay.localinvreqid": 14, "Pay.localofferid": 9, "Pay.maxdelay": 6, "Pay.maxfee": 11, @@ -909,6 +914,7 @@ "SendOnion.first_hop": 2, "SendOnion.groupid": 11, "SendOnion.label": 4, + "SendOnion.localinvreqid": 13, "SendOnion.localofferid": 10, "SendOnion.msatoshi": 8, "SendOnion.onion": 1, @@ -936,6 +942,7 @@ "SendPay.bolt11": 5, "SendPay.groupid": 9, "SendPay.label": 3, + "SendPay.localinvreqid": 11, "SendPay.localofferid": 8, "SendPay.msatoshi": 4, "SendPay.partid": 7, diff --git a/CHANGELOG.md b/CHANGELOG.md index 486ce6457620..2e341d0923a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,266 @@ # Changelog All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [22.11.1] - 2022-12-09: "Alameda Yield Generator II" + +### Added + + - JSON-RPC: reverts requirement for "jsonrpc" "2.0" inside requests (still deprecated though, just for a while longer!) ([#5783]) + +### Changed + + - config: `announce-addr-dns` needs to be set to *true* to put DNS names into node announcements, otherwise they are suppressed. + +### Deprecated + +Note: You should always set `allow-deprecated-apis=false` to test for changes. + + - config: `announce-addr-dns` (currently defaults to `false`). This will default to `true` once enough of the network has upgraded to understand DNS entries. ([#5796]) + +### Fixed + + - Build: arm32 compiler error in fetchinvoice, due to bad types on 32-bit platforms. ([#5785]) + - JSON-RPC: `autoclean-once` response `uncleaned` count is now correct. ([#5775]) + - Plugin: `autoclean` could misperform or get killed due to lightningd's invalid handling of JSON batching. ([#5775]) + - reckless verbosity properly applied. ([#5781]) + - wireaddr: #5657 allow '_' underscore in hostname part of DNS FQDN ([#5789]) + +[#5781]: https://github.com/ElementsProject/lightning/pull/5781 +[#5783]: https://github.com/ElementsProject/lightning/pull/5783 +[#5775]: https://github.com/ElementsProject/lightning/pull/5775 +[#5789]: https://github.com/ElementsProject/lightning/pull/5789 +[#5796]: https://github.com/ElementsProject/lightning/pull/5796 +[#5785]: https://github.com/ElementsProject/lightning/pull/5785 +[#5775]: https://github.com/ElementsProject/lightning/pull/5775 +[22.11.1]: https://github.com/ElementsProject/lightning/releases/tag/v22.11.1 + + +## [22.11] - 2022-11-30: "Alameda Yield Generator" + + +This release named by @endothermicdev. +### Added + + - Reckless - a Core Lightning plugin manager ([#5647]) + - Config: `--database-upgrade=true` required if a non-release version wants to (irrevocably!) upgrade the db. ([#5550]) + - Documentation: `lightningd-rpc` manual page describes details of our JSON-RPC interface, including compatibility and filtering. ([#5681]) + - JSON-RPC: `filter` object allows reduction of JSON response to (most) commands. ([#5681]) + - cli: new `--filter` parameter to reduce JSON output. ([#5681]) + - pyln: LightningRpc has new `reply_filter` context manager for reducing output of RPC commands. ([#5681]) + - JSON-RPC: `listhtlcs` new command to list all known HTLCS. ([#5594]) + - Plugins: `autoclean` can now delete old forwards, payments, and invoices automatically. ([#5594]) + - Plugins: `autoclean-once` command for a single cleanup. ([#5594]) + - Plugins: `autoclean-status` command to see what autoclean is doing. ([#5594]) + - Config: `accept-htlc-tlv-types` lets us accept unknown even HTLC TLV fields we would normally reject on parsing (was EXPERIMENTAL-only `experimental-accept-extra-tlv-types`). ([#5619]) + - JSON-RPC: The `extratlvs` argument for `keysend` now allows quoting the type numbers in string ([#5674]) + - JSON-RPC: `batching` command to allow database transactions to cross multiple back-to-back JSON commands. ([#5594]) + - JSON-RPC: `channel_opened` notification `channel_ready` flag. ([#5490]) + - JSON-RPC: `delforward` command to delete listforwards entries. ([#5594]) + - JSON-RPC: `delpay` takes optional `groupid` and `partid` parameters to specify exactly what payment to delete. ([#5594]) + - JSON-RPC: `fundchannel`, `multifundchannel` and `fundchannel_start` now accept a `reserve` parameter to indicate the absolute reserve to impose on the peer. ([#5315]) + - Plugins: `keysend` will now attach the longest valid text field in the onion to the invoice (so you can have Sphinx.chat users spam you!) ([#5619]) + - JSON-RPC: `keysend` now has `extratlvs` option in non-EXPERIMENTAL builds. ([#5619]) + - JSON-RPC: `listforwards` now shows `in_htlc_id` and `out_htlc_id` ([#5594]) + - JSON-RPC: `makesecret` can take a string argument instead of hex. ([#5633]) + - JSON-RPC: `pay` and `listpays` now lists the completion time. ([#5398]) + - Plugins: Added notification topic "block_processed". ([#5581]) + - Plugins: `keysend` now exposes the `extratlvs` field ([#5674]) + - Plugins: The `openchannel` hook may return a custom absolute `reserve` value that the peer must not dip below. ([#5315]) + - Plugins: `getmanfest` response can contain `nonnumericids` to indicate support for modern string-based JSON request ids. ([#5727]) + - Protocol: We now delay forgetting funding-spent channels for 12 blocks (as per latest BOLTs, to support splicing in future). ([#5592]) + - Protocol: We now set the `dont_forward` bit on private channel_update's message_flags (as per latest BOLTs). ([#5592]) + - cln-plugin: Options are no longer required to have a default value ([#5369]) + + +### Changed + + - Protocol: We now require all channel_update messages include htlc_maximum_msat (as per latest BOLTs) ([#5592]) + - Protocol: Bolt7 #911 DNS annoucenent support is no longer EXPERIMENTAL ([#5487]) + - JSON-RPC: `listfunds` now lists coinbase outputs as 'immature' until they're spendable ([#5664]) + - JSON-RPC: UTXOs aren't spendable while immature ([#5664]) + - Plugins: `openchannel2` now always includes the `channel_max_msat` ([#5650]) + - JSON-RPC: `createonion` no longer allows non-TLV-style payloads. ([#5639]) + - cln-plugin: Moved the state binding to the plugin until after the configuration step ([#5493]) + - pyln-spec: package updated to latest spec version. ([#5621]) + - JSON-RPC: `listforwards` now never shows `payment_hash`; use `listhtlcs`. ([#5594]) + - cln-rpc: The `wrong_funding` argument for `close` was changed from `bytes` to `outpoint` ([#5444]) + - JSON-RPC: Error code from bcli plugin changed from 400 to 500. ([#5596]) + - Plugins: `balance_snapshot` notification does not send balances for channels that aren't locked-in/opened yet ([#5587]) + - Plugins: RPC operations are now still available during shutdown. ([#5577]) + - JSON-RPC: `listpeers` `status` now refers to "channel ready" rather than "funding locked" (BOLT language change for zeroconf channels) ([#5490]) + - Protocol: `funding_locked` is now called `channel_ready` as per latest BOLTs. ([#5490]) + + +### Deprecated + +Note: You should always set `allow-deprecated-apis=false` to test for changes. + + - JSON-RPC: `autocleaninvoice` (use option `autoclean-expiredinvoices-age`) ([#5594]) + - JSON-RPC: `delexpiredinvoice`: use `autoclean-once`. ([#5594]) + - JSON-RPC: `commando-rune` restrictions is always an array, each element an array of alternatives. Replaces a string with `|`-separators, so no escaping necessary except for `\\`. ([#5539]) + - JSON-RPC: `channel_opened` notification `funding_locked` flag (use `channel_ready`: BOLTs namechange). ([#5490]) + - Plugins: numeric JSON request ids: modern ones will be strings (see doc/lightningd-rpc.7.md!) ([#5727]) + + +### Removed + + - Protocol: we no longer forward HTLCs with legacy onions. ([#5639]) + - `hsmtool`: hsm_secret (ignored) on cmdline for dumponchaindescriptors (deprecated in v0.9.3) ([#5490]) + - Plugins: plugin init `use_proxy_always` (deprecated v0.10.2) ([#5490]) + - JSON-RPC: plugins must supply `usage` parameter (deprecated v0.7) ([#5490]) + - Old order of the `status` parameter in the `listforwards` rpc command (deprecated in v0.10.2) ([#5490]) + - JSONRPC: RPC framework now requires the `"jsonrpc"` property inside the request (deprecated in v0.10.2) ([#5490]) + - JSON API: Removed double wrapping of `rpc_command` payload in `rpc_command` JSON field (deprecated v0.8.2) ([#5490]) + + +### Fixed + + - plugins: `pay` now knows it can use locally-connected wumbo channels for large payments. ([#5746]) + - lightningd: do not abort while parsing hsm pwd ([#5725]) + - plugins: on large/slow nodes we could blame plugins for failing to answer init in time, when we were just slow. ([#5741]) + - ld: Reduce identification of own transactions to not slow down over time, reducing block processing time ([#5715]) + - Fixed gossip_store corruption from duplicate private channel updates ([#5661]) + - Fixed a condition for newly created channels that could trigger a need for reconnect. ([#5601]) + - proper gossip_store operation may resolve some previous gossip propagation issues ([#5591]) + - onchaind: Witness weight estimations could be slightly lower than the VLS signer ([#5669]) + - Protocol: we now correctly decrypt non-256-length onion errors (we always forwarded them fine, now we actually can parse them). ([#5698]) + - devtools: `mkfunding` command no longer crashes (abort) ([#5677]) + - plugins: on large/slow nodes we could blame plugins for failing to answer init in time, when we were just slow. ([#5741]) + - Plugins: `funder` now honors lease requests across RBFs ([#5650]) + - Plugins: `keysend` now removes unknown even (technically illegal!) fields, to try to accept more payments. ([#5645]) + - channeld: Channel reinitialization no longer fails when the number of outstanding outgoing HTLCs exceeds `max_accepted_htlcs`. ([#5640]) + - pay: Squeezed out the last `msat` from our local view of the network ([#5315]) + - peer_control: getinfo shows the correct port on discovered IPs ([#5585]) + - bcli: don't expose bitcoin RPC password on commandline ([#5509]) + - Plugins: topology plugin could crash when it sees duplicate private channel announcements. ([#5593]) + - JSON-RPC: `commando-rune` now handles \\ escapes properly. ([#5539]) + - peer_control: getinfo showing unannounced addresses. ([#5584]) + + +### EXPERIMENTAL + + - JSON-RPC: `pay` and `sendpay` `localofferid` is now `localinvreqid`. ([#5676]) + - Protocol: Support for forwarding blinded payments (as per latest draft) ([#5646]) + - offers: complete rework of spec from other teams (yay!) breaks previous compatibility (boo!) ([#5646]) + - offers: old `payer_key` proofs won't work. ([#5646]) + - bolt12: remove "vendor" (use "issuer") and "timestamp" (use "created_at") fields (deprecated v0.10.2). ([#5490]) + + + +[#5315]: https://github.com/ElementsProject/lightning/pull/5315 +[#5664]: https://github.com/ElementsProject/lightning/pull/5664 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5640]: https://github.com/ElementsProject/lightning/pull/5640 +[#5398]: https://github.com/ElementsProject/lightning/pull/5398 +[#5585]: https://github.com/ElementsProject/lightning/pull/5585 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5587]: https://github.com/ElementsProject/lightning/pull/5587 +[#5584]: https://github.com/ElementsProject/lightning/pull/5584 +[#5674]: https://github.com/ElementsProject/lightning/pull/5674 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5601]: https://github.com/ElementsProject/lightning/pull/5601 +[#5315]: https://github.com/ElementsProject/lightning/pull/5315 +[#5669]: https://github.com/ElementsProject/lightning/pull/5669 +[#5681]: https://github.com/ElementsProject/lightning/pull/5681 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5619]: https://github.com/ElementsProject/lightning/pull/5619 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5645]: https://github.com/ElementsProject/lightning/pull/5645 +[#5619]: https://github.com/ElementsProject/lightning/pull/5619 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5539]: https://github.com/ElementsProject/lightning/pull/5539 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5646]: https://github.com/ElementsProject/lightning/pull/5646 +[#5596]: https://github.com/ElementsProject/lightning/pull/5596 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5677]: https://github.com/ElementsProject/lightning/pull/5677 +[#5287]: https://github.com/ElementsProject/lightning/pull/5287 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5315]: https://github.com/ElementsProject/lightning/pull/5315 +[#5539]: https://github.com/ElementsProject/lightning/pull/5539 +[#5592]: https://github.com/ElementsProject/lightning/pull/5592 +[#5741]: https://github.com/ElementsProject/lightning/pull/5741 +[#5746]: https://github.com/ElementsProject/lightning/pull/5746 +[#5647]: https://github.com/ElementsProject/lightning/pull/5647 +[#5577]: https://github.com/ElementsProject/lightning/pull/5577 +[#5639]: https://github.com/ElementsProject/lightning/pull/5639 +[#5621]: https://github.com/ElementsProject/lightning/pull/5621 +[#5581]: https://github.com/ElementsProject/lightning/pull/5581 +[#5369]: https://github.com/ElementsProject/lightning/pull/5369 +[#5727]: https://github.com/ElementsProject/lightning/pull/5727 +[#5592]: https://github.com/ElementsProject/lightning/pull/5592 +[#5487]: https://github.com/ElementsProject/lightning/pull/5487 +[#5509]: https://github.com/ElementsProject/lightning/pull/5509 +[#5676]: https://github.com/ElementsProject/lightning/pull/5676 +[#5664]: https://github.com/ElementsProject/lightning/pull/5664 +[#5715]: https://github.com/ElementsProject/lightning/pull/5715 +[#5681]: https://github.com/ElementsProject/lightning/pull/5681 +[#5727]: https://github.com/ElementsProject/lightning/pull/5727 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5681]: https://github.com/ElementsProject/lightning/pull/5681 +[#5698]: https://github.com/ElementsProject/lightning/pull/5698 +[#5619]: https://github.com/ElementsProject/lightning/pull/5619 +[#5493]: https://github.com/ElementsProject/lightning/pull/5493 +[#5633]: https://github.com/ElementsProject/lightning/pull/5633 +[#5646]: https://github.com/ElementsProject/lightning/pull/5646 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5646]: https://github.com/ElementsProject/lightning/pull/5646 +[#5593]: https://github.com/ElementsProject/lightning/pull/5593 +[#5674]: https://github.com/ElementsProject/lightning/pull/5674 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5650]: https://github.com/ElementsProject/lightning/pull/5650 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5592]: https://github.com/ElementsProject/lightning/pull/5592 +[#5639]: https://github.com/ElementsProject/lightning/pull/5639 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5550]: https://github.com/ElementsProject/lightning/pull/5550 +[#5490]: https://github.com/ElementsProject/lightning/pull/5490 +[#5725]: https://github.com/ElementsProject/lightning/pull/5725 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5444]: https://github.com/ElementsProject/lightning/pull/5444 +[#5650]: https://github.com/ElementsProject/lightning/pull/5650 +[#5594]: https://github.com/ElementsProject/lightning/pull/5594 +[#5661]: https://github.com/ElementsProject/lightning/pull/5661 +[#5681]: https://github.com/ElementsProject/lightning/pull/5681 +[#5591]: https://github.com/ElementsProject/lightning/pull/5591 +[22.11]: https://github.com/ElementsProject/lightning/releases/tag/v22.11 + + +## [0.12.1] - 2022-09-13: Web-8 init (dot one) + +Point release with some bugfixes and patches. + +### Removed + +- build: `mrkd` and `mistune` not required to build project + +### Fixed + +- lnprototest: builds for lnprototest tests now use 22.04 LTS, which fixes a problem with loading `mako`. ([#5583]) +- Plugins: topology plugin could crash when it sees duplicate private channel announcements ([#5593]) +- connectd: proper `gossip_store` operation may resolve some previous gossip propagation issues and connectd crashes ([#5591]) +- connectd: Fixed a condition for newly created channels that could trigger a need for reconnect. ([#5601]) +- `peer_control`: getinfo showing unannounced addresses. ([#5584]) +- `peer_control`: getinfo shows the correct port on discovered IPs ([#5585]) + + +[#5583]: https://github.com/ElementsProject/lightning/pull/5583 +[#5584]: https://github.com/ElementsProject/lightning/pull/5584 +[#5593]: https://github.com/ElementsProject/lightning/pull/5593 +[#5591]: https://github.com/ElementsProject/lightning/pull/5591 + + ## [0.12.0] - 2022-08-23: Web-8 init This release named by @adi2011. diff --git a/Cargo.lock b/Cargo.lock index 15d0c1185d25..06f85d1d87df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,57 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "asn1-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "async-stream" @@ -40,9 +79,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -50,33 +89,86 @@ dependencies = [ ] [[package]] -name = "atty" -version = "0.2.14" +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "axum-core" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bech32" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bitcoin" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" +dependencies = [ + "bech32", + "bitcoin_hashes", + "secp256k1", + "serde", +] [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ "serde", ] @@ -89,21 +181,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -111,22 +203,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "cln-grpc" -version = "0.0.1" +version = "0.1.2" dependencies = [ "anyhow", - "bitcoin_hashes", + "bitcoin", "cln-rpc", "hex", "log", @@ -138,7 +220,7 @@ dependencies = [ [[package]] name = "cln-grpc-plugin" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "cln-grpc", @@ -153,12 +235,11 @@ dependencies = [ [[package]] name = "cln-plugin" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anyhow", "bytes", "cln-grpc", - "cln-rpc", "env_logger", "futures", "log", @@ -166,55 +247,55 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", ] [[package]] name = "cln-rpc" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anyhow", - "bitcoin_hashes", + "bitcoin", "bytes", "env_logger", "futures-util", "hex", "log", - "secp256k1", "serde", "serde_json", "tokio", - "tokio-util 0.6.10", + "tokio-util", ] [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] -name = "der-oid-macro" -version = "0.5.0" +name = "der-parser" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436" +checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1" dependencies = [ + "asn1-rs", + "displaydoc", + "nom", "num-bigint", "num-traits", - "syn", + "rusticata-macros", ] [[package]] -name = "der-parser" -version = "6.0.1" +name = "displaydoc" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cddf120f700b411b2b02ebeb7f04dc0b7c8835909a6c2f52bf72ed0dd3433b2" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" dependencies = [ - "der-oid-macro", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -225,17 +306,38 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "env_logger" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -247,9 +349,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.2.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" @@ -259,9 +361,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -274,9 +376,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -284,15 +386,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -301,15 +403,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -318,21 +420,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -348,9 +450,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -359,9 +461,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -372,7 +474,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tracing", ] @@ -384,18 +486,15 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] @@ -428,6 +527,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -448,9 +553,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -484,9 +589,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -501,6 +606,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -512,9 +639,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -533,9 +660,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.133" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "log" @@ -546,12 +679,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -560,9 +705,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", @@ -618,9 +763,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi", "libc", @@ -628,18 +773,18 @@ dependencies = [ [[package]] name = "oid-registry" -version = "0.2.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe554cb2393bc784fd678c82c84cc0599c31ceadc7f03a594911f822cb8d1815" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" dependencies = [ - "der-parser", + "asn1-rs", ] [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "pem" @@ -658,9 +803,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "petgraph" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -700,24 +845,34 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773" +dependencies = [ + "proc-macro2", + "syn", +] [[package]] name = "proc-macro2" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.8.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" +checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" dependencies = [ "bytes", "prost-derive", @@ -725,27 +880,31 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.8.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603" +checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" dependencies = [ "bytes", "heck", "itertools", + "lazy_static", "log", "multimap", "petgraph", + "prettyplease", "prost", "prost-types", + "regex", + "syn", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.8.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba" +checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" dependencies = [ "anyhow", "itertools", @@ -756,9 +915,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.8.0" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b" +checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" dependencies = [ "bytes", "prost", @@ -766,9 +925,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -805,13 +964,13 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.8.14" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5911d1403f4143c9d56a702069d593e8d0f3fab880a85e103604d0893ea31ba7" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ - "chrono", "pem", "ring", + "time", "x509-parser", "yasna", ] @@ -827,9 +986,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -838,9 +997,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -875,13 +1034,26 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustls" -version = "0.19.1" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ - "base64", "log", "ring", "sct", @@ -889,16 +1061,31 @@ dependencies = [ ] [[package]] -name = "ryu" +name = "rustls-pemfile" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "sct" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", @@ -906,37 +1093,38 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3" dependencies = [ + "bitcoin_hashes", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] [[package]] name = "serde" -version = "1.0.145" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" dependencies = [ "proc-macro2", "quote", @@ -945,9 +1133,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -981,15 +1169,33 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "syn" -version = "1.0.100" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1015,29 +1221,56 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "time" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + [[package]] name = "tokio" -version = "1.21.1" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" +checksum = "38a54aca0c15d014013256222ba0ebed095673f89345dd79119d912eb561b7a8" dependencies = [ "autocfg", "bytes", @@ -1045,11 +1278,10 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] @@ -1064,9 +1296,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -1075,9 +1307,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.22.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", "tokio", @@ -1086,29 +1318,15 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.4" @@ -1125,12 +1343,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.5.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796c5e1cd49905e65dd8e700d4cb1dffcbfdb4fc9d017de08c1a537afd83627c" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", + "axum", "base64", "bytes", "futures-core", @@ -1144,10 +1363,11 @@ dependencies = [ "pin-project", "prost", "prost-derive", + "rustls-pemfile", "tokio", "tokio-rustls", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -1157,10 +1377,11 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.5.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b52d07035516c2b74337d2ac7746075e7dcae7643816c1b12c5ff8a7484c08" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", @@ -1181,17 +1402,36 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" @@ -1201,9 +1441,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -1214,9 +1454,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -1225,9 +1465,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] @@ -1250,15 +1490,15 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] -name = "unicode-segmentation" -version = "1.10.0" +name = "unicode-xid" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "untrusted" @@ -1348,9 +1588,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ "ring", "untrusted", @@ -1400,55 +1640,69 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "x509-parser" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" dependencies = [ + "asn1-rs", "base64", - "chrono", "data-encoding", "der-parser", "lazy_static", @@ -1457,13 +1711,14 @@ dependencies = [ "ring", "rusticata-macros", "thiserror", + "time", ] [[package]] name = "yasna" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +checksum = "aed2e7a52e3744ab4d0c05c20aa065258e84c49fd4226f5191b2ed29712710b4" dependencies = [ - "chrono", + "time", ] diff --git a/Dockerfile b/Dockerfile index 56d04b9b1e67..bea8ba0f2645 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,9 +32,7 @@ RUN mkdir /opt/bitcoin && cd /opt/bitcoin \ && rm $BITCOIN_TARBALL ENV LITECOIN_VERSION 0.16.3 -ENV LITECOIN_PGP_KEY FE3348877809386C ENV LITECOIN_URL https://download.litecoin.org/litecoin-${LITECOIN_VERSION}/linux/litecoin-${LITECOIN_VERSION}-x86_64-linux-gnu.tar.gz -ENV LITECOIN_ASC_URL https://download.litecoin.org/litecoin-${LITECOIN_VERSION}/linux/litecoin-${LITECOIN_VERSION}-linux-signatures.asc ENV LITECOIN_SHA256 686d99d1746528648c2c54a1363d046436fd172beadaceea80bdc93043805994 # install litecoin binaries @@ -70,27 +68,29 @@ RUN apt-get update -qq && \ python3-setuptools \ wget -RUN wget -q https://zlib.net/zlib-1.2.13.tar.gz \ -&& tar xvf zlib-1.2.13.tar.gz \ -&& cd zlib-1.2.13 \ -&& ./configure \ -&& make \ -&& make install && cd .. && rm zlib-1.2.13.tar.gz && rm -rf zlib-1.2.13 +RUN wget -q https://zlib.net/fossils/zlib-1.2.13.tar.gz \ + && tar xvf zlib-1.2.13.tar.gz \ + && cd zlib-1.2.13 \ + && ./configure \ + && make \ + && make install && cd .. && \ + rm zlib-1.2.13.tar.gz && \ + rm -rf zlib-1.2.13 RUN apt-get install -y --no-install-recommends unzip tclsh \ -&& wget -q https://www.sqlite.org/2019/sqlite-src-3290000.zip \ -&& unzip sqlite-src-3290000.zip \ -&& cd sqlite-src-3290000 \ -&& ./configure --enable-static --disable-readline --disable-threadsafe --disable-load-extension \ -&& make \ -&& make install && cd .. && rm sqlite-src-3290000.zip && rm -rf sqlite-src-3290000 + && wget -q https://www.sqlite.org/2019/sqlite-src-3290000.zip \ + && unzip sqlite-src-3290000.zip \ + && cd sqlite-src-3290000 \ + && ./configure --enable-static --disable-readline --disable-threadsafe --disable-load-extension \ + && make \ + && make install && cd .. && rm sqlite-src-3290000.zip && rm -rf sqlite-src-3290000 RUN wget -q https://gmplib.org/download/gmp/gmp-6.1.2.tar.xz \ -&& tar xvf gmp-6.1.2.tar.xz \ -&& cd gmp-6.1.2 \ -&& ./configure --disable-assembly \ -&& make \ -&& make install && cd .. && rm gmp-6.1.2.tar.xz && rm -rf gmp-6.1.2 + && tar xvf gmp-6.1.2.tar.xz \ + && cd gmp-6.1.2 \ + && ./configure --disable-assembly \ + && make \ + && make install && cd .. && rm gmp-6.1.2.tar.xz && rm -rf gmp-6.1.2 ENV RUST_PROFILE=release ENV PATH=$PATH:/root/.cargo/bin/ @@ -101,21 +101,30 @@ WORKDIR /opt/lightningd COPY . /tmp/lightning RUN git clone --recursive /tmp/lightning . && \ git checkout $(git --work-tree=/tmp/lightning --git-dir=/tmp/lightning/.git rev-parse HEAD) -ARG DEVELOPER=0 + +ARG DEVELOPER=1 ENV PYTHON_VERSION=3 -RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python3 - \ +RUN curl -sSL https://install.python-poetry.org | python3 - \ && pip3 install -U pip \ && pip3 install -U wheel \ - && /root/.local/bin/poetry config virtualenvs.create false \ && /root/.local/bin/poetry install -RUN ./configure --prefix=/tmp/lightning_install --enable-static && make -j3 DEVELOPER=${DEVELOPER} && make install +RUN ./configure --prefix=/tmp/lightning_install --enable-static && \ + make DEVELOPER=${DEVELOPER} && \ + /root/.local/bin/poetry run make install FROM debian:bullseye-slim as final COPY --from=downloader /opt/tini /usr/bin/tini -RUN apt-get update && apt-get install -y --no-install-recommends socat inotify-tools python3 python3-pip libpq5\ - && rm -rf /var/lib/apt/lists/* + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + socat \ + inotify-tools \ + python3 \ + python3-pip \ + libpq5 && \ + rm -rf /var/lib/apt/lists/* ENV LIGHTNINGD_DATA=/root/.lightning ENV LIGHTNINGD_RPC_PORT=9835 diff --git a/Makefile b/Makefile index bc7983ec17c5..883238703b55 100644 --- a/Makefile +++ b/Makefile @@ -289,7 +289,7 @@ default: show-flags all-programs all-test-programs doc-all default-targets ifneq ($(SUPPRESS_GENERATION),1) FORCE = FORCE -FORCE:: +FORCE: endif show-flags: config.vars @@ -411,7 +411,8 @@ ALL_NONGEN_SRCFILES := $(ALL_NONGEN_HEADERS) $(ALL_NONGEN_SOURCES) BIN_PROGRAMS = \ cli/lightning-cli \ lightningd/lightningd \ - tools/lightning-hsmtool + tools/lightning-hsmtool\ + tools/reckless PKGLIBEXEC_PROGRAMS = \ lightningd/lightning_channeld \ lightningd/lightning_closingd \ @@ -550,7 +551,7 @@ check-cppcheck: .cppcheck-suppress @trap 'rm -f .cppcheck-suppress' 0; git ls-files -- "*.c" "*.h" | grep -vE '^ccan/' | xargs cppcheck ${CPPCHECK_OPTS} check-shellcheck: - @git ls-files -- "*.sh" | xargs shellcheck + @git ls-files -- "*.sh" | xargs shellcheck -f gcc check-setup_locale: @tools/check-setup_locale.sh diff --git a/README.md b/README.md index 325a99b49f55..f6015c3dc97d 100644 --- a/README.md +++ b/README.md @@ -117,9 +117,7 @@ Once you've started for the first time, there's a script called the lightning network. There are also numerous plugins available for Core Lightning which add -capabilities: in particular there's a collection at: - - https://github.com/lightningd/plugins +capabilities: in particular there's a collection at: https://github.com/lightningd/plugins Including [helpme][helpme-github] which guides you through setting up your first channels and customizing your node. diff --git a/bitcoin/pubkey.c b/bitcoin/pubkey.c index 00ce2f1398ab..b97379838b0d 100644 --- a/bitcoin/pubkey.c +++ b/bitcoin/pubkey.c @@ -125,37 +125,3 @@ void towire_pubkey(u8 **pptr, const struct pubkey *pubkey) towire(pptr, output, outputlen); } - -void fromwire_point32(const u8 **cursor, size_t *max, struct point32 *point32) -{ - u8 raw[32]; - - if (!fromwire(cursor, max, raw, sizeof(raw))) - return; - - if (secp256k1_xonly_pubkey_parse(secp256k1_ctx, - &point32->pubkey, - raw) != 1) { - SUPERVERBOSE("not a valid point"); - fromwire_fail(cursor, max); - } -} - -void towire_point32(u8 **pptr, const struct point32 *point32) -{ - u8 output[32]; - - secp256k1_xonly_pubkey_serialize(secp256k1_ctx, output, - &point32->pubkey); - towire(pptr, output, sizeof(output)); -} - -static char *point32_to_hexstr(const tal_t *ctx, const struct point32 *point32) -{ - u8 output[32]; - - secp256k1_xonly_pubkey_serialize(secp256k1_ctx, output, - &point32->pubkey); - return tal_hexstr(ctx, output, sizeof(output)); -} -REGISTER_TYPE_TO_STRING(point32, point32_to_hexstr); diff --git a/bitcoin/pubkey.h b/bitcoin/pubkey.h index 3eb52add0645..f3231a8ba10f 100644 --- a/bitcoin/pubkey.h +++ b/bitcoin/pubkey.h @@ -19,13 +19,6 @@ struct pubkey { /* Define pubkey_eq (no padding) */ STRUCTEQ_DEF(pubkey, 0, pubkey.data); -struct point32 { - /* Unpacked pubkey (as used by libsecp256k1 internally) */ - secp256k1_xonly_pubkey pubkey; -}; -/* Define pubkey_eq (no padding) */ -STRUCTEQ_DEF(point32, 0, pubkey.data); - /* Convert from hex string of DER (scriptPubKey from validateaddress) */ bool pubkey_from_hexstr(const char *derstr, size_t derlen, struct pubkey *key); @@ -63,13 +56,4 @@ void pubkey_to_hash160(const struct pubkey *pk, struct ripemd160 *hash); void towire_pubkey(u8 **pptr, const struct pubkey *pubkey); void fromwire_pubkey(const u8 **cursor, size_t *max, struct pubkey *pubkey); -/* FIXME: Old spec uses pubkey32 */ -#define pubkey32 point32 -#define towire_pubkey32 towire_point32 -#define fromwire_pubkey32 fromwire_point32 - -/* marshal/unmarshal functions */ -void towire_point32(u8 **pptr, const struct point32 *pubkey); -void fromwire_point32(const u8 **cursor, size_t *max, struct point32 *pubkey); - #endif /* LIGHTNING_BITCOIN_PUBKEY_H */ diff --git a/bitcoin/test/run-bitcoin_block_from_hex.c b/bitcoin/test/run-bitcoin_block_from_hex.c index 9c85afe28b4a..4c910f8b85d8 100644 --- a/bitcoin/test/run-bitcoin_block_from_hex.c +++ b/bitcoin/test/run-bitcoin_block_from_hex.c @@ -168,13 +168,13 @@ int main(int argc, const char *argv[]) block, strlen(block)); assert(b); - assert(b->hdr.version == CPU_TO_LE32(0x6592a000)); + assert(b->hdr.version == 0x6592a000); bitcoin_blkid_from_hex("0000000000000f31173e973bc00e452b1fac350066df7db2adec1e3224ea5bc1", strlen("0000000000000f31173e973bc00e452b1fac350066df7db2adec1e3224ea5bc1"), &prev); assert(bitcoin_blkid_eq(&prev, &b->hdr.prev_hash)); hex_decode("8a0ee58ded5de949325ebc99583e3ca84f96a6597465c611685413f50f0ead7e", strlen("8a0ee58ded5de949325ebc99583e3ca84f96a6597465c611685413f50f0ead7e"), &merkle, sizeof(merkle)); assert(sha256_double_eq(&merkle, &b->hdr.merkle_hash)); - assert(b->hdr.timestamp == CPU_TO_LE32(1550507183)); - assert(b->hdr.nonce == CPU_TO_LE32(1226407989)); + assert(b->hdr.timestamp == 1550507183); + assert(b->hdr.nonce == 1226407989); assert(tal_count(b->tx) == 3); bitcoin_txid(b->tx[0], &txid); diff --git a/bitcoin/test/run-tx-bitcoin_tx_2of2_input_witness_weight.c b/bitcoin/test/run-tx-bitcoin_tx_2of2_input_witness_weight.c index d1c6a3c25d98..b387279c2ba0 100644 --- a/bitcoin/test/run-tx-bitcoin_tx_2of2_input_witness_weight.c +++ b/bitcoin/test/run-tx-bitcoin_tx_2of2_input_witness_weight.c @@ -145,6 +145,14 @@ int main(int argc, const char *argv[]) /* 1 byte for num witnesses, one per witness element */ weight = 1; + + /* Two signatures, slightly overestimated to be 73 bytes each, + * while the actual witness will often be smaller.*/ + /* BOLT #03: + * Signatures are 73 bytes long (the maximum length). + */ + weight += 2 + 2; + for (size_t i = 0; i < tal_count(wit); i++) weight += 1 + tal_bytelen(wit[i]); assert(bitcoin_tx_2of2_input_witness_weight() == weight); diff --git a/bitcoin/tx.c b/bitcoin/tx.c index 401c5464619d..7d575196c729 100644 --- a/bitcoin/tx.c +++ b/bitcoin/tx.c @@ -886,12 +886,18 @@ size_t bitcoin_tx_simple_input_weight(bool p2sh) size_t bitcoin_tx_2of2_input_witness_weight(void) { - /* witness[0] = "" - * witness[1] = sig - * witness[2] = sig - * witness[3] = 2 key key 2 CHECKMULTISIG - */ - return 1 + (1 + 0) + (1 + 72) + (1 + 72) + (1 + 1 + 33 + 33 + 1 + 1); + /* BOLT #03: + * Signatures are 73 bytes long (the maximum length). + */ + return 1 + /* Prefix: 4 elements to push on stack */ + (1 + 0) + /* [0]: witness-marker-and-flag */ + (1 + 73) + /* [1] Party A signature and length prefix */ + (1 + 73) + /* [2] Party B signature and length prefix */ + (1 + 1 + /* [3] length prefix and numpushes (2) */ + 1 + 33 + /* pubkey A (with prefix) */ + 1 + 33 + /* pubkey B (with prefix) */ + 1 + 1 /* num sigs required and checkmultisig */ + ); } struct amount_sat change_amount(struct amount_sat excess, u32 feerate_perkw, diff --git a/channeld/channeld.c b/channeld/channeld.c index 9871d71fdce1..79cb31dff390 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -834,31 +834,20 @@ static void handle_peer_add_htlc(struct peer *peer, const u8 *msg) u8 onion_routing_packet[TOTAL_PACKET_SIZE(ROUTING_INFO_SIZE)]; enum channel_add_err add_err; struct htlc *htlc; -#if EXPERIMENTAL_FEATURES struct tlv_update_add_tlvs *tlvs; -#endif - struct pubkey *blinding = NULL; - if (!fromwire_update_add_htlc -#if EXPERIMENTAL_FEATURES - (msg, msg, &channel_id, &id, &amount, - &payment_hash, &cltv_expiry, - onion_routing_packet, &tlvs) -#else - (msg, &channel_id, &id, &amount, - &payment_hash, &cltv_expiry, - onion_routing_packet) -#endif - ) + if (!fromwire_update_add_htlc(msg, msg, &channel_id, &id, &amount, + &payment_hash, &cltv_expiry, + onion_routing_packet, &tlvs) + /* This is an *even* field: don't send if we didn't understand */ + || (tlvs->blinding && !feature_offered(peer->our_features->bits[INIT_FEATURE], + OPT_ROUTE_BLINDING))) { peer_failed_warn(peer->pps, &peer->channel_id, "Bad peer_add_htlc %s", tal_hex(msg, msg)); - -#if EXPERIMENTAL_FEATURES - blinding = tlvs->blinding; -#endif + } add_err = channel_add_htlc(peer->channel, REMOTE, id, amount, cltv_expiry, &payment_hash, - onion_routing_packet, blinding, &htlc, NULL, + onion_routing_packet, tlvs->blinding, &htlc, NULL, /* We don't immediately fail incoming htlcs, * instead we wait and fail them after * they've been committed */ @@ -1709,11 +1698,7 @@ static void marshall_htlc_info(const tal_t *ctx, memcpy(a.onion_routing_packet, htlc->routing, sizeof(a.onion_routing_packet)); - if (htlc->blinding) { - a.blinding = htlc->blinding; - ecdh(a.blinding, &a.blinding_ss); - } else - a.blinding = NULL; + a.blinding = htlc->blinding; a.fail_immediate = htlc->fail_immediate; tal_arr_expand(added, a); } else if (htlc->state == RCVD_REMOVE_COMMIT) { @@ -4027,7 +4012,6 @@ static void resend_commitment(struct peer *peer, struct changed_htlc *last) last[i].id); if (h->state == SENT_ADD_COMMIT) { -#if EXPERIMENTAL_FEATURES struct tlv_update_add_tlvs *tlvs; if (h->blinding) { tlvs = tlv_update_add_tlvs_new(tmpctx); @@ -4035,17 +4019,12 @@ static void resend_commitment(struct peer *peer, struct changed_htlc *last) h->blinding); } else tlvs = NULL; -#endif msg = towire_update_add_htlc(NULL, &peer->channel_id, h->id, h->amount, &h->rhash, abs_locktime_to_blocks( &h->expiry), - h->routing -#if EXPERIMENTAL_FEATURES - , tlvs -#endif - ); + h->routing, tlvs); peer_write(peer->pps, take(msg)); } } @@ -4942,6 +4921,7 @@ static void handle_offer_htlc(struct peer *peer, const u8 *inmsg) const char *failstr; struct amount_sat htlc_fee; struct pubkey *blinding; + struct tlv_update_add_tlvs *tlvs; if (!peer->channel_ready[LOCAL] || !peer->channel_ready[REMOTE]) status_failed(STATUS_FAIL_MASTER_IO, @@ -4952,14 +4932,11 @@ static void handle_offer_htlc(struct peer *peer, const u8 *inmsg) onion_routing_packet, &blinding)) master_badmsg(WIRE_CHANNELD_OFFER_HTLC, inmsg); -#if EXPERIMENTAL_FEATURES - struct tlv_update_add_tlvs *tlvs; if (blinding) { tlvs = tlv_update_add_tlvs_new(tmpctx); tlvs->blinding = tal_dup(tlvs, struct pubkey, blinding); } else tlvs = NULL; -#endif e = channel_add_htlc(peer->channel, LOCAL, peer->htlc_id, amount, cltv_expiry, &payment_hash, @@ -4977,11 +4954,7 @@ static void handle_offer_htlc(struct peer *peer, const u8 *inmsg) msg = towire_update_add_htlc(NULL, &peer->channel_id, peer->htlc_id, amount, &payment_hash, cltv_expiry, - onion_routing_packet -#if EXPERIMENTAL_FEATURES - , tlvs -#endif - ); + onion_routing_packet, tlvs); peer_write(peer->pps, take(msg)); start_commit_timer(peer); /* Tell the master. */ diff --git a/cli/lightning-cli.c b/cli/lightning-cli.c index 9ef4b81246af..517283584392 100644 --- a/cli/lightning-cli.c +++ b/cli/lightning-cli.c @@ -544,7 +544,7 @@ static bool handle_notify(const char *buf, jsmntok_t *toks, snprintf(totstr, sizeof(totstr), "%u", tot); printf("%*u/%s ", (int)strlen(totstr), n+1, totstr); memset(bar, ' ', sizeof(bar)-1); - memset(bar, '=', (double)strlen(bar) / (tot-1) * n); + memset(bar, '=', (double)(sizeof(bar)-1) / (tot-1) * n); bar[sizeof(bar)-1] = '\0'; printf("|%s|", bar); /* Leave bar there if it's finished. */ @@ -620,7 +620,7 @@ int main(int argc, char *argv[]) enum input input = DEFAULT_INPUT; enum log_level notification_level = LOG_INFORM; bool last_was_progress = false; - char *command = NULL; + char *command = NULL, *filter = NULL; err_set_progname(argv[0]); jsmn_init(&parser); @@ -650,6 +650,9 @@ int main(int argc, char *argv[]) opt_register_arg("-N|--notifications", opt_set_level, opt_show_level, ¬ification_level, "Set notification level, or none"); + opt_register_arg("-l|--filter", opt_set_charp, + opt_show_charp, &filter, + "Set JSON reply filter"); opt_register_version(); @@ -713,8 +716,11 @@ int main(int argc, char *argv[]) enable_notifications(fd); cmd = tal_fmt(ctx, - "{ \"jsonrpc\" : \"2.0\", \"method\" : \"%s\", \"id\" : \"%s\", \"params\" :", + "{ \"jsonrpc\" : \"2.0\", \"method\" : \"%s\", \"id\" : \"%s\",", json_escape(ctx, method)->s, idstr); + if (filter) + tal_append_fmt(&cmd, "\"filter\": %s,", filter); + tal_append_fmt(&cmd, " \"params\" :"); if (input == DEFAULT_INPUT) { /* Hacky autodetect; only matters if more than single arg */ diff --git a/cln-grpc/Cargo.toml b/cln-grpc/Cargo.toml index dd96e8fa8043..b95246138089 100644 --- a/cln-grpc/Cargo.toml +++ b/cln-grpc/Cargo.toml @@ -1,19 +1,24 @@ [package] name = "cln-grpc" -version = "0.0.1" +version = "0.1.2" edition = "2021" +license = "MIT" +description = "The Core Lightning API as grpc primitives. Provides the bindings used to expose the API over the network." +homepage = "https://github.com/ElementsProject/lightning/tree/master/cln-grpc" +repository = "https://github.com/ElementsProject/lightning" +documentation = "https://docs.rs/cln-grpc" [dependencies] anyhow = "1.0" log = "0.4" -cln-rpc = { path="../cln-rpc/" } -tonic = { version = "^0.5", features = ["tls", "transport"] } -prost = "0.8" +cln-rpc = { path="../cln-rpc/", version = "^0.1" } +tonic = { version = "0.8", features = ["tls", "transport"] } +prost = "0.11" hex = "0.4.3" -bitcoin_hashes = { version = "0.10.0", features = [ "serde" ] } +bitcoin = { version = "0.29", features = [ "serde" ] } [dev-dependencies] serde_json = "1.0.72" [build-dependencies] -tonic-build = "^0.5" +tonic-build = "0.8" diff --git a/cln-grpc/build.rs b/cln-grpc/build.rs index 17f86d001bdf..cd13d0529f0c 100644 --- a/cln-grpc/build.rs +++ b/cln-grpc/build.rs @@ -1,3 +1,7 @@ fn main() { - tonic_build::compile_protos("proto/node.proto").unwrap(); + let builder = tonic_build::configure(); + builder + .protoc_arg("--experimental_allow_proto3_optional") + .compile(&["proto/node.proto"], &["proto"]) + .unwrap(); } diff --git a/cln-grpc/proto/node.proto b/cln-grpc/proto/node.proto index 48a75b37043a..e2a8d0b72c11 100644 --- a/cln-grpc/proto/node.proto +++ b/cln-grpc/proto/node.proto @@ -70,6 +70,7 @@ message GetinfoResponse { uint32 num_inactive_channels = 7; string version = 8; string lightning_dir = 9; + optional GetinfoOur_features our_features = 10; uint32 blockheight = 11; string network = 12; optional uint64 msatoshi_fees_collected = 18; @@ -173,6 +174,7 @@ message ListpeersPeersChannels { } ListpeersPeersChannelsState state = 1; optional bytes scratch_txid = 2; + optional ListpeersPeersChannelsFeerate feerate = 3; optional string owner = 4; optional string short_channel_id = 5; optional bytes channel_id = 6; @@ -188,6 +190,7 @@ message ListpeersPeersChannels { ChannelSide opener = 16; optional ChannelSide closer = 17; repeated string features = 18; + optional ListpeersPeersChannelsFunding funding = 19; optional Amount to_us_msat = 20; optional Amount min_to_us_msat = 21; optional Amount max_to_us_msat = 22; @@ -206,6 +209,7 @@ message ListpeersPeersChannels { optional uint32 their_to_self_delay = 33; optional uint32 our_to_self_delay = 34; optional uint32 max_accepted_htlcs = 35; + optional ListpeersPeersChannelsAlias alias = 50; repeated string status = 37; optional uint64 in_payments_offered = 38; optional Amount in_offered_msat = 39; @@ -278,6 +282,7 @@ message ListfundsOutputs { UNCONFIRMED = 0; CONFIRMED = 1; SPENT = 2; + IMMATURE = 3; } bytes txid = 1; uint32 output = 2; @@ -309,7 +314,7 @@ message SendpayRequest { optional string bolt11 = 5; optional bytes payment_secret = 6; optional uint32 partid = 7; - optional bytes localofferid = 8; + optional bytes localinvreqid = 11; optional uint64 groupid = 9; } @@ -437,6 +442,7 @@ message ConnectResponse { bytes id = 1; bytes features = 2; ConnectDirection direction = 3; + ConnectAddress address = 4; } message ConnectAddress { @@ -480,7 +486,7 @@ message CreateinvoiceResponse { optional uint64 paid_at = 11; optional bytes payment_preimage = 12; optional bytes local_offer_id = 13; - optional string payer_note = 14; + optional string invreq_payer_note = 15; } message DatastoreRequest { @@ -570,7 +576,7 @@ message DelinvoiceResponse { DelinvoiceStatus status = 7; uint64 expires_at = 8; optional bytes local_offer_id = 9; - optional string payer_note = 10; + optional string invreq_payer_note = 11; } message InvoiceRequest { @@ -639,7 +645,7 @@ message ListinvoicesInvoices { optional string bolt11 = 7; optional string bolt12 = 8; optional bytes local_offer_id = 9; - optional string payer_note = 10; + optional string invreq_payer_note = 15; optional uint64 pay_index = 11; optional Amount amount_received_msat = 12; optional uint64 paid_at = 13; @@ -648,6 +654,7 @@ message ListinvoicesInvoices { message SendonionRequest { bytes onion = 1; + SendonionFirst_hop first_hop = 2; bytes payment_hash = 3; optional string label = 4; repeated bytes shared_secrets = 5; @@ -655,7 +662,7 @@ message SendonionRequest { optional string bolt11 = 7; optional Amount amount_msat = 12; optional bytes destination = 9; - optional bytes localofferid = 10; + optional bytes localinvreqid = 13; optional uint64 groupid = 11; } @@ -797,7 +804,7 @@ message PayRequest { optional uint32 retry_for = 5; optional uint32 maxdelay = 6; optional Amount exemptfee = 7; - optional bytes localofferid = 9; + optional bytes localinvreqid = 14; repeated string exclude = 10; optional Amount maxfee = 11; optional string description = 12; @@ -968,6 +975,7 @@ message KeysendRequest { optional uint32 maxdelay = 6; optional Amount exemptfee = 7; optional RoutehintList routehints = 8; + optional TlvStream extratlvs = 9; } message KeysendResponse { @@ -986,9 +994,6 @@ message KeysendResponse { KeysendStatus status = 9; } -message KeysendExtratlvs { -} - message FundpsbtRequest { AmountOrAll satoshi = 1; Feerate feerate = 2; @@ -1116,6 +1121,9 @@ message FeeratesRequest { message FeeratesResponse { optional string warning_missing_feerates = 1; + optional FeeratesPerkb perkb = 2; + optional FeeratesPerkw perkw = 3; + optional FeeratesOnchain_fee_estimates onchain_fee_estimates = 4; } message FeeratesPerkb { @@ -1282,8 +1290,8 @@ message ListpaysPays { message PingRequest { bytes id = 1; - optional double len = 2; - optional double pongbytes = 3; + optional uint32 len = 2; + optional uint32 pongbytes = 3; } message PingResponse { diff --git a/cln-grpc/proto/primitives.proto b/cln-grpc/proto/primitives.proto index 2dd7409909a8..0f469295ad9b 100644 --- a/cln-grpc/proto/primitives.proto +++ b/cln-grpc/proto/primitives.proto @@ -72,4 +72,13 @@ message Routehint { } message RoutehintList { repeated Routehint hints = 2; -} \ No newline at end of file +} + + +message TlvEntry { + uint64 type = 1; + bytes value = 2; +} +message TlvStream { + repeated TlvEntry entries = 1; +} diff --git a/cln-grpc/src/convert.rs b/cln-grpc/src/convert.rs index 9ad55fc46cc6..da276661b9d7 100644 --- a/cln-grpc/src/convert.rs +++ b/cln-grpc/src/convert.rs @@ -8,10 +8,22 @@ use std::convert::From; use cln_rpc::model::{responses,requests}; use crate::pb; use std::str::FromStr; -use bitcoin_hashes::sha256::Hash as Sha256; -use bitcoin_hashes::Hash; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::Hash; use cln_rpc::primitives::PublicKey; +#[allow(unused_variables)] +impl From for pb::GetinfoOurFeatures { + fn from(c: responses::GetinfoOur_features) -> Self { + Self { + init: hex::decode(&c.init).unwrap(), // Rule #2 for type hex + node: hex::decode(&c.node).unwrap(), // Rule #2 for type hex + channel: hex::decode(&c.channel).unwrap(), // Rule #2 for type hex + invoice: hex::decode(&c.invoice).unwrap(), // Rule #2 for type hex + } + } +} + #[allow(unused_variables)] impl From for pb::GetinfoAddress { fn from(c: responses::GetinfoAddress) -> Self { @@ -48,6 +60,7 @@ impl From for pb::GetinfoResponse { num_inactive_channels: c.num_inactive_channels, // Rule #2 for type u32 version: c.version, // Rule #2 for type string lightning_dir: c.lightning_dir, // Rule #2 for type string + our_features: c.our_features.map(|v| v.into()), blockheight: c.blockheight, // Rule #2 for type u32 network: c.network, // Rule #2 for type string msatoshi_fees_collected: c.msatoshi_fees_collected, // Rule #2 for type u64? @@ -75,6 +88,16 @@ impl From for pb::ListpeersPeersLog { } } +#[allow(unused_variables)] +impl From for pb::ListpeersPeersChannelsFeerate { + fn from(c: responses::ListpeersPeersChannelsFeerate) -> Self { + Self { + perkw: c.perkw, // Rule #2 for type u32 + perkb: c.perkb, // Rule #2 for type u32 + } + } +} + #[allow(unused_variables)] impl From for pb::ListpeersPeersChannelsInflight { fn from(c: responses::ListpeersPeersChannelsInflight) -> Self { @@ -89,6 +112,31 @@ impl From for pb::ListpeersPeersChann } } +#[allow(unused_variables)] +impl From for pb::ListpeersPeersChannelsFunding { + fn from(c: responses::ListpeersPeersChannelsFunding) -> Self { + Self { + local_msat: c.local_msat.map(|f| f.into()), // Rule #2 for type msat? + remote_msat: c.remote_msat.map(|f| f.into()), // Rule #2 for type msat? + pushed_msat: c.pushed_msat.map(|f| f.into()), // Rule #2 for type msat? + local_funds_msat: Some(c.local_funds_msat.into()), // Rule #2 for type msat + remote_funds_msat: Some(c.remote_funds_msat.into()), // Rule #2 for type msat + fee_paid_msat: c.fee_paid_msat.map(|f| f.into()), // Rule #2 for type msat? + fee_rcvd_msat: c.fee_rcvd_msat.map(|f| f.into()), // Rule #2 for type msat? + } + } +} + +#[allow(unused_variables)] +impl From for pb::ListpeersPeersChannelsAlias { + fn from(c: responses::ListpeersPeersChannelsAlias) -> Self { + Self { + local: c.local.map(|v| v.to_string()), // Rule #2 for type short_channel_id? + remote: c.remote.map(|v| v.to_string()), // Rule #2 for type short_channel_id? + } + } +} + #[allow(unused_variables)] impl From for pb::ListpeersPeersChannelsHtlcs { fn from(c: responses::ListpeersPeersChannelsHtlcs) -> Self { @@ -110,6 +158,7 @@ impl From for pb::ListpeersPeersChannels { Self { state: c.state as i32, scratch_txid: c.scratch_txid.map(|v| hex::decode(v).unwrap()), // Rule #2 for type txid? + feerate: c.feerate.map(|v| v.into()), owner: c.owner, // Rule #2 for type string? short_channel_id: c.short_channel_id.map(|v| v.to_string()), // Rule #2 for type short_channel_id? channel_id: c.channel_id.map(|v| v.to_vec()), // Rule #2 for type hash? @@ -125,6 +174,7 @@ impl From for pb::ListpeersPeersChannels { opener: c.opener as i32, closer: c.closer.map(|v| v as i32), features: c.features.into_iter().map(|i| i.into()).collect(), // Rule #3 for type ListpeersPeersChannelsFeatures + funding: c.funding.map(|v| v.into()), to_us_msat: c.to_us_msat.map(|f| f.into()), // Rule #2 for type msat? min_to_us_msat: c.min_to_us_msat.map(|f| f.into()), // Rule #2 for type msat? max_to_us_msat: c.max_to_us_msat.map(|f| f.into()), // Rule #2 for type msat? @@ -143,6 +193,7 @@ impl From for pb::ListpeersPeersChannels { their_to_self_delay: c.their_to_self_delay, // Rule #2 for type u32? our_to_self_delay: c.our_to_self_delay, // Rule #2 for type u32? max_accepted_htlcs: c.max_accepted_htlcs, // Rule #2 for type u32? + alias: c.alias.map(|v| v.into()), status: c.status.map(|arr| arr.into_iter().map(|i| i.into()).collect()).unwrap_or(vec![]), // Rule #3 in_payments_offered: c.in_payments_offered, // Rule #2 for type u64? in_offered_msat: c.in_offered_msat.map(|f| f.into()), // Rule #2 for type msat? @@ -320,6 +371,18 @@ impl From for pb::CloseResponse { } } +#[allow(unused_variables)] +impl From for pb::ConnectAddress { + fn from(c: responses::ConnectAddress) -> Self { + Self { + item_type: c.item_type as i32, + socket: c.socket, // Rule #2 for type string? + address: c.address, // Rule #2 for type string? + port: c.port.map(|v| v.into()), // Rule #2 for type u16? + } + } +} + #[allow(unused_variables)] impl From for pb::ConnectResponse { fn from(c: responses::ConnectResponse) -> Self { @@ -327,6 +390,7 @@ impl From for pb::ConnectResponse { id: c.id.serialize().to_vec(), // Rule #2 for type pubkey features: hex::decode(&c.features).unwrap(), // Rule #2 for type hex direction: c.direction as i32, + address: Some(c.address.into()), } } } @@ -348,7 +412,7 @@ impl From for pb::CreateinvoiceResponse { paid_at: c.paid_at, // Rule #2 for type u64? payment_preimage: c.payment_preimage.map(|v| v.to_vec()), // Rule #2 for type secret? local_offer_id: c.local_offer_id.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? - payer_note: c.payer_note, // Rule #2 for type string? + invreq_payer_note: c.invreq_payer_note, // Rule #2 for type string? } } } @@ -408,7 +472,7 @@ impl From for pb::DelinvoiceResponse { status: c.status as i32, expires_at: c.expires_at, // Rule #2 for type u64 local_offer_id: c.local_offer_id.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? - payer_note: c.payer_note, // Rule #2 for type string? + invreq_payer_note: c.invreq_payer_note, // Rule #2 for type string? } } } @@ -464,7 +528,7 @@ impl From for pb::ListinvoicesInvoices { bolt11: c.bolt11, // Rule #2 for type string? bolt12: c.bolt12, // Rule #2 for type string? local_offer_id: c.local_offer_id.map(|v| hex::decode(v).unwrap()), // Rule #2 for type hex? - payer_note: c.payer_note, // Rule #2 for type string? + invreq_payer_note: c.invreq_payer_note, // Rule #2 for type string? pay_index: c.pay_index, // Rule #2 for type u64? amount_received_msat: c.amount_received_msat.map(|f| f.into()), // Rule #2 for type msat? paid_at: c.paid_at, // Rule #2 for type u64? @@ -850,11 +914,59 @@ impl From for pb::DisconnectResponse { } } +#[allow(unused_variables)] +impl From for pb::FeeratesPerkb { + fn from(c: responses::FeeratesPerkb) -> Self { + Self { + min_acceptable: c.min_acceptable, // Rule #2 for type u32 + max_acceptable: c.max_acceptable, // Rule #2 for type u32 + opening: c.opening, // Rule #2 for type u32? + mutual_close: c.mutual_close, // Rule #2 for type u32? + unilateral_close: c.unilateral_close, // Rule #2 for type u32? + delayed_to_us: c.delayed_to_us, // Rule #2 for type u32? + htlc_resolution: c.htlc_resolution, // Rule #2 for type u32? + penalty: c.penalty, // Rule #2 for type u32? + } + } +} + +#[allow(unused_variables)] +impl From for pb::FeeratesPerkw { + fn from(c: responses::FeeratesPerkw) -> Self { + Self { + min_acceptable: c.min_acceptable, // Rule #2 for type u32 + max_acceptable: c.max_acceptable, // Rule #2 for type u32 + opening: c.opening, // Rule #2 for type u32? + mutual_close: c.mutual_close, // Rule #2 for type u32? + unilateral_close: c.unilateral_close, // Rule #2 for type u32? + delayed_to_us: c.delayed_to_us, // Rule #2 for type u32? + htlc_resolution: c.htlc_resolution, // Rule #2 for type u32? + penalty: c.penalty, // Rule #2 for type u32? + } + } +} + +#[allow(unused_variables)] +impl From for pb::FeeratesOnchainFeeEstimates { + fn from(c: responses::FeeratesOnchain_fee_estimates) -> Self { + Self { + opening_channel_satoshis: c.opening_channel_satoshis, // Rule #2 for type u64 + mutual_close_satoshis: c.mutual_close_satoshis, // Rule #2 for type u64 + unilateral_close_satoshis: c.unilateral_close_satoshis, // Rule #2 for type u64 + htlc_timeout_satoshis: c.htlc_timeout_satoshis, // Rule #2 for type u64 + htlc_success_satoshis: c.htlc_success_satoshis, // Rule #2 for type u64 + } + } +} + #[allow(unused_variables)] impl From for pb::FeeratesResponse { fn from(c: responses::FeeratesResponse) -> Self { Self { warning_missing_feerates: c.warning_missing_feerates, // Rule #2 for type string? + perkb: c.perkb.map(|v| v.into()), + perkw: c.perkw.map(|v| v.into()), + onchain_fee_estimates: c.onchain_fee_estimates.map(|v| v.into()), } } } @@ -1057,7 +1169,7 @@ impl From for requests::SendpayRequest { bolt11: c.bolt11, // Rule #1 for type string? payment_secret: c.payment_secret.map(|v| v.try_into().unwrap()), // Rule #1 for type secret? partid: c.partid.map(|v| v as u16), // Rule #1 for type u16? - localofferid: c.localofferid.map(|v| hex::encode(v)), // Rule #1 for type hex? + localinvreqid: c.localinvreqid.map(|v| hex::encode(v)), // Rule #1 for type hex? groupid: c.groupid, // Rule #1 for type u64? } } @@ -1244,11 +1356,23 @@ impl From for requests::ListinvoicesRequest { } } +#[allow(unused_variables)] +impl From for requests::SendonionFirst_hop { + fn from(c: pb::SendonionFirstHop) -> Self { + Self { + id: PublicKey::from_slice(&c.id).unwrap(), // Rule #1 for type pubkey + amount_msat: c.amount_msat.unwrap().into(), // Rule #1 for type msat + delay: c.delay as u16, // Rule #1 for type u16 + } + } +} + #[allow(unused_variables)] impl From for requests::SendonionRequest { fn from(c: pb::SendonionRequest) -> Self { Self { onion: hex::encode(&c.onion), // Rule #1 for type hex + first_hop: c.first_hop.unwrap().into(), payment_hash: Sha256::from_slice(&c.payment_hash).unwrap(), // Rule #1 for type hash label: c.label, // Rule #1 for type string? shared_secrets: Some(c.shared_secrets.into_iter().map(|s| s.try_into().unwrap()).collect()), // Rule #4 @@ -1256,7 +1380,7 @@ impl From for requests::SendonionRequest { bolt11: c.bolt11, // Rule #1 for type string? amount_msat: c.amount_msat.map(|a| a.into()), // Rule #1 for type msat? destination: c.destination.map(|v| PublicKey::from_slice(&v).unwrap()), // Rule #1 for type pubkey? - localofferid: c.localofferid.map(|v| Sha256::from_slice(&v).unwrap()), // Rule #1 for type hash? + localinvreqid: c.localinvreqid.map(|v| Sha256::from_slice(&v).unwrap()), // Rule #1 for type hash? groupid: c.groupid, // Rule #1 for type u64? } } @@ -1293,7 +1417,7 @@ impl From for requests::PayRequest { retry_for: c.retry_for.map(|v| v as u16), // Rule #1 for type u16? maxdelay: c.maxdelay.map(|v| v as u16), // Rule #1 for type u16? exemptfee: c.exemptfee.map(|a| a.into()), // Rule #1 for type msat? - localofferid: c.localofferid.map(|v| hex::encode(v)), // Rule #1 for type hex? + localinvreqid: c.localinvreqid.map(|v| hex::encode(v)), // Rule #1 for type hex? exclude: Some(c.exclude.into_iter().map(|s| s.into()).collect()), // Rule #4 maxfee: c.maxfee.map(|a| a.into()), // Rule #1 for type msat? description: c.description, // Rule #1 for type string? @@ -1375,6 +1499,7 @@ impl From for requests::KeysendRequest { maxdelay: c.maxdelay, // Rule #1 for type u32? exemptfee: c.exemptfee.map(|a| a.into()), // Rule #1 for type msat? routehints: c.routehints.map(|rl| rl.into()), // Rule #1 for type RoutehintList? + extratlvs: c.extratlvs.map(|s| s.into()), // Rule #1 for type TlvStream? } } } @@ -1544,8 +1669,8 @@ impl From for requests::PingRequest { fn from(c: pb::PingRequest) -> Self { Self { id: PublicKey::from_slice(&c.id).unwrap(), // Rule #1 for type pubkey - len: c.len, // Rule #1 for type number? - pongbytes: c.pongbytes, // Rule #1 for type number? + len: c.len.map(|v| v as u16), // Rule #1 for type u16? + pongbytes: c.pongbytes.map(|v| v as u16), // Rule #1 for type u16? } } } diff --git a/cln-grpc/src/pb.rs b/cln-grpc/src/pb.rs index 9a9817660329..87baccba11af 100644 --- a/cln-grpc/src/pb.rs +++ b/cln-grpc/src/pb.rs @@ -1,5 +1,5 @@ tonic::include_proto!("cln"); -use bitcoin_hashes::Hash; +use bitcoin::hashes::Hash; use std::str::FromStr; use cln_rpc::primitives::{ @@ -31,7 +31,7 @@ impl From for Outpoint { impl From for JOutpoint { fn from(a: Outpoint) -> Self { JOutpoint { - txid: bitcoin_hashes::sha256::Hash::from_slice(&a.txid).unwrap(), + txid: bitcoin::hashes::sha256::Hash::from_slice(&a.txid).unwrap(), outnum: a.outnum, } } @@ -114,6 +114,7 @@ impl From for cln_rpc::primitives::Routehop { } } } + impl From for cln_rpc::primitives::Routehint { fn from(c: Routehint) -> Self { Self { @@ -121,6 +122,7 @@ impl From for cln_rpc::primitives::Routehint { } } } + impl From for cln_rpc::primitives::RoutehintList { fn from(c: RoutehintList) -> Self { Self { @@ -128,6 +130,24 @@ impl From for cln_rpc::primitives::RoutehintList { } } } + +impl From for cln_rpc::primitives::TlvStream { + fn from(s: TlvStream) -> Self { + Self { + entries: s.entries.into_iter().map(|e| e.into()).collect(), + } + } +} + +impl From for cln_rpc::primitives::TlvEntry { + fn from(e: TlvEntry) -> Self { + Self { + typ: e.r#type, + value: e.value, + } + } +} + #[cfg(test)] mod test { use super::*; @@ -347,6 +367,6 @@ mod test { ] }); let u: cln_rpc::model::ListpeersResponse = serde_json::from_value(j).unwrap(); - let g: ListpeersResponse = (&u).into(); + let _g: ListpeersResponse = u.into(); } } diff --git a/cln-grpc/src/test.rs b/cln-grpc/src/test.rs index 1a600dee60e8..d7d9ea8b4d91 100644 --- a/cln-grpc/src/test.rs +++ b/cln-grpc/src/test.rs @@ -278,6 +278,7 @@ fn test_keysend() { }], }], }), + extratlvs: None, }; let u: cln_rpc::model::KeysendRequest = g.into(); diff --git a/cln-rpc/Cargo.toml b/cln-rpc/Cargo.toml index 086a106ac727..b1b3865d2291 100644 --- a/cln-rpc/Cargo.toml +++ b/cln-rpc/Cargo.toml @@ -1,25 +1,29 @@ [package] name = "cln-rpc" -version = "0.1.0" +version = "0.1.2" edition = "2021" +license = "MIT" +description = "An async RPC client for Core Lightning." +homepage = "https://github.com/ElementsProject/lightning/tree/master/cln-rpc" +repository = "https://github.com/ElementsProject/lightning" +documentation = "https://docs.rs/cln-rpc" [[example]] name = "cln-rpc-getinfo" path = "examples/getinfo.rs" [dependencies] -anyhow = "1.0.51" -bitcoin_hashes = { version = "0.10.0", features = [ "serde" ] } -bytes = "1.1.0" -log = "0.4.14" -secp256k1 = { version = "0.22.1", features = [ "serde" ] } -serde = { version = "1.0.131", features = ["derive"] } -serde_json = "1.0.72" -tokio-util = { version = "0.6.9", features = ["codec"] } -tokio = { version = "1", features = ["net"]} -futures-util = { version = "*", features = [ "sink" ] } +anyhow = "1.0" +bitcoin = { version = "0.29", features = [ "serde" ] } +bytes = "1" +futures-util = { version = "0.3", features = [ "sink" ] } hex = "0.4.3" +log = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1", features = ["net"]} +tokio-util = { version = "0.7", features = ["codec"] } [dev-dependencies] +env_logger = "0.10" tokio = { version = "1", features = ["net", "macros", "rt-multi-thread"]} -env_logger = "*" diff --git a/cln-rpc/src/lib.rs b/cln-rpc/src/lib.rs index 1e603d1e264f..51f2b53cae07 100644 --- a/cln-rpc/src/lib.rs +++ b/cln-rpc/src/lib.rs @@ -146,7 +146,7 @@ mod test { let read_req = dbg!(read.next().await.unwrap().unwrap()); assert_eq!( - json!({"id": "1", "method": "getinfo", "params": {}, "jsonrpc": "2.0"}), + json!({"id": 1, "method": "getinfo", "params": {}, "jsonrpc": "2.0"}), read_req ); } diff --git a/cln-rpc/src/model.rs b/cln-rpc/src/model.rs index e3ec5d927839..704d8ce7d735 100644 --- a/cln-rpc/src/model.rs +++ b/cln-rpc/src/model.rs @@ -209,8 +209,8 @@ pub mod requests { pub payment_secret: Option, #[serde(alias = "partid", skip_serializing_if = "Option::is_none")] pub partid: Option, - #[serde(alias = "localofferid", skip_serializing_if = "Option::is_none")] - pub localofferid: Option, + #[serde(alias = "localinvreqid", skip_serializing_if = "Option::is_none")] + pub localinvreqid: Option, #[serde(alias = "groupid", skip_serializing_if = "Option::is_none")] pub groupid: Option, } @@ -608,6 +608,8 @@ pub mod requests { pub struct SendonionRequest { #[serde(alias = "onion")] pub onion: String, + #[serde(alias = "first_hop")] + pub first_hop: SendonionFirst_hop, #[serde(alias = "payment_hash")] pub payment_hash: Sha256, #[serde(alias = "label", skip_serializing_if = "Option::is_none")] @@ -622,8 +624,8 @@ pub mod requests { pub amount_msat: Option, #[serde(alias = "destination", skip_serializing_if = "Option::is_none")] pub destination: Option, - #[serde(alias = "localofferid", skip_serializing_if = "Option::is_none")] - pub localofferid: Option, + #[serde(alias = "localinvreqid", skip_serializing_if = "Option::is_none")] + pub localinvreqid: Option, #[serde(alias = "groupid", skip_serializing_if = "Option::is_none")] pub groupid: Option, } @@ -711,8 +713,8 @@ pub mod requests { pub maxdelay: Option, #[serde(alias = "exemptfee", skip_serializing_if = "Option::is_none")] pub exemptfee: Option, - #[serde(alias = "localofferid", skip_serializing_if = "Option::is_none")] - pub localofferid: Option, + #[serde(alias = "localinvreqid", skip_serializing_if = "Option::is_none")] + pub localinvreqid: Option, #[serde(alias = "exclude", skip_serializing_if = "crate::is_none_or_empty")] pub exclude: Option>, #[serde(alias = "maxfee", skip_serializing_if = "Option::is_none")] @@ -864,10 +866,6 @@ pub mod requests { type Response = super::responses::WithdrawResponse; } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct KeysendExtratlvs { - } - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct KeysendRequest { #[serde(alias = "destination")] @@ -886,6 +884,8 @@ pub mod requests { pub exemptfee: Option, #[serde(alias = "routehints", skip_serializing_if = "Option::is_none")] pub routehints: Option, + #[serde(alias = "extratlvs", skip_serializing_if = "Option::is_none")] + pub extratlvs: Option, } impl From for Request { @@ -1261,9 +1261,9 @@ pub mod requests { #[serde(alias = "id")] pub id: PublicKey, #[serde(alias = "len", skip_serializing_if = "Option::is_none")] - pub len: Option, + pub len: Option, #[serde(alias = "pongbytes", skip_serializing_if = "Option::is_none")] - pub pongbytes: Option, + pub pongbytes: Option, } impl From for Request { @@ -1457,6 +1457,8 @@ pub mod responses { pub version: String, #[serde(alias = "lightning-dir")] pub lightning_dir: String, + #[serde(alias = "our_features", skip_serializing_if = "Option::is_none")] + pub our_features: Option, #[serde(alias = "blockheight")] pub blockheight: u32, #[serde(alias = "network")] @@ -1697,6 +1699,8 @@ pub mod responses { pub state: ListpeersPeersChannelsState, #[serde(alias = "scratch_txid", skip_serializing_if = "Option::is_none")] pub scratch_txid: Option, + #[serde(alias = "feerate", skip_serializing_if = "Option::is_none")] + pub feerate: Option, #[serde(alias = "owner", skip_serializing_if = "Option::is_none")] pub owner: Option, #[serde(alias = "short_channel_id", skip_serializing_if = "Option::is_none")] @@ -1728,6 +1732,8 @@ pub mod responses { pub closer: Option, #[serde(alias = "features")] pub features: Vec, + #[serde(alias = "funding", skip_serializing_if = "Option::is_none")] + pub funding: Option, #[serde(alias = "to_us_msat", skip_serializing_if = "Option::is_none")] pub to_us_msat: Option, #[serde(alias = "min_to_us_msat", skip_serializing_if = "Option::is_none")] @@ -1764,6 +1770,8 @@ pub mod responses { pub our_to_self_delay: Option, #[serde(alias = "max_accepted_htlcs", skip_serializing_if = "Option::is_none")] pub max_accepted_htlcs: Option, + #[serde(alias = "alias", skip_serializing_if = "Option::is_none")] + pub alias: Option, #[serde(alias = "state_changes", skip_serializing_if = "crate::is_none_or_empty")] pub state_changes: Option>, #[serde(alias = "status", skip_serializing_if = "crate::is_none_or_empty")] @@ -1833,6 +1841,8 @@ pub mod responses { CONFIRMED, #[serde(rename = "spent")] SPENT, + #[serde(rename = "immature")] + IMMATURE, } impl TryFrom for ListfundsOutputsStatus { @@ -1842,6 +1852,7 @@ pub mod responses { 0 => Ok(ListfundsOutputsStatus::UNCONFIRMED), 1 => Ok(ListfundsOutputsStatus::CONFIRMED), 2 => Ok(ListfundsOutputsStatus::SPENT), + 3 => Ok(ListfundsOutputsStatus::IMMATURE), o => Err(anyhow::anyhow!("Unknown variant {} for enum ListfundsOutputsStatus", o)), } } @@ -2193,6 +2204,8 @@ pub mod responses { // Path `Connect.direction` #[serde(rename = "direction")] pub direction: ConnectDirection, + #[serde(alias = "address")] + pub address: ConnectAddress, } impl TryFrom for ConnectResponse { @@ -2257,8 +2270,8 @@ pub mod responses { pub payment_preimage: Option, #[serde(alias = "local_offer_id", skip_serializing_if = "Option::is_none")] pub local_offer_id: Option, - #[serde(alias = "payer_note", skip_serializing_if = "Option::is_none")] - pub payer_note: Option, + #[serde(alias = "invreq_payer_note", skip_serializing_if = "Option::is_none")] + pub invreq_payer_note: Option, } impl TryFrom for CreateinvoiceResponse { @@ -2395,8 +2408,8 @@ pub mod responses { pub expires_at: u64, #[serde(alias = "local_offer_id", skip_serializing_if = "Option::is_none")] pub local_offer_id: Option, - #[serde(alias = "payer_note", skip_serializing_if = "Option::is_none")] - pub payer_note: Option, + #[serde(alias = "invreq_payer_note", skip_serializing_if = "Option::is_none")] + pub invreq_payer_note: Option, } impl TryFrom for DelinvoiceResponse { @@ -2515,8 +2528,8 @@ pub mod responses { pub bolt12: Option, #[serde(alias = "local_offer_id", skip_serializing_if = "Option::is_none")] pub local_offer_id: Option, - #[serde(alias = "payer_note", skip_serializing_if = "Option::is_none")] - pub payer_note: Option, + #[serde(alias = "invreq_payer_note", skip_serializing_if = "Option::is_none")] + pub invreq_payer_note: Option, #[serde(alias = "pay_index", skip_serializing_if = "Option::is_none")] pub pay_index: Option, #[serde(alias = "amount_received_msat", skip_serializing_if = "Option::is_none")] @@ -3487,6 +3500,12 @@ pub mod responses { pub struct FeeratesResponse { #[serde(alias = "warning_missing_feerates", skip_serializing_if = "Option::is_none")] pub warning_missing_feerates: Option, + #[serde(alias = "perkb", skip_serializing_if = "Option::is_none")] + pub perkb: Option, + #[serde(alias = "perkw", skip_serializing_if = "Option::is_none")] + pub perkw: Option, + #[serde(alias = "onchain_fee_estimates", skip_serializing_if = "Option::is_none")] + pub onchain_fee_estimates: Option, } impl TryFrom for FeeratesResponse { diff --git a/cln-rpc/src/primitives.rs b/cln-rpc/src/primitives.rs index 1c53f81ce047..02ef24be77de 100644 --- a/cln-rpc/src/primitives.rs +++ b/cln-rpc/src/primitives.rs @@ -1,14 +1,14 @@ -use std::fmt::{Display, Formatter}; use anyhow::Context; use anyhow::{anyhow, Error, Result}; +use bitcoin::hashes::Hash as BitcoinHash; use serde::{Deserialize, Serialize}; use serde::{Deserializer, Serializer}; +use std::fmt::{Display, Formatter}; use std::str::FromStr; use std::string::ToString; -use bitcoin_hashes::Hash as BitcoinHash; -pub use bitcoin_hashes::sha256::Hash as Sha256; -pub use secp256k1::PublicKey; +pub use bitcoin::hashes::sha256::Hash as Sha256; +pub use bitcoin::secp256k1::PublicKey; #[derive(Copy, Clone, Serialize, Deserialize, Debug)] #[allow(non_camel_case_types)] @@ -62,11 +62,13 @@ pub struct Amount { impl Amount { pub fn from_msat(msat: u64) -> Amount { - Amount { msat: msat } + Amount { msat } } + pub fn from_sat(sat: u64) -> Amount { Amount { msat: 1_000 * sat } } + pub fn from_btc(btc: u64) -> Amount { Amount { msat: 100_000_000_000 * btc, @@ -83,7 +85,7 @@ impl std::ops::Add for Amount { fn add(self, rhs: Self) -> Self::Output { Amount { - msat: self.msat + rhs.msat + msat: self.msat + rhs.msat, } } } @@ -93,7 +95,7 @@ impl std::ops::Sub for Amount { fn sub(self, rhs: Self) -> Self::Output { Amount { - msat: self.msat - rhs.msat + msat: self.msat - rhs.msat, } } } @@ -234,7 +236,8 @@ impl<'de> Deserialize<'de> for Outpoint { let txid_bytes = hex::decode(splits[0]).map_err(|_| Error::custom("not a valid hex encoded txid"))?; - let txid= Sha256::from_slice(&txid_bytes).map_err(|e| Error::custom(format!("Invalid TxId: {}", e)))?; + let txid = Sha256::from_slice(&txid_bytes) + .map_err(|e| Error::custom(format!("Invalid TxId: {}", e)))?; let outnum: u32 = splits[1] .parse() @@ -548,6 +551,25 @@ mod test { let serialized: String = serde_json::to_string(&od).unwrap(); assert_eq!(a, serialized); } + + #[test] + fn tlvstream() { + let stream = TlvStream { + entries: vec![ + TlvEntry { + typ: 31337, + value: vec![1, 2, 3, 4, 5], + }, + TlvEntry { + typ: 42, + value: vec![], + }, + ], + }; + + let res = serde_json::to_string(&stream).unwrap(); + assert_eq!(res, "{\"31337\":\"0102030405\",\"42\":\"\"}"); + } } #[derive(Clone, Debug, PartialEq)] @@ -623,4 +645,50 @@ impl Display for RpcError { } } -impl std::error::Error for RpcError {} \ No newline at end of file +impl std::error::Error for RpcError {} + +#[derive(Clone, Debug)] +pub struct TlvEntry { + pub typ: u64, + pub value: Vec, +} + +#[derive(Clone, Debug)] +pub struct TlvStream { + pub entries: Vec, +} + +impl<'de> Deserialize<'de> for TlvStream { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let map: std::collections::HashMap = Deserialize::deserialize(deserializer)?; + + let entries = map + .iter() + .map(|(k, v)| TlvEntry { + typ: *k, + value: hex::decode(v).unwrap(), + }) + .collect(); + + Ok(TlvStream { entries }) + } +} + +impl Serialize for TlvStream { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeMap; + + let mut map = serializer.serialize_map(Some(self.entries.len()))?; + for e in &self.entries { + map.serialize_key(&e.typ)?; + map.serialize_value(&hex::encode(&e.value))?; + } + map.end() + } +} diff --git a/common/Makefile b/common/Makefile index 6949a6882c3a..273bd153c653 100644 --- a/common/Makefile +++ b/common/Makefile @@ -10,6 +10,7 @@ COMMON_SRC_NOGEN := \ common/billboard.c \ common/bip32.c \ common/blindedpath.c \ + common/blindedpay.c \ common/blinding.c \ common/blockheight_states.c \ common/bolt11.c \ @@ -46,7 +47,9 @@ COMMON_SRC_NOGEN := \ common/interactivetx.c \ common/initial_channel.c \ common/initial_commit_tx.c \ + common/invoice_path_id.c \ common/iso4217.c \ + common/json_filter.c \ common/json_param.c \ common/json_parse.c \ common/json_parse_simple.c \ @@ -57,8 +60,10 @@ COMMON_SRC_NOGEN := \ common/memleak.c \ common/msg_queue.c \ common/node_id.c \ - common/onion.c \ + common/onion_decode.c \ + common/onion_encode.c \ common/onionreply.c \ + common/onion_message_parse.c \ common/peer_billboard.c \ common/peer_failed.c \ common/peer_io.c \ @@ -99,6 +104,7 @@ COMMON_HEADERS_NOGEN := $(COMMON_SRC_NOGEN:.c=.h) \ common/ecdh.h \ common/errcode.h \ common/gossip_constants.h \ + common/hsm_version.h \ common/htlc.h \ common/json_command.h \ common/jsonrpc_errors.h \ diff --git a/common/blindedpath.c b/common/blindedpath.c index 5307999d3976..46176eab1360 100644 --- a/common/blindedpath.c +++ b/common/blindedpath.c @@ -19,36 +19,27 @@ static bool blind_node(const struct privkey *blinding, struct pubkey *node_alias, struct privkey *next_blinding) { - struct secret node_id_blinding; struct pubkey blinding_pubkey; struct sha256 h; - /* - * Blinded node_id for N(i), private key known only by N(i): - * B(i) = HMAC256("blinded_node_id", ss(i)) * P(i) - */ - subkey_from_hmac("blinded_node_id", ss, &node_id_blinding); - SUPERVERBOSE("\t\"HMAC256('blinded_node_id', ss)\": \"%s\",\n", - type_to_string(tmpctx, struct secret, - &node_id_blinding)); - - *node_alias = *node; - if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, - &node_alias->pubkey, - node_id_blinding.data) != 1) + if (!blindedpath_get_alias(ss, node, node_alias)) return false; SUPERVERBOSE("\t\"blinded_node_id\": \"%s\",\n", type_to_string(tmpctx, struct pubkey, node_alias)); - /* - * Ephemeral private key, only known by N(r): - * e(i+1) = H(E(i) || ss(i)) * e(i) + /* BOLT-route-blinding #4: + * - `E(i+1) = SHA256(E(i) || ss(i)) * E(i)` + * (NB: `N(i)` MUST NOT learn `e(i)`) */ if (!pubkey_from_privkey(blinding, &blinding_pubkey)) return false; SUPERVERBOSE("\t\"E\": \"%s\",\n", type_to_string(tmpctx, struct pubkey, &blinding_pubkey)); + /* BOLT-route-blinding #4: + * - `e(i+1) = SHA256(E(i) || ss(i)) * e(i)` + * (blinding ephemeral private key, only known by `N(r)`) + */ blinding_hash_e_and_ss(&blinding_pubkey, ss, &h); SUPERVERBOSE("\t\"H(E || ss)\": \"%s\",\n", type_to_string(tmpctx, struct sha256, &h)); @@ -66,16 +57,15 @@ static u8 *enctlv_from_encmsg_raw(const tal_t *ctx, struct privkey *next_blinding, struct pubkey *node_alias) { - /* https://github.com/lightning/bolts/blob/route-blinding/proposals/route-blinding.md */ struct secret ss, rho; u8 *ret; int ok; /* All-zero npub */ static const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; - /* - * shared secret known only by N(r) and N(i): - * ss(i) = H(e(i) * P(i)) = H(k(i) * E(i)) + /* BOLT-route-blinding #4: + * - `ss(i) = SHA256(e(i) * N(i)) = SHA256(k(i) * E(i))` + * (ECDH shared secret known only by `N(r)` and `N(i)`) */ if (secp256k1_ecdh(secp256k1_ctx, ss.data, &node->pubkey, blinding->secret.data, @@ -89,19 +79,22 @@ static u8 *enctlv_from_encmsg_raw(const tal_t *ctx, return NULL; ret = tal_dup_talarr(ctx, u8, raw_encmsg); - SUPERVERBOSE("\t\"encmsg_hex\": \"%s\",\n", tal_hex(tmpctx, ret)); - /* - * Key used to encrypt payload for N(i) by N(r): - * rho(i) = HMAC256("rho", ss(i)) + /* BOLT-route-blinding #4: + * - `rho(i) = HMAC256("rho", ss(i))` + * (key used to encrypt the payload for `N(i)` by `N(r)`) */ subkey_from_hmac("rho", &ss, &rho); SUPERVERBOSE("\t\"rho\": \"%s\",\n", type_to_string(tmpctx, struct secret, &rho)); + /* BOLT-route-blinding #4: + * - MUST encrypt each `encrypted_data_tlv(i)` with ChaCha20-Poly1305 + * using the corresponding `rho(i)` key and an all-zero nonce to + * produce `encrypted_recipient_data(i)` + */ /* Encrypt in place */ towire_pad(&ret, crypto_aead_chacha20poly1305_ietf_ABYTES); - ok = crypto_aead_chacha20poly1305_ietf_encrypt(ret, NULL, ret, tal_bytelen(ret) @@ -114,15 +107,20 @@ static u8 *enctlv_from_encmsg_raw(const tal_t *ctx, return ret; } -static u8 *enctlv_from_encmsg(const tal_t *ctx, - const struct privkey *blinding, - const struct pubkey *node, - const struct tlv_encrypted_data_tlv *encmsg, - struct privkey *next_blinding, - struct pubkey *node_alias) +u8 *encrypt_tlv_encrypted_data(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct tlv_encrypted_data_tlv *encmsg, + struct privkey *next_blinding, + struct pubkey *node_alias) { + struct privkey unused; u8 *encmsg_raw = tal_arr(NULL, u8, 0); towire_tlv_encrypted_data_tlv(&encmsg_raw, encmsg); + + /* last hop doesn't care about next_blinding */ + if (!next_blinding) + next_blinding = &unused; return enctlv_from_encmsg_raw(ctx, blinding, node, take(encmsg_raw), next_blinding, node_alias); } @@ -134,15 +132,24 @@ bool unblind_onion(const struct pubkey *blinding, { struct secret hmac; - /* E(i) */ + /* BOLT-route-blinding #4: + * A reader: + *... + * - MUST compute: + * - `ss(i) = SHA256(k(i) * E(i))` (standard ECDH) + * - `b(i) = HMAC256("blinded_node_id", ss(i)) * k(i)` + */ ecdh(blinding, ss); - - /* b(i) = HMAC256("blinded_node_id", ss(i)) * k(i) */ subkey_from_hmac("blinded_node_id", ss, &hmac); /* We instead tweak the *ephemeral* key from the onion and use * our normal privkey: since hsmd knows only how to ECDH with - * our real key */ + * our real key. IOW: */ + /* BOLT-route-blinding #4: + * - MUST use `b(i)` instead of its private key `k(i)` to decrypt the onion. Note + * that the node may instead tweak the onion ephemeral key with + * `HMAC256("blinded_node_id", ss(i))` which achieves the same result. + */ return secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &onion_key->pubkey, hmac.data) == 1; @@ -158,13 +165,18 @@ static u8 *decrypt_encmsg_raw(const tal_t *ctx, /* All-zero npub */ static const unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; - /* We need this to decrypt enctlv */ + /* BOLT-route-blinding #4: + * A reader: + *... + *- MUST decrypt the `encrypted_data` field using `rho(i)` and use + * the decrypted fields to locate the next node + */ subkey_from_hmac("rho", ss, &rho); /* BOLT-onion-message #4: - * - if `enctlv` is not present, or does not decrypt with the - * shared secret from the given `blinding` parameter: - * - MUST drop the message. + *- If the `encrypted_data` field is missing or cannot + * be decrypted: + * - MUST return an error */ /* Too short? */ if (tal_bytelen(enctlv) < crypto_aead_chacha20poly1305_ietf_ABYTES) @@ -183,10 +195,10 @@ static u8 *decrypt_encmsg_raw(const tal_t *ctx, return dec; } -static struct tlv_encrypted_data_tlv *decrypt_encmsg(const tal_t *ctx, - const struct pubkey *blinding, - const struct secret *ss, - const u8 *enctlv) +struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx, + const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv) { const u8 *cursor = decrypt_encmsg_raw(tmpctx, blinding, ss, enctlv); size_t maxlen = tal_bytelen(cursor); @@ -196,132 +208,53 @@ static struct tlv_encrypted_data_tlv *decrypt_encmsg(const tal_t *ctx, * - if the `enctlv` is not a valid TLV... * - MUST drop the message. */ + /* Note: our parser consider nothing is a valid TLV, but decrypt_encmsg_raw + * returns NULL if it couldn't decrypt. */ + if (!cursor) + return NULL; return fromwire_tlv_encrypted_data_tlv(ctx, &cursor, &maxlen); } -bool decrypt_enctlv(const struct pubkey *blinding, - const struct secret *ss, - const u8 *enctlv, - struct pubkey *next_node, - struct pubkey *next_blinding) +bool blindedpath_get_alias(const struct secret *ss, + const struct pubkey *my_id, + struct pubkey *alias) { - struct tlv_encrypted_data_tlv *encmsg; - - encmsg = decrypt_encmsg(tmpctx, blinding, ss, enctlv); - if (!encmsg) - return false; + struct secret node_id_blinding; - /* BOLT-onion-message #4: - * - * The reader: - * - if it is not the final node according to the onion encryption: - *... - * - if the `enctlv` ... does not contain - * `next_node_id`: - * - MUST drop the message. + /* BOLT-route-blinding #4: + * - `B(i) = HMAC256("blinded_node_id", ss(i)) * N(i)` + * (blinded `node_id` for `N(i)`, private key known only by `N(i)`) */ - if (!encmsg->next_node_id) - return false; + subkey_from_hmac("blinded_node_id", ss, &node_id_blinding); + SUPERVERBOSE("\t\"HMAC256('blinded_node_id', ss)\": \"%s\",\n", + type_to_string(tmpctx, struct secret, + &node_id_blinding)); - /* BOLT-onion-message #4: - * The reader: - * - if it is not the final node according to the onion encryption: - *... - * - if the `enctlv` contains `path_id`: - * - MUST drop the message. - */ - if (encmsg->path_id) - return false; + *alias = *my_id; + return secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &alias->pubkey, + node_id_blinding.data) == 1; +} - /* BOLT-onion-message #4: - * The reader: - * - if it is not the final node according to the onion encryption: - *... - * - if `blinding` is specified in the `enctlv`: - * - MUST pass that as `blinding` in the `onion_message` - * - otherwise: - * - MUST pass `blinding` derived as in - * [Route Blinding][route-blinding] (i.e. - * `E(i+1) = H(E(i) || ss(i)) * E(i)`). +void blindedpath_next_blinding(const struct tlv_encrypted_data_tlv *enc, + const struct pubkey *blinding, + const struct secret *ss, + struct pubkey *next_blinding) +{ + /* BOLT-route + * - `E(1) = SHA256(E(0) || ss(0)) * E(0)` + * ... + * - If `encrypted_data` contains a `next_blinding_override`: + * - MUST use it as the next blinding point instead of `E(1)` + * - Otherwise: + * - MUST use `E(1)` as the next blinding point */ - *next_node = *encmsg->next_node_id; - if (encmsg->next_blinding_override) - *next_blinding = *encmsg->next_blinding_override; + if (enc->next_blinding_override) + *next_blinding = *enc->next_blinding_override; else { /* E(i-1) = H(E(i) || ss(i)) * E(i) */ struct sha256 h; blinding_hash_e_and_ss(blinding, ss, &h); blinding_next_pubkey(blinding, &h, next_blinding); } - return true; -} - -bool decrypt_final_enctlv(const tal_t *ctx, - const struct pubkey *blinding, - const struct secret *ss, - const u8 *enctlv, - const struct pubkey *my_id, - struct pubkey *alias, - struct secret **path_id) -{ - struct tlv_encrypted_data_tlv *encmsg; - struct secret node_id_blinding; - - /* Repeat the tweak to get the alias it was using for us */ - subkey_from_hmac("blinded_node_id", ss, &node_id_blinding); - *alias = *my_id; - if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, - &alias->pubkey, - node_id_blinding.data) != 1) - return false; - - encmsg = decrypt_encmsg(tmpctx, blinding, ss, enctlv); - if (!encmsg) - return false; - - if (tal_bytelen(encmsg->path_id) == sizeof(**path_id)) { - *path_id = tal(ctx, struct secret); - memcpy(*path_id, encmsg->path_id, sizeof(**path_id)); - } else - *path_id = NULL; - - return true; -} - -u8 *create_enctlv(const tal_t *ctx, - const struct privkey *blinding, - const struct pubkey *node, - const struct pubkey *next_node, - size_t padlen, - const struct pubkey *next_blinding_override, - struct privkey *next_blinding, - struct pubkey *node_alias) -{ - struct tlv_encrypted_data_tlv *encmsg = tlv_encrypted_data_tlv_new(tmpctx); - if (padlen) - encmsg->padding = tal_arrz(encmsg, u8, padlen); - encmsg->next_node_id = cast_const(struct pubkey *, next_node); - encmsg->next_blinding_override = cast_const(struct pubkey *, next_blinding_override); - - return enctlv_from_encmsg(ctx, blinding, node, encmsg, - next_blinding, node_alias); -} - -u8 *create_final_enctlv(const tal_t *ctx, - const struct privkey *blinding, - const struct pubkey *final_node, - size_t padlen, - const struct secret *path_id, - struct pubkey *node_alias) -{ - struct tlv_encrypted_data_tlv *encmsg = tlv_encrypted_data_tlv_new(tmpctx); - struct privkey unused_next_blinding; - - if (padlen) - encmsg->padding = tal_arrz(encmsg, u8, padlen); - if (path_id) - encmsg->path_id = (u8 *)tal_dup(encmsg, struct secret, path_id); - - return enctlv_from_encmsg(ctx, blinding, final_node, encmsg, - &unused_next_blinding, node_alias); } diff --git a/common/blindedpath.h b/common/blindedpath.h index 285ec189d972..7df0dc2aadf6 100644 --- a/common/blindedpath.h +++ b/common/blindedpath.h @@ -9,48 +9,30 @@ struct route_info; struct pubkey; struct privkey; struct secret; +struct short_channel_id; +struct tlv_encrypted_data_tlv; +struct tlv_encrypted_data_tlv_payment_constraints; +struct tlv_encrypted_data_tlv_payment_relay; /** - * create_enctlv - Encrypt an encmsg to form an enctlv. + * encrypt_tlv_encrypted_data - Encrypt a tlv_encrypted_data_tlv. * @ctx: tal context * @blinding: e(i), the blinding secret * @node: the pubkey of the node to encrypt for - * @next_node: the pubkey of the next node, to place in enctlv - * @padlen: if non-zero, the bytes of padding to add (also adds 2 byte padding hdr) - * @next_blinding_override: the optional blinding point to place in enctlv - * @next_blinding: (out) e(i+1), the next blinding secret. + * @tlv: the message to encrypt. + * @next_blinding: (out) e(i+1), the next blinding secret (optional) * @node_alias: (out) the blinded pubkey of the node to tell the recipient. * - * Returns the enctlv blob, or NULL if the secret is invalid. + * You create a blinding secret using randombytes_buf(), then call this + * iteratively for each node in the path. */ -u8 *create_enctlv(const tal_t *ctx, - const struct privkey *blinding, - const struct pubkey *node, - const struct pubkey *next_node, - size_t padlen, - const struct pubkey *next_blinding_override, - struct privkey *next_blinding, - struct pubkey *node_alias) - NON_NULL_ARGS(2, 3, 4, 7, 8); - -/** - * create_final_enctlv - Encrypt an encmsg to form the final enctlv. - * @ctx: tal context - * @blinding: e(i), the blinding secret - * @final_node: the pubkey of the node to encrypt for - * @padlen: if non-zero, the bytes of padding to add (also adds 2 byte padding hdr) - * @path_id: secret to include in enctlv, if not NULL. - * @node_alias: (out) the blinded pubkey of the node to tell the recipient. - * - * If it fails, it means one of the privkeys is bad. - */ -u8 *create_final_enctlv(const tal_t *ctx, - const struct privkey *blinding, - const struct pubkey *final_node, - size_t padlen, - const struct secret *path_id, - struct pubkey *node_alias) - NON_NULL_ARGS(2, 3, 6); +u8 *encrypt_tlv_encrypted_data(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct tlv_encrypted_data_tlv *tlv, + struct privkey *next_blinding, + struct pubkey *node_alias) + NON_NULL_ARGS(2, 3, 4, 6); /** * unblind_onion - tweak onion epheremeral key so we can decode it with ours. @@ -68,41 +50,38 @@ bool unblind_onion(const struct pubkey *blinding, NO_NULL_ARGS; /** - * decrypt_enctlv - Decrypt an encmsg to form an enctlv. - * @blinding: E(i), the blinding pubkey the previous peer gave us. - * @ss: the blinding secret from unblind_onion(). - * @enctlv: the enctlv from the onion (tal, may be NULL). - * @next_node: (out) the next node_id. - * @next_blinding: (out) the next blinding E(i+1). + * blindedpath_get_alias - tweak our id to see alias they used. + * @ss: the shared secret from unblind_onion + * @my_id: my node_id + * @alias: (out) the alias. * - * Returns false if decryption failed or encmsg was malformed. + * Returns false on ECDH fail. */ -bool decrypt_enctlv(const struct pubkey *blinding, - const struct secret *ss, - const u8 *enctlv, - struct pubkey *next_node, - struct pubkey *next_blinding) - NON_NULL_ARGS(1, 2, 4, 5); +bool blindedpath_get_alias(const struct secret *ss, + const struct pubkey *my_id, + struct pubkey *alias); /** - * decrypt_final_enctlv - Decrypt an encmsg to form an enctlv. - * @ctx: tal context for @path_id + * decrypt_encrypted_data - Decrypt an encmsg to form an tlv_encrypted_data_tlv. + * @ctx: the context to allocate off. * @blinding: E(i), the blinding pubkey the previous peer gave us. * @ss: the blinding secret from unblind_onion(). * @enctlv: the enctlv from the onion (tal, may be NULL). - * @my_id: the pubkey of this node. - * @alias: (out) the node_id this was addressed to. - * @path_id: (out) the secret contained in the enctlv, if any (NULL if invalid or unset) * - * Returns false if decryption failed or encmsg was malformed. + * Returns NULL if decryption failed or encmsg was malformed. + */ +struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx, + const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv) + NON_NULL_ARGS(2, 3); + +/** + * blindedpath_next_blinding - Calculate or extract next blinding pubkey */ -bool decrypt_final_enctlv(const tal_t *ctx, - const struct pubkey *blinding, - const struct secret *ss, - const u8 *enctlv, - const struct pubkey *my_id, - struct pubkey *alias, - struct secret **path_id) - NON_NULL_ARGS(1, 2, 4, 5); +void blindedpath_next_blinding(const struct tlv_encrypted_data_tlv *enc, + const struct pubkey *blinding, + const struct secret *ss, + struct pubkey *next_blinding); #endif /* LIGHTNING_COMMON_BLINDEDPATH_H */ diff --git a/common/blindedpay.c b/common/blindedpay.c new file mode 100644 index 000000000000..b542bb26a621 --- /dev/null +++ b/common/blindedpay.c @@ -0,0 +1,39 @@ +#include "config.h" +#include +#include +#include +#include + +u8 **blinded_onion_hops(const tal_t *ctx, + struct amount_msat final_amount, + u32 final_cltv, + const struct blinded_path *path) +{ + u8 **onions = tal_arr(ctx, u8 *, tal_count(path->path)); + + assert(tal_count(onions) > 0); + + for (size_t i = 0; i < tal_count(onions); i++) { + bool first = (i == 0); + bool final = (i == tal_count(onions) - 1); + + /* BOLT-route-blinding #4: + * - For every node inside a blinded route: + * - MUST include the `encrypted_recipient_data` provided by the + * recipient + * - For the first node in the blinded route: + * - MUST include the `blinding_point` provided by the + * recipient in `current_blinding_point` + * - If it is the final node: + * - MUST include `amt_to_forward` and `outgoing_cltv_value`. + * - MUST include `total_amount_msat` when using `basic_mpp`. + * - MUST NOT include any other tlv field. + */ + onions[i] = onion_blinded_hop(onions, + final ? &final_amount : NULL, + final ? &final_cltv : NULL, + path->path[i]->encrypted_recipient_data, + first ? &path->blinding : NULL); + } + return onions; +} diff --git a/common/blindedpay.h b/common/blindedpay.h new file mode 100644 index 000000000000..5ef1b0ddf6a6 --- /dev/null +++ b/common/blindedpay.h @@ -0,0 +1,25 @@ +/* Code to create onion fragments to make payment down this struct blinded_path */ +#ifndef LIGHTNING_COMMON_BLINDEDPAY_H +#define LIGHTNING_COMMON_BLINDEDPAY_H +#include "config.h" +#include +#include + +struct blinded_path; + +/** + * blinded_onion_hops - turn this path into a series of onion hops + * @ctx: context to allocate from + * @final_amount: amount we want to reach the end + * @final_cltv: cltv we want to at end + * @payinfo: fee and other restriction info + * + * This calls onion_nonfinal_hop and onion_final_hop to create onion + * blobs. + */ +u8 **blinded_onion_hops(const tal_t *ctx, + struct amount_msat final_amount, + u32 final_cltv, + const struct blinded_path *path); + +#endif /* LIGHTNING_COMMON_BLINDEDPAY_H */ diff --git a/common/bolt12.c b/common/bolt12.c index ec1569c90561..4f1a5ab33321 100644 --- a/common/bolt12.c +++ b/common/bolt12.c @@ -1,4 +1,5 @@ #include "config.h" +#include #include #include #include @@ -10,23 +11,25 @@ #include /* If chains is NULL, max_num_chains is ignored */ -static bool bolt12_chains_match(const struct bitcoin_blkid *chains, - size_t max_num_chains, - const struct chainparams *must_be_chain) +bool bolt12_chains_match(const struct bitcoin_blkid *chains, + size_t max_num_chains, + const struct chainparams *must_be_chain) { /* BOLT-offers #12: * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. + * - MUST specify `offer_chains` the offer is valid for. * - otherwise: - * - the bitcoin chain is implied as the first and only entry. + * - MAY omit `offer_chains`, implying that bitcoin is only chain. */ /* BOLT-offers #12: - * The reader of an invoice_request: + * A reader of an offer: *... - * - if `chain` is not present: - * - MUST fail the request if bitcoin is not a supported chain. - * - otherwise: - * - MUST fail the request if `chain` is not a supported chain. + * - if `offer_chains` is not set: + * - if the node does not accept bitcoin invoices: + * - MUST NOT respond to the offer + * - otherwise: (`offer_chains` is set): + * - if the node does not accept invoices for any of the `chains`: + * - MUST NOT respond to the offer */ if (!chains) { max_num_chains = 1; @@ -52,6 +55,7 @@ static char *check_features_and_chain(const tal_t *ctx, const struct feature_set *our_features, const struct chainparams *must_be_chain, const u8 *features, + enum feature_place fplace, const struct bitcoin_blkid *chains, size_t num_chains) { @@ -61,8 +65,7 @@ static char *check_features_and_chain(const tal_t *ctx, } if (our_features) { - int badf = features_unsupported(our_features, features, - BOLT11_FEATURE); + int badf = features_unsupported(our_features, features, fplace); if (badf != -1) return tal_fmt(ctx, "unknown feature bit %i", badf); } @@ -73,25 +76,22 @@ static char *check_features_and_chain(const tal_t *ctx, bool bolt12_check_signature(const struct tlv_field *fields, const char *messagename, const char *fieldname, - const struct point32 *key, + const struct pubkey *key, const struct bip340sig *sig) { struct sha256 m, shash; merkle_tlv(fields, &m); sighash_from_merkle(messagename, fieldname, &m, &shash); - return secp256k1_schnorrsig_verify(secp256k1_ctx, - sig->u8, - shash.u.u8, - sizeof(shash.u.u8), - &key->pubkey) == 1; + + return check_schnorr_sig(&shash, &key->pubkey, sig); } static char *check_signature(const tal_t *ctx, const struct tlv_field *fields, const char *messagename, const char *fieldname, - const struct point32 *node_id, + const struct pubkey *node_id, const struct bip340sig *sig) { if (!node_id) @@ -183,25 +183,13 @@ struct tlv_offer *offer_decode(const tal_t *ctx, *fail = check_features_and_chain(ctx, our_features, must_be_chain, - offer->features, - offer->chains, - tal_count(offer->chains)); + offer->offer_features, + BOLT12_OFFER_FEATURE, + offer->offer_chains, + tal_count(offer->offer_chains)); if (*fail) return tal_free(offer); - /* BOLT-offers #12: - * - if `signature` is present, but is not a valid signature using - * `node_id` as described in [Signature Calculation](#signature-calculation): - * - MUST NOT respond to the offer. - */ - if (offer->signature) { - *fail = check_signature(ctx, offer->fields, - "offer", "signature", - offer->node_id, offer->signature); - if (*fail) - return tal_free(offer); - } - return offer; } @@ -231,14 +219,15 @@ struct tlv_invoice_request *invrequest_decode(const tal_t *ctx, invrequest = fromwire_tlv_invoice_request(ctx, &data, &dlen); if (!invrequest) { - *fail = tal_fmt(ctx, "invalid invoice_request data"); + *fail = tal_fmt(ctx, "invalid invreq data"); return NULL; } *fail = check_features_and_chain(ctx, our_features, must_be_chain, - invrequest->features, - invrequest->chain, 1); + invrequest->invreq_features, + BOLT12_INVREQ_FEATURE, + invrequest->invreq_chain, 1); if (*fail) return tal_free(invrequest); @@ -277,8 +266,9 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx, *fail = check_features_and_chain(ctx, our_features, must_be_chain, - invoice->features, - invoice->chain, 1); + invoice->invoice_features, + BOLT12_INVOICE_FEATURE, + invoice->invreq_chain, 1); if (*fail) return tal_free(invoice); @@ -327,7 +317,7 @@ static u64 time_change(u64 prevstart, u32 number, } u64 offer_period_start(u64 basetime, size_t n, - const struct tlv_offer_recurrence *recur) + const struct recurrence *recur) { /* BOLT-offers-recurrence #12: * 1. A `time_unit` defining 0 (seconds), 1 (days), 2 (months), @@ -348,9 +338,9 @@ u64 offer_period_start(u64 basetime, size_t n, } } -void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, - const struct tlv_offer_recurrence_paywindow *recurrence_paywindow, - const struct tlv_offer_recurrence_base *recurrence_base, +void offer_period_paywindow(const struct recurrence *recurrence, + const struct recurrence_paywindow *recurrence_paywindow, + const struct recurrence_base *recurrence_base, u64 basetime, u64 period_idx, u64 *start, u64 *end) { @@ -363,9 +353,9 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, /* BOLT-offers-recurrence #12: * - if the offer has a `recurrence_basetime` or the * `recurrence_counter` is non-zero: - * - SHOULD NOT send an `invoice_request` for a period prior to + * - SHOULD NOT send an `invreq` for a period prior to * `seconds_before` seconds before that period start. - * - SHOULD NOT send an `invoice_request` for a period later + * - SHOULD NOT send an `invreq` for a period later * than `seconds_after` seconds past that period start. */ *start = pstart - recurrence_paywindow->seconds_before; @@ -380,7 +370,7 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, } else { /* BOLT-offers-recurrence #12: * - otherwise: - * - SHOULD NOT send an `invoice_request` with + * - SHOULD NOT send an `invreq` with * `recurrence_counter` is non-zero for a period whose * immediate predecessor has not yet begun. */ @@ -391,7 +381,7 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, recurrence); /* BOLT-offers-recurrence #12: - * - SHOULD NOT send an `invoice_request` for a period which + * - SHOULD NOT send an `invreq` for a period which * has already passed. */ *end = offer_period_start(basetime, period_idx+1, @@ -412,7 +402,8 @@ struct tlv_invoice *invoice_decode(const tal_t *ctx, if (invoice) { *fail = check_signature(ctx, invoice->fields, "invoice", "signature", - invoice->node_id, invoice->signature); + invoice->invoice_node_id, + invoice->signature); if (*fail) invoice = tal_free(invoice); } @@ -439,3 +430,141 @@ bool bolt12_has_prefix(const char *str) return bolt12_has_invoice_prefix(str) || bolt12_has_offer_prefix(str) || bolt12_has_request_prefix(str); } + +/* Inclusive span of tlv range >= minfield and <= maxfield */ +size_t tlv_span(const u8 *tlvstream, u64 minfield, u64 maxfield, + size_t *startp) +{ + const u8 *cursor = tlvstream; + size_t tlvlen = tal_bytelen(tlvstream); + const u8 *start, *end; + + start = end = NULL; + while (tlvlen) { + const u8 *before = cursor; + bigsize_t type = fromwire_bigsize(&cursor, &tlvlen); + bigsize_t len = fromwire_bigsize(&cursor, &tlvlen); + if (type >= minfield && start == NULL) + start = before; + if (type > maxfield) + break; + fromwire_pad(&cursor, &tlvlen, len); + end = cursor; + } + if (!start) + start = end; + + if (startp) + *startp = start - tlvstream; + return end - start; +} + +static void calc_offer(const u8 *tlvstream, struct sha256 *id) +{ + size_t start, len; + + /* BOLT-offers #12: + * A writer of an offer: + * - MUST NOT set any tlv fields greater or equal to 80, or tlv field 0. + */ + len = tlv_span(tlvstream, 1, 79, &start); + sha256(id, tlvstream + start, len); +} + +void offer_offer_id(const struct tlv_offer *offer, struct sha256 *id) +{ + u8 *wire = tal_arr(tmpctx, u8, 0); + + towire_tlv_offer(&wire, offer); + calc_offer(wire, id); +} + +void invreq_offer_id(const struct tlv_invoice_request *invreq, struct sha256 *id) +{ + u8 *wire = tal_arr(tmpctx, u8, 0); + + towire_tlv_invoice_request(&wire, invreq); + calc_offer(wire, id); +} + +void invoice_offer_id(const struct tlv_invoice *invoice, struct sha256 *id) +{ + u8 *wire = tal_arr(tmpctx, u8, 0); + + towire_tlv_invoice(&wire, invoice); + calc_offer(wire, id); +} + +static void calc_invreq(const u8 *tlvstream, struct sha256 *id) +{ + size_t start, len; + + /* BOLT-offers #12: + * - if the invoice is a response to an `invoice_request`: + * - MUST reject the invoice if all fields less than type 160 + * do not exactly match the `invoice_request`. + */ + len = tlv_span(tlvstream, 0, 159, &start); + sha256(id, tlvstream + start, len); +} + +void invreq_invreq_id(const struct tlv_invoice_request *invreq, struct sha256 *id) +{ + u8 *wire = tal_arr(tmpctx, u8, 0); + + towire_tlv_invoice_request(&wire, invreq); + calc_invreq(wire, id); +} + +void invoice_invreq_id(const struct tlv_invoice *invoice, struct sha256 *id) +{ + u8 *wire = tal_arr(tmpctx, u8, 0); + + towire_tlv_invoice(&wire, invoice); + calc_invreq(wire, id); +} + + +/* BOLT-offers #12: + * ## Requirements for Invoice Requests + * + * The writer: + * - if it is responding to an offer: + * - MUST copy all fields from the offer (including unknown fields). + */ +struct tlv_invoice_request *invoice_request_for_offer(const tal_t *ctx, + const struct tlv_offer *offer) +{ + const u8 *cursor; + size_t max; + u8 *wire = tal_arr(tmpctx, u8, 0); + towire_tlv_offer(&wire, offer); + + cursor = wire; + max = tal_bytelen(wire); + return fromwire_tlv_invoice_request(ctx, &cursor, &max); +} + +/** + * Prepare a new invoice based on an invoice_request. + */ +struct tlv_invoice *invoice_for_invreq(const tal_t *ctx, + const struct tlv_invoice_request *invreq) +{ + const u8 *cursor; + size_t start, len; + u8 *wire = tal_arr(tmpctx, u8, 0); + towire_tlv_invoice_request(&wire, invreq); + + /* BOLT-offers #12: + * A writer of an invoice: + *... + * - if the invoice is in response to an `invoice_request`: + * - MUST copy all non-signature fields from the `invoice_request` (including + * unknown fields). + */ + len = tlv_span(wire, 0, 159, &start); + cursor = wire + start; + return fromwire_tlv_invoice(ctx, &cursor, &len); +} + diff --git a/common/bolt12.h b/common/bolt12.h index 4aa4ce76149c..277a3f80bf31 100644 --- a/common/bolt12.h +++ b/common/bolt12.h @@ -10,12 +10,12 @@ struct feature_set; /* BOLT-offers #12: - * - if `relative_expiry` is present: + * - if `invoice_relative_expiry` is present: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus `seconds_from_creation`. + * is greater than `invoice_created_at` plus `seconds_from_creation`. * - otherwise: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus 7200. + * is greater than `invoice_created_at` plus 7200. */ #define BOLT12_DEFAULT_REL_EXPIRY 7200 @@ -49,13 +49,13 @@ char *invrequest_encode(const tal_t *ctx, /** * invrequest_decode - decode this complete bolt12 text into a TLV. * @ctx: the context to allocate return or *@fail off. - * @b12: the invoice_request string - * @b12len: the invoice_request string length + * @b12: the invreq string + * @b12len: the invreq string length * @our_features: if non-NULL, feature set to check against. * @must_be_chain: if non-NULL, chain to enforce. * @fail: pointer to descriptive error string, set if this returns NULL. * - * Note: invoice_request doesn't always have a signature, so no checking is done! + * Note: invreq doesn't always have a signature, so no checking is done! */ struct tlv_invoice_request *invrequest_decode(const tal_t *ctx, const char *b12, size_t b12len, @@ -96,21 +96,27 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx, bool bolt12_check_signature(const struct tlv_field *fields, const char *messagename, const char *fieldname, - const struct point32 *key, + const struct pubkey *key, const struct bip340sig *sig); /* Given a single bolt12 chain, does it match? (NULL == bitcoin) */ bool bolt12_chain_matches(const struct bitcoin_blkid *chain, const struct chainparams *must_be_chain); +/* Given an array of max_num_chains chains (or NULL == bitcoin), does + * it match? */ +bool bolt12_chains_match(const struct bitcoin_blkid *chains, + size_t max_num_chains, + const struct chainparams *must_be_chain); + /* Given a basetime, when does period N start? */ u64 offer_period_start(u64 basetime, size_t n, - const struct tlv_offer_recurrence *recurrence); + const struct recurrence *recurrence); /* Get the start and end of the payment window for period N. */ -void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, - const struct tlv_offer_recurrence_paywindow *recurrence_paywindow, - const struct tlv_offer_recurrence_base *recurrence_base, +void offer_period_paywindow(const struct recurrence *recurrence, + const struct recurrence_paywindow *recurrence_paywindow, + const struct recurrence_base *recurrence_base, u64 basetime, u64 period_idx, u64 *period_start, u64 *period_end); @@ -120,4 +126,38 @@ void offer_period_paywindow(const struct tlv_offer_recurrence *recurrence, */ bool bolt12_has_prefix(const char *str); +/** + * tlv_span: Find span of this inclusive range of tlv types + * @tlvstream: the tlv stream + * @minfield: lowest field to find + * @maxfield: highest field to find + * @start: (out) optional offset of start. + * + * Returns length, so 0 means nothing found. +*/ +size_t tlv_span(const u8 *tlvstream, u64 minfield, u64 maxfield, + size_t *start); + +/* Get offer_id referred to by various structures. */ +void offer_offer_id(const struct tlv_offer *offer, struct sha256 *id); +void invreq_offer_id(const struct tlv_invoice_request *invreq, struct sha256 *id); +void invoice_offer_id(const struct tlv_invoice *invoice, struct sha256 *id); + +/* Get invreq_id: this is used to match incoming invoices to invoice_requests + * we publish. */ +void invreq_invreq_id(const struct tlv_invoice_request *invreq, struct sha256 *id); +void invoice_invreq_id(const struct tlv_invoice *invoice, struct sha256 *id); + +/** + * Prepare a new invoice_request based on an offer. + */ +struct tlv_invoice_request *invoice_request_for_offer(const tal_t *ctx, + const struct tlv_offer *offer); + +/** + * Prepare a new invoice based on an invoice_request. + */ +struct tlv_invoice *invoice_for_invreq(const tal_t *ctx, + const struct tlv_invoice_request *invreq); + #endif /* LIGHTNING_COMMON_BOLT12_H */ diff --git a/common/bolt12_merkle.c b/common/bolt12_merkle.c index 591685e4e700..870e440ee4ab 100644 --- a/common/bolt12_merkle.c +++ b/common/bolt12_merkle.c @@ -1,4 +1,5 @@ #include "config.h" +#include #include #include #include @@ -9,7 +10,8 @@ #endif /* BOLT-offers #12: - * TLV types 240 through 1000 are considered signature elements. + * Each form is signed using one or more *signature TLV elements*: TLV + * types 240 through 1000 (inclusive). */ static bool is_signature_field(const struct tlv_field *field) { @@ -54,23 +56,21 @@ static void h_simpletag_ctx(struct sha256_ctx *sctx, const char *tag) /* BOLT-offers #12: * The Merkle tree's leaves are, in TLV-ascending order for each tlv: - * 1. The H(`LnLeaf`,tlv). - * 2. The H(`LnAll`||all-tlvs,tlv) where "all-tlvs" consists of all non-signature TLV entries appended in ascending order. + * 1. The H("LnLeaf",tlv). + * 2. The H("LnNonce"||first-tlv,tlv-type) where first-tlv is the numerically-first TLV entry in the stream, and tlv-type is the "type" field (1-9 bytes) of the current tlv. */ /* Create a sha256_ctx which has the tag part done. */ -static void h_lnall_ctx(struct sha256_ctx *sctx, const struct tlv_field *fields) +static void h_lnnonce_ctx(struct sha256_ctx *sctx, const struct tlv_field *fields) { struct sha256_ctx inner_sctx; struct sha256 sha; sha256_init(&inner_sctx); - sha256_update(&inner_sctx, "LnAll", 5); - SUPERVERBOSE("tag=SHA256(%s", tal_hexstr(tmpctx, "LnAll", 5)); - for (size_t i = 0; i < tal_count(fields); i++) { - if (!is_signature_field(&fields[i])) - sha256_update_tlvfield(&inner_sctx, &fields[i]); - } + sha256_update(&inner_sctx, "LnNonce", 7); + SUPERVERBOSE("tag=SHA256(%s", tal_hexstr(tmpctx, "LnNonce", 7)); + assert(tal_count(fields)); + sha256_update_tlvfield(&inner_sctx, &fields[0]); sha256_done(&inner_sctx, &sha); SUPERVERBOSE(") -> %s\n", type_to_string(tmpctx, struct sha256, &sha)); @@ -80,16 +80,16 @@ static void h_lnall_ctx(struct sha256_ctx *sctx, const struct tlv_field *fields) sha256_update(sctx, &sha, sizeof(sha)); } -/* Use h_lnall_ctx to create nonce */ -static void calc_nonce(const struct sha256_ctx *lnall_ctx, +/* Use h_lnnonce_ctx to create nonce */ +static void calc_nonce(const struct sha256_ctx *lnnonce_ctx, const struct tlv_field *field, struct sha256 *hash) { /* Copy context, to add field */ - struct sha256_ctx ctx = *lnall_ctx; + struct sha256_ctx ctx = *lnnonce_ctx; SUPERVERBOSE("nonce: H(noncetag,"); - sha256_update_tlvfield(&ctx, field); + sha256_update_bigsize(&ctx, field->numtype); sha256_done(&ctx, hash); SUPERVERBOSE(") = %s\n", type_to_string(tmpctx, struct sha256, hash)); @@ -108,7 +108,7 @@ static void calc_lnleaf(const struct tlv_field *field, struct sha256 *hash) } /* BOLT-offers #12: - * The Merkle tree inner nodes are H(`LnBranch`, lesser-SHA256||greater-SHA256) + * The Merkle tree inner nodes are H("LnBranch", lesser-SHA256||greater-SHA256) */ static struct sha256 *merkle_pair(const tal_t *ctx, const struct sha256 *a, const struct sha256 *b) @@ -159,11 +159,11 @@ static const struct sha256 *merkle_recurse(const struct sha256 **base, void merkle_tlv(const struct tlv_field *fields, struct sha256 *merkle) { struct sha256 **arr; - struct sha256_ctx lnall_ctx; + struct sha256_ctx lnnonce_ctx; size_t n; SUPERVERBOSE("nonce tag:"); - h_lnall_ctx(&lnall_ctx, fields); + h_lnnonce_ctx(&lnnonce_ctx, fields); /* We build an oversized power-of-2 symmentic tree, but with * NULL nodes at the end. When we recurse, we pass through @@ -178,7 +178,7 @@ void merkle_tlv(const struct tlv_field *fields, struct sha256 *merkle) if (is_signature_field(&fields[i])) continue; calc_lnleaf(&fields[i], &leaf); - calc_nonce(&lnall_ctx, &fields[i], &nonce); + calc_nonce(&lnnonce_ctx, &fields[i], &nonce); arr[n++] = merkle_pair(arr, &leaf, &nonce); } @@ -194,18 +194,17 @@ void merkle_tlv(const struct tlv_field *fields, struct sha256 *merkle) /* BOLT-offers #12: * All signatures are created as per - * [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki), + * [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) * and tagged as recommended there. Thus we define H(`tag`,`msg`) as * SHA256(SHA256(`tag`) || SHA256(`tag`) || `msg`), and SIG(`tag`,`msg`,`key`) * as the signature of H(`tag`,`msg`) using `key`. * - * Each form is signed using one or more TLV signature elements; TLV - * types 240 through 1000 are considered signature elements. For these - * the tag is "lightning" || `messagename` || `fieldname`, and `msg` is the - * Merkle-root; "lightning" is the literal 9-byte ASCII string, - * `messagename` is the name of the TLV stream being signed (i.e. "offer", - * "invoice_request" or "invoice") and the `fieldname` is the TLV field - * containing the signature (e.g. "signature" or "refund_signature"). + * Each form is signed using one or more *signature TLV elements*: TLV types + * 240 through 1000 (inclusive). For these, the tag is "lightning" || + * `messagename` || `fieldname`, and `msg` is the Merkle-root; "lightning" is + * the literal 9-byte ASCII string, `messagename` is the name of the TLV + * stream being signed (i.e. "invoice_request" or "invoice") and the + * `fieldname` is the TLV field containing the signature (e.g. "signature"). */ void sighash_from_merkle(const char *messagename, const char *fieldname, @@ -220,15 +219,17 @@ void sighash_from_merkle(const char *messagename, } /* We use the SHA(pubkey | publictweak); so reader cannot figure out the - * tweak and derive the base key */ -void payer_key_tweak(const struct point32 *bolt12, + * tweak and derive the base key. + */ +void payer_key_tweak(const struct pubkey *bolt12, const u8 *publictweak, size_t publictweaklen, struct sha256 *tweak) { - u8 rawkey[32]; + u8 rawkey[PUBKEY_CMPR_LEN]; struct sha256_ctx sha; - secp256k1_xonly_pubkey_serialize(secp256k1_ctx, rawkey, &bolt12->pubkey); + pubkey_to_der(rawkey, bolt12); + sha256_init(&sha); sha256_update(&sha, rawkey, sizeof(rawkey)); sha256_update(&sha, diff --git a/common/bolt12_merkle.h b/common/bolt12_merkle.h index 08ae9fc208fc..cf7f91813b79 100644 --- a/common/bolt12_merkle.h +++ b/common/bolt12_merkle.h @@ -25,7 +25,7 @@ void sighash_from_merkle(const char *messagename, /** * payer_key_tweak - get the actual tweak to use for a payer_key */ -void payer_key_tweak(const struct point32 *bolt12, +void payer_key_tweak(const struct pubkey *bolt12, const u8 *publictweak, size_t publictweaklen, struct sha256 *tweak); diff --git a/common/features.c b/common/features.c index 05c5b39fbf8e..2a090f1e80f9 100644 --- a/common/features.c +++ b/common/features.c @@ -59,7 +59,8 @@ static const struct feature_style feature_styles[] = { { OPT_BASIC_MPP, .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, - [BOLT11_FEATURE] = FEATURE_REPRESENT } }, + [BOLT11_FEATURE] = FEATURE_REPRESENT, + [BOLT12_INVOICE_FEATURE] = FEATURE_REPRESENT } }, /* BOLT #9: * | 18/19 | `option_support_large_channel` |... IN ... */ @@ -109,6 +110,11 @@ static const struct feature_style feature_styles[] = { [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [BOLT11_FEATURE] = FEATURE_DONT_REPRESENT, [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT} }, + { OPT_ROUTE_BLINDING, + .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, + [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, + [BOLT11_FEATURE] = FEATURE_REPRESENT, + [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } }, { OPT_SHUTDOWN_ANYSEGWIT, .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, @@ -168,6 +174,12 @@ static const struct dependency feature_deps[] = { * `option_dual_fund` | ... | ... | `option_anchor_outputs` */ { OPT_DUAL_FUND, OPT_ANCHOR_OUTPUTS }, + /* BOLT-route-blinding #9: + * Name | Description | Context | Dependencies | + * ... + * `option_route_blinding` | ... | ... | `var_onion_optin` + */ + { OPT_ROUTE_BLINDING, OPT_VAR_ONION }, }; static void trim_features(u8 **features) @@ -436,7 +448,7 @@ const char *feature_name(const tal_t *ctx, size_t f) "option_support_large_channel", "option_anchor_outputs", /* 20/21 */ "option_anchors_zero_fee_htlc_tx", - "option_trampoline_routing", /* https://github.com/lightning/bolts/pull/836 */ + "option_route_blinding", /* https://github.com/lightning/bolts/pull/765 */ "option_shutdown_anysegwit", "option_dual_fund", "option_amp", /* 30/31 */ /* https://github.com/lightning/bolts/pull/658 */ @@ -452,7 +464,7 @@ const char *feature_name(const tal_t *ctx, size_t f) "option_zeroconf", /* 50/51, https://github.com/lightning/bolts/pull/910 */ NULL, "option_keysend", - NULL, + "option_trampoline_routing", /* https://github.com/lightning/bolts/pull/836 */ NULL, NULL, /* 60/61 */ NULL, diff --git a/common/features.h b/common/features.h index ccb914020bfd..a862bafe1c53 100644 --- a/common/features.h +++ b/common/features.h @@ -10,8 +10,11 @@ enum feature_place { NODE_ANNOUNCE_FEATURE, CHANNEL_FEATURE, BOLT11_FEATURE, + BOLT12_OFFER_FEATURE, + BOLT12_INVREQ_FEATURE, + BOLT12_INVOICE_FEATURE, }; -#define NUM_FEATURE_PLACE (BOLT11_FEATURE+1) +#define NUM_FEATURE_PLACE (BOLT12_INVOICE_FEATURE+1) extern const char *feature_place_names[NUM_FEATURE_PLACE]; @@ -132,6 +135,11 @@ struct feature_set *feature_set_dup(const tal_t *ctx, #define OPT_CHANNEL_TYPE 44 #define OPT_PAYMENT_METADATA 48 +/* BOLT-route-blinding #9: + * | 24/25 | `option_route_blinding` | Node supports blinded paths | IN9 | `var_onion_optin` | ... + */ +#define OPT_ROUTE_BLINDING 24 + /* BOLT-f53ca2301232db780843e894f55d95d512f297f9 #9: * | 28/29 | `option_dual_fund` | ... IN9 ... */ diff --git a/common/gossmap.c b/common/gossmap.c index b5d326938d99..9ae3738e3ed9 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -1290,18 +1290,21 @@ int gossmap_node_get_feature(const struct gossmap *map, n->nann_off + feature_len_off + 2, feature_len); } -/* There are two 33-byte pubkeys possible: choose the one which appears - * in the graph (otherwise payment will fail anyway). */ -void gossmap_guess_node_id(const struct gossmap *map, - const struct point32 *point32, - struct node_id *id) -{ - id->k[0] = SECP256K1_TAG_PUBKEY_EVEN; - secp256k1_xonly_pubkey_serialize(secp256k1_ctx, - id->k + 1, - &point32->pubkey); - - /* If we don't find this, let's assume it's odd. */ - if (!gossmap_find_node(map, id)) - id->k[0] = SECP256K1_TAG_PUBKEY_ODD; +u8 *gossmap_node_get_features(const tal_t *ctx, + const struct gossmap *map, + const struct gossmap_node *n) +{ + u8 *ret; + /* Note that first two bytes are message type */ + const size_t feature_len_off = 2 + 64; + size_t feature_len; + + if (n->nann_off == 0) + return NULL; + + feature_len = map_be16(map, n->nann_off + feature_len_off); + ret = tal_arr(ctx, u8, feature_len); + + map_copy(map, n->nann_off + feature_len_off + 2, ret, feature_len); + return ret; } diff --git a/common/gossmap.h b/common/gossmap.h index 6b08ef684a87..b173c9bd5515 100644 --- a/common/gossmap.h +++ b/common/gossmap.h @@ -8,7 +8,6 @@ #include struct node_id; -struct point32; struct gossmap_node { /* Offset in memory map for node_announce, or 0. */ @@ -135,21 +134,26 @@ u8 *gossmap_node_get_announce(const tal_t *ctx, const struct gossmap *map, const struct gossmap_node *n); -/* Return the feature bit (odd or even), or -1 if neither. */ +/* Return the channel feature bit (odd or even), or -1 if neither. */ int gossmap_chan_get_feature(const struct gossmap *map, const struct gossmap_chan *c, int fbit); -/* Return the feature bitmap */ +/* Return the channel feature bitmap */ u8 *gossmap_chan_get_features(const tal_t *ctx, const struct gossmap *map, const struct gossmap_chan *c); -/* Return the feature bit (odd or even), or -1 if neither (or no announcement) */ +/* Return the node feature bit (odd or even), or -1 if neither (or no announcement) */ int gossmap_node_get_feature(const struct gossmap *map, const struct gossmap_node *n, int fbit); +/* Return the node feature bitmap: NULL if no announcement. */ +u8 *gossmap_node_get_features(const tal_t *ctx, + const struct gossmap *map, + const struct gossmap_node *n); + /* Returns details from channel_update (must be gossmap_chan_set, and * does not work for local_updatechan! */ void gossmap_chan_get_update_details(const struct gossmap *map, @@ -200,11 +204,4 @@ size_t gossmap_num_chans(const struct gossmap *map); struct gossmap_chan *gossmap_first_chan(const struct gossmap *map); struct gossmap_chan *gossmap_next_chan(const struct gossmap *map, struct gossmap_chan *prev); - -/* Each x-only pubkey has two possible values: we can figure out which by - * examining the gossmap. */ -void gossmap_guess_node_id(const struct gossmap *map, - const struct point32 *point32, - struct node_id *id); - #endif /* LIGHTNING_COMMON_GOSSMAP_H */ diff --git a/common/hsm_version.h b/common/hsm_version.h new file mode 100644 index 000000000000..8acf80112fce --- /dev/null +++ b/common/hsm_version.h @@ -0,0 +1,17 @@ +#ifndef LIGHTNING_COMMON_HSM_VERSION_H +#define LIGHTNING_COMMON_HSM_VERSION_H +#include "config.h" + +/* We give a maximum and minimum compatibility version to HSM, to allow + * some API adaptation. */ + +/* wire/hsmd_wire.csv contents version: + * 409cffa355ab6cc76bd298910adca9936a68223267ddc4815ba16aeac5d0acc3 + */ +#define HSM_MIN_VERSION 1 + +/* wire/hsmd_wire.csv contents version: + * dd89bf9323dff42200003fb864abb6608f3aa645b636fdae3ec81d804ac05196 + */ +#define HSM_MAX_VERSION 2 +#endif /* LIGHTNING_COMMON_HSM_VERSION_H */ diff --git a/common/htlc_wire.c b/common/htlc_wire.c index 72ce1ac3de39..fb47e000309e 100644 --- a/common/htlc_wire.c +++ b/common/htlc_wire.c @@ -82,7 +82,6 @@ void towire_added_htlc(u8 **pptr, const struct added_htlc *added) if (added->blinding) { towire_bool(pptr, true); towire_pubkey(pptr, added->blinding); - towire_secret(pptr, &added->blinding_ss); } else towire_bool(pptr, false); towire_bool(pptr, added->fail_immediate); @@ -184,7 +183,6 @@ void fromwire_added_htlc(const u8 **cursor, size_t *max, if (fromwire_bool(cursor, max)) { added->blinding = tal(added, struct pubkey); fromwire_pubkey(cursor, max, added->blinding); - fromwire_secret(cursor, max, &added->blinding_ss); } else added->blinding = NULL; added->fail_immediate = fromwire_bool(cursor, max); diff --git a/common/htlc_wire.h b/common/htlc_wire.h index b89b5961a90b..72d359f57f26 100644 --- a/common/htlc_wire.h +++ b/common/htlc_wire.h @@ -16,10 +16,7 @@ struct added_htlc { u32 cltv_expiry; u8 onion_routing_packet[TOTAL_PACKET_SIZE(ROUTING_INFO_SIZE)]; bool fail_immediate; - - /* If this is non-NULL, secret is the resulting shared secret */ struct pubkey *blinding; - struct secret blinding_ss; }; /* This is how lightningd tells us about HTLCs which already exist at startup */ diff --git a/common/invoice_path_id.c b/common/invoice_path_id.c new file mode 100644 index 000000000000..5cccbcc50430 --- /dev/null +++ b/common/invoice_path_id.c @@ -0,0 +1,19 @@ +#include "config.h" +#include +#include +#include + +u8 *invoice_path_id(const tal_t *ctx, + const struct secret *base_secret, + const struct sha256 *payment_hash) +{ + struct sha256_ctx shactx; + struct sha256 secret; + + sha256_init(&shactx); + sha256_update(&shactx, base_secret, sizeof(*base_secret)); + sha256_update(&shactx, payment_hash, sizeof(*payment_hash)); + sha256_done(&shactx, &secret); + + return (u8 *)tal_dup(ctx, struct sha256, &secret); +} diff --git a/common/invoice_path_id.h b/common/invoice_path_id.h new file mode 100644 index 000000000000..4e4566de4f1b --- /dev/null +++ b/common/invoice_path_id.h @@ -0,0 +1,29 @@ +#ifndef LIGHTNING_COMMON_INVOICE_PATH_ID_H +#define LIGHTNING_COMMON_INVOICE_PATH_ID_H +#include "config.h" +#include +#include + +struct secret; +struct sha256; + +/* String to use with makesecret to get the invoice base secret */ +#define INVOICE_PATH_BASE_STRING "bolt12-invoice-base" + +/** + * invoice_path_id: generate the "path_id" field for the tlv_encrypted_data_tlv + * @ctx: tal context + * @payment_hash: the invoice payment hash + * @base_secret: the node-specific secret makesecret("bolt12-invoice-base") + * + * Receiving a blinded, encrypted tlv_encrypted_data_tlv containing + * the correct path_id is how we know this blinded path is the correct + * one for this invoice payment. + * + * It's exposed here as plugins may want to generate blinded paths. + */ +u8 *invoice_path_id(const tal_t *ctx, + const struct secret *base_secret, + const struct sha256 *payment_hash); + +#endif /* LIGHTNING_COMMON_INVOICE_PATH_ID_H */ diff --git a/common/iso4217.h b/common/iso4217.h index aca72b4b05c3..96f34221de5e 100644 --- a/common/iso4217.h +++ b/common/iso4217.h @@ -5,8 +5,8 @@ /* BOLT-offers #12: * - * - MUST specify `iso4217` as an ISO 4712 three-letter code. - * - MUST specify `amount` in the currency unit adjusted by the ISO 4712 + * - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code. + * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712 * exponent (e.g. USD cents). */ struct iso4217_name_and_divisor { diff --git a/common/json_command.h b/common/json_command.h index e4a2dbd679b0..8586ba72d1b0 100644 --- a/common/json_command.h +++ b/common/json_command.h @@ -15,6 +15,9 @@ struct command_result *command_fail(struct command *cmd, enum jsonrpc_errcode co const char *fmt, ...) PRINTF_FMT(3, 4) WARN_UNUSED_RESULT RETURNS_NONNULL; +/* Caller supplies this too: must provide this to reach into cmd */ +struct json_filter **command_filter_ptr(struct command *cmd); + /* Convenient wrapper for "paramname: msg: invalid token '.*%s'" */ static inline struct command_result * command_fail_badparam(struct command *cmd, diff --git a/common/json_filter.c b/common/json_filter.c new file mode 100644 index 000000000000..bda4b91eb25d --- /dev/null +++ b/common/json_filter.c @@ -0,0 +1,251 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include + +/* If they set a filter, we keep it in a tree. */ +struct json_filter { + /* We accumulate errors: if they treat an array as an object */ + bool misused; + + /* Pointer to parent, or NULL at top. */ + struct json_filter *parent; + + /* Tracks how far we are into filter, e.g. + * if they specify "peers.foo" and we're + * in "peer.foo.bar" depth will be 1. */ + size_t depth; + /* If we're in "peer.bar", we're negative */ + bool positive; + + /* If this is an array */ + struct json_filter *filter_array; + + /* Otherwise, object: one per keyword */ + STRMAP(struct json_filter *) filter_map; +}; + +/* Returns true if we should print this member: this is a shortcut for: + * + * json_filter_down(filter, member); + * ret = json_filter_ok(filter, NULL); + * json_filter_up(filter); + * + */ +bool json_filter_ok(const struct json_filter *filter, const char *member) +{ + if (!filter) + return true; + if (filter->depth > 0 || !member) + return filter->positive; + return strmap_get(&filter->filter_map, member) != NULL; +} + +/* Returns true if we should print this new obj/array */ +bool json_filter_down(struct json_filter **filter, const char *member) +{ + struct json_filter *child; + + if (!*filter) + return true; + if ((*filter)->depth > 0) { + (*filter)->depth++; + return (*filter)->positive; + } + + /* If we're a leaf node: all true. */ + if (!(*filter)->filter_array && strmap_empty(&(*filter)->filter_map)) { + assert((*filter)->positive); + (*filter)->depth = 1; + return true; + } + + /* Array? */ + if (!member) { + if (!(*filter)->filter_array) { + (*filter)->misused = true; + goto fail; + } + child = (*filter)->filter_array; + } else { + if ((*filter)->filter_array) { + (*filter)->misused = true; + goto fail; + } + child = strmap_get(&(*filter)->filter_map, member); + } + + if (child) { + /* Should have been cleaned up last time. */ + assert(child->depth == 0); + /* We only have positive filters natively. */ + assert(child->positive == true); + *filter = child; + return true; + } + + /* OK, this path wasn't specified. */ +fail: + (*filter)->positive = false; + (*filter)->depth = 1; + return false; +} + +/* Returns true if we were printing (i.e. close object/arr) */ +bool json_filter_up(struct json_filter **filter) +{ + if (!*filter) + return true; + + if ((*filter)->depth == 0) { + assert((*filter)->parent); + assert((*filter)->parent->depth == 0); + /* Reset for next time */ + (*filter)->positive = true; + *filter = (*filter)->parent; + return true; + } + + (*filter)->depth--; + return (*filter)->positive; +} + +static void destroy_json_filter(struct json_filter *filter) +{ + strmap_clear(&filter->filter_map); +} + +struct json_filter *json_filter_new(const tal_t *ctx) +{ + struct json_filter *filter = tal(ctx, struct json_filter); + filter->misused = false; + filter->parent = NULL; + filter->depth = 0; + filter->positive = true; + filter->filter_array = NULL; + strmap_init(&filter->filter_map); + tal_add_destructor(filter, destroy_json_filter); + return filter; +} + +struct json_filter *json_filter_subobj(struct json_filter *filter, + const char *fieldname, + size_t fieldnamelen) +{ + struct json_filter *subfilter = json_filter_new(filter); + subfilter->parent = filter; + strmap_add(&filter->filter_map, + tal_strndup(filter, fieldname, fieldnamelen), + subfilter); + return subfilter; +} + +struct json_filter *json_filter_subarr(struct json_filter *filter) +{ + struct json_filter *subfilter = json_filter_new(filter); + subfilter->parent = filter; + filter->filter_array = subfilter; + return subfilter; +} + +bool json_filter_finished(const struct json_filter *filter) +{ + return !filter->parent && filter->depth == 0; +} + +static bool strmap_filter_misused(const char *member, + struct json_filter *filter, + const char **ret) +{ + *ret = json_filter_misused(tmpctx, filter); + if (*ret == NULL) + return true; + + /* If there was a problem, prepend member and stop iterating */ + *ret = tal_fmt(tmpctx, ".%s%s", member, *ret); + return false; +} + +const char *json_filter_misused(const tal_t *ctx, const struct json_filter *f) +{ + const char *ret; + + if (f->misused) { + if (f->filter_array) + return tal_fmt(ctx, " is an object"); + else + return tal_fmt(ctx, " is an array"); + } + + if (f->filter_array) { + ret = json_filter_misused(tmpctx, f->filter_array); + if (ret) + return tal_fmt(ctx, "[]%s", ret); + return NULL; + } else { + ret = NULL; + strmap_iterate(&f->filter_map, strmap_filter_misused, &ret); + return tal_steal(ctx, ret); + } +} + +/* Recursively populate filter. NULL on success. + * + * Example for listtransactions to include output type, amount_msat, + * {"transactions": [{"outputs": [{"amount_msat": true, "type": true}]}]} + */ +static struct command_result * +build_filter(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct json_filter *filter) +{ + struct command_result *ret; + size_t i; + const jsmntok_t *t; + struct json_filter *subf; + + if (tok->type == JSMN_ARRAY) { + if (tok->size != 1) + return command_fail_badparam(cmd, name, buffer, tok, + "Arrays can only have one element"); + subf = json_filter_subarr(filter); + return build_filter(cmd, name, buffer, tok + 1, subf); + } + + json_for_each_obj(i, t, tok) { + bool is_true; + const jsmntok_t *val = t + 1; + + if (t->type != JSMN_STRING) + return command_fail_badparam(cmd, name, buffer, t, + "expected string key"); + subf = json_filter_subobj(filter, buffer + t->start, t->end - t->start); + if (val->type == JSMN_OBJECT || val->type == JSMN_ARRAY) { + ret = build_filter(cmd, name, buffer, val, subf); + if (ret) + return ret; + } else if (!json_to_bool(buffer, val, &is_true) || !is_true) + return command_fail_badparam(cmd, name, buffer, val, "value must be true"); + } + return NULL; +} + +struct command_result *parse_filter(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok) +{ + struct json_filter **filter = command_filter_ptr(cmd); + + if (tok->type != JSMN_OBJECT) + return command_fail_badparam(cmd, name, buffer, tok, + "Expected object"); + + *filter = json_filter_new(cmd); + return build_filter(cmd, name, buffer, tok, *filter); +} diff --git a/common/json_filter.h b/common/json_filter.h new file mode 100644 index 000000000000..dee177aebe8a --- /dev/null +++ b/common/json_filter.h @@ -0,0 +1,41 @@ +/* + * Helpers for filtering JSON results while generating. + */ +#ifndef LIGHTNING_COMMON_JSON_FILTER_H +#define LIGHTNING_COMMON_JSON_FILTER_H +#include "config.h" +#include +#include +#include + +struct command; +struct json_filter; + +/* Print this? */ +bool json_filter_ok(const struct json_filter *filter, const char *member); + +/* Returns true if we should print this new obj/array */ +bool json_filter_down(struct json_filter **filter, const char *member); + +/* Returns true if we were printing (i.e. close object/arr) */ +bool json_filter_up(struct json_filter **filter); + +/* Is filter finished (i.e. balanced!) */ +bool json_filter_finished(const struct json_filter *filter); + +/* Has filter been misused? If so, returns explanatory string, otherwise NULL */ +const char *json_filter_misused(const tal_t *ctx, const struct json_filter *f); + +/* Filter allocation */ +struct json_filter *json_filter_new(const tal_t *ctx); +struct json_filter *json_filter_subobj(struct json_filter *filter, + const char *fieldname, + size_t fieldnamelen); +struct json_filter *json_filter_subarr(struct json_filter *filter); + +/* Turn this "filter" field into cmd->filter and return NULL, or fail command */ +struct command_result *parse_filter(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok); +#endif /* LIGHTNING_COMMON_JSON_FILTER_H */ diff --git a/common/json_param.c b/common/json_param.c index 0eecf4a497fe..7aa2174645eb 100644 --- a/common/json_param.c +++ b/common/json_param.c @@ -883,7 +883,11 @@ struct command_result *param_extra_tlvs(struct command *cmd, const char *name, temp = tal_arr(cmd, struct tlv_field, tok->size); json_for_each_obj(i, curr, tok) { f = &temp[i]; - if (!json_to_u64(buffer, curr, &f->numtype)) { + /* Accept either bare ints as keys (not spec + * compliant, but simpler), or ints in strings, which + * are JSON spec compliant. */ + if (!(json_str_to_u64(buffer, curr, &f->numtype) || + json_to_u64(buffer, curr, &f->numtype))) { return command_fail( cmd, JSONRPC2_INVALID_PARAMS, "\"%s\" is not a valid numeric TLV type.", diff --git a/common/json_param.h b/common/json_param.h index 6086d5c2a663..19c924b66b92 100644 --- a/common/json_param.h +++ b/common/json_param.h @@ -21,14 +21,12 @@ * * unsigned *cltv; * u64 *msatoshi; - * const jsmntok_t *note; * u64 *expiry; * * if (!param(cmd, buffer, params, - * p_req("cltv", json_tok_number, &cltv), - * p_opt("msatoshi", json_tok_u64, &msatoshi), - * p_opt("note", json_tok_tok, ¬e), - * p_opt_def("expiry", json_tok_u64, &expiry, 3600), + * p_req("cltv", param_number, &cltv), + * p_opt("msatoshi", param_u64, &msatoshi), + * p_opt_def("expiry", param_u64, &expiry, 3600), * NULL)) * return; * @@ -36,7 +34,7 @@ * * All the command handlers throughout the code use this system. * json_invoice() is a great example. The common callbacks can be found in - * common/json_tok.c. Use them directly or feel free to write your own. + * common/json_param.c. Use them directly or feel free to write your own. */ struct command; diff --git a/common/json_parse.c b/common/json_parse.c index 958d94eb7a3a..e4776d87b5f1 100644 --- a/common/json_parse.c +++ b/common/json_parse.c @@ -647,15 +647,15 @@ struct wally_psbt *json_to_psbt(const tal_t *ctx, const char *buffer, return psbt_from_b64(ctx, buffer + tok->start, tok->end - tok->start); } -struct tlv_onionmsg_payload_reply_path * -json_to_reply_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok) +struct blinded_path * +json_to_blinded_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok) { - struct tlv_onionmsg_payload_reply_path *rpath; + struct blinded_path *rpath; const jsmntok_t *hops, *t; size_t i; const char *err; - rpath = tal(ctx, struct tlv_onionmsg_payload_reply_path); + rpath = tal(ctx, struct blinded_path); err = json_scan(tmpctx, buffer, tok, "{blinding:%,first_node_id:%}", JSON_SCAN(json_to_pubkey, &rpath->blinding), JSON_SCAN(json_to_pubkey, &rpath->first_node_id), @@ -667,12 +667,12 @@ json_to_reply_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok) if (!hops || hops->size < 1) return tal_free(rpath); - rpath->path = tal_arr(rpath, struct onionmsg_path *, hops->size); + rpath->path = tal_arr(rpath, struct onionmsg_hop *, hops->size); json_for_each_arr(i, t, hops) { - rpath->path[i] = tal(rpath->path, struct onionmsg_path); - err = json_scan(tmpctx, buffer, t, "{id:%,encrypted_recipient_data:%}", + rpath->path[i] = tal(rpath->path, struct onionmsg_hop); + err = json_scan(tmpctx, buffer, t, "{blinded_node_id:%,encrypted_recipient_data:%}", JSON_SCAN(json_to_pubkey, - &rpath->path[i]->node_id), + &rpath->path[i]->blinded_node_id), JSON_SCAN_TAL(rpath->path[i], json_tok_bin_from_hex, &rpath->path[i]->encrypted_recipient_data)); diff --git a/common/json_parse.h b/common/json_parse.h index 9a7d962b3151..0c45a925b3ea 100644 --- a/common/json_parse.h +++ b/common/json_parse.h @@ -115,8 +115,8 @@ bool json_to_coin_mvt_tag(const char *buffer, const jsmntok_t *tok, enum mvt_tag *tag); /* Extract reply path from this JSON */ -struct tlv_onionmsg_payload_reply_path * -json_to_reply_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok); +struct blinded_path * +json_to_blinded_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok); bool json_tok_channel_id(const char *buffer, const jsmntok_t *tok, struct channel_id *cid); diff --git a/common/json_parse_simple.c b/common/json_parse_simple.c index f7ce964cae13..9fe94229c87b 100644 --- a/common/json_parse_simple.c +++ b/common/json_parse_simple.c @@ -88,6 +88,19 @@ bool json_to_u64(const char *buffer, const jsmntok_t *tok, u64 *num) return true; } +bool json_str_to_u64(const char *buffer, const jsmntok_t *tok, u64 *num) +{ + jsmntok_t temp; + if (tok->type != JSMN_STRING) + return false; + + temp = *tok; + temp.start += 1; + temp.end -= 1; + + return json_to_u64(buffer, &temp, num); +} + bool json_to_u32(const char *buffer, const jsmntok_t *tok, u32 *num) { uint64_t u64; diff --git a/common/json_parse_simple.h b/common/json_parse_simple.h index 4188a4eb4a84..4092dad75d29 100644 --- a/common/json_parse_simple.h +++ b/common/json_parse_simple.h @@ -35,6 +35,10 @@ char *json_strdup(const tal_t *ctx, const char *buffer, const jsmntok_t *tok); /* Extract number from this (may be a string, or a number literal) */ bool json_to_u64(const char *buffer, const jsmntok_t *tok, u64 *num); +/* Extract number from string. The number must be the entirety of the + * string between the '"' */ +bool json_str_to_u64(const char *buffer, const jsmntok_t *tok, u64 *num); + /* Extract number from this (may be a string, or a number literal) */ bool json_to_u32(const char *buffer, const jsmntok_t *tok, u32 *num); diff --git a/common/json_stream.c b/common/json_stream.c index 5e9401c629b6..59a21d28005d 100644 --- a/common/json_stream.c +++ b/common/json_stream.c @@ -12,13 +12,15 @@ #include #include #include +#include +#include #include #include +#include #include #include #include #include -#include #include #include #include @@ -46,9 +48,29 @@ struct json_stream *new_json_stream(const tal_t *ctx, js->writer = writer; js->reader = NULL; js->log = log; + js->filter = NULL; return js; } +void json_stream_attach_filter(struct json_stream *js, + struct json_filter *filter STEALS) +{ + assert(!js->filter); + js->filter = tal_steal(js, filter); +} + +const char *json_stream_detach_filter(const tal_t *ctx, struct json_stream *js) +{ + const char *err; + assert(js->filter); + /* Should be well-formed at this point! */ + assert(json_filter_finished(js->filter)); + + err = json_filter_misused(ctx, js->filter); + js->filter = tal_free(js->filter); + return err; +} + struct json_stream *json_stream_dup(const tal_t *ctx, struct json_stream *original, struct log *log) @@ -57,6 +79,8 @@ struct json_stream *json_stream_dup(const tal_t *ctx, js->jout = json_out_dup(js, original->jout); js->log = log; + /* You can't dup things with filters! */ + assert(!js->filter); return js; } @@ -83,6 +107,8 @@ void json_stream_append(struct json_stream *js, { char *dest; + /* Only on low-level streams! */ + assert(!js->filter); dest = json_out_direct(js->jout, len); memcpy(dest, str, len); } @@ -115,7 +141,7 @@ void json_stream_close(struct json_stream *js, struct command *writer) * I used to assert(writer); here. */ assert(js->writer == writer); - /* Should be well-formed at this point! */ + assert(!js->filter); json_stream_double_cr(js); json_stream_flush(js); js->writer = NULL; @@ -130,22 +156,26 @@ void json_stream_flush(struct json_stream *js) void json_array_start(struct json_stream *js, const char *fieldname) { - json_out_start(js->jout, fieldname, '['); + if (json_filter_down(&js->filter, fieldname)) + json_out_start(js->jout, fieldname, '['); } void json_array_end(struct json_stream *js) { - json_out_end(js->jout, ']'); + if (json_filter_up(&js->filter)) + json_out_end(js->jout, ']'); } void json_object_start(struct json_stream *js, const char *fieldname) { - json_out_start(js->jout, fieldname, '{'); + if (json_filter_down(&js->filter, fieldname)) + json_out_start(js->jout, fieldname, '{'); } void json_object_end(struct json_stream *js) { - json_out_end(js->jout, '}'); + if (json_filter_up(&js->filter)) + json_out_end(js->jout, '}'); } void json_add_primitive_fmt(struct json_stream *js, @@ -154,9 +184,11 @@ void json_add_primitive_fmt(struct json_stream *js, { va_list ap; - va_start(ap, fmt); - json_out_addv(js->jout, fieldname, false, fmt, ap); - va_end(ap); + if (json_filter_ok(js->filter, fieldname)) { + va_start(ap, fmt); + json_out_addv(js->jout, fieldname, false, fmt, ap); + va_end(ap); + } } void json_add_str_fmt(struct json_stream *js, @@ -165,9 +197,11 @@ void json_add_str_fmt(struct json_stream *js, { va_list ap; - va_start(ap, fmt); - json_out_addv(js->jout, fieldname, true, fmt, ap); - va_end(ap); + if (json_filter_ok(js->filter, fieldname)) { + va_start(ap, fmt); + json_out_addv(js->jout, fieldname, true, fmt, ap); + va_end(ap); + } } void json_add_primitive(struct json_stream *js, @@ -183,7 +217,8 @@ void json_add_string(struct json_stream *js, const char *fieldname, const char *str TAKES) { - json_out_addstr(js->jout, fieldname, str); + if (json_filter_ok(js->filter, fieldname)) + json_out_addstr(js->jout, fieldname, str); if (taken(str)) tal_free(str); } @@ -204,6 +239,13 @@ void json_add_jsonstr(struct json_stream *js, { char *p; + if (!json_filter_ok(js->filter, fieldname)) + return; + + /* NOTE: Filtering doesn't really work here! */ + if (!json_filter_ok(js->filter, fieldname)) + return; + p = json_member_direct(js, fieldname, jsonstrlen); memcpy(p, jsonstr, jsonstrlen); } @@ -321,13 +363,15 @@ void json_add_hex_talarr(struct json_stream *result, void json_add_escaped_string(struct json_stream *result, const char *fieldname, const struct json_escape *esc TAKES) { - /* Already escaped, don't re-escape! */ - char *dest = json_member_direct(result, fieldname, - 1 + strlen(esc->s) + 1); + if (json_filter_ok(result->filter, fieldname)) { + /* Already escaped, don't re-escape! */ + char *dest = json_member_direct(result, fieldname, + 1 + strlen(esc->s) + 1); - dest[0] = '"'; - memcpy(dest + 1, esc->s, strlen(esc->s)); - dest[1+strlen(esc->s)] = '"'; + dest[0] = '"'; + memcpy(dest + 1, esc->s, strlen(esc->s)); + dest[1+strlen(esc->s)] = '"'; + } if (taken(esc)) tal_free(esc); } @@ -373,6 +417,9 @@ void json_add_tok(struct json_stream *result, const char *fieldname, char *space; assert(tok->type != JSMN_UNDEFINED); + if (!json_filter_ok(result->filter, fieldname)) + return; + space = json_member_direct(result, fieldname, json_tok_full_len(tok)); memcpy(space, json_tok_full(buffer, tok), json_tok_full_len(tok)); } @@ -415,16 +462,6 @@ void json_add_pubkey(struct json_stream *response, json_add_hex(response, fieldname, der, sizeof(der)); } -void json_add_point32(struct json_stream *response, - const char *fieldname, - const struct point32 *key) -{ - u8 output[32]; - - secp256k1_xonly_pubkey_serialize(secp256k1_ctx, output, &key->pubkey); - json_add_hex(response, fieldname, output, sizeof(output)); -} - void json_add_bip340sig(struct json_stream *response, const char *fieldname, const struct bip340sig *sig) diff --git a/common/json_stream.h b/common/json_stream.h index 7b0601e82956..afcd29a57e05 100644 --- a/common/json_stream.h +++ b/common/json_stream.h @@ -13,13 +13,13 @@ #include #include #include +#include struct command; struct io_conn; struct log; struct json_escape; struct pubkey; -struct point32; struct bip340sig; struct secret; struct node_id; @@ -49,6 +49,9 @@ struct json_stream { void *reader_arg; size_t len_read; + /* If non-NULL, reflects the current filter position */ + struct json_filter *filter; + /* Where to log I/O */ struct log *log; }; @@ -79,6 +82,14 @@ struct json_stream *json_stream_dup(const tal_t *ctx, struct json_stream *original, struct log *log); +/* Attach a filter. Usually this works at the result level: you don't + * want to filter out id, etc! */ +void json_stream_attach_filter(struct json_stream *js, + struct json_filter *filter STEALS); + +/* Detach the filter: returns non-NULL string if it was misused. */ +const char *json_stream_detach_filter(const tal_t *ctx, struct json_stream *js); + /** * json_stream_close - finished writing to a JSON stream. * @js: the json_stream. @@ -271,11 +282,6 @@ void json_add_pubkey(struct json_stream *response, const char *fieldname, const struct pubkey *key); -/* '"fieldname" : "89abcdef..."' or "89abcdef..." if fieldname is NULL */ -void json_add_point32(struct json_stream *response, - const char *fieldname, - const struct point32 *key); - /* '"fieldname" : "89abcdef..."' or "89abcdef..." if fieldname is NULL */ void json_add_bip340sig(struct json_stream *response, const char *fieldname, diff --git a/common/jsonrpc_errors.h b/common/jsonrpc_errors.h index 9514680654b3..abccefc0601f 100644 --- a/common/jsonrpc_errors.h +++ b/common/jsonrpc_errors.h @@ -45,7 +45,7 @@ enum jsonrpc_errcode { PAY_UNSPECIFIED_ERROR = 209, PAY_STOPPED_RETRYING = 210, PAY_STATUS_UNEXPECTED = 211, - PAY_OFFER_INVALID = 212, + PAY_INVOICE_REQUEST_INVALID = 212, /* `fundchannel` or `withdraw` errors */ FUND_MAX_EXCEEDED = 300, diff --git a/common/node_id.c b/common/node_id.c index d130dcff4330..1d1a1955f946 100644 --- a/common/node_id.c +++ b/common/node_id.c @@ -25,16 +25,6 @@ bool pubkey_from_node_id(struct pubkey *key, const struct node_id *id) sizeof(id->k)); } -WARN_UNUSED_RESULT -bool point32_from_node_id(struct point32 *key, const struct node_id *id) -{ - struct pubkey k; - if (!pubkey_from_node_id(&k, id)) - return false; - return secp256k1_xonly_pubkey_from_pubkey(secp256k1_ctx, &key->pubkey, - NULL, &k.pubkey) == 1; -} - /* It's valid if we can convert to a real pubkey. */ bool node_id_valid(const struct node_id *id) { diff --git a/common/node_id.h b/common/node_id.h index 5f9fbb1ff456..1f23c9d335fc 100644 --- a/common/node_id.h +++ b/common/node_id.h @@ -24,10 +24,6 @@ void node_id_from_pubkey(struct node_id *id, const struct pubkey *key); WARN_UNUSED_RESULT bool pubkey_from_node_id(struct pubkey *key, const struct node_id *id); -/* Returns false if not a valid pubkey: relatively expensive */ -WARN_UNUSED_RESULT -bool point32_from_node_id(struct point32 *key, const struct node_id *id); - /* Convert to hex string of SEC1 encoding. */ char *node_id_to_hexstr(const tal_t *ctx, const struct node_id *id); diff --git a/common/onion.c b/common/onion.c deleted file mode 100644 index 93a4ec107543..000000000000 --- a/common/onion.c +++ /dev/null @@ -1,218 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include - -/* BOLT #4: - * - * ### `tlv_payload` format - * - * This is a more flexible format, which avoids the redundant - * `short_channel_id` field for the final node. It is formatted - * according to the Type-Length-Value format defined in [BOLT - * #1](01-messaging.md#type-length-value-format). - */ -static u8 *make_tlv_hop(const tal_t *ctx, - const struct tlv_tlv_payload *tlv) -{ - /* We can't have over 64k anyway */ - u8 *tlvs = tal_arr(ctx, u8, 3); - - towire_tlv_tlv_payload(&tlvs, tlv); - - switch (bigsize_put(tlvs, tal_bytelen(tlvs) - 3)) { - case 1: - /* Move over two unused bytes */ - memmove(tlvs + 1, tlvs + 3, tal_bytelen(tlvs) - 3); - tal_resize(&tlvs, tal_bytelen(tlvs) - 2); - return tlvs; - case 3: - return tlvs; - } - abort(); -} - -u8 *onion_nonfinal_hop(const tal_t *ctx, - const struct short_channel_id *scid, - struct amount_msat forward, - u32 outgoing_cltv, - const struct pubkey *blinding, - const u8 *enctlv) -{ - struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx); - - /* BOLT #4: - * - * The writer: - *... - * - For every node: - * - MUST include `amt_to_forward` and `outgoing_cltv_value`. - * - For every non-final node: - * - MUST include `short_channel_id` - * - MUST NOT include `payment_data` - */ - tlv->amt_to_forward = &forward.millisatoshis; /* Raw: TLV convert */ - tlv->outgoing_cltv_value = &outgoing_cltv; - tlv->short_channel_id = cast_const(struct short_channel_id *, scid); - tlv->blinding_point = cast_const(struct pubkey *, blinding); - tlv->encrypted_recipient_data = cast_const(u8 *, enctlv); - return make_tlv_hop(ctx, tlv); -} - -u8 *onion_final_hop(const tal_t *ctx, - struct amount_msat forward, - u32 outgoing_cltv, - struct amount_msat total_msat, - const struct pubkey *blinding, - const u8 *enctlv, - const struct secret *payment_secret, - const u8 *payment_metadata) -{ - struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx); - struct tlv_tlv_payload_payment_data tlv_pdata; - - /* These go together! */ - if (!payment_secret) - assert(amount_msat_eq(total_msat, forward)); - - /* BOLT #4: - * - * The writer: - *... - * - For every node: - * - MUST include `amt_to_forward` and `outgoing_cltv_value`. - *... - * - For the final node: - * - MUST NOT include `short_channel_id` - * - if the recipient provided `payment_secret`: - * - MUST include `payment_data` - * - MUST set `payment_secret` to the one provided - * - MUST set `total_msat` to the total amount it will send - */ - tlv->amt_to_forward = &forward.millisatoshis; /* Raw: TLV convert */ - tlv->outgoing_cltv_value = &outgoing_cltv; - - if (payment_secret) { - tlv_pdata.payment_secret = *payment_secret; - tlv_pdata.total_msat = total_msat.millisatoshis; /* Raw: TLV convert */ - tlv->payment_data = &tlv_pdata; - } - tlv->payment_metadata = cast_const(u8 *, payment_metadata); - tlv->blinding_point = cast_const(struct pubkey *, blinding); - tlv->encrypted_recipient_data = cast_const(u8 *, enctlv); - return make_tlv_hop(ctx, tlv); -} - -struct onion_payload *onion_decode(const tal_t *ctx, - const struct route_step *rs, - const struct pubkey *blinding, - const struct secret *blinding_ss, - const u64 *accepted_extra_tlvs, - u64 *failtlvtype, - size_t *failtlvpos) -{ - struct onion_payload *p = tal(ctx, struct onion_payload); - const u8 *cursor = rs->raw_payload; - size_t max = tal_bytelen(cursor), len; - struct tlv_tlv_payload *tlv; - - /* BOLT-remove-legacy-onion #4: - * 1. type: `hop_payloads` - * 2. data: - * * [`bigsize`:`length`] - * * [`length*byte`:`payload`] - */ - len = fromwire_bigsize(&cursor, &max); - if (!cursor || len > max) { - *failtlvtype = 0; - *failtlvpos = tal_bytelen(rs->raw_payload); - goto fail_no_tlv; - } - - /* We do this manually so we can accept extra types, and get - * error off and type. */ - tlv = tlv_tlv_payload_new(p); - if (!fromwire_tlv(&cursor, &max, tlvs_tlv_tlv_payload, - TLVS_ARRAY_SIZE_tlv_tlv_payload, - tlv, &tlv->fields, accepted_extra_tlvs, - failtlvpos, failtlvtype)) { - goto fail; - } - - /* BOLT #4: - * - * The reader: - * - MUST return an error if `amt_to_forward` or - * `outgoing_cltv_value` are not present. - */ - if (!tlv->amt_to_forward) { - *failtlvtype = TLV_TLV_PAYLOAD_AMT_TO_FORWARD; - goto field_bad; - } - if (!tlv->outgoing_cltv_value) { - *failtlvtype = TLV_TLV_PAYLOAD_OUTGOING_CLTV_VALUE; - goto field_bad; - } - - p->amt_to_forward = amount_msat(*tlv->amt_to_forward); - p->outgoing_cltv = *tlv->outgoing_cltv_value; - - /* BOLT #4: - * - * The writer: - *... - * - For every non-final node: - * - MUST include `short_channel_id` - */ - if (rs->nextcase == ONION_FORWARD) { - if (!tlv->short_channel_id) { - *failtlvtype = TLV_TLV_PAYLOAD_SHORT_CHANNEL_ID; - goto field_bad; - } - p->forward_channel = tal_dup(p, struct short_channel_id, - tlv->short_channel_id); - p->total_msat = NULL; - } else { - p->forward_channel = NULL; - /* BOLT #4: - * - if it is the final node: - * - MUST treat `total_msat` as if it were equal to - * `amt_to_forward` if it is not present. */ - p->total_msat = tal_dup(p, struct amount_msat, - &p->amt_to_forward); - } - - p->payment_secret = NULL; - p->blinding = tal_dup_or_null(p, struct pubkey, blinding); - - if (tlv->payment_data) { - p->payment_secret = tal_dup(p, struct secret, - &tlv->payment_data->payment_secret); - tal_free(p->total_msat); - p->total_msat = tal(p, struct amount_msat); - *p->total_msat - = amount_msat(tlv->payment_data->total_msat); - } - if (tlv->payment_metadata) - p->payment_metadata - = tal_dup_talarr(p, u8, tlv->payment_metadata); - else - p->payment_metadata = NULL; - - p->tlv = tal_steal(p, tlv); - return p; - -field_bad: - *failtlvpos = tlv_field_offset(rs->raw_payload, tal_bytelen(rs->raw_payload), - *failtlvtype); -fail: - tal_free(tlv); - -fail_no_tlv: - tal_free(p); - return NULL; -} diff --git a/common/onion.h b/common/onion.h deleted file mode 100644 index 23dd2c92ceef..000000000000 --- a/common/onion.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef LIGHTNING_COMMON_ONION_H -#define LIGHTNING_COMMON_ONION_H -#include "config.h" -#include -#include - -struct route_step; - -struct onion_payload { - struct amount_msat amt_to_forward; - u32 outgoing_cltv; - struct amount_msat *total_msat; - struct short_channel_id *forward_channel; - struct secret *payment_secret; - u8 *payment_metadata; - - /* If blinding is set, blinding_ss is the shared secret.*/ - struct pubkey *blinding; - struct secret blinding_ss; - - /* The raw TLVs contained in the payload. */ - struct tlv_tlv_payload *tlv; -}; - -u8 *onion_nonfinal_hop(const tal_t *ctx, - const struct short_channel_id *scid, - struct amount_msat forward, - u32 outgoing_cltv, - const struct pubkey *blinding, - const u8 *enctlv); - -/* Note that this can fail if we supply payment_secret or payment_metadata and !use_tlv! */ -u8 *onion_final_hop(const tal_t *ctx, - struct amount_msat forward, - u32 outgoing_cltv, - struct amount_msat total_msat, - const struct pubkey *blinding, - const u8 *enctlv, - const struct secret *payment_secret, - const u8 *payment_metadata); - -/** - * onion_decode: decode payload from a decrypted onion. - * @ctx: context to allocate onion_contents off. - * @rs: the route_step, whose raw_payload is of at least length - * onion_payload_length(). - * @blinding: the optional incoming blinding point. - * @blinding_ss: the shared secret derived from @blinding (iff that's non-NULL) - * @accepted_extra_tlvs: Allow these types to be in the TLV without failing - * @failtlvtype: (out) the tlv type which failed to parse. - * @failtlvpos: (out) the offset in the tlv which failed to parse. - * - * If the payload is not valid, returns NULL. - */ -struct onion_payload *onion_decode(const tal_t *ctx, - const struct route_step *rs, - const struct pubkey *blinding, - const struct secret *blinding_ss, - const u64 *accepted_extra_tlvs, - u64 *failtlvtype, - size_t *failtlvpos); - -#endif /* LIGHTNING_COMMON_ONION_H */ diff --git a/common/onion_decode.c b/common/onion_decode.c new file mode 100644 index 000000000000..a17c8e87ebe5 --- /dev/null +++ b/common/onion_decode.c @@ -0,0 +1,380 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static u64 ceil_div(u64 a, u64 b) +{ + return (a + b - 1) / b; +} + +static bool handle_blinded_forward(struct onion_payload *p, + struct amount_msat amount_in, + u32 cltv_expiry, + const struct tlv_tlv_payload *tlv, + const struct tlv_encrypted_data_tlv *enc, + u64 *failtlvtype) +{ + u64 amt = amount_in.millisatoshis; /* Raw: allowed to wrap */ + + /* BOLT-route-blinding #4: + * - If it is not the final node: + * - MUST return an error if the payload contains other tlv fields + * than `encrypted_recipient_data` and `current_blinding_point`. + */ + for (size_t i = 0; i < tal_count(tlv->fields); i++) { + if (tlv->fields[i].numtype != TLV_TLV_PAYLOAD_BLINDING_POINT + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA) { + *failtlvtype = tlv->fields[i].numtype; + return false; + } + } + + /* BOLT-route-blinding #4: + * - If it is not the final node: + *... + * - MUST return an error if `encrypted_recipient_data` does not + * contain either `short_channel_id` or `next_node_id`. + */ + if (!enc->short_channel_id && !enc->next_node_id) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + return false; + } + + if (enc->short_channel_id) { + p->forward_channel = tal_dup(p, struct short_channel_id, + enc->short_channel_id); + p->forward_node_id = NULL; + } else { + p->forward_channel = NULL; + p->forward_node_id = tal_dup(p, struct pubkey, + enc->next_node_id); + } + + p->total_msat = NULL; + + /* BOLT-route-blinding #4: + * - If it is not the final node: + *... + * - MUST return an error if `encrypted_recipient_data` does not + * contain `payment_relay`. + */ + if (!enc->payment_relay) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + return false; + } + + /* FIXME: Put these formulae in BOLT 4! */ + /* amt_to_forward = ceil((amount_msat - fee_base_msat) * 1000000 / (1000000 + fee_proportional_millionths)) */ + /* If these values are crap, that's OK: the HTLC will fail. */ + p->amt_to_forward = amount_msat(ceil_div((amt - enc->payment_relay->fee_base_msat) * 1000000, + 1000000 + enc->payment_relay->fee_proportional_millionths)); + p->outgoing_cltv = cltv_expiry - enc->payment_relay->cltv_expiry_delta; + return true; +} + +static bool handle_blinded_terminal(struct onion_payload *p, + const struct tlv_tlv_payload *tlv, + const struct tlv_encrypted_data_tlv *enc, + u64 *failtlvtype) +{ + /* BOLT-route-blinding #4: + * - If it is the final node: + * - MUST return an error if the payload contains other tlv fields than + * `encrypted_recipient_data`, `current_blinding_point`, `amt_to_forward`, + * `outgoing_cltv_value` and `total_amount_msat`. + */ + for (size_t i = 0; i < tal_count(tlv->fields); i++) { + if (tlv->fields[i].numtype != TLV_TLV_PAYLOAD_BLINDING_POINT + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_AMT_TO_FORWARD + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_OUTGOING_CLTV_VALUE + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_TOTAL_AMOUNT_MSAT) { + *failtlvtype = tlv->fields[i].numtype; + return false; + } + } + + /* BOLT-route-blinding #4: + * - MUST return an error if `amt_to_forward` or + * `outgoing_cltv_value` are not present. + * - MUST return an error if `amt_to_forward` is below what it expects + * for the payment. + */ + if (!tlv->amt_to_forward) { + *failtlvtype = TLV_TLV_PAYLOAD_AMT_TO_FORWARD; + return false; + } + + if (!tlv->outgoing_cltv_value) { + *failtlvtype = TLV_TLV_PAYLOAD_OUTGOING_CLTV_VALUE; + return false; + } + + p->amt_to_forward = amount_msat(*tlv->amt_to_forward); + p->outgoing_cltv = *tlv->outgoing_cltv_value; + + p->forward_channel = NULL; + p->forward_node_id = NULL; + + if (tlv->total_amount_msat) { + p->total_msat = tal(p, struct amount_msat); + *p->total_msat = amount_msat(*tlv->total_amount_msat); + } else { + /* BOLT #4: + * - if it is the final node: + * - MUST treat `total_msat` as if it were equal to + * `amt_to_forward` if it is not present. */ + p->total_msat = tal_dup(p, struct amount_msat, + &p->amt_to_forward); + } + return true; +} + +struct onion_payload *onion_decode(const tal_t *ctx, + bool blinding_support, + const struct route_step *rs, + const struct pubkey *blinding, + const u64 *accepted_extra_tlvs, + struct amount_msat amount_in, + u32 cltv_expiry, + u64 *failtlvtype, + size_t *failtlvpos) +{ + struct onion_payload *p = tal(ctx, struct onion_payload); + const u8 *cursor = rs->raw_payload; + size_t max = tal_bytelen(cursor), len; + + /* BOLT-remove-legacy-onion #4: + * 1. type: `hop_payloads` + * 2. data: + * * [`bigsize`:`length`] + * * [`length*byte`:`payload`] + */ + len = fromwire_bigsize(&cursor, &max); + if (!cursor || len > max) { + *failtlvtype = 0; + *failtlvpos = tal_bytelen(rs->raw_payload); + return tal_free(p); + } + + /* We do this manually so we can accept extra types, and get + * error off and type. */ + p->tlv = tlv_tlv_payload_new(p); + if (!fromwire_tlv(&cursor, &max, tlvs_tlv_tlv_payload, + TLVS_ARRAY_SIZE_tlv_tlv_payload, + p->tlv, &p->tlv->fields, accepted_extra_tlvs, + failtlvpos, failtlvtype)) { + return tal_free(p); + } + + /* BOLT-route-blinding #4: + * + * The reader: + * + * - If `encrypted_recipient_data` is present: + */ + if (p->tlv->encrypted_recipient_data) { + struct tlv_encrypted_data_tlv *enc; + + /* Only supported with --experimental-onion-messages! */ + if (!blinding_support) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + /* BOLT-route-blinding #4: + * + * - If `blinding_point` is set in the incoming `update_add_htlc`: + * - MUST return an error if `current_blinding_point` is present. + * - MUST use that `blinding_point` as the blinding point for decryption. + * - Otherwise: + * - MUST return an error if `current_blinding_point` is not present. + * - MUST use that `current_blinding_point` as the blinding point for decryption. + */ + if (blinding) { + if (p->tlv->blinding_point) { + *failtlvtype = TLV_TLV_PAYLOAD_BLINDING_POINT; + goto field_bad; + } + p->blinding = tal_dup(p, struct pubkey, blinding); + } else { + if (!p->tlv->blinding_point) { + *failtlvtype = TLV_TLV_PAYLOAD_BLINDING_POINT; + goto field_bad; + } + p->blinding = tal_dup(p, struct pubkey, + p->tlv->blinding_point); + } + + /* BOLT-route-blinding #4: + * The reader: + *... + * - MUST return an error if `encrypted_recipient_data` does + * not decrypt using the blinding point as described in + * [Route Blinding](#route-blinding). + */ + ecdh(p->blinding, &p->blinding_ss); + enc = decrypt_encrypted_data(tmpctx, p->blinding, &p->blinding_ss, + p->tlv->encrypted_recipient_data); + if (!enc) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + if (enc->payment_constraints) { + /* BOLT-route-blinding #4: + * - MUST return an error if: + * - the expiry is greater than + * `encrypted_recipient_data.payment_constraints.max_cltv_expiry`. + */ + if (cltv_expiry > enc->payment_constraints->max_cltv_expiry) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + /* BOLT-route-blinding #4: + * - MUST return an error if: + *... + * - the amount is below + * `encrypted_recipient_data.payment_constraints.htlc_minimum_msat`. + */ + if (amount_msat_less(amount_in, + amount_msat(enc->payment_constraints->htlc_minimum_msat))) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + /* BOLT-route-blinding #4: + * - If `allowed_features` is present: + * - MUST return an error if: + *... + * - the payment uses a feature not included in + * `encrypted_recipient_data.allowed_features.features` + */ + /* We don't have any features yet... */ + } + + /* BOLT-route-blinding #4: + * - If `allowed_features` is present: + * - MUST return an error if: + * - `encrypted_recipient_data.allowed_features.features` + * contains an unknown feature bit (even if it is odd). + * - the payment uses a feature not included in + * `encrypted_recipient_data.allowed_features.features`. + */ + /* No features, this is easy */ + if (!memeqzero(enc->allowed_features, + tal_bytelen(enc->allowed_features))) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + if (rs->nextcase == ONION_FORWARD) { + if (!handle_blinded_forward(p, amount_in, cltv_expiry, + p->tlv, enc, failtlvtype)) + goto field_bad; + } else { + if (!handle_blinded_terminal(p, p->tlv, enc, failtlvtype)) + goto field_bad; + } + + /* We stash path_id (if present and valid!) in payment_secret */ + if (tal_bytelen(enc->path_id) == sizeof(*p->payment_secret)) { + p->payment_secret = tal_steal(p, + (struct secret *)enc->path_id); + } else + p->payment_secret = NULL; + + /* FIXME: if we supported metadata, it would also be in path_id */ + p->payment_metadata = NULL; + return p; + } + + /* BOLT-route-blinding-fix #4: + * - Otherwise (it is not part of a blinded route): + * - MUST return an error if `blinding_point` is set in the + * incoming `update_add_htlc` or `current_blinding_point` + * is present. + */ + if (blinding || p->tlv->blinding_point) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + /* BOLT #4: + * + * The reader: + * - MUST return an error if `amt_to_forward` or + * `outgoing_cltv_value` are not present. + */ + if (!p->tlv->amt_to_forward) { + *failtlvtype = TLV_TLV_PAYLOAD_AMT_TO_FORWARD; + goto field_bad; + } + if (!p->tlv->outgoing_cltv_value) { + *failtlvtype = TLV_TLV_PAYLOAD_OUTGOING_CLTV_VALUE; + goto field_bad; + } + + p->amt_to_forward = amount_msat(*p->tlv->amt_to_forward); + p->outgoing_cltv = *p->tlv->outgoing_cltv_value; + + /* BOLT #4: + * + * The writer: + *... + * - For every non-final node: + * - MUST include `short_channel_id` + */ + if (rs->nextcase == ONION_FORWARD) { + if (!p->tlv->short_channel_id) { + *failtlvtype = TLV_TLV_PAYLOAD_SHORT_CHANNEL_ID; + goto field_bad; + } + p->forward_channel = tal_dup(p, struct short_channel_id, + p->tlv->short_channel_id); + p->total_msat = NULL; + } else { + p->forward_channel = NULL; + /* BOLT #4: + * - if it is the final node: + * - MUST treat `total_msat` as if it were equal to + * `amt_to_forward` if it is not present. */ + p->total_msat = tal_dup(p, struct amount_msat, + &p->amt_to_forward); + } + + /* Non-blinded is (currently) always by scid */ + p->forward_node_id = NULL; + + p->payment_secret = NULL; + if (p->tlv->payment_data) { + p->payment_secret = tal_dup(p, struct secret, + &p->tlv->payment_data->payment_secret); + tal_free(p->total_msat); + p->total_msat = tal(p, struct amount_msat); + *p->total_msat + = amount_msat(p->tlv->payment_data->total_msat); + } + if (p->tlv->payment_metadata) + p->payment_metadata + = tal_dup_talarr(p, u8, p->tlv->payment_metadata); + else + p->payment_metadata = NULL; + + p->blinding = NULL; + + return p; + +field_bad: + *failtlvpos = tlv_field_offset(rs->raw_payload, tal_bytelen(rs->raw_payload), + *failtlvtype); + return tal_free(p); +} diff --git a/common/onion_decode.h b/common/onion_decode.h new file mode 100644 index 000000000000..3ed65f55bc05 --- /dev/null +++ b/common/onion_decode.h @@ -0,0 +1,32 @@ +#ifndef LIGHTNING_COMMON_ONION_DECODE_H +#define LIGHTNING_COMMON_ONION_DECODE_H +#include "config.h" +#include +#include +#include + +/** + * onion_decode: decode payload from a decrypted onion. + * @ctx: context to allocate onion_contents off. + * @blinding_support: --experimental-route-blinding? + * @rs: the route_step, whose raw_payload is of at least length + * onion_payload_length(). + * @blinding: the optional incoming blinding point. + * @accepted_extra_tlvs: Allow these types to be in the TLV without failing + * @amount_in: Incoming HTLC amount + * @cltv_expiry: Incoming HTLC cltv_expiry + * @failtlvtype: (out) the tlv type which failed to parse. + * @failtlvpos: (out) the offset in the tlv which failed to parse. + * + * If the payload is not valid, returns NULL. + */ +struct onion_payload *onion_decode(const tal_t *ctx, + bool blinding_support, + const struct route_step *rs, + const struct pubkey *blinding, + const u64 *accepted_extra_tlvs, + struct amount_msat amount_in, + u32 cltv_expiry, + u64 *failtlvtype, + size_t *failtlvpos); +#endif /* LIGHTNING_COMMON_ONION_DECODE_H */ diff --git a/common/onion_encode.c b/common/onion_encode.c new file mode 100644 index 000000000000..711b8dc9c76a --- /dev/null +++ b/common/onion_encode.c @@ -0,0 +1,122 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* BOLT #4: + * + * ### `tlv_payload` format + * + * This is a more flexible format, which avoids the redundant + * `short_channel_id` field for the final node. It is formatted + * according to the Type-Length-Value format defined in [BOLT + * #1](01-messaging.md#type-length-value-format). + */ +static u8 *make_tlv_hop(const tal_t *ctx, + const struct tlv_tlv_payload *tlv) +{ + /* We can't have over 64k anyway */ + u8 *tlvs = tal_arr(ctx, u8, 3); + + towire_tlv_tlv_payload(&tlvs, tlv); + + switch (bigsize_put(tlvs, tal_bytelen(tlvs) - 3)) { + case 1: + /* Move over two unused bytes */ + memmove(tlvs + 1, tlvs + 3, tal_bytelen(tlvs) - 3); + tal_resize(&tlvs, tal_bytelen(tlvs) - 2); + return tlvs; + case 3: + return tlvs; + } + abort(); +} + +u8 *onion_nonfinal_hop(const tal_t *ctx, + const struct short_channel_id *scid, + struct amount_msat forward, + u32 outgoing_cltv) +{ + struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx); + + /* BOLT #4: + * + * The writer: + *... + * - For every node: + * - MUST include `amt_to_forward` and `outgoing_cltv_value`. + * - For every non-final node: + * - MUST include `short_channel_id` + * - MUST NOT include `payment_data` + */ + tlv->amt_to_forward = &forward.millisatoshis; /* Raw: TLV convert */ + tlv->outgoing_cltv_value = &outgoing_cltv; + tlv->short_channel_id = cast_const(struct short_channel_id *, scid); + return make_tlv_hop(ctx, tlv); +} + +u8 *onion_final_hop(const tal_t *ctx, + struct amount_msat forward, + u32 outgoing_cltv, + struct amount_msat total_msat, + const struct secret *payment_secret, + const u8 *payment_metadata) +{ + struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx); + struct tlv_tlv_payload_payment_data tlv_pdata; + + /* These go together! */ + if (!payment_secret) + assert(amount_msat_eq(total_msat, forward)); + + /* BOLT #4: + * + * The writer: + *... + * - For every node: + * - MUST include `amt_to_forward` and `outgoing_cltv_value`. + *... + * - For the final node: + * - MUST NOT include `short_channel_id` + * - if the recipient provided `payment_secret`: + * - MUST include `payment_data` + * - MUST set `payment_secret` to the one provided + * - MUST set `total_msat` to the total amount it will send + */ + tlv->amt_to_forward = &forward.millisatoshis; /* Raw: TLV convert */ + tlv->outgoing_cltv_value = &outgoing_cltv; + + if (payment_secret) { + tlv_pdata.payment_secret = *payment_secret; + tlv_pdata.total_msat = total_msat.millisatoshis; /* Raw: TLV convert */ + tlv->payment_data = &tlv_pdata; + } + tlv->payment_metadata = cast_const(u8 *, payment_metadata); + return make_tlv_hop(ctx, tlv); +} + +u8 *onion_blinded_hop(const tal_t *ctx, + const struct amount_msat *amt_to_forward, + const u32 *outgoing_cltv_value, + const u8 *enctlv, + const struct pubkey *blinding) +{ + struct tlv_tlv_payload *tlv = tlv_tlv_payload_new(tmpctx); + + if (amt_to_forward) { + tlv->amt_to_forward + = cast_const(u64 *, + &amt_to_forward->millisatoshis); /* Raw: TLV convert */ + } + tlv->outgoing_cltv_value = cast_const(u32 *, outgoing_cltv_value); + tlv->encrypted_recipient_data = cast_const(u8 *, enctlv); + tlv->blinding_point = cast_const(struct pubkey *, blinding); + + return make_tlv_hop(ctx, tlv); +} diff --git a/common/onion_encode.h b/common/onion_encode.h new file mode 100644 index 000000000000..ba48211917af --- /dev/null +++ b/common/onion_encode.h @@ -0,0 +1,58 @@ +#ifndef LIGHTNING_COMMON_ONION_ENCODE_H +#define LIGHTNING_COMMON_ONION_ENCODE_H +#include "config.h" +#include +#include + +struct route_step; +struct tlv_encrypted_data_tlv_payment_relay; + +enum onion_payload_type { + ONION_V0_PAYLOAD = 0, + ONION_TLV_PAYLOAD = 1, +}; + +struct onion_payload { + enum onion_payload_type type; + + struct amount_msat amt_to_forward; + u32 outgoing_cltv; + struct amount_msat *total_msat; + + /* One of these is set */ + struct short_channel_id *forward_channel; + struct pubkey *forward_node_id; + + struct secret *payment_secret; + u8 *payment_metadata; + + /* If blinding is set, blinding_ss is the shared secret.*/ + struct pubkey *blinding; + struct secret blinding_ss; + + /* The raw TLVs contained in the payload. */ + struct tlv_tlv_payload *tlv; +}; + +u8 *onion_nonfinal_hop(const tal_t *ctx, + const struct short_channel_id *scid, + struct amount_msat forward, + u32 outgoing_cltv); + +/* Note that this can fail if we supply payment_secret or payment_metadata and !use_tlv! */ +u8 *onion_final_hop(const tal_t *ctx, + struct amount_msat forward, + u32 outgoing_cltv, + struct amount_msat total_msat, + const struct secret *payment_secret, + const u8 *payment_metadata); + +/* Blinding has more complex rules on what fields are encoded: this is the + * generic interface, as used by blindedpay.h */ +u8 *onion_blinded_hop(const tal_t *ctx, + const struct amount_msat *amt_to_forward, + const u32 *outgoing_cltv_value, + const u8 *enctlv, + const struct pubkey *blinding) + NON_NULL_ARGS(4); +#endif /* LIGHTNING_COMMON_ONION_ENCODE_H */ diff --git a/common/onion_message_parse.c b/common/onion_message_parse.c new file mode 100644 index 000000000000..311c33ef5ec1 --- /dev/null +++ b/common/onion_message_parse.c @@ -0,0 +1,187 @@ +/* Caller does fromwire_onion_message(), this does the rest. */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool decrypt_final_onionmsg(const tal_t *ctx, + const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + const struct pubkey *my_id, + struct pubkey *alias, + struct secret **path_id) +{ + struct tlv_encrypted_data_tlv *encmsg; + + if (!blindedpath_get_alias(ss, my_id, alias)) + return false; + + encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv); + if (!encmsg) + return false; + + if (tal_bytelen(encmsg->path_id) == sizeof(**path_id)) { + *path_id = tal(ctx, struct secret); + memcpy(*path_id, encmsg->path_id, sizeof(**path_id)); + } else + *path_id = NULL; + + return true; +} + +static bool decrypt_forwarding_onionmsg(const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + struct pubkey *next_node, + struct pubkey *next_blinding) +{ + struct tlv_encrypted_data_tlv *encmsg; + + encmsg = decrypt_encrypted_data(tmpctx, blinding, ss, enctlv); + if (!encmsg) + return false; + + /* BOLT-onion-message #4: + * + * The reader: + * - if it is not the final node according to the onion encryption: + *... + * - if the `enctlv` ... does not contain + * `next_node_id`: + * - MUST drop the message. + */ + if (!encmsg->next_node_id) + return false; + + /* BOLT-onion-message #4: + * The reader: + * - if it is not the final node according to the onion encryption: + *... + * - if the `enctlv` contains `path_id`: + * - MUST drop the message. + */ + if (encmsg->path_id) + return false; + + *next_node = *encmsg->next_node_id; + blindedpath_next_blinding(encmsg, blinding, ss, next_blinding); + return true; +} + +/* Returns false on failure */ +bool onion_message_parse(const tal_t *ctx, + const u8 *onion_message_packet, + const struct pubkey *blinding, + const struct node_id *peer, + const struct pubkey *me, + u8 **next_onion_msg, + struct pubkey *next_node_id, + struct tlv_onionmsg_tlv **final_om, + struct pubkey *final_alias, + struct secret **final_path_id) +{ + enum onion_wire badreason; + struct onionpacket *op; + struct pubkey ephemeral; + struct route_step *rs; + struct tlv_onionmsg_tlv *om; + struct secret ss, onion_ss; + const u8 *cursor; + size_t max, maxlen; + + /* We unwrap the onion now. */ + op = parse_onionpacket(tmpctx, + onion_message_packet, + tal_bytelen(onion_message_packet), + &badreason); + if (!op) { + status_peer_debug(peer, "onion_message_parse: can't parse onionpacket: %s", + onion_wire_name(badreason)); + return false; + } + + ephemeral = op->ephemeralkey; + if (!unblind_onion(blinding, ecdh, &ephemeral, &ss)) { + status_peer_debug(peer, "onion_message_parse: can't unblind onionpacket"); + return false; + } + + /* Now get onion shared secret and parse it. */ + ecdh(&ephemeral, &onion_ss); + rs = process_onionpacket(tmpctx, op, &onion_ss, NULL, 0, false); + if (!rs) { + status_peer_debug(peer, + "onion_message_parse: can't process onionpacket ss=%s", + type_to_string(tmpctx, struct secret, &onion_ss)); + return false; + } + + /* The raw payload is prepended with length in the modern onion. */ + cursor = rs->raw_payload; + max = tal_bytelen(rs->raw_payload); + maxlen = fromwire_bigsize(&cursor, &max); + if (!cursor) { + status_peer_debug(peer, "onion_message_parse: Invalid hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return false; + } + if (maxlen > max) { + status_peer_debug(peer, "onion_message_parse: overlong hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return false; + } + + om = fromwire_tlv_onionmsg_tlv(tmpctx, &cursor, &maxlen); + if (!om) { + status_peer_debug(peer, "onion_message_parse: invalid onionmsg_tlv %s", + tal_hex(tmpctx, rs->raw_payload)); + return false; + } + + if (rs->nextcase == ONION_END) { + *next_onion_msg = NULL; + *final_om = tal_steal(ctx, om); + /* Final enctlv is actually optional */ + if (!om->encrypted_recipient_data) { + *final_alias = *me; + *final_path_id = NULL; + } else if (!decrypt_final_onionmsg(ctx, blinding, &ss, + om->encrypted_recipient_data, me, + final_alias, + final_path_id)) { + status_peer_debug(peer, + "onion_message_parse: failed to decrypt encrypted_recipient_data" + " %s", tal_hex(tmpctx, om->encrypted_recipient_data)); + return false; + } + } else { + struct pubkey next_blinding; + + *final_om = NULL; + + /* This fails as expected if no enctlv. */ + if (!decrypt_forwarding_onionmsg(blinding, &ss, om->encrypted_recipient_data, next_node_id, + &next_blinding)) { + status_peer_debug(peer, + "onion_message_parse: invalid encrypted_recipient_data %s", + tal_hex(tmpctx, om->encrypted_recipient_data)); + return false; + } + + *next_onion_msg = towire_onion_message(ctx, + &next_blinding, + serialize_onionpacket(tmpctx, rs->next)); + } + + /* Exactly one is set */ + assert(!*next_onion_msg + !*final_om == 1); + return true; +} diff --git a/common/onion_message_parse.h b/common/onion_message_parse.h new file mode 100644 index 000000000000..364eae40c9a7 --- /dev/null +++ b/common/onion_message_parse.h @@ -0,0 +1,37 @@ +#ifndef LIGHTNING_COMMON_ONION_MESSAGE_PARSE_H +#define LIGHTNING_COMMON_ONION_MESSAGE_PARSE_H +#include "config.h" +#include +#include + +struct tlv_onionmsg_tlv; +struct node_id; +struct pubkey; + +/** + * onion_message_parse: core routine to check onion_message + * @ctx: context to allocate @next_onion_msg or @final_om/@path_id off + * @onion_message_packet: Sphinx-encrypted onion + * @blinding: Blinding we were given for @onion_message_packet + * @peer: node_id of peer (for status_peer_debug msgs) + * @me: my pubkey + * @next_onion_msg (out): set if we should forward, otherwise NULL. + * @next_node_id (out): set to node id to fwd to, iff *@next_onion_msg. + * @final_om (out): set if we're the final hop, otherwise NULL. + * @final_alias (out): our alias (if *@final_om), or our own ID + * @final_path_id (out): secret enclosed, if any (iff *@final_om). + * + * Returns false if it wasn't valid. + */ +bool onion_message_parse(const tal_t *ctx, + const u8 *onion_message_packet, + const struct pubkey *blinding, + const struct node_id *peer, + const struct pubkey *me, + u8 **next_onion_msg, + struct pubkey *next_node_id, + struct tlv_onionmsg_tlv **final_om, + struct pubkey *final_alias, + struct secret **final_path_id); + +#endif /* LIGHTNING_COMMON_ONION_MESSAGE_PARSE_H */ diff --git a/common/route.c b/common/route.c index 3ad9b4e964ee..f88cb0082ac2 100644 --- a/common/route.c +++ b/common/route.c @@ -102,10 +102,6 @@ static bool dijkstra_to_hops(struct route_hop **hops, next = gossmap_nth_node(gossmap, c, !(*hops)[num_hops].direction); gossmap_node_get_id(gossmap, next, &(*hops)[num_hops].node_id); - /* These are (ab)used by others. */ - (*hops)[num_hops].blinding = NULL; - (*hops)[num_hops].enctlv = NULL; - if (!dijkstra_to_hops(hops, gossmap, dij, next, amount, cltv)) return false; diff --git a/common/route.h b/common/route.h index f28b0d0730be..1bdb7f0f95c8 100644 --- a/common/route.h +++ b/common/route.h @@ -19,8 +19,6 @@ struct gossmap_node; * @node_id: the node_id of the destination of this hop. * @amount: amount to send through this hop. * @delay: total cltv delay at this hop. - * @blinding: blinding key for this hop (if any) - * @enctlv: encrypted TLV for this hop (if any) */ struct route_hop { struct short_channel_id scid; @@ -28,8 +26,6 @@ struct route_hop { struct node_id node_id; struct amount_msat amount; u32 delay; - struct pubkey *blinding; - u8 *enctlv; }; /* Can c carry amount in dir? */ diff --git a/common/shutdown_scriptpubkey.c b/common/shutdown_scriptpubkey.c index 4a09ef30a810..d7497f295276 100644 --- a/common/shutdown_scriptpubkey.c +++ b/common/shutdown_scriptpubkey.c @@ -48,9 +48,9 @@ static bool is_valid_witnessprog(const u8 *scriptpubkey) bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey, bool anysegwit, - bool anchors) + bool allow_oldstyle) { - if (!anchors) { + if (allow_oldstyle) { if (is_p2pkh(scriptpubkey, NULL) || is_p2sh(scriptpubkey, NULL)) return true; diff --git a/common/shutdown_scriptpubkey.h b/common/shutdown_scriptpubkey.h index e8c1fac1c26b..a4a9d295d5a1 100644 --- a/common/shutdown_scriptpubkey.h +++ b/common/shutdown_scriptpubkey.h @@ -16,8 +16,11 @@ * - if the `scriptpubkey` is not in one of the above forms: * - SHOULD send a `warning` */ + +/* We still allow them to specify an old-style P2PKH or P2SH (though we + * never will send such a thing!) if they're not using anchors. */ bool valid_shutdown_scriptpubkey(const u8 *scriptpubkey, bool anysegwit, - bool anchors); + bool allow_oldstyle); #endif /* LIGHTNING_COMMON_SHUTDOWN_SCRIPTPUBKEY_H */ diff --git a/common/sphinx.c b/common/sphinx.c index fff2a2d297b5..19d50ba89f10 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -16,8 +16,6 @@ #define BLINDING_FACTOR_SIZE 32 -#define ONION_REPLY_SIZE 256 - #define RHO_KEYTYPE "rho" struct hop_params { @@ -679,17 +677,36 @@ struct route_step *process_onionpacket( return step; } +#if DEVELOPER +unsigned dev_onion_reply_length = 256; +#endif + struct onionreply *create_onionreply(const tal_t *ctx, const struct secret *shared_secret, const u8 *failure_msg) { size_t msglen = tal_count(failure_msg); - size_t padlen = ONION_REPLY_SIZE - msglen; + size_t padlen; struct onionreply *reply = tal(ctx, struct onionreply); u8 *payload = tal_arr(ctx, u8, 0); struct secret key; struct hmac hmac; + /* BOLT #4: + * The _erring node_: + * - SHOULD set `pad` such that the `failure_len` plus `pad_len` + * is equal to 256. + * - Note: this value is 118 bytes longer than the longest + * currently-defined message. + */ + const u16 onion_reply_size = IFDEV(dev_onion_reply_length, 256); + + /* We never do this currently, but could in future! */ + if (msglen > onion_reply_size) + padlen = 0; + else + padlen = onion_reply_size - msglen; + /* BOLT #4: * * The node generating the error message (_erring node_) builds a return @@ -708,15 +725,8 @@ struct onionreply *create_onionreply(const tal_t *ctx, towire_u16(&payload, padlen); towire_pad(&payload, padlen); - /* BOLT #4: - * - * The _erring node_: - * - SHOULD set `pad` such that the `failure_len` plus `pad_len` is - * equal to 256. - * - Note: this value is 118 bytes longer than the longest - * currently-defined message. - */ - assert(tal_count(payload) == ONION_REPLY_SIZE + 4); + /* Two bytes for each length: failure_len and pad_len */ + assert(tal_count(payload) == onion_reply_size + 4); /* BOLT #4: * @@ -763,21 +773,17 @@ u8 *unwrap_onionreply(const tal_t *ctx, int *origin_index) { struct onionreply *r; - struct secret key; - struct hmac hmac; const u8 *cursor; - u8 *final; size_t max; u16 msglen; - if (tal_count(reply->contents) != ONION_REPLY_SIZE + sizeof(hmac) + 4) { - return NULL; - } - r = new_onionreply(tmpctx, reply->contents); *origin_index = -1; for (int i = 0; i < numhops; i++) { + struct secret key; + struct hmac hmac, expected_hmac; + /* Since the encryption is just XORing with the cipher * stream encryption is identical to decryption */ r = wrap_onionreply(tmpctx, &shared_secrets[i], r); @@ -785,30 +791,29 @@ u8 *unwrap_onionreply(const tal_t *ctx, /* Check if the HMAC matches, this means that this is * the origin */ subkey_from_hmac("um", &shared_secrets[i], &key); - compute_hmac(&key, r->contents + sizeof(hmac.bytes), - tal_count(r->contents) - sizeof(hmac.bytes), - NULL, 0, &hmac); - if (memcmp(hmac.bytes, r->contents, sizeof(hmac.bytes)) == 0) { + + cursor = r->contents; + max = tal_count(r->contents); + + fromwire_hmac(&cursor, &max, &hmac); + /* Too short. */ + if (!cursor) + return NULL; + + compute_hmac(&key, cursor, max, NULL, 0, &expected_hmac); + if (hmac_eq(&hmac, &expected_hmac)) { *origin_index = i; break; } } + + /* Didn't find source, it's garbled */ if (*origin_index == -1) { return NULL; } - cursor = r->contents + sizeof(hmac); - max = tal_count(r->contents) - sizeof(hmac); msglen = fromwire_u16(&cursor, &max); - - if (msglen > ONION_REPLY_SIZE) { - return NULL; - } - - final = tal_arr(ctx, u8, msglen); - if (!fromwire(&cursor, &max, final, msglen)) - return tal_free(final); - return final; + return fromwire_tal_arrn(ctx, &cursor, &max, msglen); } struct onionpacket *sphinx_decompress(const tal_t *ctx, diff --git a/common/sphinx.h b/common/sphinx.h index 6ba537cf36d3..9b80c29b3d04 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -267,6 +267,9 @@ sphinx_compressed_onion_deserialize(const tal_t *ctx, const u8 *src); #if DEVELOPER /* Override to force us to reject valid onion packets */ extern bool dev_fail_process_onionpacket; + +/* Override to set custom onion error lengths. */ +extern unsigned dev_onion_reply_length; #endif #endif /* LIGHTNING_COMMON_SPHINX_H */ diff --git a/common/test/run-blindedpath_enctlv.c b/common/test/run-blindedpath_enctlv.c index 9c619b7b3fd9..fd88d7cbb1e9 100644 --- a/common/test/run-blindedpath_enctlv.c +++ b/common/test/run-blindedpath_enctlv.c @@ -91,19 +91,22 @@ static void test_decrypt(const struct pubkey *blinding, const struct pubkey *expected_next_node, const struct privkey *expected_next_blinding_priv) { - struct pubkey expected_next_blinding, dummy, next_node, next_blinding; + struct pubkey expected_next_blinding, dummy, next_blinding; struct secret ss; + struct tlv_encrypted_data_tlv *enc; /* We don't actually have an onion, so we put some dummy */ pubkey_from_privkey(me, &dummy); mykey = me; assert(unblind_onion(blinding, test_ecdh, &dummy, &ss)); - assert(decrypt_enctlv(blinding, &ss, enctlv, &next_node, &next_blinding)); + enc = decrypt_encrypted_data(tmpctx, blinding, &ss, enctlv); + assert(enc); pubkey_from_privkey(expected_next_blinding_priv, &expected_next_blinding); + blindedpath_next_blinding(enc, blinding, &ss, &next_blinding); assert(pubkey_eq(&next_blinding, &expected_next_blinding)); - assert(pubkey_eq(&next_node, expected_next_node)); + assert(pubkey_eq(enc->next_node_id, expected_next_node)); } static void test_final_decrypt(const struct pubkey *blinding, @@ -113,7 +116,8 @@ static void test_final_decrypt(const struct pubkey *blinding, const struct secret *expected_self_id) { struct pubkey my_pubkey, dummy, alias; - struct secret ss, *self_id; + struct secret ss; + struct tlv_encrypted_data_tlv *enc; /* We don't actually have an onion, so we put some dummy */ pubkey_from_privkey(me, &dummy); @@ -121,11 +125,13 @@ static void test_final_decrypt(const struct pubkey *blinding, mykey = me; pubkey_from_privkey(me, &my_pubkey); assert(unblind_onion(blinding, test_ecdh, &dummy, &ss)); - assert(decrypt_final_enctlv(tmpctx, blinding, &ss, enctlv, &my_pubkey, - &alias, &self_id)); + enc = decrypt_encrypted_data(tmpctx, blinding, &ss, enctlv); + assert(enc); + assert(blindedpath_get_alias(&ss, &my_pubkey, &alias)); assert(pubkey_eq(&alias, expected_alias)); - assert(secret_eq_consttime(self_id, expected_self_id)); + assert(memeq(enc->path_id, tal_bytelen(enc->path_id), expected_self_id, + sizeof(*expected_self_id))); } int main(int argc, char *argv[]) @@ -134,6 +140,7 @@ int main(int argc, char *argv[]) struct pubkey alice_id, bob_id, carol_id, dave_id, blinding_pub, override_blinding_pub, alias; struct secret self_id; u8 *enctlv; + struct tlv_encrypted_data_tlv *tlv; common_setup(argv[0]); @@ -165,8 +172,10 @@ int main(int argc, char *argv[]) "\t},\n", type_to_string(tmpctx, struct pubkey, &bob_id)); - enctlv = create_enctlv(tmpctx, &blinding, &alice_id, &bob_id, - 0, NULL, &blinding, &alias); + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->next_node_id = &bob_id; + enctlv = encrypt_tlv_encrypted_data(tmpctx, &blinding, &alice_id, tlv, + &blinding, &alias); printf("\t\"encrypted_recipient_data_hex\": \"%s\"\n" "},\n", tal_hex(tmpctx, enctlv)); @@ -195,8 +204,11 @@ int main(int argc, char *argv[]) type_to_string(tmpctx, struct pubkey, &carol_id), type_to_string(tmpctx, struct privkey, &override_blinding)); - enctlv = create_enctlv(tmpctx, &blinding, &bob_id, &carol_id, - 0, &override_blinding_pub, &blinding, &alias); + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->next_node_id = &carol_id; + tlv->next_blinding_override = &override_blinding_pub; + enctlv = encrypt_tlv_encrypted_data(tmpctx, &blinding, &bob_id, tlv, + &blinding, &alias); printf("\t\"encrypted_recipient_data_hex\": \"%s\"\n" "},\n", tal_hex(tmpctx, enctlv)); @@ -224,8 +236,11 @@ int main(int argc, char *argv[]) type_to_string(tmpctx, struct pubkey, &dave_id), tal_hex(tmpctx, tal_arrz(tmpctx, u8, 35))); - enctlv = create_enctlv(tmpctx, &blinding, &carol_id, &dave_id, - 35, NULL, &blinding, &alias); + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->padding = tal_arrz(tlv, u8, 35); + tlv->next_node_id = &dave_id; + enctlv = encrypt_tlv_encrypted_data(tmpctx, &blinding, &carol_id, tlv, + &blinding, &alias); printf("\t\"encrypted_recipient_data_hex\": \"%s\"\n" "},\n", tal_hex(tmpctx, enctlv)); @@ -249,8 +264,11 @@ int main(int argc, char *argv[]) "\t},\n", type_to_string(tmpctx, struct secret, &self_id)); - enctlv = create_final_enctlv(tmpctx, &blinding, &dave_id, - 0, &self_id, &alias); + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->path_id = tal_dup_arr(tlv, u8, + self_id.data, ARRAY_SIZE(self_id.data), 0); + enctlv = encrypt_tlv_encrypted_data(tmpctx, &blinding, &dave_id, tlv, + NULL, &alias); printf("\t\"encrypted_recipient_data_hex\": \"%s\"\n", tal_hex(tmpctx, enctlv)); diff --git a/common/test/run-blindedpath_onion.c b/common/test/run-blindedpath_onion.c index ef9711dca824..5aa521b25945 100644 --- a/common/test/run-blindedpath_onion.c +++ b/common/test/run-blindedpath_onion.c @@ -4,7 +4,8 @@ #include "../blindedpath.c" #include "../blinding.c" #include "../hmac.c" -#include "../onion.c" +#include "../onion_decode.c" +#include "../onion_encode.c" #include "../sphinx.c" #include "../type_to_string.c" #include @@ -24,6 +25,9 @@ struct amount_msat amount_msat(u64 millisatoshis UNNEEDED) /* Generated stub for amount_msat_eq */ bool amount_msat_eq(struct amount_msat a UNNEEDED, struct amount_msat b UNNEEDED) { fprintf(stderr, "amount_msat_eq called!\n"); abort(); } +/* Generated stub for amount_msat_less */ +bool amount_msat_less(struct amount_msat a UNNEEDED, struct amount_msat b UNNEEDED) +{ fprintf(stderr, "amount_msat_less called!\n"); abort(); } /* Generated stub for amount_sat */ struct amount_sat amount_sat(u64 satoshis UNNEEDED) { fprintf(stderr, "amount_sat called!\n"); abort(); } @@ -108,12 +112,13 @@ static u8 *next_onion(const tal_t *ctx, u8 *omsg, { struct onionpacket *op; struct pubkey blinding, ephemeral; - struct pubkey next_node, next_blinding; - struct tlv_onionmsg_payload *om; + struct pubkey next_blinding; + struct tlv_onionmsg_tlv *om; struct secret ss, onion_ss; const u8 *cursor; size_t max, maxlen; struct route_step *rs; + struct tlv_encrypted_data_tlv *enc; assert(fromwire_onion_message(tmpctx, omsg, &blinding, &omsg)); assert(pubkey_eq(&blinding, expected_blinding)); @@ -132,13 +137,14 @@ static u8 *next_onion(const tal_t *ctx, u8 *omsg, cursor = rs->raw_payload; max = tal_bytelen(rs->raw_payload); maxlen = fromwire_bigsize(&cursor, &max); - om = fromwire_tlv_onionmsg_payload(tmpctx, &cursor, &maxlen); + om = fromwire_tlv_onionmsg_tlv(tmpctx, &cursor, &maxlen); if (rs->nextcase == ONION_END) return NULL; - assert(decrypt_enctlv(&blinding, &ss, om->encrypted_data_tlv, &next_node, - &next_blinding)); + enc = decrypt_encrypted_data(tmpctx, &blinding, &ss, om->encrypted_recipient_data); + assert(enc); + blindedpath_next_blinding(enc, &blinding, &ss, &next_blinding); return towire_onion_message(ctx, &next_blinding, serialize_onionpacket(tmpctx, rs->next)); } @@ -148,8 +154,9 @@ int main(int argc, char *argv[]) struct privkey nodekey[4], blinding[4], override_blinding; struct pubkey id[4], blinding_pub[4], override_blinding_pub, alias[4]; struct secret self_id; + struct tlv_encrypted_data_tlv *tlv[4]; u8 *enctlv[4]; - u8 *onionmsg_payload[4]; + u8 *onionmsg_tlv[4]; u8 *omsg; struct sphinx_path *sphinx_path; struct secret *path_secrets; @@ -167,44 +174,65 @@ int main(int argc, char *argv[]) memset(&blinding[ALICE], 5, sizeof(blinding[ALICE])); pubkey_from_privkey(&blinding[ALICE], &blinding_pub[ALICE]); - enctlv[ALICE] = create_enctlv(tmpctx, &blinding[ALICE], - &id[ALICE], &id[BOB], - 0, NULL, &blinding[BOB], &alias[ALICE]); + tlv[ALICE] = tlv_encrypted_data_tlv_new(tmpctx); + tlv[ALICE]->next_node_id = &id[BOB]; + enctlv[ALICE] = encrypt_tlv_encrypted_data(tmpctx, + &blinding[ALICE], + &id[ALICE], tlv[ALICE], + &blinding[BOB], + &alias[ALICE]); pubkey_from_privkey(&blinding[BOB], &blinding_pub[BOB]); /* We override blinding for Carol. */ memset(&override_blinding, 7, sizeof(override_blinding)); pubkey_from_privkey(&override_blinding, &override_blinding_pub); - enctlv[BOB] = create_enctlv(tmpctx, &blinding[BOB], - &id[BOB], &id[CAROL], - 0, &override_blinding_pub, - &blinding[CAROL], &alias[BOB]); + + tlv[BOB] = tlv_encrypted_data_tlv_new(tmpctx); + tlv[BOB]->next_node_id = &id[CAROL]; + tlv[BOB]->next_blinding_override = &override_blinding_pub; + enctlv[BOB] = encrypt_tlv_encrypted_data(tmpctx, + &blinding[BOB], + &id[BOB], tlv[BOB], + &blinding[CAROL], + &alias[BOB]); /* That replaced the blinding */ blinding[CAROL] = override_blinding; blinding_pub[CAROL] = override_blinding_pub; - enctlv[CAROL] = create_enctlv(tmpctx, &blinding[CAROL], - &id[CAROL], &id[DAVE], - 35, NULL, &blinding[DAVE], &alias[CAROL]); + tlv[CAROL] = tlv_encrypted_data_tlv_new(tmpctx); + tlv[CAROL]->next_node_id = &id[DAVE]; + tlv[CAROL]->padding = tal_arrz(tlv[CAROL], u8, 35); + enctlv[CAROL] = encrypt_tlv_encrypted_data(tmpctx, + &blinding[CAROL], + &id[CAROL], tlv[CAROL], + &blinding[DAVE], + &alias[CAROL]); for (size_t i = 0; i < sizeof(self_id); i++) self_id.data[i] = i+1; - enctlv[DAVE] = create_final_enctlv(tmpctx, &blinding[DAVE], &id[DAVE], - 0, &self_id, &alias[DAVE]); + tlv[DAVE] = tlv_encrypted_data_tlv_new(tmpctx); + tlv[DAVE]->path_id = tal_dup_arr(tlv[DAVE], u8, + self_id.data, ARRAY_SIZE(self_id.data), + 0); + enctlv[DAVE] = encrypt_tlv_encrypted_data(tmpctx, + &blinding[DAVE], + &id[DAVE], tlv[DAVE], + NULL, + &alias[DAVE]); pubkey_from_privkey(&blinding[DAVE], &blinding_pub[DAVE]); /* Create an onion which encodes this. */ sphinx_path = sphinx_path_new(tmpctx, NULL); for (size_t i = 0; i < 4; i++) { - struct tlv_onionmsg_payload *payload - = tlv_onionmsg_payload_new(tmpctx); - payload->encrypted_data_tlv = enctlv[i]; - onionmsg_payload[i] = tal_arr(tmpctx, u8, 0); - towire_tlv_onionmsg_payload(&onionmsg_payload[i], payload); - sphinx_add_hop(sphinx_path, &alias[i], onionmsg_payload[i]); + struct tlv_onionmsg_tlv *payload + = tlv_onionmsg_tlv_new(tmpctx); + payload->encrypted_recipient_data = enctlv[i]; + onionmsg_tlv[i] = tal_arr(tmpctx, u8, 0); + towire_tlv_onionmsg_tlv(&onionmsg_tlv[i], payload); + sphinx_add_hop(sphinx_path, &alias[i], onionmsg_tlv[i]); } op = create_onionpacket(tmpctx, sphinx_path, ROUTING_INFO_SIZE, &path_secrets); @@ -235,8 +263,8 @@ int main(int argc, char *argv[]) type_to_string(tmpctx, struct pubkey, &blinding_pub[i])); json_strfield("blinded_alias", type_to_string(tmpctx, struct pubkey, &alias[i])); - json_strfield("onionmsg_payload", - tal_hex(tmpctx, onionmsg_payload[i])); + json_strfield("onionmsg_tlv", + tal_hex(tmpctx, onionmsg_tlv[i])); printf("\"enctlv\": \"%s\"}\n", tal_hex(tmpctx, enctlv[i])); printf("}"); diff --git a/common/test/run-bolt12_decode.c b/common/test/run-bolt12_decode.c index 1138561f4177..9043b876e453 100644 --- a/common/test/run-bolt12_decode.c +++ b/common/test/run-bolt12_decode.c @@ -49,12 +49,18 @@ int features_unsupported(const struct feature_set *our_features UNNEEDED, /* Generated stub for fromwire */ const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) { fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_bigsize */ +bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } /* Generated stub for fromwire_bool */ bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) { fprintf(stderr, "fromwire_bool called!\n"); abort(); } /* Generated stub for fromwire_fail */ void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) { fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_pad */ +void fromwire_pad(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_pad called!\n"); abort(); } /* Generated stub for fromwire_secp256k1_ecdsa_signature */ void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, secp256k1_ecdsa_signature *signature UNNEEDED) diff --git a/common/test/run-bolt12_merkle-json.c b/common/test/run-bolt12_merkle-json.c index ae597cb7cd18..d1eaf16d9011 100644 --- a/common/test/run-bolt12_merkle-json.c +++ b/common/test/run-bolt12_merkle-json.c @@ -19,6 +19,9 @@ #include /* AUTOGENERATED MOCKS START */ +/* Generated stub for fromwire_blinded_path */ +struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) +{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } /* Generated stub for fromwire_channel_id */ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct channel_id *channel_id UNNEEDED) @@ -26,9 +29,6 @@ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, /* Generated stub for fromwire_node_id */ void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED) { fprintf(stderr, "fromwire_node_id called!\n"); abort(); } -/* Generated stub for fromwire_onionmsg_path */ -struct onionmsg_path *fromwire_onionmsg_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_onionmsg_path called!\n"); abort(); } /* Generated stub for mvt_tag_str */ const char *mvt_tag_str(enum mvt_tag tag UNNEEDED) { fprintf(stderr, "mvt_tag_str called!\n"); abort(); } @@ -38,6 +38,9 @@ bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct /* Generated stub for towire */ void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) { fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_blinded_path */ +void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) +{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } /* Generated stub for towire_bool */ void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) { fprintf(stderr, "towire_bool called!\n"); abort(); } @@ -47,9 +50,6 @@ void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id U /* Generated stub for towire_node_id */ void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) { fprintf(stderr, "towire_node_id called!\n"); abort(); } -/* Generated stub for towire_onionmsg_path */ -void towire_onionmsg_path(u8 **p UNNEEDED, const struct onionmsg_path *onionmsg_path UNNEEDED) -{ fprintf(stderr, "towire_onionmsg_path called!\n"); abort(); } /* Generated stub for towire_secp256k1_ecdsa_signature */ void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, const secp256k1_ecdsa_signature *signature UNNEEDED) @@ -57,9 +57,6 @@ void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, /* Generated stub for towire_sha256 */ void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) { fprintf(stderr, "towire_sha256 called!\n"); abort(); } -/* Generated stub for towire_tu16 */ -void towire_tu16(u8 **pptr UNNEEDED, u16 v UNNEEDED) -{ fprintf(stderr, "towire_tu16 called!\n"); abort(); } /* Generated stub for towire_tu32 */ void towire_tu32(u8 **pptr UNNEEDED, u32 v UNNEEDED) { fprintf(stderr, "towire_tu32 called!\n"); abort(); } diff --git a/common/test/run-bolt12_merkle.c b/common/test/run-bolt12_merkle.c index 990692f4dbd0..0ab4904eb302 100644 --- a/common/test/run-bolt12_merkle.c +++ b/common/test/run-bolt12_merkle.c @@ -9,6 +9,7 @@ #include #include #include +#include /* Definition of n1 from the spec */ #include @@ -19,19 +20,19 @@ int features_unsupported(const struct feature_set *our_features UNNEEDED, const u8 *their_features UNNEEDED, enum feature_place p UNNEEDED) { fprintf(stderr, "features_unsupported called!\n"); abort(); } +/* Generated stub for fromwire_blinded_path */ +struct blinded_path *fromwire_blinded_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) +{ fprintf(stderr, "fromwire_blinded_path called!\n"); abort(); } /* Generated stub for fromwire_channel_id */ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct channel_id *channel_id UNNEEDED) { fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } -/* Generated stub for fromwire_onionmsg_path */ -struct onionmsg_path *fromwire_onionmsg_path(const tal_t *ctx UNNEEDED, const u8 **cursor UNNEEDED, size_t *plen UNNEEDED) -{ fprintf(stderr, "fromwire_onionmsg_path called!\n"); abort(); } +/* Generated stub for towire_blinded_path */ +void towire_blinded_path(u8 **p UNNEEDED, const struct blinded_path *blinded_path UNNEEDED) +{ fprintf(stderr, "towire_blinded_path called!\n"); abort(); } /* Generated stub for towire_channel_id */ void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) { fprintf(stderr, "towire_channel_id called!\n"); abort(); } -/* Generated stub for towire_onionmsg_path */ -void towire_onionmsg_path(u8 **p UNNEEDED, const struct onionmsg_path *onionmsg_path UNNEEDED) -{ fprintf(stderr, "towire_onionmsg_path called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ /* Contat several tal objects */ @@ -53,6 +54,17 @@ static LAST_ARG_NULL void *concat_(const void *p, ...) return ret; } +/* Just return type field from tlv */ +static const u8 *tlv_type(const void *tlv) +{ + size_t len = tal_bytelen(tlv); + const u8 *cursor = tlv; + + fromwire_bigsize(&cursor, &len); + assert(cursor); + return tal_dup_arr(tmpctx, u8, tlv, tal_bytelen(tlv) - len, 0); +} + /* Hashes a tal object */ static struct sha256 *SHA256(const void *obj) { @@ -121,23 +133,28 @@ static void merkle_n1(const struct tlv_n1 *n1, struct sha256 *test_m) merkle_tlv(tmp->fields, test_m); } -/* As a bonus, you get the merkle-test.json by running: +/* As a bonus, you get the bolt12/signature-test.json by running: * common/test/run-bolt12_merkle | grep '^JSON:' | cut -d: -f2- | jq */ #define json_out(fmt, ...) printf("JSON: " fmt "\n" , ## __VA_ARGS__) int main(int argc, char *argv[]) { struct sha256 *m, test_m, *leaf[6]; - const char *LnBranch, *LnAll, *LnLeaf; - u8 *tlv1, *tlv2, *tlv3, *all; + const char *LnBranch, *LnNonce, *LnLeaf; + u8 *tlv1, *tlv2, *tlv3; struct tlv_n1 *n1; + u8 node_id[PUBKEY_CMPR_LEN]; + struct secret alice_secret, bob_secret; + struct pubkey alice, bob; + struct sha256 sha; + secp256k1_keypair kp; char *fail; common_setup(argv[0]); /* Note: no nul term */ LnBranch = tal_dup_arr(tmpctx, char, "LnBranch", strlen("LnBranch"), 0); LnLeaf = tal_dup_arr(tmpctx, char, "LnLeaf", strlen("LnLeaf"), 0); - LnAll = tal_dup_arr(tmpctx, char, "LnAll", strlen("LnAll"), 0); + LnNonce = tal_dup_arr(tmpctx, char, "LnNonce", strlen("LnNonce"), 0); /* Create the tlvs, as per example `n1` in spec */ { @@ -168,17 +185,16 @@ int main(int argc, char *argv[]) json_out("{\"comment\": \"Simple n1 test, tlv1 = 1000\","); json_out("\"tlv\": \"n1\","); /* Simplest case, a single (msat) element. */ - all = tlv1; - json_out("\"all-tlvs\": \"%s\",", tal_hex(tmpctx, all)); + json_out("\"first-tlv\": \"%s\",", tal_hex(tmpctx, tlv1)); json_out("\"leaves\": ["); leaf[0] = H(LnBranch, ordered(H(LnLeaf, tlv1), - H(concat(LnAll, all), tlv1))); - json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnAll`|all-tlvs,tlv1)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }", + H(concat(LnNonce, tlv1), tlv_type(tlv1)))); + json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnNonce`|first-tlv,tlv1-type)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }", tal_hex(tmpctx, tlv1), type_to_string(tmpctx, struct sha256, H(LnLeaf, tlv1)), - type_to_string(tmpctx, struct sha256, H(concat(LnAll, all), tlv1)), + type_to_string(tmpctx, struct sha256, H(concat(LnNonce, tlv1), tlv_type(tlv1))), type_to_string(tmpctx, struct sha256, leaf[0])); json_out("],"); @@ -189,10 +205,6 @@ int main(int argc, char *argv[]) type_to_string(tmpctx, struct sha256, m)); json_out("},"); - printf("n1 = %s, merkle = %s\n", - tal_hex(tmpctx, all), - type_to_string(tmpctx, struct sha256, m)); - /* Create, linearize (populates ->fields) */ n1 = tlv_n1_new(tmpctx); n1->tlv1 = tal(n1, u64); @@ -203,25 +215,24 @@ int main(int argc, char *argv[]) /* Two elements. */ json_out("{\"comment\": \"n1 test, tlv1 = 1000, tlv2 = 1x2x3\","); json_out("\"tlv\": \"n1\","); - all = concat(tlv1, tlv2); + json_out("\"first-tlv\": \"%s\",", tal_hex(tmpctx, tlv1)); - json_out("\"all-tlvs\": \"%s\",", tal_hex(tmpctx, all)); json_out("\"leaves\": ["); leaf[0] = H(LnBranch, ordered(H(LnLeaf, tlv1), - H(concat(LnAll, all), tlv1))); + H(concat(LnNonce, tlv1), tlv_type(tlv1)))); leaf[1] = H(LnBranch, ordered(H(LnLeaf, tlv2), - H(concat(LnAll, all), tlv2))); + H(concat(LnNonce, tlv1), tlv_type(tlv2)))); - json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnAll`|all-tlvs,tlv1)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" },", + json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnNonce`|first-tlv,tlv1-type)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" },", tal_hex(tmpctx, tlv1), type_to_string(tmpctx, struct sha256, H(LnLeaf, tlv1)), - type_to_string(tmpctx, struct sha256, H(concat(LnAll, all), tlv1)), + type_to_string(tmpctx, struct sha256, H(concat(LnNonce, tlv1), tlv_type(tlv1))), type_to_string(tmpctx, struct sha256, leaf[0])); - json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnAll`|all-tlvs,tlv2)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }", + json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnNonce`|first-tlv,tlv2-type)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }", tal_hex(tmpctx, tlv2), type_to_string(tmpctx, struct sha256, H(LnLeaf, tlv2)), - type_to_string(tmpctx, struct sha256, H(concat(LnAll, all), tlv2)), + type_to_string(tmpctx, struct sha256, H(concat(LnNonce, tlv1), tlv_type(tlv2))), type_to_string(tmpctx, struct sha256, leaf[1])); json_out("],"); @@ -237,10 +248,6 @@ int main(int argc, char *argv[]) type_to_string(tmpctx, struct sha256, m)); json_out("},"); - printf("n1 = %s, merkle = %s\n", - tal_hex(tmpctx, all), - type_to_string(tmpctx, struct sha256, m)); - n1->tlv2 = tal(n1, struct short_channel_id); if (!mk_short_channel_id(n1->tlv2, 1, 2, 3)) abort(); @@ -250,30 +257,29 @@ int main(int argc, char *argv[]) /* Three elements. */ json_out("{\"comment\": \"n1 test, tlv1 = 1000, tlv2 = 1x2x3, tlv3 = 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518, 1, 2\","); json_out("\"tlv\": \"n1\","); - all = concat(tlv1, tlv2, tlv3); - json_out("\"all-tlvs\": \"%s\",", tal_hex(tmpctx, all)); + json_out("\"first-tlv\": \"%s\",", tal_hex(tmpctx, tlv1)); json_out("\"leaves\": ["); leaf[0] = H(LnBranch, ordered(H(LnLeaf, tlv1), - H(concat(LnAll, all), tlv1))); + H(concat(LnNonce, tlv1), tlv_type(tlv1)))); leaf[1] = H(LnBranch, ordered(H(LnLeaf, tlv2), - H(concat(LnAll, all), tlv2))); + H(concat(LnNonce, tlv1), tlv_type(tlv2)))); leaf[2] = H(LnBranch, ordered(H(LnLeaf, tlv3), - H(concat(LnAll, all), tlv3))); - json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnAll`|all-tlvs,tlv1)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" },", + H(concat(LnNonce, tlv1), tlv_type(tlv3)))); + json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnNonce`|first-tlv,1)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" },", tal_hex(tmpctx, tlv1), type_to_string(tmpctx, struct sha256, H(LnLeaf, tlv1)), - type_to_string(tmpctx, struct sha256, H(concat(LnAll, all), tlv1)), + type_to_string(tmpctx, struct sha256, H(concat(LnNonce, tlv1), tlv_type(tlv1))), type_to_string(tmpctx, struct sha256, leaf[0])); - json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnAll`|all-tlvs,tlv2)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" },", + json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnNonce`|first-tlv,2)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" },", tal_hex(tmpctx, tlv2), type_to_string(tmpctx, struct sha256, H(LnLeaf, tlv2)), - type_to_string(tmpctx, struct sha256, H(concat(LnAll, all), tlv2)), + type_to_string(tmpctx, struct sha256, H(concat(LnNonce, tlv1), tlv_type(tlv2))), type_to_string(tmpctx, struct sha256, leaf[1])); - json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnAll`|all-tlvs,tlv3)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }", + json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnNonce`|first-tlv,3)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }", tal_hex(tmpctx, tlv3), type_to_string(tmpctx, struct sha256, H(LnLeaf, tlv3)), - type_to_string(tmpctx, struct sha256, H(concat(LnAll, all), tlv3)), + type_to_string(tmpctx, struct sha256, H(concat(LnNonce, tlv1), tlv_type(tlv3))), type_to_string(tmpctx, struct sha256, leaf[2])); json_out("],"); @@ -297,10 +303,6 @@ int main(int argc, char *argv[]) type_to_string(tmpctx, struct sha256, m)); json_out("},"); - printf("n1 = %s, merkle = %s\n", - tal_hex(tmpctx, all), - type_to_string(tmpctx, struct sha256, m)); - n1->tlv3 = tal(n1, struct tlv_n1_tlv3); pubkey_from_hexstr("0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518", strlen("0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518"), &n1->tlv3->node_id); n1->tlv3->amount_msat_1 = AMOUNT_MSAT(1); @@ -308,56 +310,86 @@ int main(int argc, char *argv[]) merkle_n1(n1, &test_m); assert(sha256_eq(&test_m, m)); - /* Now try with an actual offer, with 6 fields. */ - struct tlv_offer *offer = offer_decode(tmpctx, - "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczs", - strlen("lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczs"), - NULL, NULL, &fail); - - assert(tal_count(offer->fields) == 6); + /* Now try with an invoice request. */ + struct tlv_invoice_request *invreq = tlv_invoice_request_new(tmpctx); + + memset(&alice_secret, 'A', sizeof(alice_secret)); + pubkey_from_secret(&alice_secret, &alice); + memset(&bob_secret, 'B', sizeof(bob_secret)); + pubkey_from_secret(&bob_secret, &bob); + + invreq->offer_node_id = tal_dup(invreq, struct pubkey, &alice); + invreq->offer_description = tal_dup_arr(invreq, char, "A Mathematical Treatise", strlen("A Mathematical Treatise"), 0); + invreq->offer_amount = tal(invreq, u64); + *invreq->offer_amount = 100; + invreq->offer_currency = tal_dup_arr(invreq, char, "USD", strlen("USD"), 0); + invreq->invreq_payer_id = tal_dup(invreq, struct pubkey, &bob); + invreq->invreq_metadata = tal_arrz(invreq, u8, 8); + + /* Populate ->fields array, for merkle routine */ + invreq->fields = tlv_make_fields(invreq, tlv_invoice_request); + merkle_tlv(invreq->fields, &test_m); + + /* BOLT-offers #12: + * - MUST set `signature`.`sig` as detailed in [Signature Calculation](#signature-calculation) using the `invreq_payer_id`. + */ + invreq->signature = tal(invreq, struct bip340sig); + sighash_from_merkle("invoice_request", "signature", &test_m, &sha); + assert(secp256k1_keypair_create(secp256k1_ctx, &kp, bob_secret.data) == 1); + assert(secp256k1_schnorrsig_sign32(secp256k1_ctx, invreq->signature->u8, + sha.u.u8, + &kp, + NULL) == 1); + + char *invreqtext = invrequest_encode(tmpctx, invreq); + invreq = invrequest_decode(tmpctx, invreqtext, strlen(invreqtext), NULL, NULL, &fail); + + json_out("{\"comment\": \"invoice_request test: offer_node_id = Alice (privkey 0x414141...), offer_description = 'A Mathematical Treatise', offer_amount = 100, offer_currency = 'USD', invreq_payer_id = Bob (privkey 0x424242...), invreq_metadata = 0x0000000000000000\","); + json_out("\"bolt12\": \"%s\",", invreqtext); + json_out("\"tlv\": \"invoice_request\","); + + assert(tal_count(invreq->fields) == 7); u8 *fieldwires[6]; - /* currency: USD */ - fieldwires[0] = tlv(6, "USD", strlen("USD")); - /* amount: 1000 */ - fieldwires[1] = tlv(8, "\x03\xe8", 2); - /* description: 10USD every day */ - fieldwires[2] = tlv(10, "10USD every day", strlen("10USD every day")); - /* issuer: rusty.ozlabs.org */ - fieldwires[3] = tlv(20, "rusty.ozlabs.org", strlen("rusty.ozlabs.org")); - /* recurrence: time_unit = 1, period = 1 */ - fieldwires[4] = tlv(26, "\x01\x01", 2); - /* node_id: 4b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605 */ - fieldwires[5] = tlv(30, "\x4b\x9a\x1f\xa8\xe0\x06\xf1\xe3\x93\x7f\x65\xf6\x6c\x40\x8e\x6d\xa8\xe1\xca\x72\x8e\xa4\x32\x22\xa7\x38\x1d\xf1\xcc\x44\x96\x05", 32); - - json_out("{\"comment\": \"offer test, currency = USD, amount = 1000, description = 10USD every day, issuer = rusty.ozlabs.org, recurrence = time_unit = 1, period = 1, node_id = 4b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605\","); - json_out("\"tlv\": \"offer\","); - - all = concat(fieldwires[0], fieldwires[1], fieldwires[2], - fieldwires[3], fieldwires[4], fieldwires[5]); - json_out("\"all-tlvs\": \"%s\",", tal_hex(tmpctx, all)); + /* invreq_metadata: 8 bytes of 0 */ + fieldwires[0] = tlv(0, "\0\0\0\0\0\0\0\0", 8); + /* offer_currency: USD */ + fieldwires[1] = tlv(6, "USD", strlen("USD")); + /* offer_amount: 100 */ + fieldwires[2] = tlv(8, "\x64", 1); + /* offer_description: A Mathematical Treatise */ + fieldwires[3] = tlv(10, "A Mathematical Treatise", strlen("A Mathematical Treatise")); + /* offer_node_id: Alice */ + pubkey_to_der(node_id, &alice); + fieldwires[4] = tlv(22, node_id, PUBKEY_CMPR_LEN); + /* invreq_payer_id: Bob */ + pubkey_to_der(node_id, &bob); + fieldwires[5] = tlv(88, node_id, PUBKEY_CMPR_LEN); + + json_out("\"first-tlv\": \"%s\",", tal_hex(tmpctx, fieldwires[0])); json_out("\"leaves\": ["); for (size_t i = 0; i < ARRAY_SIZE(fieldwires); i++) { leaf[i] = H(LnBranch, ordered(H(LnLeaf, fieldwires[i]), - H(concat(LnAll, all), fieldwires[i]))); - json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnAll`|all-tlvs,tlv)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }%s", + H(concat(LnNonce, fieldwires[0]), tlv_type(fieldwires[i])))); + json_out("{ \"H(`LnLeaf`,%s)\": \"%s\", \"H(`LnNonce`|first-tlv,%u)\": \"%s\", \"H(`LnBranch`,leaf+nonce)\": \"%s\" }%s", tal_hex(tmpctx, fieldwires[i]), type_to_string(tmpctx, struct sha256, H(LnLeaf, fieldwires[i])), + tlv_type(fieldwires[i])[0], /* Works becuase they're all 1-byte types! */ type_to_string(tmpctx, struct sha256, - H(concat(LnAll, all), fieldwires[0])), + H(concat(LnNonce, fieldwires[0]), tlv_type(fieldwires[i]))), type_to_string(tmpctx, struct sha256, leaf[i]), i == ARRAY_SIZE(fieldwires) - 1 ? "" : ","); } json_out("],"); json_out("\"branches\": ["); - json_out("{ \"desc\": \"1: currency+nonce and amount+nonce\", \"H(`LnBranch`,%s)\": \"%s\" },", + json_out("{ \"desc\": \"1: metadata+nonce and currency+nonce\", \"H(`LnBranch`,%s)\": \"%s\" },", tal_hex(tmpctx, ordered(leaf[0], leaf[1])), tal_hex(tmpctx, H(LnBranch, ordered(leaf[0], leaf[1])))); - json_out("{ \"desc\": \"2: description+nonce and issuer+nonce\", \"H(`LnBranch`,%s)\": \"%s\"},", + json_out("{ \"desc\": \"2: amount+nonce and descripton+nonce\", \"H(`LnBranch`,%s)\": \"%s\"},", tal_hex(tmpctx, ordered(leaf[2], leaf[3])), tal_hex(tmpctx, H(LnBranch, ordered(leaf[2], leaf[3])))); struct sha256 *b12 = H(LnBranch, @@ -369,7 +401,7 @@ int main(int argc, char *argv[]) tal_hex(tmpctx, ordered(H(LnBranch, ordered(leaf[0], leaf[1])), H(LnBranch, ordered(leaf[2], leaf[3])))), tal_hex(tmpctx, b12)); - json_out("{ \"desc\": \"4: recurrence+nonce and node_id+nonce\", \"H(`LnBranch`,%s)\": \"%s\" },", + json_out("{ \"desc\": \"4: node_id+nonce and payer_id+nonce\", \"H(`LnBranch`,%s)\": \"%s\" },", tal_hex(tmpctx, ordered(leaf[4], leaf[5])), tal_hex(tmpctx, H(LnBranch, ordered(leaf[4], leaf[5])))); json_out("{ \"desc\": \"5: 3 and 4\", \"H(`LnBranch`,%s)\": \"%s\" }", @@ -387,15 +419,13 @@ int main(int argc, char *argv[]) H(LnBranch, ordered(leaf[4], leaf[5])))); json_out("],"); - json_out("\"merkle\": \"%s\"", + json_out("\"merkle\": \"%s\",", type_to_string(tmpctx, struct sha256, m)); + json_out("\"signature_tag\": \"lightninginvoice_requestsignature\","); + json_out("\"H(signature_tag,merkle)\": \"%s\",", type_to_string(tmpctx, struct sha256, &sha)); + json_out("\"signature\": \"%s\"", type_to_string(tmpctx, struct bip340sig, invreq->signature)); json_out("}]"); - printf("offer = %s, merkle = %s\n", - tal_hex(tmpctx, all), - type_to_string(tmpctx, struct sha256, m)); - - merkle_tlv(offer->fields, &test_m); assert(sha256_eq(&test_m, m)); common_shutdown(); diff --git a/common/test/run-bolt12_period.c b/common/test/run-bolt12_period.c index 3d6ab9a63224..8b44328bda10 100644 --- a/common/test/run-bolt12_period.c +++ b/common/test/run-bolt12_period.c @@ -52,12 +52,18 @@ bool from_bech32_charset(const tal_t *ctx UNNEEDED, /* Generated stub for fromwire */ const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) { fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_bigsize */ +bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } /* Generated stub for fromwire_bool */ bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) { fprintf(stderr, "fromwire_bool called!\n"); abort(); } /* Generated stub for fromwire_fail */ void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) { fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_pad */ +void fromwire_pad(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_pad called!\n"); abort(); } /* Generated stub for fromwire_secp256k1_ecdsa_signature */ void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, secp256k1_ecdsa_signature *signature UNNEEDED) @@ -186,7 +192,7 @@ int main(int argc, char *argv[]) /* We deal in UTC; mktime() uses local time */ setenv("TZ", "", 1); json_for_each_arr(i, t, toks) { - struct tlv_offer_recurrence recurrence; + struct recurrence recurrence; int unit; const jsmntok_t *exp; u64 base, secs, n; diff --git a/common/test/run-gossmap_guess_node_id.c b/common/test/run-gossmap_guess_node_id.c deleted file mode 100644 index bf0f55b796d5..000000000000 --- a/common/test/run-gossmap_guess_node_id.c +++ /dev/null @@ -1,125 +0,0 @@ -/* Test conversion assumptions used by gossmap_guess_node_id */ -#include "config.h" -#include "../node_id.c" -#include -#include -#include -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for amount_asset_is_main */ -bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) -{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } -/* Generated stub for amount_asset_to_sat */ -struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) -{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } -/* Generated stub for amount_sat */ -struct amount_sat amount_sat(u64 satoshis UNNEEDED) -{ fprintf(stderr, "amount_sat called!\n"); abort(); } -/* Generated stub for amount_sat_add */ - bool amount_sat_add(struct amount_sat *val UNNEEDED, - struct amount_sat a UNNEEDED, - struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } -/* Generated stub for amount_sat_eq */ -bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } -/* Generated stub for amount_sat_greater_eq */ -bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } -/* Generated stub for amount_sat_sub */ - bool amount_sat_sub(struct amount_sat *val UNNEEDED, - struct amount_sat a UNNEEDED, - struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } -/* Generated stub for amount_sat_to_asset */ -struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) -{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } -/* Generated stub for amount_tx_fee */ -struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) -{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } -/* Generated stub for fromwire */ -const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) -{ fprintf(stderr, "fromwire called!\n"); abort(); } -/* Generated stub for fromwire_bool */ -bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } -/* Generated stub for fromwire_fail */ -void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } -/* Generated stub for fromwire_secp256k1_ecdsa_signature */ -void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, - secp256k1_ecdsa_signature *signature UNNEEDED) -{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } -/* Generated stub for fromwire_sha256 */ -void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) -{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } -/* Generated stub for fromwire_tal_arrn */ -u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, - const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) -{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } -/* Generated stub for fromwire_u32 */ -u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } -/* Generated stub for fromwire_u64 */ -u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } -/* Generated stub for fromwire_u8 */ -u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } -/* Generated stub for fromwire_u8_array */ -void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) -{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } -/* Generated stub for towire */ -void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) -{ fprintf(stderr, "towire called!\n"); abort(); } -/* Generated stub for towire_bool */ -void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) -{ fprintf(stderr, "towire_bool called!\n"); abort(); } -/* Generated stub for towire_secp256k1_ecdsa_signature */ -void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, - const secp256k1_ecdsa_signature *signature UNNEEDED) -{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } -/* Generated stub for towire_sha256 */ -void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) -{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } -/* Generated stub for towire_u32 */ -void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) -{ fprintf(stderr, "towire_u32 called!\n"); abort(); } -/* Generated stub for towire_u64 */ -void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) -{ fprintf(stderr, "towire_u64 called!\n"); abort(); } -/* Generated stub for towire_u8 */ -void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) -{ fprintf(stderr, "towire_u8 called!\n"); abort(); } -/* Generated stub for towire_u8_array */ -void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) -{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -int main(int argc, char *argv[]) -{ - common_setup(argv[0]); - - for (size_t i = 1; i < 255; i++) { - struct privkey priv; - secp256k1_keypair keypair; - secp256k1_pubkey pubkey; - secp256k1_xonly_pubkey xpubkey; - u8 output32[32]; - u8 output33[33]; - size_t len = sizeof(output33); - - memset(&priv, i, sizeof(priv)); - assert(secp256k1_keypair_create(secp256k1_ctx, &keypair, priv.secret.data) == 1); - assert(secp256k1_keypair_pub(secp256k1_ctx, &pubkey, &keypair) == 1); - assert(secp256k1_keypair_xonly_pub(secp256k1_ctx, &xpubkey, NULL, &keypair) == 1); - - assert(secp256k1_xonly_pubkey_serialize(secp256k1_ctx, output32, &xpubkey) == 1); - assert(secp256k1_ec_pubkey_serialize(secp256k1_ctx, output33, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); - assert(memcmp(output32, output33 + 1, sizeof(output32)) == 0); - assert(output33[0] == SECP256K1_TAG_PUBKEY_EVEN - || output33[0] == SECP256K1_TAG_PUBKEY_ODD); - } - common_shutdown(); -} diff --git a/common/test/run-ip_port_parsing.c b/common/test/run-ip_port_parsing.c index f8150c511033..37487f8ebc52 100644 --- a/common/test/run-ip_port_parsing.c +++ b/common/test/run-ip_port_parsing.c @@ -126,11 +126,16 @@ int main(int argc, char *argv[]) assert(is_dnsaddr("123example.com")); assert(is_dnsaddr("example123.com")); assert(is_dnsaddr("is-valid.3hostname123.com")); - assert(!is_dnsaddr("UPPERCASE.invalid.com")); + assert(is_dnsaddr("just-a-hostname-with-dashes")); + assert(is_dnsaddr("lightningd_dest.underscore.allowed.in.hostname.part.com")); + assert(is_dnsaddr("UpperCase.valiD.COM")); + assert(is_dnsaddr("punycode.xn--bcher-kva.valid.com")); + assert(!is_dnsaddr("nonpunycode.bücher.invalid.com")); assert(!is_dnsaddr("-.invalid.com")); assert(!is_dnsaddr("invalid.-example.com")); assert(!is_dnsaddr("invalid.example-.com")); assert(!is_dnsaddr("invalid..example.com")); + assert(!is_dnsaddr("underscore.not.allowed.in.domain_name.com")); /* Grossly invalid. */ assert(!separate_address_and_port(tmpctx, "[", &ip, &port)); diff --git a/common/test/run-json.c b/common/test/run-json.c index d283fbcd5c84..a9dc6daa9f5f 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,21 @@ bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *record UNNEEDED, struct tlv_field **fields UNNEEDED, const u64 *extra_types UNNEEDED, size_t *err_off UNNEEDED, u64 *err_type UNNEEDED) { fprintf(stderr, "fromwire_tlv called!\n"); abort(); } +/* Generated stub for json_filter_down */ +bool json_filter_down(struct json_filter **filter UNNEEDED, const char *member UNNEEDED) +{ fprintf(stderr, "json_filter_down called!\n"); abort(); } +/* Generated stub for json_filter_finished */ +bool json_filter_finished(const struct json_filter *filter UNNEEDED) +{ fprintf(stderr, "json_filter_finished called!\n"); abort(); } +/* Generated stub for json_filter_misused */ +const char *json_filter_misused(const tal_t *ctx UNNEEDED, const struct json_filter *f UNNEEDED) +{ fprintf(stderr, "json_filter_misused called!\n"); abort(); } +/* Generated stub for json_filter_ok */ +bool json_filter_ok(const struct json_filter *filter UNNEEDED, const char *member UNNEEDED) +{ fprintf(stderr, "json_filter_ok called!\n"); abort(); } +/* Generated stub for json_filter_up */ +bool json_filter_up(struct json_filter **filter UNNEEDED) +{ fprintf(stderr, "json_filter_up called!\n"); abort(); } /* Generated stub for towire_tlv */ void towire_tlv(u8 **pptr UNNEEDED, const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED, diff --git a/common/test/run-json_filter.c b/common/test/run-json_filter.c new file mode 100644 index 000000000000..5ac68b4fb9ba --- /dev/null +++ b/common/test/run-json_filter.c @@ -0,0 +1,252 @@ +#include "config.h" +#include "../amount.c" +#include "../json_filter.c" +#include "../json_param.c" +#include "../json_parse_simple.c" +#include "../json_stream.c" +#include +#include +#include +#include + +struct command; + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for command_check_only */ +bool command_check_only(const struct command *cmd UNNEEDED) +{ fprintf(stderr, "command_check_only called!\n"); abort(); } +/* Generated stub for command_fail */ +struct command_result *command_fail(struct command *cmd UNNEEDED, enum jsonrpc_errcode code UNNEEDED, + const char *fmt UNNEEDED, ...) + +{ fprintf(stderr, "command_fail called!\n"); abort(); } +/* Generated stub for command_set_usage */ +void command_set_usage(struct command *cmd UNNEEDED, const char *usage UNNEEDED) +{ fprintf(stderr, "command_set_usage called!\n"); abort(); } +/* Generated stub for command_usage_only */ +bool command_usage_only(const struct command *cmd UNNEEDED) +{ fprintf(stderr, "command_usage_only called!\n"); abort(); } +/* Generated stub for fmt_wireaddr_without_port */ +char *fmt_wireaddr_without_port(const tal_t *ctx UNNEEDED, const struct wireaddr *a UNNEEDED) +{ fprintf(stderr, "fmt_wireaddr_without_port called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for json_scan */ +const char *json_scan(const tal_t *ctx UNNEEDED, + const char *buffer UNNEEDED, + const jsmntok_t *tok UNNEEDED, + const char *guide UNNEEDED, + ...) +{ fprintf(stderr, "json_scan called!\n"); abort(); } +/* Generated stub for json_to_channel_id */ +bool json_to_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct channel_id *cid UNNEEDED) +{ fprintf(stderr, "json_to_channel_id called!\n"); abort(); } +/* Generated stub for json_to_millionths */ +bool json_to_millionths(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + u64 *millionths UNNEEDED) +{ fprintf(stderr, "json_to_millionths called!\n"); abort(); } +/* Generated stub for json_to_msat */ +bool json_to_msat(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct amount_msat *msat UNNEEDED) +{ fprintf(stderr, "json_to_msat called!\n"); abort(); } +/* Generated stub for json_to_node_id */ +bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct node_id *id UNNEEDED) +{ fprintf(stderr, "json_to_node_id called!\n"); abort(); } +/* Generated stub for json_to_number */ +bool json_to_number(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + unsigned int *num UNNEEDED) +{ fprintf(stderr, "json_to_number called!\n"); abort(); } +/* Generated stub for json_to_outpoint */ +bool json_to_outpoint(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct bitcoin_outpoint *op UNNEEDED) +{ fprintf(stderr, "json_to_outpoint called!\n"); abort(); } +/* Generated stub for json_to_pubkey */ +bool json_to_pubkey(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct pubkey *pubkey UNNEEDED) +{ fprintf(stderr, "json_to_pubkey called!\n"); abort(); } +/* Generated stub for json_to_short_channel_id */ +bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct short_channel_id *scid UNNEEDED) +{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); } +/* Generated stub for json_to_txid */ +bool json_to_txid(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct bitcoin_txid *txid UNNEEDED) +{ fprintf(stderr, "json_to_txid called!\n"); abort(); } +/* Generated stub for json_to_u16 */ +bool json_to_u16(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + uint16_t *num UNNEEDED) +{ fprintf(stderr, "json_to_u16 called!\n"); abort(); } +/* Generated stub for json_tok_bin_from_hex */ +u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); } +/* Generated stub for lease_rates_fromhex */ +struct lease_rates *lease_rates_fromhex(const tal_t *ctx UNNEEDED, + const char *hexdata UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "lease_rates_fromhex called!\n"); abort(); } +/* Generated stub for segwit_addr_decode */ +int segwit_addr_decode( + int* ver UNNEEDED, + uint8_t* prog UNNEEDED, + size_t* prog_len UNNEEDED, + const char* hrp UNNEEDED, + const char* addr +) +{ fprintf(stderr, "segwit_addr_decode called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* Generated stub for type_to_string_ */ +const char *type_to_string_(const tal_t *ctx UNNEEDED, const char *typename UNNEEDED, + union printable_types u UNNEEDED) +{ fprintf(stderr, "type_to_string_ called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +bool deprecated_apis; + +static bool dump_filter(const char *key, struct json_filter *filter, u32 *depth) +{ + for (size_t i = 0; i < *depth; i++) + printf(" "); + printf("%s\n", key); + (*depth)++; + if (filter->filter_array) + dump_filter("[]", filter->filter_array, depth); + else + strmap_iterate(&filter->filter_map, dump_filter, depth); + (*depth)--; + return true; +} + +struct command { + struct json_filter *filter; +}; + +struct json_filter **command_filter_ptr(struct command *cmd) +{ + return &cmd->filter; +} + +int main(int argc, char *argv[]) +{ + jsmntok_t *toks; + bool valid, complete; + const char *str; + jsmn_parser parser; + struct command *cmd; + struct json_stream *js; + u32 depth; + size_t len; + + common_setup(argv[0]); + cmd = tal(tmpctx, struct command); + str = "{\"transactions\": [{\"outputs\": [{\"amount_msat\": true, \"type\": true}]}]}"; + toks = toks_alloc(cmd); + jsmn_init(&parser); + valid = json_parse_input(&parser, &toks, str, strlen(str), &complete); + assert(valid); + assert(complete); + + assert(parse_filter(cmd, "_field", str, toks) == NULL); + assert(cmd->filter); + depth = 0; + dump_filter("[root]", cmd->filter, &depth); + + /* Simulate listtransactions example */ + js = new_json_stream(cmd, cmd, NULL); + json_object_start(js, NULL); + json_object_start(js, "result"); + json_stream_attach_filter(js, cmd->filter); + + json_array_start(js, "transactions"); + for (size_t i = 0; i < 2; i++) { + json_object_start(js, NULL); + json_add_num(js, "blockheight", 1); + json_add_num(js, "txindex", 2); + json_array_start(js, "inputs"); + for (size_t j = 0; j < 5; j++) { + json_object_start(js, NULL); + json_add_u32(js, "index", i+j); + json_add_u32(js, "sequence", i+j+1); + json_object_end(js); + } + json_array_end(js); + + json_array_start(js, "outputs"); + for (size_t j = 0; j < 2; j++) { + json_object_start(js, NULL); + + json_add_u32(js, "index", i+j); + json_add_amount_msat_only(js, "amount_msat", amount_msat(12)); + if (j == 0) + json_add_string(js, "type", "sometype"); + json_add_string(js, "scriptPubKey", "00000000"); + json_object_end(js); + } + json_array_end(js); + json_object_end(js); + } + json_array_end(js); + str = json_stream_detach_filter(tmpctx, js); + assert(!str); + json_object_end(js); + json_object_end(js); + + str = json_out_contents(js->jout, &len); + printf("%.*s\n", (int)len, str); + + common_shutdown(); +} diff --git a/common/test/run-json_stream-filter.c b/common/test/run-json_stream-filter.c new file mode 100644 index 000000000000..16f6ea60919c --- /dev/null +++ b/common/test/run-json_stream-filter.c @@ -0,0 +1,287 @@ +#include "config.h" +#include "../json_filter.c" +#include "../json_stream.c" +#include +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_msat */ +struct amount_msat amount_msat(u64 millisatoshis UNNEEDED) +{ fprintf(stderr, "amount_msat called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_sat_to_msat */ + bool amount_sat_to_msat(struct amount_msat *msat UNNEEDED, + struct amount_sat sat UNNEEDED) +{ fprintf(stderr, "amount_sat_to_msat called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for command_fail */ +struct command_result *command_fail(struct command *cmd UNNEEDED, enum jsonrpc_errcode code UNNEEDED, + const char *fmt UNNEEDED, ...) + +{ fprintf(stderr, "command_fail called!\n"); abort(); } +/* Generated stub for command_filter_ptr */ +struct json_filter **command_filter_ptr(struct command *cmd UNNEEDED) +{ fprintf(stderr, "command_filter_ptr called!\n"); abort(); } +/* Generated stub for deprecated_apis */ +bool deprecated_apis; +/* Generated stub for fmt_amount_msat */ +const char *fmt_amount_msat(const tal_t *ctx UNNEEDED, struct amount_msat msat UNNEEDED) +{ fprintf(stderr, "fmt_amount_msat called!\n"); abort(); } +/* Generated stub for fmt_amount_sat */ +const char *fmt_amount_sat(const tal_t *ctx UNNEEDED, struct amount_sat sat UNNEEDED) +{ fprintf(stderr, "fmt_amount_sat called!\n"); abort(); } +/* Generated stub for fmt_wireaddr_without_port */ +char *fmt_wireaddr_without_port(const tal_t *ctx UNNEEDED, const struct wireaddr *a UNNEEDED) +{ fprintf(stderr, "fmt_wireaddr_without_port called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for json_next */ +const jsmntok_t *json_next(const jsmntok_t *tok UNNEEDED) +{ fprintf(stderr, "json_next called!\n"); abort(); } +/* Generated stub for json_to_bool */ +bool json_to_bool(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, bool *b UNNEEDED) +{ fprintf(stderr, "json_to_bool called!\n"); abort(); } +/* Generated stub for json_tok_full */ +const char *json_tok_full(const char *buffer UNNEEDED, const jsmntok_t *t UNNEEDED) +{ fprintf(stderr, "json_tok_full called!\n"); abort(); } +/* Generated stub for json_tok_full_len */ +int json_tok_full_len(const jsmntok_t *t UNNEEDED) +{ fprintf(stderr, "json_tok_full_len called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* Generated stub for type_to_string_ */ +const char *type_to_string_(const tal_t *ctx UNNEEDED, const char *typename UNNEEDED, + union printable_types u UNNEEDED) +{ fprintf(stderr, "type_to_string_ called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +int main(int argc, char *argv[]) +{ + struct json_stream *js; + struct json_filter *filter, *subf; + const char *str; + size_t len; + + common_setup(argv[0]); + + /* First with an empty filter. */ + js = new_json_stream(tmpctx, NULL, NULL); + filter = json_filter_new(js); + /* Filters assume we start inside an object! */ + json_object_start(js, NULL); + json_stream_attach_filter(js, filter); + + json_object_start(js, "result"); + json_object_start(js, "message"); + json_add_string(js, "string", "string"); + json_object_end(js); + json_object_end(js); + + str = json_out_contents(js->jout, &len); + assert(strncmp(str, "{\"result\":{\"message\":{\"string\":\"string\"}}", len) == 0); + + /* Now try a result filter. */ + js = new_json_stream(tmpctx, NULL, NULL); + filter = json_filter_new(js); + json_filter_subobj(filter, "result", strlen("result")); + json_object_start(js, NULL); + json_stream_attach_filter(js, filter); + + json_object_start(js, "result"); + json_object_start(js, "message"); + json_add_string(js, "string", "string"); + json_object_end(js); + json_object_end(js); + + str = json_out_contents(js->jout, &len); + assert(strncmp(str, "{\"result\":{\"message\":{\"string\":\"string\"}}", len) == 0); + + /* Now try a result->message->string filter. */ + js = new_json_stream(tmpctx, NULL, NULL); + filter = json_filter_new(js); + subf = json_filter_subobj(filter, "result", strlen("result")); + subf = json_filter_subobj(subf, "message", strlen("message")); + json_filter_subobj(subf, "string", strlen("string")); + json_object_start(js, NULL); + json_stream_attach_filter(js, filter); + + json_object_start(js, "result"); + json_object_start(js, "message"); + json_add_string(js, "string", "string"); + json_object_end(js); + json_object_end(js); + + str = json_out_contents(js->jout, &len); + assert(strncmp(str, "{\"result\":{\"message\":{\"string\":\"string\"}}", len) == 0); + + /* Now a sub-filter which doesn't match */ + js = new_json_stream(tmpctx, NULL, NULL); + filter = json_filter_new(js); + subf = json_filter_subobj(filter, "result", strlen("result")); + subf = json_filter_subobj(subf, "message", strlen("message")); + json_filter_subobj(subf, "dne", strlen("dne")); + json_object_start(js, NULL); + json_stream_attach_filter(js, filter); + + json_object_start(js, "result"); + json_object_start(js, "message"); + json_add_string(js, "string", "string"); + json_object_end(js); + json_object_end(js); + + str = json_out_contents(js->jout, &len); + assert(strncmp(str, "{\"result\":{\"message\":{}}", len) == 0); + + /* Multple, one of three matchs */ + js = new_json_stream(tmpctx, NULL, NULL); + filter = json_filter_new(js); + subf = json_filter_subobj(filter, "result", strlen("result")); + subf = json_filter_subobj(subf, "message", strlen("message")); + json_filter_subobj(subf, "f1", strlen("f1")); + json_filter_subobj(subf, "f3", strlen("f3")); + json_object_start(js, NULL); + json_stream_attach_filter(js, filter); + + json_object_start(js, "result"); + json_object_start(js, "message"); + json_add_string(js, "f1", "f1string"); + json_object_start(js, "f2"); + json_add_string(js, "f2sub", "f2string"); + json_object_end(js); + json_add_string(js, "f3", "f3string"); + json_object_end(js); + json_object_end(js); + + str = json_out_contents(js->jout, &len); + assert(strncmp(str, "{\"result\":{\"message\":{\"f1\":\"f1string\",\"f3\":\"f3string\"}}", len) == 0); + + /* Now inside arrays! */ + js = new_json_stream(tmpctx, NULL, NULL); + filter = json_filter_new(js); + subf = json_filter_subobj(filter, "result", strlen("result")); + subf = json_filter_subobj(subf, "messages", strlen("messages")); + subf = json_filter_subarr(subf); + json_filter_subobj(subf, "string", strlen("string")); + json_object_start(js, NULL); + json_stream_attach_filter(js, filter); + + json_object_start(js, "result"); + json_array_start(js, "messages"); + json_object_start(js, NULL); + json_add_string(js, "string", "string1"); + json_object_end(js); + json_object_start(js, NULL); + json_add_string(js, "string", "string2"); + json_object_end(js); + json_array_end(js); + json_object_end(js); + + str = json_out_contents(js->jout, &len); + assert(strncmp(str, "{\"result\":{\"messages\":[{\"string\":\"string1\"},{\"string\":\"string2\"}]}", + len) == 0); + + /* Now filter out arrays. */ + js = new_json_stream(tmpctx, NULL, NULL); + filter = json_filter_new(js); + json_filter_subobj(filter, "result", strlen("result")); + json_object_start(js, NULL); + json_stream_attach_filter(js, filter); + + json_add_string(js, "result", "resultstr"); + json_add_string(js, "ignored", "ignoredstr"); + json_array_start(js, "fallbacks"); + json_object_start(js, NULL); + json_add_string(js, "type", "P2PKH"); + json_object_end(js); + json_array_end(js); + + str = json_out_contents(js->jout, &len); + assert(strncmp(str, "{\"result\":\"resultstr\"", len) == 0); + common_shutdown(); +} diff --git a/common/test/run-onion-message-test.c b/common/test/run-onion-message-test.c new file mode 100644 index 000000000000..a2e3bba977d1 --- /dev/null +++ b/common/test/run-onion-message-test.c @@ -0,0 +1,394 @@ +/* Creates test vector bolt04/blinded-onion-message-onion-test.json. Run output through jq! */ +static void maybe_print(const char *fmt, ...); +#define SUPERVERBOSE maybe_print +#include "config.h" +#include "../../wire/fromwire.c" +#include "../../wire/tlvstream.c" +#include "../../wire/towire.c" +#include "../amount.c" +#include "../bigsize.c" +#include "../blindedpath.c" +#include "../blindedpay.c" +#include "../blinding.c" +#include "../features.c" +#include "../hmac.c" +#include "../onion_encode.c" +#include "../onion_message_parse.c" +#include "../sphinx.c" +#include "../type_to_string.c" +#if EXPERIMENTAL_FEATURES + #include "../../wire/onion_exp_wiregen.c" + #include "../../wire/peer_exp_wiregen.c" +#else + #include "../../wire/onion_wiregen.c" + #include "../../wire/peer_wiregen.c" +#endif +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for fromwire_channel_id */ +bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + struct channel_id *channel_id UNNEEDED) +{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } +/* Generated stub for fromwire_node_id */ +void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED) +{ fprintf(stderr, "fromwire_node_id called!\n"); abort(); } +/* Generated stub for new_onionreply */ +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +{ fprintf(stderr, "new_onionreply called!\n"); abort(); } +/* Generated stub for pubkey_from_node_id */ +bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); } +/* Generated stub for status_fmt */ +void status_fmt(enum log_level level UNNEEDED, + const struct node_id *peer UNNEEDED, + const char *fmt UNNEEDED, ...) + +{ fprintf(stderr, "status_fmt called!\n"); abort(); } +/* Generated stub for towire_channel_id */ +void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) +{ fprintf(stderr, "towire_channel_id called!\n"); abort(); } +/* Generated stub for towire_node_id */ +void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "towire_node_id called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static bool comma; + +static void flush_comma(void) +{ + if (comma) + printf(",\n"); + comma = false; +} + +static void maybe_comma(void) +{ + comma = true; +} + +static void json_start(const char *name, const char what) +{ + flush_comma(); + if (name) + printf("\"%s\":", name); + printf("%c\n", what); +} + +static void json_end(const char what) +{ + comma = false; + printf("%c\n", what); +} + +static void json_strfield(const char *name, const char *val) +{ + flush_comma(); + printf("\t\"%s\": \"%s\"", name, val); + maybe_comma(); +} + +static void json_hexfield(const char *name, const u8 *val, size_t len) +{ + json_strfield(name, tal_hexstr(tmpctx, val, len)); +} + +static void json_hex_talfield(const char *name, const u8 *val) +{ + json_strfield(name, tal_hexstr(tmpctx, val, tal_bytelen(val))); +} + +static void json_pubkey(const char *name, const struct pubkey *key) +{ + json_strfield(name, pubkey_to_hexstr(tmpctx, key)); +} + +static bool enable_superverbose; +static void maybe_print(const char *fmt, ...) +{ + if (enable_superverbose) { + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + } +} + +/* Updated each time, as we pretend to be Alice, Bob, Carol */ +static const struct privkey *mykey; + +void ecdh(const struct pubkey *point, struct secret *ss) +{ + if (secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey, + mykey->secret.data, NULL, NULL) != 1) + abort(); +} + +/* This established by trial and error! */ +#define LARGEST_TLV_SIZE 70 + +/* Generic, ugly, function to calc encrypted_recipient_data, + alias and next blinding, and print out JSON */ +static u8 *add_hop(const char *name, + const char *comment, + const struct pubkey *me, + const struct pubkey *next, + const struct privkey *override_blinding, + const char *additional_field_hexstr, + const char *path_id_hexstr, + struct privkey *blinding, /* in and out */ + struct pubkey *alias /* out */) +{ + struct tlv_encrypted_data_tlv *tlv = tlv_encrypted_data_tlv_new(tmpctx); + u8 *enctlv, *encrypted_recipient_data; + const u8 *additional; + + json_start(NULL, '{'); + json_strfield("alias", name); + json_strfield("comment", comment); + json_hexfield("blinding_secret", blinding->secret.data, sizeof(*blinding)); + + /* We have to calc first, to make padding correct */ + tlv->next_node_id = tal_dup_or_null(tlv, struct pubkey, next); + if (path_id_hexstr) + tlv->path_id = tal_hexdata(tlv, path_id_hexstr, + strlen(path_id_hexstr)); + + /* Normally we wouldn't know blinding privkey, and we'd just + * paste in the rest of the path as given, but here we're actually + * generating the lot. */ + if (override_blinding) { + tlv->next_blinding_override = tal(tlv, struct pubkey); + assert(pubkey_from_privkey(override_blinding, tlv->next_blinding_override)); + } + + /* This is assumed to be a valid TLV tuple, and greater than + * any previous, so we simply append. */ + if (additional_field_hexstr) + additional = tal_hexdata(tlv, additional_field_hexstr, + strlen(additional_field_hexstr)); + else + additional = NULL; + + enctlv = tal_arr(tmpctx, u8, 0); + towire_tlv_encrypted_data_tlv(&enctlv, tlv); + + /* Now create padding, and reencode */ + if (tal_bytelen(enctlv) + tal_bytelen(additional) != LARGEST_TLV_SIZE) + tlv->padding = tal_arrz(tlv, u8, + LARGEST_TLV_SIZE + - tal_bytelen(enctlv) + - tal_bytelen(additional) + - 2); + enctlv = tal_arr(tmpctx, u8, 0); + towire_tlv_encrypted_data_tlv(&enctlv, tlv); + towire(&enctlv, additional, tal_bytelen(additional)); + assert(tal_bytelen(enctlv) == LARGEST_TLV_SIZE); + + json_start("tlvs", '{'); + if (tlv->padding) + json_hex_talfield("padding", tlv->padding); + if (tlv->next_node_id) + json_pubkey("next_node_id", tlv->next_node_id); + if (tlv->path_id) + json_hex_talfield("path_id", tlv->path_id); + if (tlv->next_blinding_override) { + json_pubkey("next_blinding_override", + tlv->next_blinding_override); + json_hexfield("blinding_override_secret", + override_blinding->secret.data, + sizeof(*override_blinding)); + } + if (additional) { + /* Deconstruct into type, len, value */ + size_t totlen = tal_bytelen(additional); + u64 type = fromwire_bigsize(&additional, &totlen); + u64 len = fromwire_bigsize(&additional, &totlen); + assert(len == totlen); + json_hexfield(tal_fmt(tmpctx, "unknown_tag_%"PRIu64, type), + additional, len); + } + json_end('}'); + + maybe_comma(); + json_hex_talfield("encrypted_data_tlv", enctlv); + flush_comma(); + enable_superverbose = true; + encrypted_recipient_data = enctlv_from_encmsg_raw(tmpctx, + blinding, + me, + enctlv, + blinding, + alias); + enable_superverbose = false; + json_hex_talfield("encrypted_recipient_data", + encrypted_recipient_data); + if (override_blinding) + *blinding = *override_blinding; + + json_end('}'); + maybe_comma(); + return encrypted_recipient_data; +} + +int main(int argc, char *argv[]) +{ + const char *alias[] = {"Alice", "Bob", "Carol", "Dave"}; + struct privkey privkey[ARRAY_SIZE(alias)], blinding, override_blinding; + struct pubkey id[ARRAY_SIZE(alias)], blinding_pub; + struct secret session_key; + u8 *erd[ARRAY_SIZE(alias)]; /* encrypted_recipient_data */ + u8 *onion_message_packet, *onion_message; + struct pubkey blinded[ARRAY_SIZE(alias)]; + struct sphinx_path *sphinx_path; + struct onionpacket *op; + struct secret *path_secrets; + + common_setup(argv[0]); + + /* Alice is AAA... Bob is BBB... */ + for (size_t i = 0; i < ARRAY_SIZE(alias); i++) { + memset(&privkey[i], alias[i][0], sizeof(privkey[i])); + assert(pubkey_from_privkey(&privkey[i], &id[i])); + } + + memset(&session_key, 3, sizeof(session_key)); + + json_start(NULL, '{'); + json_strfield("comment", "Test vector creating an onionmessage, including joining an existing one"); + json_start("generate", '{'); + json_strfield("comment", "This sections contains test data for Dave's blinded path Bob->Dave; sender has to prepend a hop to Alice to reach Bob"); + json_hexfield("session_key", session_key.data, sizeof(session_key)); + json_start("hops", '['); + + memset(&blinding, 99, sizeof(blinding)); + memset(&override_blinding, 1, sizeof(override_blinding)); + erd[0] = add_hop("Alice", "Alice->Bob: note next_blinding_override to match that give by Dave for Bob", + &id[0], &id[1], + &override_blinding, NULL, NULL, + &blinding, &blinded[0]); + erd[1] = add_hop("Bob", "Bob->Carol", + &id[1], &id[2], + NULL, "fd023103123456", NULL, + &blinding, &blinded[1]); + erd[2] = add_hop("Carol", "Carol->Dave", + &id[2], &id[3], + NULL, NULL, NULL, + &blinding, &blinded[2]); + erd[3] = add_hop("Dave", "Dave is final node, hence path_id", + &id[3], NULL, + NULL, "fdffff0206c1", + "deadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0", + &blinding, &blinded[3]); + + json_end(']'); + json_end('}'); + maybe_comma(); + + memset(&blinding, 99, sizeof(blinding)); + assert(pubkey_from_privkey(&blinding, &blinding_pub)); + json_start("route", '{'); + json_strfield("comment", "The resulting blinded route Alice to Dave."); + json_pubkey("introduction_node_id", &id[0]); + json_pubkey("blinding", &blinding_pub); + + json_start("hops", '['); + for (size_t i = 0; i < ARRAY_SIZE(erd); i++) { + json_start(NULL, '{'); + if (i != 0) + json_pubkey("blinded_node_id", &blinded[i]); + json_hex_talfield("encrypted_recipient_data", erd[i]); + json_end('}'); + maybe_comma(); + } + json_end(']'); + json_end('}'); + maybe_comma(); + + json_start("onionmessage", '{'); + json_strfield("comment", "An onion message which sends a 'hello' to Dave"); + json_strfield("unknown_tag_1", "68656c6c6f"); + + /* Create the onionmessage */ + sphinx_path = sphinx_path_new(tmpctx, NULL); + for (size_t i = 0; i < ARRAY_SIZE(erd); i++) { + struct tlv_onionmsg_tlv *tlv = tlv_onionmsg_tlv_new(tmpctx); + u8 *onionmsg_tlv; + tlv->encrypted_recipient_data = erd[i]; + onionmsg_tlv = tal_arr(tmpctx, u8, 0); + /* For final hop, add unknown 'hello' field */ + if (i == ARRAY_SIZE(erd) - 1) { + towire_bigsize(&onionmsg_tlv, 1); /* type */ + towire_bigsize(&onionmsg_tlv, strlen("hello")); /* length */ + towire(&onionmsg_tlv, "hello", strlen("hello")); + } + towire_tlv_onionmsg_tlv(&onionmsg_tlv, tlv); + sphinx_add_hop(sphinx_path, &blinded[i], onionmsg_tlv); + } + + /* Make it use our session key! */ + sphinx_path->session_key = &session_key; + + /* BOLT-onion-message #4: + * - SHOULD set `len` to 1366 or 32834. + */ + op = create_onionpacket(tmpctx, sphinx_path, ROUTING_INFO_SIZE, + &path_secrets); + onion_message_packet = serialize_onionpacket(tmpctx, op); + json_hex_talfield("onion_message_packet", onion_message_packet); + json_end('}'); + maybe_comma(); + + json_start("decrypt", '{'); + json_strfield("comment", "This section contains the internal values generated by intermediate nodes when decrypting the onion."); + + onion_message = towire_onion_message(tmpctx, &blinding_pub, onion_message_packet); + + json_start("hops", '['); + for (size_t i = 0; i < ARRAY_SIZE(erd); i++) { + struct pubkey next_node_id; + struct tlv_onionmsg_tlv *final_om; + struct pubkey final_alias; + struct secret *final_path_id; + + json_start(NULL, '{'); + json_strfield("alias", alias[i]); + json_hexfield("privkey", privkey[i].secret.data, sizeof(privkey[i])); + json_hex_talfield("onion_message", onion_message); + + /* Now, do full decrypt code, to check */ + assert(fromwire_onion_message(tmpctx, onion_message, + &blinding_pub, &onion_message_packet)); + + /* For test_ecdh */ + mykey = &privkey[i]; + assert(onion_message_parse(tmpctx, onion_message_packet, &blinding_pub, NULL, + &id[i], + &onion_message, &next_node_id, + &final_om, + &final_alias, + &final_path_id)); + if (onion_message) { + json_pubkey("next_node_id", &next_node_id); + } else { + const struct tlv_field *hello; + json_start("tlvs", '{'); + json_hexfield("path_id", final_path_id->data, sizeof(*final_path_id)); + hello = &final_om->fields[0]; + json_hexfield(tal_fmt(tmpctx, + "unknown_tag_%"PRIu64, + hello->numtype), + hello->value, + hello->length); + json_end('}'); + } + json_end('}'); + maybe_comma(); + } + json_end(']'); + json_end('}'); + json_end('}'); + common_shutdown(); +} diff --git a/common/test/run-onion-test-vector.c b/common/test/run-onion-test-vector.c index 3effdb5b5b1b..9c1dd4de7c99 100644 --- a/common/test/run-onion-test-vector.c +++ b/common/test/run-onion-test-vector.c @@ -2,7 +2,7 @@ #include "../bigsize.c" #include "../json_parse.c" #include "../json_parse_simple.c" -#include "../onion.c" +#include "../onion_decode.c" #include "../sphinx.c" #include "../hmac.c" #include "../type_to_string.c" @@ -31,9 +31,9 @@ struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) /* Generated stub for amount_msat */ struct amount_msat amount_msat(u64 millisatoshis UNNEEDED) { fprintf(stderr, "amount_msat called!\n"); abort(); } -/* Generated stub for amount_msat_eq */ -bool amount_msat_eq(struct amount_msat a UNNEEDED, struct amount_msat b UNNEEDED) -{ fprintf(stderr, "amount_msat_eq called!\n"); abort(); } +/* Generated stub for amount_msat_less */ +bool amount_msat_less(struct amount_msat a UNNEEDED, struct amount_msat b UNNEEDED) +{ fprintf(stderr, "amount_msat_less called!\n"); abort(); } /* Generated stub for amount_sat */ struct amount_sat amount_sat(u64 satoshis UNNEEDED) { fprintf(stderr, "amount_sat called!\n"); abort(); } @@ -59,6 +59,13 @@ struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u /* Generated stub for amount_tx_fee */ struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) { fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for decrypt_encrypted_data */ +struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx UNNEEDED, + const struct pubkey *blinding UNNEEDED, + const struct secret *ss UNNEEDED, + const u8 *enctlv) + +{ fprintf(stderr, "decrypt_encrypted_data called!\n"); abort(); } /* Generated stub for ecdh */ void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) { fprintf(stderr, "ecdh called!\n"); abort(); } diff --git a/common/test/run-param.c b/common/test/run-param.c index bd450aef3b2d..6609247db72e 100644 --- a/common/test/run-param.c +++ b/common/test/run-param.c @@ -1,4 +1,5 @@ #include "config.h" +#include "../json_filter.c" #include "../json_parse.c" #include "../json_parse_simple.c" #include "../json_param.c" @@ -35,6 +36,9 @@ struct command_result *command_fail(struct command *cmd, } /* AUTOGENERATED MOCKS START */ +/* Generated stub for command_filter_ptr */ +struct json_filter **command_filter_ptr(struct command *cmd UNNEEDED) +{ fprintf(stderr, "command_filter_ptr called!\n"); abort(); } /* Generated stub for deprecated_apis */ bool deprecated_apis; /* Generated stub for fromwire_tlv */ diff --git a/common/test/run-route_blinding_onion_test.c b/common/test/run-route_blinding_onion_test.c new file mode 100644 index 000000000000..b2239772774e --- /dev/null +++ b/common/test/run-route_blinding_onion_test.c @@ -0,0 +1,153 @@ +#include "config.h" +#include "../../wire/fromwire.c" +#include "../../wire/tlvstream.c" +#include "../../wire/towire.c" +#include "../amount.c" +#include "../bigsize.c" +#include "../blindedpath.c" +#include "../blindedpay.c" +#include "../blinding.c" +#include "../features.c" +#include "../hmac.c" +#include "../json_parse.c" +#include "../json_parse_simple.c" +#include "../onion_encode.c" +#include "../sphinx.c" +#include "../type_to_string.c" +#if EXPERIMENTAL_FEATURES + #include "../../wire/onion_exp_wiregen.c" +#else + #include "../../wire/onion_wiregen.c" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for mvt_tag_str */ +const char *mvt_tag_str(enum mvt_tag tag UNNEEDED) +{ fprintf(stderr, "mvt_tag_str called!\n"); abort(); } +/* Generated stub for new_onionreply */ +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +{ fprintf(stderr, "new_onionreply called!\n"); abort(); } +/* Generated stub for node_id_from_hexstr */ +bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED) +{ fprintf(stderr, "node_id_from_hexstr called!\n"); abort(); } +/* Generated stub for pubkey_from_node_id */ +bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static bool json_to_tok(const char *buffer, const jsmntok_t *tok, + const jsmntok_t **tokp) +{ + *tokp = tok; + return true; +} + +int main(int argc, char *argv[]) +{ + char *json; + size_t i; + jsmn_parser parser; + jsmntok_t toks[5000]; + const jsmntok_t *t, *hops_tok; + struct blinded_path *bpath; + struct pubkey *ids; + u8 **onionhops, *associated_data, *onion, *expected_onion; + struct short_channel_id initscid; + struct sphinx_path *sp; + struct secret session_key, *path_secrets; + + common_setup(argv[0]); + + if (argv[1]) + json = grab_file(tmpctx, argv[1]); + else { + char *dir = getenv("BOLTDIR"); + json = grab_file(tmpctx, + path_join(tmpctx, + dir ? dir : "../bolts", + "bolt04/onion-route-blinding-test.json")); + if (!json) { + printf("test file not found, skipping\n"); + goto out; + } + } + + jsmn_init(&parser); + if (jsmn_parse(&parser, json, strlen(json), toks, ARRAY_SIZE(toks)) < 0) + abort(); + + bpath = tal(tmpctx, struct blinded_path); + + assert(json_scan(tmpctx, json, toks, "{generate:{session_key:%,associated_data:%,blinded_route:{introduction_node_id:%,blinding:%,hops:%}}}", + JSON_SCAN(json_to_secret, &session_key), + JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &associated_data), + JSON_SCAN(json_to_pubkey, &bpath->first_node_id), + JSON_SCAN(json_to_pubkey, &bpath->blinding), + JSON_SCAN(json_to_tok, &hops_tok)) == NULL); + + bpath->path = tal_arr(bpath, struct onionmsg_hop *, hops_tok->size); + json_for_each_arr(i, t, hops_tok) { + bpath->path[i] = tal(bpath->path, struct onionmsg_hop); + assert(json_scan(tmpctx, json, t, "{blinded_node_id:%,encrypted_data:%}", + JSON_SCAN(json_to_pubkey, + &bpath->path[i]->blinded_node_id), + JSON_SCAN_TAL(bpath->path[i], + json_tok_bin_from_hex, + &bpath->path[i]->encrypted_recipient_data)) == NULL); + } + + /* FIXME: These amounts / scid should be in test vectors! */ + onionhops = blinded_onion_hops(tmpctx, AMOUNT_MSAT(200), 700, bpath); + assert(mk_short_channel_id(&initscid, 0, 0, 10)); + + /* Prepend Alice: poor thing doesn't speak blinding! */ + tal_resize(&onionhops, tal_count(onionhops) + 1); + memmove(onionhops + 1, onionhops, + (tal_count(onionhops) - 1) * sizeof(*onionhops)); + onionhops[0] = onion_nonfinal_hop(onionhops, &initscid, + AMOUNT_MSAT(500), 1000); + + assert(json_scan(tmpctx, json, toks, "{generate:{full_route:{hops:%}}}", + JSON_SCAN(json_to_tok, &hops_tok)) == NULL); + + ids = tal_arr(tmpctx, struct pubkey, hops_tok->size); + json_for_each_arr(i, t, hops_tok) { + u8 *payload; + assert(json_scan(tmpctx, json, t, "{payload:%,pubkey:%}", + JSON_SCAN_TAL(tmpctx, + json_tok_bin_from_hex, + &payload), + JSON_SCAN(json_to_pubkey, &ids[i])) == NULL); + assert(memeq(payload, tal_bytelen(payload), + onionhops[i], tal_bytelen(onionhops[i]))); + } + + /* Now, create onion! */ + sp = sphinx_path_new_with_key(tmpctx, associated_data, &session_key); + for (i = 0; i < tal_count(ids); i++) + sphinx_add_hop(sp, &ids[i], onionhops[i]); + + onion = serialize_onionpacket(tmpctx, + create_onionpacket(tmpctx, sp, ROUTING_INFO_SIZE, + &path_secrets)); + assert(json_scan(tmpctx, json, toks, "{generate:{onion:%}}", + JSON_SCAN_TAL(tmpctx, + json_tok_bin_from_hex, + &expected_onion)) == NULL); + assert(memeq(expected_onion, tal_bytelen(expected_onion), + onion, tal_bytelen(onion))); + + /* FIXME: unwrap and test! */ + +out: + common_shutdown(); +} diff --git a/common/test/run-route_blinding_override_test.c b/common/test/run-route_blinding_override_test.c deleted file mode 100644 index 8f8c916f1b9a..000000000000 --- a/common/test/run-route_blinding_override_test.c +++ /dev/null @@ -1,362 +0,0 @@ -#include "config.h" -#include "../bigsize.c" -#include "../blindedpath.c" -#include "../blinding.c" -#include "../hmac.c" -#include "../type_to_string.c" -#include -#include -#include -#include -#include -#include -#include -#include - -/* AUTOGENERATED MOCKS START */ -/* Generated stub for amount_asset_is_main */ -bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) -{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } -/* Generated stub for amount_asset_to_sat */ -struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) -{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } -/* Generated stub for amount_sat */ -struct amount_sat amount_sat(u64 satoshis UNNEEDED) -{ fprintf(stderr, "amount_sat called!\n"); abort(); } -/* Generated stub for amount_sat_add */ - bool amount_sat_add(struct amount_sat *val UNNEEDED, - struct amount_sat a UNNEEDED, - struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } -/* Generated stub for amount_sat_eq */ -bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } -/* Generated stub for amount_sat_greater_eq */ -bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } -/* Generated stub for amount_sat_sub */ - bool amount_sat_sub(struct amount_sat *val UNNEEDED, - struct amount_sat a UNNEEDED, - struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } -/* Generated stub for amount_sat_to_asset */ -struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) -{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } -/* Generated stub for amount_sat_to_msat */ - bool amount_sat_to_msat(struct amount_msat *msat UNNEEDED, - struct amount_sat sat UNNEEDED) -{ fprintf(stderr, "amount_sat_to_msat called!\n"); abort(); } -/* Generated stub for amount_tx_fee */ -struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) -{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } -/* Generated stub for fromwire_amount_msat */ -struct amount_msat fromwire_amount_msat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_amount_msat called!\n"); abort(); } -/* Generated stub for fromwire_amount_sat */ -struct amount_sat fromwire_amount_sat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_amount_sat called!\n"); abort(); } -/* Generated stub for fromwire_channel_id */ -bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, - struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } -/* Generated stub for fromwire_node_id */ -void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED) -{ fprintf(stderr, "fromwire_node_id called!\n"); abort(); } -/* Generated stub for json_get_member */ -const jsmntok_t *json_get_member(const char *buffer UNNEEDED, const jsmntok_t tok[] UNNEEDED, - const char *label UNNEEDED) -{ fprintf(stderr, "json_get_member called!\n"); abort(); } -/* Generated stub for json_next */ -const jsmntok_t *json_next(const jsmntok_t *tok UNNEEDED) -{ fprintf(stderr, "json_next called!\n"); abort(); } -/* Generated stub for json_to_pubkey */ -bool json_to_pubkey(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, - struct pubkey *pubkey UNNEEDED) -{ fprintf(stderr, "json_to_pubkey called!\n"); abort(); } -/* Generated stub for json_to_secret */ -bool json_to_secret(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct secret *dest UNNEEDED) -{ fprintf(stderr, "json_to_secret called!\n"); abort(); } -/* Generated stub for json_to_short_channel_id */ -bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, - struct short_channel_id *scid UNNEEDED) -{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); } -/* Generated stub for json_tok_bin_from_hex */ -u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) -{ fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); } -/* Generated stub for json_tok_startswith */ -bool json_tok_startswith(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, - const char *prefix UNNEEDED) -{ fprintf(stderr, "json_tok_startswith called!\n"); abort(); } -/* Generated stub for json_tok_streq */ -bool json_tok_streq(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, const char *str UNNEEDED) -{ fprintf(stderr, "json_tok_streq called!\n"); abort(); } -/* Generated stub for towire_amount_msat */ -void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED) -{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); } -/* Generated stub for towire_amount_sat */ -void towire_amount_sat(u8 **pptr UNNEEDED, const struct amount_sat sat UNNEEDED) -{ fprintf(stderr, "towire_amount_sat called!\n"); abort(); } -/* Generated stub for towire_channel_id */ -void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) -{ fprintf(stderr, "towire_channel_id called!\n"); abort(); } -/* Generated stub for towire_node_id */ -void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) -{ fprintf(stderr, "towire_node_id called!\n"); abort(); } -/* AUTOGENERATED MOCKS END */ - -static u8 *json_to_enctlvs(const tal_t *ctx, - const char *buf, const jsmntok_t *tlvs) -{ - struct tlv_encrypted_data_tlv *enctlv = tlv_encrypted_data_tlv_new(tmpctx); - size_t i; - const jsmntok_t *t; - u8 *ret, *appended = tal_arr(tmpctx, u8, 0); - - json_for_each_obj(i, t, tlvs) { - if (json_tok_streq(buf, t, "short_channel_id")) { - enctlv->short_channel_id = tal(enctlv, struct short_channel_id); - assert(json_to_short_channel_id(buf, t+1, - enctlv->short_channel_id)); - } else if (json_tok_streq(buf, t, "padding")) { - enctlv->padding = json_tok_bin_from_hex(enctlv, - buf, t+1); - assert(enctlv->padding); - } else if (json_tok_streq(buf, t, "next_node_id")) { - enctlv->next_node_id = tal(enctlv, struct pubkey); - assert(json_to_pubkey(buf, t+1, - enctlv->next_node_id)); - } else if (json_tok_streq(buf, t, "path_id")) { - enctlv->path_id = json_tok_bin_from_hex(enctlv, - buf, t+1); - assert(enctlv->path_id); - } else if (json_tok_streq(buf, t, "next_blinding_override")) { - enctlv->next_blinding_override = tal(enctlv, struct pubkey); - assert(json_to_pubkey(buf, t+1, - enctlv->next_blinding_override)); - } else { - u16 tagnum; - u8 *val; - assert(json_tok_startswith(buf, t, "unknown_tag_")); - tagnum = atoi(buf + t->start + strlen("unknown_tag_")); - assert(tagnum); - val = json_tok_bin_from_hex(enctlv, buf, t+1); - assert(val); - - /* We can't actually represent these in a way towire_ - * will see, so we literally append them */ - towire_bigsize(&appended, tagnum); - towire_bigsize(&appended, tal_bytelen(val)); - towire_u8_array(&appended, val, tal_bytelen(val)); - } - } - ret = tal_arr(ctx, u8, 0); - towire_tlv_encrypted_data_tlv(&ret, enctlv); - towire_u8_array(&ret, appended, tal_bytelen(appended)); - return ret; -} - -/* Updated each time, as we pretend to be Alice, Bob, Carol */ -static const struct privkey *mykey; - -static void test_ecdh(const struct pubkey *point, struct secret *ss) -{ - if (secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey, - mykey->secret.data, NULL, NULL) != 1) - abort(); -} - -int main(int argc, char *argv[]) -{ - char *json; - size_t i, num_sender_hops; - jsmn_parser parser; - jsmntok_t toks[5000]; - const jsmntok_t *t, *recip_route_hops, *recip_blinding_hops, - *sender_route_hops, *sender_blinding_hops, *unblinding_hops; - struct pubkey *ids; - u8 **enctlvs, **encrypted_data; - struct privkey blinding; - - common_setup(argv[0]); - - if (argv[1]) - json = grab_file(tmpctx, argv[1]); - else { - char *dir = getenv("BOLTDIR"); - json = grab_file(tmpctx, - path_join(tmpctx, - dir ? dir : "../bolts", - "bolt04/route-blinding-override-test.json")); - if (!json) { - printf("test file not found, skipping\n"); - goto out; - } - } - - jsmn_init(&parser); - if (jsmn_parse(&parser, json, strlen(json), toks, ARRAY_SIZE(toks)) < 0) - abort(); - - /* We concatenate the sender_route_blinding and the - * recipient_route_blinding to form a contiguous sequence of - * enctlvs */ - recip_route_hops = json_get_member(json, json_get_member(json, toks, "recipient_route"), "hops"); - sender_route_hops = json_get_member(json, json_get_member(json, toks, "sender_route"), "hops"); - recip_blinding_hops = json_get_member(json, json_get_member(json, toks, "recipient_route_blinding"), "hops"); - sender_blinding_hops = json_get_member(json, json_get_member(json, toks, "sender_route_blinding"), "hops"); - unblinding_hops = json_get_member(json, json_get_member(json, toks, "unblinding"), "hops"); - - assert(recip_route_hops->size == recip_blinding_hops->size); - assert(sender_route_hops->size == sender_blinding_hops->size); - num_sender_hops = sender_route_hops->size; - - ids = tal_arr(tmpctx, struct pubkey, - num_sender_hops + recip_route_hops->size); - enctlvs = tal_arr(tmpctx, u8 *, num_sender_hops + recip_route_hops->size); - json_for_each_arr(i, t, sender_route_hops) { - u8 *expected; - assert(json_to_pubkey(json, json_get_member(json, t, "node_id"), - &ids[i])); - enctlvs[i] = json_tok_bin_from_hex(enctlvs, json, - json_get_member(json, t, "encoded_tlvs")); - expected = json_to_enctlvs(tmpctx, json, - json_get_member(json, t, "tlvs")); - assert(memeq(expected, tal_bytelen(expected), - enctlvs[i], tal_bytelen(enctlvs[i]))); - } - - json_for_each_arr(i, t, recip_route_hops) { - u8 *expected; - assert(json_to_pubkey(json, json_get_member(json, t, "node_id"), - &ids[i + num_sender_hops])); - enctlvs[i + num_sender_hops] - = json_tok_bin_from_hex(enctlvs, json, - json_get_member(json, t, "encoded_tlvs")); - expected = json_to_enctlvs(tmpctx, json, - json_get_member(json, t, "tlvs")); - assert(memeq(expected, tal_bytelen(expected), - enctlvs[i + num_sender_hops], - tal_bytelen(enctlvs[i + num_sender_hops]))); - } - - encrypted_data = tal_arr(tmpctx, u8 *, - num_sender_hops + recip_route_hops->size); - - /* Now do the blinding. */ - json_for_each_arr(i, t, sender_blinding_hops) { - struct secret s; - struct pubkey pubkey, expected_pubkey; - u8 *expected_encdata; - struct pubkey alias, expected_alias; - - assert(json_to_secret(json, - json_get_member(json, t, "ephemeral_privkey"), - &s)); - - /* First blinding is stated, remainder are derived! */ - if (i == 0) { - blinding.secret = s; - } else - assert(secret_eq_consttime(&blinding.secret, &s)); - - assert(pubkey_from_privkey(&blinding, &pubkey)); - json_to_pubkey(json, json_get_member(json, t, "ephemeral_pubkey"), - &expected_pubkey); - assert(pubkey_eq(&pubkey, &expected_pubkey)); - - encrypted_data[i] = enctlv_from_encmsg_raw(encrypted_data, - &blinding, - &ids[i], - enctlvs[i], - &blinding, - &alias); - expected_encdata = json_tok_bin_from_hex(tmpctx,json, - json_get_member(json, t, - "encrypted_data")); - assert(memeq(encrypted_data[i], tal_bytelen(encrypted_data[i]), - expected_encdata, tal_bytelen(expected_encdata))); - - json_to_pubkey(json, json_get_member(json, t, "blinded_node_id"), - &expected_alias); - assert(pubkey_eq(&alias, &expected_alias)); - } - - /* At this point, we override the blinding! */ - json_for_each_arr(i, t, recip_blinding_hops) { - struct secret s; - struct pubkey pubkey, expected_pubkey; - u8 *expected_encdata; - struct pubkey alias, expected_alias; - - assert(json_to_secret(json, - json_get_member(json, t, "ephemeral_privkey"), - &s)); - - /* First blinding is from next_blinding_override, - * remainder are derived! */ - if (i == 0) { - blinding.secret = s; - } else - assert(secret_eq_consttime(&blinding.secret, &s)); - - assert(pubkey_from_privkey(&blinding, &pubkey)); - json_to_pubkey(json, json_get_member(json, t, "ephemeral_pubkey"), - &expected_pubkey); - assert(pubkey_eq(&pubkey, &expected_pubkey)); - - encrypted_data[i + num_sender_hops] - = enctlv_from_encmsg_raw(tmpctx, - &blinding, - &ids[i + num_sender_hops], - enctlvs[i + num_sender_hops], - &blinding, - &alias); - expected_encdata = json_tok_bin_from_hex(tmpctx,json, - json_get_member(json, t, - "encrypted_data")); - assert(memeq(encrypted_data[i + num_sender_hops], - tal_bytelen(encrypted_data[i + num_sender_hops]), - expected_encdata, tal_bytelen(expected_encdata))); - - json_to_pubkey(json, json_get_member(json, t, "blinded_node_id"), - &expected_alias); - assert(pubkey_eq(&alias, &expected_alias)); - } - - /* Now try unblinding */ - json_for_each_arr(i, t, unblinding_hops) { - struct privkey me; - struct secret ss; - struct pubkey blindingpub, expected_blinding; - struct pubkey onion_key, next_node; - - assert(json_to_secret(json, - json_get_member(json, t, "node_privkey"), - &me.secret)); - - mykey = &me; - assert(json_to_pubkey(json, - json_get_member(json, t, "ephemeral_pubkey"), - &blindingpub)); - - assert(unblind_onion(&blindingpub, test_ecdh, &onion_key, &ss)); - if (i != unblinding_hops->size - 1) { - assert(decrypt_enctlv(&blindingpub, &ss, encrypted_data[i], &next_node, &blindingpub)); - assert(json_to_pubkey(json, - json_get_member(json, t, "next_ephemeral_pubkey"), - &expected_blinding)); - assert(pubkey_eq(&blindingpub, &expected_blinding)); - } else { - struct secret *path_id; - struct pubkey my_id, alias; - assert(pubkey_from_privkey(&me, &my_id)); - assert(decrypt_final_enctlv(tmpctx, &blindingpub, &ss, - encrypted_data[i], - &my_id, &alias, - &path_id)); - } - } - -out: - common_shutdown(); -} diff --git a/common/test/run-route_blinding_test.c b/common/test/run-route_blinding_test.c index de1b7e038806..4b079ed0dde3 100644 --- a/common/test/run-route_blinding_test.c +++ b/common/test/run-route_blinding_test.c @@ -1,8 +1,12 @@ #include "config.h" +#include "../amount.c" #include "../bigsize.c" #include "../blindedpath.c" #include "../blinding.c" +#include "../features.c" #include "../hmac.c" +#include "../json_parse.c" +#include "../json_parse_simple.c" #include "../type_to_string.c" #include #include @@ -14,47 +18,6 @@ #include /* AUTOGENERATED MOCKS START */ -/* Generated stub for amount_asset_is_main */ -bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) -{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } -/* Generated stub for amount_asset_to_sat */ -struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) -{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } -/* Generated stub for amount_sat */ -struct amount_sat amount_sat(u64 satoshis UNNEEDED) -{ fprintf(stderr, "amount_sat called!\n"); abort(); } -/* Generated stub for amount_sat_add */ - bool amount_sat_add(struct amount_sat *val UNNEEDED, - struct amount_sat a UNNEEDED, - struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } -/* Generated stub for amount_sat_eq */ -bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } -/* Generated stub for amount_sat_greater_eq */ -bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } -/* Generated stub for amount_sat_sub */ - bool amount_sat_sub(struct amount_sat *val UNNEEDED, - struct amount_sat a UNNEEDED, - struct amount_sat b UNNEEDED) -{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } -/* Generated stub for amount_sat_to_asset */ -struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) -{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } -/* Generated stub for amount_sat_to_msat */ - bool amount_sat_to_msat(struct amount_msat *msat UNNEEDED, - struct amount_sat sat UNNEEDED) -{ fprintf(stderr, "amount_sat_to_msat called!\n"); abort(); } -/* Generated stub for amount_tx_fee */ -struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) -{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } -/* Generated stub for fromwire_amount_msat */ -struct amount_msat fromwire_amount_msat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_amount_msat called!\n"); abort(); } -/* Generated stub for fromwire_amount_sat */ -struct amount_sat fromwire_amount_sat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) -{ fprintf(stderr, "fromwire_amount_sat called!\n"); abort(); } /* Generated stub for fromwire_channel_id */ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct channel_id *channel_id UNNEEDED) @@ -62,40 +25,9 @@ bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, /* Generated stub for fromwire_node_id */ void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED) { fprintf(stderr, "fromwire_node_id called!\n"); abort(); } -/* Generated stub for json_get_member */ -const jsmntok_t *json_get_member(const char *buffer UNNEEDED, const jsmntok_t tok[] UNNEEDED, - const char *label UNNEEDED) -{ fprintf(stderr, "json_get_member called!\n"); abort(); } -/* Generated stub for json_next */ -const jsmntok_t *json_next(const jsmntok_t *tok UNNEEDED) -{ fprintf(stderr, "json_next called!\n"); abort(); } -/* Generated stub for json_to_pubkey */ -bool json_to_pubkey(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, - struct pubkey *pubkey UNNEEDED) -{ fprintf(stderr, "json_to_pubkey called!\n"); abort(); } -/* Generated stub for json_to_secret */ -bool json_to_secret(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct secret *dest UNNEEDED) -{ fprintf(stderr, "json_to_secret called!\n"); abort(); } -/* Generated stub for json_to_short_channel_id */ -bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, - struct short_channel_id *scid UNNEEDED) -{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); } -/* Generated stub for json_tok_bin_from_hex */ -u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED) -{ fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); } -/* Generated stub for json_tok_startswith */ -bool json_tok_startswith(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, - const char *prefix UNNEEDED) -{ fprintf(stderr, "json_tok_startswith called!\n"); abort(); } -/* Generated stub for json_tok_streq */ -bool json_tok_streq(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, const char *str UNNEEDED) -{ fprintf(stderr, "json_tok_streq called!\n"); abort(); } -/* Generated stub for towire_amount_msat */ -void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED) -{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); } -/* Generated stub for towire_amount_sat */ -void towire_amount_sat(u8 **pptr UNNEEDED, const struct amount_sat sat UNNEEDED) -{ fprintf(stderr, "towire_amount_sat called!\n"); abort(); } +/* Generated stub for node_id_from_hexstr */ +bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED) +{ fprintf(stderr, "node_id_from_hexstr called!\n"); abort(); } /* Generated stub for towire_channel_id */ void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) { fprintf(stderr, "towire_channel_id called!\n"); abort(); } @@ -104,6 +36,53 @@ void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) { fprintf(stderr, "towire_node_id called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ +static struct tlv_encrypted_data_tlv_payment_constraints * +json_to_payment_constraints(const tal_t *ctx, const char *buf, const jsmntok_t *t) +{ + struct tlv_encrypted_data_tlv_payment_constraints *pc + = tal(ctx, struct tlv_encrypted_data_tlv_payment_constraints); + + assert(json_to_u32(buf, json_get_member(buf, t, "max_cltv_expiry"), + &pc->max_cltv_expiry)); + assert(json_to_u64(buf, json_get_member(buf, t, "htlc_minimum_msat"), + &pc->htlc_minimum_msat)); + return pc; +} + +static struct tlv_encrypted_data_tlv_payment_relay * +json_to_payment_relay(const tal_t *ctx, const char *buf, const jsmntok_t *t) +{ + struct tlv_encrypted_data_tlv_payment_relay *relay + = tal(ctx, struct tlv_encrypted_data_tlv_payment_relay); + + assert(json_to_u16(buf, json_get_member(buf, t, "cltv_expiry_delta"), + &relay->cltv_expiry_delta)); + assert(json_to_u32(buf, json_get_member(buf, t, "fee_proportional_millionths"), + &relay->fee_proportional_millionths)); + if (json_get_member(buf, t, "fee_base_msat")) + assert(json_to_u32(buf, json_get_member(buf, t, "fee_base_msat"), + &relay->fee_base_msat)); + else + relay->fee_base_msat = 0; + return relay; +} + +static u8 *json_to_allowed_features(const tal_t *ctx, + const char *buf, const jsmntok_t *t) +{ + size_t i; + const jsmntok_t *f, *features = json_get_member(buf, t, "features"); + u8 *allowed_features; + + allowed_features = tal_arr(ctx, u8, 0); + json_for_each_arr(i, f, features) { + u32 n; + assert(json_to_u32(buf, f, &n)); + set_feature_bit(&allowed_features, n); + } + return allowed_features; +} + static u8 *json_to_enctlvs(const tal_t *ctx, const char *buf, const jsmntok_t *tlvs) { @@ -129,6 +108,16 @@ static u8 *json_to_enctlvs(const tal_t *ctx, enctlv->path_id = json_tok_bin_from_hex(enctlv, buf, t+1); assert(enctlv->path_id); + } else if (json_tok_streq(buf, t, "next_blinding_override")) { + enctlv->next_blinding_override = tal(enctlv, struct pubkey); + assert(json_to_pubkey(buf, t+1, + enctlv->next_blinding_override)); + } else if (json_tok_streq(buf, t, "payment_relay")) { + enctlv->payment_relay = json_to_payment_relay(enctlv, buf, t+1); + } else if (json_tok_streq(buf, t, "payment_constraints")) { + enctlv->payment_constraints = json_to_payment_constraints(enctlv, buf, t+1); + } else if (json_tok_streq(buf, t, "allowed_features")) { + enctlv->allowed_features = json_to_allowed_features(enctlv, buf, t+1); } else { u16 tagnum; u8 *val; @@ -182,7 +171,7 @@ int main(int argc, char *argv[]) if (jsmn_parse(&parser, json, strlen(json), toks, ARRAY_SIZE(toks)) < 0) abort(); - hops_tok = json_get_member(json, json_get_member(json, toks, "route"), "hops"); + hops_tok = json_get_member(json, json_get_member(json, toks, "generate"), "hops"); ids = tal_arr(tmpctx, struct pubkey, hops_tok->size); enctlvs = tal_arr(tmpctx, u8 *, hops_tok->size); @@ -199,9 +188,6 @@ int main(int argc, char *argv[]) } /* Now do the blinding. */ - hops_tok = json_get_member(json, json_get_member(json, toks, "blinding"), "hops"); - assert(hops_tok->size == tal_count(ids)); - json_for_each_arr(i, t, hops_tok) { struct secret s; struct pubkey pubkey, expected_pubkey; @@ -212,10 +198,13 @@ int main(int argc, char *argv[]) json_get_member(json, t, "ephemeral_privkey"), &s)); - /* First blinding is stated, remainder are derived! */ - if (i == 0) { - blinding.secret = s; - } else + /* First blinding/replacement is stated, remainder are + * derived! */ + if (json_get_member(json, t, "session_key")) + assert(json_to_secret(json, + json_get_member(json, t, "session_key"), + &blinding.secret)); + else assert(secret_eq_consttime(&blinding.secret, &s)); assert(pubkey_from_privkey(&blinding, &pubkey)); diff --git a/common/test/run-sphinx.c b/common/test/run-sphinx.c index 8a1e07b445fc..47de12e0558a 100644 --- a/common/test/run-sphinx.c +++ b/common/test/run-sphinx.c @@ -1,6 +1,7 @@ #include "config.h" #include "../hmac.c" -#include "../onion.c" +#include "../onion_decode.c" +#include "../onion_encode.c" #include "../onionreply.c" #include "../sphinx.c" #include @@ -20,6 +21,9 @@ struct amount_msat amount_msat(u64 millisatoshis UNNEEDED) /* Generated stub for amount_msat_eq */ bool amount_msat_eq(struct amount_msat a UNNEEDED, struct amount_msat b UNNEEDED) { fprintf(stderr, "amount_msat_eq called!\n"); abort(); } +/* Generated stub for amount_msat_less */ +bool amount_msat_less(struct amount_msat a UNNEEDED, struct amount_msat b UNNEEDED) +{ fprintf(stderr, "amount_msat_less called!\n"); abort(); } /* Generated stub for amount_sat */ struct amount_sat amount_sat(u64 satoshis UNNEEDED) { fprintf(stderr, "amount_sat called!\n"); abort(); } @@ -51,6 +55,16 @@ size_t bigsize_get(const u8 *p UNNEEDED, size_t max UNNEEDED, bigsize_t *val UNN /* Generated stub for bigsize_put */ size_t bigsize_put(u8 buf[BIGSIZE_MAX_LEN] UNNEEDED, bigsize_t v UNNEEDED) { fprintf(stderr, "bigsize_put called!\n"); abort(); } +/* Generated stub for decrypt_encrypted_data */ +struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx UNNEEDED, + const struct pubkey *blinding UNNEEDED, + const struct secret *ss UNNEEDED, + const u8 *enctlv) + +{ fprintf(stderr, "decrypt_encrypted_data called!\n"); abort(); } +/* Generated stub for ecdh */ +void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) +{ fprintf(stderr, "ecdh called!\n"); abort(); } /* Generated stub for fromwire_amount_msat */ struct amount_msat fromwire_amount_msat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) { fprintf(stderr, "fromwire_amount_msat called!\n"); abort(); } @@ -82,12 +96,6 @@ void towire_tlv(u8 **pptr UNNEEDED, { fprintf(stderr, "towire_tlv called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ -#if EXPERIMENTAL_FEATURES -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } -#endif - extern secp256k1_context *secp256k1_ctx; static struct secret secret_from_hex(const char *hex) diff --git a/common/test/run-tlv_span.c b/common/test/run-tlv_span.c new file mode 100644 index 000000000000..0fc9d03c804b --- /dev/null +++ b/common/test/run-tlv_span.c @@ -0,0 +1,133 @@ +#include "config.h" +#include "../bolt12.c" +#include "../bigsize.c" +#include "../../wire/fromwire.c" +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for features_unsupported */ +int features_unsupported(const struct feature_set *our_features UNNEEDED, + const u8 *their_features UNNEEDED, + enum feature_place p UNNEEDED) +{ fprintf(stderr, "features_unsupported called!\n"); abort(); } +/* Generated stub for from_bech32_charset */ +bool from_bech32_charset(const tal_t *ctx UNNEEDED, + const char *bech32 UNNEEDED, size_t bech32_len UNNEEDED, + char **hrp UNNEEDED, u8 **data UNNEEDED) +{ fprintf(stderr, "from_bech32_charset called!\n"); abort(); } +/* Generated stub for fromwire_tlv_invoice */ +struct tlv_invoice *fromwire_tlv_invoice(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_tlv_invoice called!\n"); abort(); } +/* Generated stub for fromwire_tlv_invoice_request */ +struct tlv_invoice_request *fromwire_tlv_invoice_request(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_tlv_invoice_request called!\n"); abort(); } +/* Generated stub for fromwire_tlv_offer */ +struct tlv_offer *fromwire_tlv_offer(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_tlv_offer called!\n"); abort(); } +/* Generated stub for merkle_tlv */ +void merkle_tlv(const struct tlv_field *fields UNNEEDED, struct sha256 *merkle UNNEEDED) +{ fprintf(stderr, "merkle_tlv called!\n"); abort(); } +/* Generated stub for sighash_from_merkle */ +void sighash_from_merkle(const char *messagename UNNEEDED, + const char *fieldname UNNEEDED, + const struct sha256 *merkle UNNEEDED, + struct sha256 *sighash UNNEEDED) +{ fprintf(stderr, "sighash_from_merkle called!\n"); abort(); } +/* Generated stub for to_bech32_charset */ +char *to_bech32_charset(const tal_t *ctx UNNEEDED, + const char *hrp UNNEEDED, const u8 *data UNNEEDED) +{ fprintf(stderr, "to_bech32_charset called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_tlv_invoice */ +void towire_tlv_invoice(u8 **pptr UNNEEDED, const struct tlv_invoice *record UNNEEDED) +{ fprintf(stderr, "towire_tlv_invoice called!\n"); abort(); } +/* Generated stub for towire_tlv_invoice_request */ +void towire_tlv_invoice_request(u8 **pptr UNNEEDED, const struct tlv_invoice_request *record UNNEEDED) +{ fprintf(stderr, "towire_tlv_invoice_request called!\n"); abort(); } +/* Generated stub for towire_tlv_offer */ +void towire_tlv_offer(u8 **pptr UNNEEDED, const struct tlv_offer *record UNNEEDED) +{ fprintf(stderr, "towire_tlv_offer called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +int main(int argc, char *argv[]) +{ + u8 *wire; + size_t len, start; + + common_setup(argv[0]); + + wire = tal_hexdata(tmpctx, "0010b8538094dbd70d8a0f0439d8e64f766f022006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0801020a0b73696d706c6520746573741621035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d502006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f582103aa144bcfb083a73e1f51416003083e63c6c68951c04733ab4d042d0cc2237f46f0408d43842db95b6377612674bb21e45ef4e1643289db27e32edb5c15422664b8ca7b052759729cafd7260634a6de053a292ca1859914a4b21e4b8e20e20d2f911b", + strlen("0010b8538094dbd70d8a0f0439d8e64f766f022006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0801020a0b73696d706c6520746573741621035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d502006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f582103aa144bcfb083a73e1f51416003083e63c6c68951c04733ab4d042d0cc2237f46f0408d43842db95b6377612674bb21e45ef4e1643289db27e32edb5c15422664b8ca7b052759729cafd7260634a6de053a292ca1859914a4b21e4b8e20e20d2f911b")); + len = tlv_span(wire, 0, UINT64_MAX, &start); + assert(start == 0); + assert(len == tal_bytelen(wire)); + + len = tlv_span(wire, 1, UINT64_MAX, &start); + assert(start == strlen("0010b8538094dbd70d8a0f0439d8e64f766f") / 2); + assert(len == tal_bytelen(wire) - start); + + len = tlv_span(wire, 0, 1, &start); + assert(start == 0); + assert(len == strlen("0010b8538094dbd70d8a0f0439d8e64f766f") / 2); + common_shutdown(); + return 0; +} diff --git a/common/type_to_string.h b/common/type_to_string.h index 171ac476a843..deb297eefe4e 100644 --- a/common/type_to_string.h +++ b/common/type_to_string.h @@ -7,7 +7,6 @@ /* This must match the type_to_string_ cases. */ union printable_types { const struct pubkey *pubkey; - const struct point32 *point32; const struct node_id *node_id; const struct bitcoin_txid *bitcoin_txid; const struct bitcoin_blkid *bitcoin_blkid; diff --git a/common/utxo.c b/common/utxo.c index ba1fb2863cb0..b2192f4a5d74 100644 --- a/common/utxo.c +++ b/common/utxo.c @@ -25,6 +25,8 @@ void towire_utxo(u8 **pptr, const struct utxo *utxo) towire_bool(pptr, utxo->close_info->option_anchor_outputs); towire_u32(pptr, utxo->close_info->csv); } + + towire_bool(pptr, utxo->is_in_coinbase); } struct utxo *fromwire_utxo(const tal_t *ctx, const u8 **ptr, size_t *max) @@ -55,6 +57,8 @@ struct utxo *fromwire_utxo(const tal_t *ctx, const u8 **ptr, size_t *max) } else { utxo->close_info = NULL; } + + utxo->is_in_coinbase = fromwire_bool(ptr, max); return utxo; } @@ -69,3 +73,21 @@ size_t utxo_spend_weight(const struct utxo *utxo, size_t min_witness_weight) return bitcoin_tx_input_weight(utxo->is_p2sh, wit_weight); } + +u32 utxo_is_immature(const struct utxo *utxo, u32 blockheight) +{ + if (utxo->is_in_coinbase) { + /* We got this from a block, it must have a known + * blockheight. */ + assert(utxo->blockheight); + + if (blockheight < *utxo->blockheight + 100) + return *utxo->blockheight + 99 - blockheight; + + else + return 0; + } else { + /* Non-coinbase outputs are always mature. */ + return 0; + } +} diff --git a/common/utxo.h b/common/utxo.h index e175a36e0e14..6c5365c8e32c 100644 --- a/common/utxo.h +++ b/common/utxo.h @@ -53,6 +53,9 @@ struct utxo { /* The scriptPubkey if it is known */ u8 *scriptPubkey; + + /* Is this utxo a coinbase output */ + bool is_in_coinbase; }; /* We lazy-evaluate whether a utxo is really still reserved. */ @@ -80,4 +83,11 @@ struct utxo *fromwire_utxo(const tal_t *ctx, const u8 **ptr, size_t *max); /* Estimate of (signed) UTXO weight in transaction */ size_t utxo_spend_weight(const struct utxo *utxo, size_t min_witness_weight); + +/** + * Determine how many blocks until a UTXO becomes mature. + * + * Returns 0 for non-coinbase outputs or the number of blocks to mature. + */ +u32 utxo_is_immature(const struct utxo *utxo, u32 blockheight); #endif /* LIGHTNING_COMMON_UTXO_H */ diff --git a/common/wireaddr.c b/common/wireaddr.c index 4950d1510aed..1c8be2fa88fa 100644 --- a/common/wireaddr.c +++ b/common/wireaddr.c @@ -375,18 +375,23 @@ bool is_wildcardaddr(const char *arg) return streq(arg, ""); } -/* Rules: +/* The rules to check for DNS FQDNs, see `man 7 hostname` * * - not longer than 255 - * - segments are separated with . dot - * - segments do not start or end with - hyphen - * - segments must be longer thant zero - * - lowercase a-z and digits 0-9 and - hyphen + * - labels are separated with . dot + * - labels do not start or end with - hyphen + * - labels must be longer than zero + * - allow ASCII letters a-z, A-Z, digits 0-9 and - hyphen + * - additionally we allow for an '_' underscore in the first hostname label + * - other characters must be punycoded rfc3492 + * + * See `man 7 hostname` and https://www.rfc-editor.org/rfc/rfc1035 */ bool is_dnsaddr(const char *arg) { size_t i, arglen; int lastdot; + int numlabels; if (is_ipaddr(arg) || is_toraddr(arg) || is_wildcardaddr(arg)) return false; @@ -396,8 +401,10 @@ bool is_dnsaddr(const char *arg) if (arglen > 255) return false; lastdot = -1; + numlabels = 0; for (i = 0; i < arglen; i++) { if (arg[i] == '.') { + numlabels++; /* segment must be longer than zero */ if (i - lastdot == 1) return false; @@ -412,10 +419,15 @@ bool is_dnsaddr(const char *arg) return false; if (arg[i] >= 'a' && arg[i] <= 'z') continue; + if (arg[i] >= 'A' && arg[i] <= 'Z') + continue; if (arg[i] >= '0' && arg[i] <= '9') continue; if (arg[i] == '-') continue; + /* allow for _ underscores in the first hostname part */ + if (arg[i] == '_' && numlabels == 0) + continue; return false; } return true; diff --git a/connectd/Makefile b/connectd/Makefile index 684349c41ed6..b8c9b3d341ac 100644 --- a/connectd/Makefile +++ b/connectd/Makefile @@ -60,8 +60,9 @@ CONNECTD_COMMON_OBJS := \ common/memleak.o \ common/msg_queue.o \ common/node_id.o \ - common/onion.o \ + common/onion_decode.o \ common/onionreply.o \ + common/onion_message_parse.o \ common/ping.o \ common/per_peer_state.o \ common/psbt_open.o \ diff --git a/connectd/connectd.c b/connectd/connectd.c index debe87693bb3..f66abd13f7bb 100644 --- a/connectd/connectd.c +++ b/connectd/connectd.c @@ -1716,7 +1716,8 @@ static void add_gossip_addrs(struct wireaddr_internal **addrs, static void try_connect_peer(struct daemon *daemon, const struct node_id *id, struct wireaddr *gossip_addrs, - struct wireaddr_internal *addrhint STEALS) + struct wireaddr_internal *addrhint STEALS, + bool dns_fallback) { struct wireaddr_internal *addrs; bool use_proxy = daemon->always_use_proxy; @@ -1762,7 +1763,7 @@ static void try_connect_peer(struct daemon *daemon, chainparams_get_ln_port(chainparams)); tal_arr_expand(&addrs, unresolved); } - } else if (daemon->use_dns) { + } else if (daemon->use_dns && dns_fallback) { add_seed_addrs(&addrs, id, daemon->broken_resolver_response); } @@ -1804,12 +1805,14 @@ static void connect_to_peer(struct daemon *daemon, const u8 *msg) struct node_id id; struct wireaddr_internal *addrhint; struct wireaddr *addrs; + bool dns_fallback; if (!fromwire_connectd_connect_to_peer(tmpctx, msg, - &id, &addrs, &addrhint)) + &id, &addrs, &addrhint, + &dns_fallback)) master_badmsg(WIRE_CONNECTD_CONNECT_TO_PEER, msg); - try_connect_peer(daemon, &id, addrs, addrhint); + try_connect_peer(daemon, &id, addrs, addrhint, dns_fallback); } /* lightningd tells us a peer should be disconnected. */ diff --git a/connectd/connectd_wire.csv b/connectd/connectd_wire.csv index 4b8666c26690..d8b77d0a4295 100644 --- a/connectd/connectd_wire.csv +++ b/connectd/connectd_wire.csv @@ -50,6 +50,7 @@ msgdata,connectd_connect_to_peer,id,node_id, msgdata,connectd_connect_to_peer,len,u32, msgdata,connectd_connect_to_peer,addrs,wireaddr,len msgdata,connectd_connect_to_peer,addrhint,?wireaddr_internal, +msgdata,connectd_connect_to_peer,dns_fallback,bool, # Connectd->master: connect failed. msgtype,connectd_connect_failed,2020 @@ -118,12 +119,8 @@ msgdata,connectd_ping_reply,totlen,u16, # We tell lightningd we got an onionmsg msgtype,connectd_got_onionmsg_to_us,2145 -msgdata,connectd_got_onionmsg_to_us,node_alias,pubkey, -msgdata,connectd_got_onionmsg_to_us,self_id,?secret, -msgdata,connectd_got_onionmsg_to_us,reply_blinding,?pubkey, -msgdata,connectd_got_onionmsg_to_us,reply_first_node,?pubkey, -msgdata,connectd_got_onionmsg_to_us,reply_path_len,u16, -msgdata,connectd_got_onionmsg_to_us,reply_path,onionmsg_path,reply_path_len +msgdata,connectd_got_onionmsg_to_us,path_secret,?secret, +msgdata,connectd_got_onionmsg_to_us,reply,?blinded_path, msgdata,connectd_got_onionmsg_to_us,rawmsg_len,u16, msgdata,connectd_got_onionmsg_to_us,rawmsg,u8,rawmsg_len diff --git a/connectd/multiplex.c b/connectd/multiplex.c index 2f74d1ea3a3a..cab431cfd153 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -104,8 +104,7 @@ static void close_peer_io_timeout(struct peer *peer) static void close_subd_timeout(struct subd *subd) { - /* BROKEN means we'll trigger CI if we see it, though it's possible */ - status_peer_broken(&subd->peer->id, "Subd did not close, forcing close"); + status_peer_debug(&subd->peer->id, "Subd did not close, forcing close"); io_close(subd->conn); } diff --git a/connectd/onion_message.c b/connectd/onion_message.c index d84349d53a4b..a20119deaafb 100644 --- a/connectd/onion_message.c +++ b/connectd/onion_message.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -39,15 +40,13 @@ void onionmsg_req(struct daemon *daemon, const u8 *msg) void handle_onion_message(struct daemon *daemon, struct peer *peer, const u8 *msg) { - enum onion_wire badreason; - struct onionpacket *op; - struct pubkey blinding, ephemeral; - struct route_step *rs; + struct pubkey blinding; u8 *onion; - struct tlv_onionmsg_payload *om; - struct secret ss, onion_ss; - const u8 *cursor; - size_t max, maxlen; + u8 *next_onion_msg; + struct pubkey next_node; + struct tlv_onionmsg_tlv *final_om; + struct pubkey final_alias; + struct secret *final_path_id; /* Ignore unless explicitly turned on. */ if (!feature_offered(daemon->our_features->bits[NODE_ANNOUNCE_FEATURE], @@ -62,110 +61,28 @@ void handle_onion_message(struct daemon *daemon, return; } - /* We unwrap the onion now. */ - op = parse_onionpacket(tmpctx, onion, tal_bytelen(onion), &badreason); - if (!op) { - status_peer_debug(&peer->id, "onion msg: can't parse onionpacket: %s", - onion_wire_name(badreason)); - return; - } - - ephemeral = op->ephemeralkey; - if (!unblind_onion(&blinding, ecdh, &ephemeral, &ss)) { - status_peer_debug(&peer->id, "onion msg: can't unblind onionpacket"); - return; - } - - /* Now get onion shared secret and parse it. */ - ecdh(&ephemeral, &onion_ss); - rs = process_onionpacket(tmpctx, op, &onion_ss, NULL, 0, false); - if (!rs) { - status_peer_debug(&peer->id, - "onion msg: can't process onionpacket ss=%s", - type_to_string(tmpctx, struct secret, &onion_ss)); - return; - } - - /* The raw payload is prepended with length in the modern onion. */ - cursor = rs->raw_payload; - max = tal_bytelen(rs->raw_payload); - maxlen = fromwire_bigsize(&cursor, &max); - if (!cursor) { - status_peer_debug(&peer->id, "onion msg: Invalid hop payload %s", - tal_hex(tmpctx, rs->raw_payload)); - return; - } - if (maxlen > max) { - status_peer_debug(&peer->id, "onion msg: overlong hop payload %s", - tal_hex(tmpctx, rs->raw_payload)); - return; - } - - om = fromwire_tlv_onionmsg_payload(msg, &cursor, &maxlen); - if (!om) { - status_peer_debug(&peer->id, "onion msg: invalid onionmsg_payload %s", - tal_hex(tmpctx, rs->raw_payload)); + if (!onion_message_parse(tmpctx, onion, &blinding, &peer->id, + &daemon->mykey, + &next_onion_msg, &next_node, + &final_om, &final_alias, &final_path_id)) return; - } - if (rs->nextcase == ONION_END) { - struct pubkey *reply_blinding, *first_node_id, me, alias; - const struct onionmsg_path **reply_path; - struct secret *self_id; + if (final_om) { u8 *omsg; - if (!pubkey_from_node_id(&me, &daemon->id)) { - status_broken("Failed to convert own id"); - return; - } - - /* Final enctlv is actually optional */ - if (!om->encrypted_data_tlv) { - alias = me; - self_id = NULL; - } else if (!decrypt_final_enctlv(tmpctx, &blinding, &ss, - om->encrypted_data_tlv, &me, &alias, - &self_id)) { - status_peer_debug(&peer->id, - "onion msg: failed to decrypt enctlv" - " %s", tal_hex(tmpctx, om->encrypted_data_tlv)); - return; - } - - if (om->reply_path) { - first_node_id = &om->reply_path->first_node_id; - reply_blinding = &om->reply_path->blinding; - reply_path = cast_const2(const struct onionmsg_path **, - om->reply_path->path); - } else { - first_node_id = NULL; - reply_blinding = NULL; - reply_path = NULL; - } - /* We re-marshall here by policy, before handing to lightningd */ omsg = tal_arr(tmpctx, u8, 0); - towire_tlvstream_raw(&omsg, om->fields); + towire_tlvstream_raw(&omsg, final_om->fields); daemon_conn_send(daemon->master, take(towire_connectd_got_onionmsg_to_us(NULL, - &alias, self_id, - reply_blinding, - first_node_id, - reply_path, + final_path_id, + final_om->reply_path, omsg))); } else { - struct pubkey next_node, next_blinding; - struct peer *next_peer; struct node_id next_node_id; + struct peer *next_peer; - /* This fails as expected if no enctlv. */ - if (!decrypt_enctlv(&blinding, &ss, om->encrypted_data_tlv, &next_node, - &next_blinding)) { - status_peer_debug(&peer->id, - "onion msg: invalid enctlv %s", - tal_hex(tmpctx, om->encrypted_data_tlv)); - return; - } + assert(next_onion_msg); /* FIXME: Handle short_channel_id! */ node_id_from_pubkey(&next_node_id, &next_node); @@ -178,10 +95,7 @@ void handle_onion_message(struct daemon *daemon, &next_node)); return; } - inject_peer_msg(next_peer, - take(towire_onion_message(NULL, - &next_blinding, - serialize_onionpacket(tmpctx, rs->next)))); + inject_peer_msg(next_peer, take(next_onion_msg)); } } diff --git a/contrib/bootstrap-node.sh b/contrib/bootstrap-node.sh index 13c26eae9e8d..ae648b99f1cf 100755 --- a/contrib/bootstrap-node.sh +++ b/contrib/bootstrap-node.sh @@ -47,8 +47,7 @@ fi # IPV4: 03ee180e8ee07f1f9c9987d98b5d5decf6bad7d058bdd8be3ad97c8e0dd2cdc7ba@85.214.212.104 # IPV4: 03f2d334ab70d50623c889400941dc80874f38498e7d09029af0f701d7089aa516@158.174.131.171 -NUM=$(grep -c '^# IPV4:' "$0") -PEERS=$(grep '^# IPV4:' "$0" | head -n $(($(date +%s) % (NUM - 3) )) | tail -n 3 | cut -d' ' -f3-) +PEERS=$(grep '^# IPV4:' "$0" | sort -R | tail -n 3 | cut -d' ' -f3-) for p in $PEERS; do echo "Trying to connect to random peer $p..." diff --git a/contrib/docker/linuxarm32v7.Dockerfile b/contrib/docker/linuxarm32v7.Dockerfile index acf2fa1784f8..8bea5cbfadc9 100644 --- a/contrib/docker/linuxarm32v7.Dockerfile +++ b/contrib/docker/linuxarm32v7.Dockerfile @@ -5,7 +5,7 @@ # * final: Copy the binaries required at runtime # The resulting image uploaded to dockerhub will only contain what is needed for runtime. # From the root of the repository, run "docker build -t yourimage:yourtag -f contrib/linuxarm32v7.Dockerfile ." -FROM debian:buster-slim as downloader +FROM debian:bullseye-slim as downloader RUN set -ex \ && apt-get update \ @@ -18,24 +18,23 @@ RUN wget -qO /opt/tini "https://github.com/krallin/tini/releases/download/v0.18. && echo "01b54b934d5f5deb32aa4eb4b0f71d0e76324f4f0237cc262d59376bf2bdc269 /opt/tini" | sha256sum -c - \ && chmod +x /opt/tini -ARG BITCOIN_VERSION=0.18.1 +ARG BITCOIN_VERSION=22.0 ENV BITCOIN_TARBALL bitcoin-$BITCOIN_VERSION-arm-linux-gnueabihf.tar.gz ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-$BITCOIN_VERSION/$BITCOIN_TARBALL -ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-$BITCOIN_VERSION/SHA256SUMS.asc +ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-$BITCOIN_VERSION/SHA256SUMS RUN mkdir /opt/bitcoin && cd /opt/bitcoin \ && wget -qO $BITCOIN_TARBALL "$BITCOIN_URL" \ - && wget -qO bitcoin.asc "$BITCOIN_ASC_URL" \ - && grep $BITCOIN_TARBALL bitcoin.asc | tee SHA256SUMS.asc \ - && sha256sum -c SHA256SUMS.asc \ + && wget -qO bitcoin "$BITCOIN_ASC_URL" \ + && grep $BITCOIN_TARBALL bitcoin | tee SHA256SUMS \ + && sha256sum -c SHA256SUMS \ && BD=bitcoin-$BITCOIN_VERSION/bin \ && tar -xzvf $BITCOIN_TARBALL $BD/bitcoin-cli --strip-components=1 \ && rm $BITCOIN_TARBALL -ENV LITECOIN_VERSION 0.14.2 -ENV LITECOIN_TARBALL litecoin-$LITECOIN_VERSION-arm-linux-gnueabihf.tar.gz -ENV LITECOIN_URL https://download.litecoin.org/litecoin-$LITECOIN_VERSION/linux/$LITECOIN_TARBALL -ENV LITECOIN_SHA256 e79f2a8e8e1b9920d07cff8482237b56aa4be2623103d3d2825ce09a2cc2f6d7 +ENV LITECOIN_VERSION 0.16.3 +ENV LITECOIN_URL https://download.litecoin.org/litecoin-${LITECOIN_VERSION}/linux/litecoin-${LITECOIN_VERSION}-arm-linux-gnueabihf.tar.gz +ENV LITECOIN_SHA256 fc6897265594985c1d09978b377d51a01cc13ee144820ddc59fbb7078f122f99 # install litecoin binaries RUN mkdir /opt/litecoin && cd /opt/litecoin \ @@ -45,11 +44,36 @@ RUN mkdir /opt/litecoin && cd /opt/litecoin \ && tar -xzvf litecoin.tar.gz $BD/litecoin-cli --strip-components=1 --exclude=*-qt \ && rm litecoin.tar.gz -FROM debian:buster-slim as builder +FROM debian:bullseye-slim as builder ENV LIGHTNINGD_VERSION=master -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates autoconf automake build-essential gettext git libtool python3 python3-pip python3-setuptools python3-mako wget gnupg dirmngr git lowdown \ - libc6-armhf-cross gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf + +RUN apt-get update -qq && \ + apt-get install -qq -y --no-install-recommends \ + autoconf \ + automake \ + build-essential \ + ca-certificates \ + curl \ + dirmngr \ + gettext \ + git \ + gnupg \ + libpq-dev \ + libtool \ + libffi-dev \ + python3 \ + python3-dev \ + python3-mako \ + python3-pip \ + python3-venv \ + python3-setuptools \ + wget && \ + # arm32v7 compilers + apt-get install -qq -y --no-install-recommends \ + libc6-armhf-cross \ + gcc-arm-linux-gnueabihf \ + g++-arm-linux-gnueabihf ENV target_host=arm-linux-gnueabihf @@ -62,12 +86,12 @@ STRIP=${target_host}-strip \ QEMU_LD_PREFIX=/usr/${target_host} \ HOST=${target_host} -RUN wget -q https://zlib.net/zlib-1.2.12.tar.gz \ -&& tar xvf zlib-1.2.12.tar.gz \ -&& cd zlib-1.2.12 \ +RUN wget -q https://zlib.net/fossils/zlib-1.2.13.tar.gz \ +&& tar xvf zlib-1.2.13.tar.gz \ +&& cd zlib-1.2.13 \ && ./configure --prefix=$QEMU_LD_PREFIX \ && make \ -&& make install && cd .. && rm zlib-1.2.12.tar.gz && rm -rf zlib-1.2.12 +&& make install && cd .. && rm zlib-1.2.13.tar.gz && rm -rf zlib-1.2.13 RUN apt-get install -y --no-install-recommends unzip tclsh \ && wget -q https://www.sqlite.org/2019/sqlite-src-3290000.zip \ @@ -92,13 +116,28 @@ RUN git clone --recursive /tmp/lightning . && \ ARG DEVELOPER=0 ENV PYTHON_VERSION=3 -RUN ./configure --prefix=/tmp/lightning_install --enable-static && make -j3 DEVELOPER=${DEVELOPER} && make install -FROM arm32v7/debian:buster-slim as final +RUN curl -sSL https://install.python-poetry.org | python3 - \ +&& pip3 install -U pip \ +&& pip3 install -U wheel \ +&& /root/.local/bin/poetry install + +RUN ./configure --prefix=/tmp/lightning_install --enable-static && \ +make DEVELOPER=${DEVELOPER} && \ +/root/.local/bin/poetry run make install + +FROM arm32v7/debian:bullseye-slim as final COPY --from=downloader /usr/bin/qemu-arm-static /usr/bin/qemu-arm-static COPY --from=downloader /opt/tini /usr/bin/tini -RUN apt-get update && apt-get install -y --no-install-recommends socat inotify-tools python3 python3-pip \ - && rm -rf /var/lib/apt/lists/* + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + socat \ + inotify-tools \ + python3 \ + python3-pip \ + libpq5 && \ + rm -rf /var/lib/apt/lists/* ENV LIGHTNINGD_DATA=/root/.lightning ENV LIGHTNINGD_RPC_PORT=9835 diff --git a/contrib/docker/linuxarm64v8.Dockerfile b/contrib/docker/linuxarm64v8.Dockerfile index 82741674e932..79f3ed8bad59 100644 --- a/contrib/docker/linuxarm64v8.Dockerfile +++ b/contrib/docker/linuxarm64v8.Dockerfile @@ -5,7 +5,7 @@ # * final: Copy the binaries required at runtime # The resulting image uploaded to dockerhub will only contain what is needed for runtime. # From the root of the repository, run "docker build -t yourimage:yourtag -f contrib/linuxarm64v8.Dockerfile ." -FROM debian:buster-slim as downloader +FROM debian:bullseye-slim as downloader RUN set -ex \ && apt-get update \ @@ -18,24 +18,24 @@ RUN wget -qO /opt/tini "https://github.com/krallin/tini/releases/download/v0.18. && echo "7c5463f55393985ee22357d976758aaaecd08defb3c5294d353732018169b019 /opt/tini" | sha256sum -c - \ && chmod +x /opt/tini -ARG BITCOIN_VERSION=0.18.1 +ARG BITCOIN_VERSION=22.0 ENV BITCOIN_TARBALL bitcoin-$BITCOIN_VERSION-aarch64-linux-gnu.tar.gz ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-$BITCOIN_VERSION/$BITCOIN_TARBALL -ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-$BITCOIN_VERSION/SHA256SUMS.asc +ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-$BITCOIN_VERSION/SHA256SUMS RUN mkdir /opt/bitcoin && cd /opt/bitcoin \ && wget -qO $BITCOIN_TARBALL "$BITCOIN_URL" \ - && wget -qO bitcoin.asc "$BITCOIN_ASC_URL" \ - && grep $BITCOIN_TARBALL bitcoin.asc | tee SHA256SUMS.asc \ - && sha256sum -c SHA256SUMS.asc \ + && wget -qO bitcoin "$BITCOIN_ASC_URL" \ + && grep $BITCOIN_TARBALL bitcoin | tee SHA256SUMS \ + && sha256sum -c SHA256SUMS \ && BD=bitcoin-$BITCOIN_VERSION/bin \ && tar -xzvf $BITCOIN_TARBALL $BD/bitcoin-cli --strip-components=1 \ && rm $BITCOIN_TARBALL -ENV LITECOIN_VERSION 0.14.2 -ENV LITECOIN_TARBALL litecoin-$LITECOIN_VERSION-aarch64-linux-gnu.tar.gz -ENV LITECOIN_URL https://download.litecoin.org/litecoin-$LITECOIN_VERSION/linux/$LITECOIN_TARBALL -ENV LITECOIN_SHA256 69449c3c8206f75cfdef929562b323326f1d0496f77f82608f9a974cbb2fd373 + +ENV LITECOIN_VERSION 0.16.3 +ENV LITECOIN_URL https://download.litecoin.org/litecoin-${LITECOIN_VERSION}/linux/litecoin-${LITECOIN_VERSION}-aarch64-linux-gnu.tar.gz +ENV LITECOIN_SHA256 3284316bdf10496528b3cd730877be3a1ea34add49dfc88fe0e96eb9925c1f08 # install litecoin binaries RUN mkdir /opt/litecoin && cd /opt/litecoin \ @@ -45,11 +45,36 @@ RUN mkdir /opt/litecoin && cd /opt/litecoin \ && tar -xzvf litecoin.tar.gz $BD/litecoin-cli --strip-components=1 --exclude=*-qt \ && rm litecoin.tar.gz -FROM debian:buster-slim as builder +FROM debian:bullseye-slim as builder ENV LIGHTNINGD_VERSION=master -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates autoconf automake build-essential gettext git libtool python3 python3-pip python3-setuptools python3-mako wget gnupg dirmngr git lowdown \ - libc6-arm64-cross gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + +RUN apt-get update -qq && \ + apt-get install -qq -y --no-install-recommends \ + autoconf \ + automake \ + build-essential \ + ca-certificates \ + curl \ + dirmngr \ + gettext \ + git \ + gnupg \ + libpq-dev \ + libtool \ + libffi-dev \ + python3 \ + python3-dev \ + python3-mako \ + python3-pip \ + python3-venv \ + python3-setuptools \ + wget && \ + # arm64v8 compilers + apt-get install -qq -y --no-install-recommends \ + libc6-arm64-cross \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu ENV target_host=aarch64-linux-gnu @@ -62,12 +87,12 @@ STRIP=${target_host}-strip \ QEMU_LD_PREFIX=/usr/${target_host} \ HOST=${target_host} -RUN wget -q https://zlib.net/zlib-1.2.12.tar.gz \ -&& tar xvf zlib-1.2.12.tar.gz \ -&& cd zlib-1.2.12 \ +RUN wget -q https://zlib.net/fossils/zlib-1.2.13.tar.gz \ +&& tar xvf zlib-1.2.13.tar.gz \ +&& cd zlib-1.2.13 \ && ./configure --prefix=$QEMU_LD_PREFIX \ && make \ -&& make install && cd .. && rm zlib-1.2.12.tar.gz && rm -rf zlib-1.2.12 +&& make install && cd .. && rm zlib-1.2.13.tar.gz && rm -rf zlib-1.2.13 RUN apt-get install -y --no-install-recommends unzip tclsh \ && wget -q https://www.sqlite.org/2019/sqlite-src-3290000.zip \ @@ -91,13 +116,28 @@ RUN git clone --recursive /tmp/lightning . && \ ARG DEVELOPER=0 ENV PYTHON_VERSION=3 -RUN ./configure --prefix=/tmp/lightning_install --enable-static && make -j3 DEVELOPER=${DEVELOPER} && make install -FROM arm64v8/debian:buster-slim as final +RUN curl -sSL https://install.python-poetry.org | python3 - \ +&& pip3 install -U pip \ +&& pip3 install -U wheel \ +&& /root/.local/bin/poetry install + +RUN ./configure --prefix=/tmp/lightning_install --enable-static && \ +make DEVELOPER=${DEVELOPER} && \ +/root/.local/bin/poetry run make install + +FROM arm64v8/debian:bullseye-slim as final COPY --from=downloader /usr/bin/qemu-aarch64-static /usr/bin/qemu-aarch64-static COPY --from=downloader /opt/tini /usr/bin/tini -RUN apt-get update && apt-get install -y --no-install-recommends socat inotify-tools python3 python3-pip \ - && rm -rf /var/lib/apt/lists/* + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + socat \ + inotify-tools \ + python3 \ + python3-pip \ + libpq5 && \ + rm -rf /var/lib/apt/lists/* ENV LIGHTNINGD_DATA=/root/.lightning ENV LIGHTNINGD_RPC_PORT=9835 diff --git a/contrib/docker/scripts/build.sh b/contrib/docker/scripts/build.sh index 82c498311be1..806e28e439e9 100755 --- a/contrib/docker/scripts/build.sh +++ b/contrib/docker/scripts/build.sh @@ -52,14 +52,14 @@ then export STRIP="$TARGET_HOST"-strip export CONFIGURATION_WRAPPER=qemu-"${TARGET_HOST%%-*}"-static - wget -q https://zlib.net/zlib-1.2.12.tar.gz - tar xf zlib-1.2.12.tar.gz - cd zlib-1.2.12 || exit 1 + wget -q https://zlib.net/fossils/zlib-1.2.13.tar.gz + tar xf zlib-1.2.13.tar.gz + cd zlib-1.2.13 || exit 1 ./configure --prefix="$QEMU_LD_PREFIX" make sudo make install cd .. || exit 1 - rm zlib-1.2.12.tar.gz && rm -rf zlib-1.2.12 + rm zlib-1.2.13.tar.gz && rm -rf zlib-1.2.13 wget -q https://www.sqlite.org/2018/sqlite-src-3260000.zip unzip -q sqlite-src-3260000.zip diff --git a/contrib/init/lightningd.service b/contrib/init/lightningd.service index 2341d2119b1b..6973d86d0124 100644 --- a/contrib/init/lightningd.service +++ b/contrib/init/lightningd.service @@ -46,6 +46,8 @@ NoNewPrivileges=true PrivateDevices=true # Deny the creation of writable and executable memory mappings. +# NOTE: This seems to break node.js plugins, notably Ride-The-Lightning/c-lightning-REST: +# https://github.com/Ride-The-Lightning/c-lightning-REST/issues/116 MemoryDenyWriteExecute=true [Install] diff --git a/contrib/keys/cdecker.txt b/contrib/keys/cdecker.txt index 9cd1f7a675fa..83910df481bf 100644 --- a/contrib/keys/cdecker.txt +++ b/contrib/keys/cdecker.txt @@ -413,294 +413,314 @@ k5g9yZQRPGPjW50CVkYusMIvNqZI4KFL0z8/vVdxN7qRYpsBUwptBYenRUKAPLvl 5u6tw4FwxcthdT3aUBddf/XoXUP7P61GUO6dJ2zoBd4kdoEZc9eL3/B/qPIecJZG NvAEHPPudwj7BTtD7XdUBX5QVuNOa2zeXGsy6iOd2UBV7bXPzGh/1fsDlP4U86dq rpSLQDdAkgTQv6AqHnwJ/ULOTGWkd1OQoElp2x1JWSUCkIJGTFRLVt/+awPfNPC0 -lGtRn3rIZ2Z+AfGyc1+DqtrUd8lbtClDaHJpc3RpYW4gRGVja2VyIDxjZGVja2Vy -QHRpay5lZS5ldGh6LmNoPokCOAQTAQIAIgUCVk+CAQIbAwYLCQgHAwIGFQgCCQoL -BBYCAwECHgECF4AACgkQom1tn+CI7VhqGhAAifBCE3iYA417U3U2590hYOnsxZbB -DPeh8w9AH8Vyc8zV25wpAhI1CkLhl0mSSUvXqH87N/g8jZ1tQgIEYokz5A5r5lb8 -Ak93JsVg7I66fM66fF01Tv++zL1c5Udt1HuceoX7cyMpTndB3ChnrFnudZ3yeZ0N -zeTNkP953BOVZDP76ktu+xtl+9z5X3ANLT2xUSpafNdn4KInrh7zzfgMZQNQJgbH -nj1dlud+d8uo45peoPOBUexXX/NhQLyGmbxM3oooxKESMR388hsf7YNTdaSN6SVl -H+nlIeDSkI+N7ljQzN6oauZpGr+IBfS4y9qRjq9/BnDIHQOpmsXso/TuKoBj7s1y -j1xNOS/bL/DpDiM2VNBzU+e3TbTttBDYA/1EmZwZXL05Z+gPUHK1zA8LPvOGsbBs -XQV726xgtq3KVfoiHjNcKJeUyqh2bV8FPfSkMQj29UI8dXRdgLSInMoWqHf5O0bR -SAiFBE1qzV9MO9vIPhXCZ5mPQ2hHUA+cSVW1Sw2Tj3Z98qVlP5/4T6qnUspQAgvH -oTwCF/Eb5+X3IO232dnTKZz09/E35g/vkbphacZ3vZjn763L6XEpVrEibQxuwppC -Tls+6EHED4OiSjNN8gatWZnZ90ABoGgDdzfJVW5Qp+pwA3q5G2BSzzExadnjLEq8 -AMfjwhEE3CaVdp+IXgQQEQoABgUCWH5qWAAKCRC4T+pJ/abHjm9XAQD0w2hfQDZj -N3E2BrUDe7lMNDMHfiSDAMORVuWAN35iAQEA4HbcWYVU7q9kkr35HsxPu0jroqYF -UNJ1xxZFPz0Kh5eIXgQTEQgABgUCWApxpAAKCRB9fRG/Yp1aZ0VUAQDE+NRvvx2I -QSYAwdzrcq/wkxg4JB+M/OoYTBfcOyTM7AEA6AVE/GAY/3ZrqLieI010v9qYGBpc -b55qqwtIlnqwnF2JAZgEEwEIAIIWIQQ37H17CiF820tOAH5/qxFCZ+T6BAUCWqAn -YwWDCWYBgF4UgAAAAAAVAEBibG9ja2hhc2hAYml0Y29pbi5vcmcwMDAwMDAwMDAw -MDAwMDAwMDAzMWY5NjFjZGJiNDcyOWI2NzEwZWY5ODkxZjgzZDEwOWM4MDQ4ODk1 -OWUwMWNjAAoJEH+rEUJn5PoEtioIAI3PAC1FIIKV7NJ8dm008qcO79ck1hGEu3XY -8bSHe1ryB+0/VJ1CFL+wz20HWBRNZ/dDl8yTHnr/s3bXpaPk7RQZuh9H/aQuiwZX -Y3IBPVySnSn756FUWAQBBU/iTPH77k3Gq7AjrTGjZ9l2e7xhhhBcFRe6MSraUmmH -pGjtB5PTUKRUnOP+UbXJHhGdxNISC0WgPgrHCBJH/qg89ysum6KfTwXllzORcZv6 -pmJZQJkYyTljGt6ik1XIiW/PSg6YuGSfzdsodxldWQQVU3aQq9YBmAL2/cgFBY+P -DzDWMcwtEM4CCePAqEb/AIWAGBtAAk9DtzZtWnx6JiWfy41AwqeJAhsEEAEIAAYF -AlqhfOYACgkQF1ZXMuCOXkGK4g/3XvixqlqsFFPx5hPoTeJcsUkdzHf1Ohk6JVPi -Cwrxf0QKldX4SOUVYLrFOZJAmTu+KzPugdhIJ2oR/uCcket6KtOnIhWcPtM8Aptm -fAjGwHtxOQQAQ63kl3euhxin0ljI48bu1/0a8bwy4ROz+l7JYwiswvL7tfEFzHJo -rli/dILjTsf2TYDogG4pOwKwFmsKhTN27ZaSeJ9CoNa/oIQzd5S/28vtvd9CVQhh -oQlVCHz0gHRmG0tJAWzAF6n084BEvu++mofYv8rdyZ1xManWV1peLd0tsx1r/bJO -lhF6uGmaZmuaf1f96X3q0zl/6dGPTkXN9xJ65uyaN8W/ZcfdaKthT2MUBkY62ehp -oCvS6j6USVl92erL5bgD+3b1XXFxUBZPqnWc0LKUgFAJmXhNR1zOVTZem0wtbkUY -6l9o0Wvbm15gaNlHnIkCjCWm85TxMa9TYqaKYLvfDIgwISEHO+WaXC8k3XByZR1z -lRB7hWlRFek84E+3W95Z7qqGfuO+EG04x1czC8UwG6yw6DfZIewnMWYftFL5aPsd -zRE15w9VeSET0kPAzx4rqo0SIq0MK1sk+GwjiPEeOoV9E95EyK8KnnRt/PYwLeUp -Yy4HonSt+5h5/xvId88gy7s3EZB167zWVFU4i0NYgM/pLgJn4q10TRK2kiYxpfzR -O2KQS4kCHAQQAQIABgUCV2iwGQAKCRDZIA5s0a248WGXD/9B3mxvezn45CVcg14E -iXWcCuQUGxrTGghl5pH1L16R7aJOJSE9iuR27fhZ6KlbdeK21Jyr0nk3V3cjj+H8 -AARvneeGoGgMUNB9oaSZ/IsUXBiz9mjcst+7rZ5ePYxzTAudIDSAtIubiWwrq8Wh -no7oI+7Yb5iE60oRPIFjtyFynoglhJ4w1W3dNq2QIddmFRsHZRKxoJgLl87s6xpG -X/HxaBs8xNq8f/Qw6ONjV2zPShIGGO1LarwmsRNiKk93z2BIEEN0ZS70TXEyYqJC -A3ffyHB55ws4zeWvbtYBWEUEpOwSNKATxL/HCAsMLfojRnMxB+1xKRGuYoYqQrYU -r3v7EPBs5i72nv9mqFJNY9s0f3Zi8TdsuNCT2aKTfyaQHt3cFhFTYX1wB+1Yg3An -do2MbKtBgVDSY93bxN4jLUPUi54hPPi6USBOyJKGUvgJfdI0hfq2TvA3k3LKppSe -mgx+ngOZm+Go4hgMhD1ZDoOEwFygHalteVLs29rugxqwEDXV16oiktgRUnBhd77W -l6jbrcLjS8Eefr5myVeWRhTCaFmDOFGytOjaGSFAqrKuDrbYRpqYEDNf/DJNmA/7 -XeRfzxrA0z2+mhLxo/IemckWyY9OJfgzPtwZD1kFetgmF0Dgemq1oZYCiEekyefk -P/KAlVSdtW8vrnQzHeEFVrUpkYkCHAQQAQIABgUCWqAh6AAKCRDTABFuHIdaPbCQ -D/9cFQb9SP7iw4z6CDoYkhLedKjyTEcpRC1eLFBgCvBSDLl2BVov+aY1EqHoL4fg -Ylj7bOPZk26mcJJOGKFOLcCLplLAHy49bW2ZT0zUW3OV4eNE2nbhyqgukqVUYXmF -StJdhrIJPKA9f6KCSvqR90DuLJyl246NYMYpSnTvolgBdiNtUBpcrmRrRJWAceR4 -H40/5H6Day9WVhQxRwmEkKV/ZV1kjX0D4WWODpk32u/qxoerFdGinl5ejGzjbhZg -VKiIQjBkvbwDlBy05Z4toQ/uqljyDCL5BAwqeO0Yub957mDigu7Iuxh1EAeh9s64 -iIODbKzlvKYtqA1cbPTjzzZX43620O7NoCjyv2z2dMOAgY8RP6M2WPMxM0jQVMT2 -gwqN7H3LwL+eaahCPJbTwjEu/9Fml7vJUfgTw9d2514U6T0WOBqPqUmOhFzwlfYD -6Sq8pQZMYrNCOEBDXjC3IBqY6b4xhXHqXWFxIjCTIq66vL7GkzW/0Um5KTN+e/Bt -qGuUVHDxP3dB6rH7OIQ6rpBgNnb4y/qV93BTSbTe9mZhgF4n8WdEc4T6vfb5EgU9 -N58hirJYxFTmO99UKHLILihuVzsjQIzbnips3F4Wg9qmfexksMDGPM5tOj+eIEyH -smSl5W1YLZfmK5zqropRrE4KzmEITu0lPgiuUFcAdiq5yIkCHAQQAQgABgUCWLM3 -PQAKCRAAAjQFnLUHFRzWD/9eb2B84yzyZrzR906EyzDVbtOiMhR6hAdmocGr2jad -P0JLGpw+eSmHgvXaSbpyhL6ENgViGwYhhGW9HgHRlCazVaF5FEMICpIc/i29j0c/ -6sB+PbhwP+6prEsWYl6VXmu84dtCuVqjIuNiIYoY5eeFk1Ds1FDPkxwepyLq++Yl -ROBr1Mt0sqeJ2duPLjPVFNBcqZnPGlTh6ChEf6G+6NT44NW5pRJiF6ixguFYkd4y -/f5lSyX6zNBrstsD8FOEzL+82nrFTy6M7RDrgaHG/TLDrcgyKWpn+XvlHdPYOgpy -+elubT1XPVLXrCehI8znULSK1MXuLig3FLjSlNYutCB1YVtKAVfr8cyGKn0+Rp0f -54CYORrFzmsd0F8Y+AM+GHKhfS4xA5WwoZqXz5CqSn8/eNjheKxYD+16COq4OPY1 -1OZy0CjXkjovrV31ARhrO94BwHfIFO3oSEj2pFtQMkMBAd0dPC/S0KnElspQQP/E -IDC/8trQOGrljaIK+NbzfNShCCCH3OKTDoY963mCs9UyNELWrhIMgWKu0sdUi4eb -RRWklzo/jt8EYOZ4emgCZvv0/Meu+ElGVN4t1sL4FmLb3ES1rsVB2C/1jY0aYEnT -6XXiFp0HB0vNa2++98aqp0l1mhNNy4/OGkKEGlplSM1ym7fNRA+bJqGZH+bgjXDr -nokCHAQQAQoABgUCVvMc7QAKCRDAwHYTL/p2leMfD/9P0+zLIAEfDnkOo2OFNgO2 -BzPahBskia8i+Bw0EkcNLbXFjH9OrKnnlNXfM/KOLPC4cw4Ygj0h9LJqEH8+xpkz -HgdmjRMb12mKouEKMbn6n127EfXeTwUejrjQRe98n6ysiQRzmGnWJBojePPwfFEu -X5Cni1qUKCcrB/6HfZGJTft/JxzczhZklPlAYLVkBLjE/Dzkv8XxOGCWcZ5rb/TK -YuNLfd0ESY2RGGXtKxkXKW6kRne0fMhlbGiLWLbbTR2GmU5Xap9nTZa07x5Og+E+ -2vuF8Jg12tbpQ15O5B+JSjwMABxuX7UmXCV5mOHMqmfeGF14X8XEyvmNstYF0bIO -tzp27bA35fyh5ZUlp80bZoroMU6DqYMFhSwzzm6lv2ySwHGfJyvSr/5JyH38I6Qw -9ibOAh9A8sPxkC6rXI0Hr4XKMHo1x6C3idxF6VeJYhDk1T8aNYUfwdV14NGg39kg -KGTdMd1p6iBXJmvXZvf/EH5X0kV6LlJ7FgKWAu/j8r894NsDpFvw1ez9B9K55qvw -LPG8V5StnXzmfenlUXm8BPzA0Wq3i8XdndyED8VO1YcCjLHsfsOk7loh3AeEIDhO -z8CXDT87eC8w6137zo1Ij598CTuDFYcZI1yK9GYVl/vBq6Io55zDHh7+9fL/u/K8 -YTQ5VbbLysXI74txdI/gFIkCHAQQAQoABgUCWHkdcAAKCRD2QwdUEg7C9MXgEACV -kY1O7b15JdnJqshmDYutybYIee+vDe76dPSvYyjTwcLrRiqS6GODgjLBH5HBZdNB -NW8/AdNXxuxzNIbkBcHfs3T1iUjHfh7pybFctn82EtxYSYJlRiuigTxg1yCssxG/ -22/CSedyrchlFP79k/gFJ5fsaHd4E4L3rn302U2xtbih/K36GEJSseOuj0jqCClk -Z1xG/Irg1aufy6/YVz7n6oKP4bOfhyRy92AqbhBM280Viu0Kk8kJtReLiV2LP8uD -7GgLcFaaslGjVoDYaffpjPsc04olZJgodIqQpZ5g80ekDdQi2bpAzLZQJOLAGvOO -ZKzRh2Q95rsIcrlcSjAPw3g/jlnqkd7rX20de8LSbh8EKMRsYPopeBhs/gsm94un -NuyNBMVnYjMHc3+gem/hJCQuDJyqabU2b3tcli25fmpbeKYp0gt1+mn7NbguBl2n -R+yc5GBJn5OSUG4VgXHrmhngNofT3Pz9mD+Z93c7UemHt2eFQYqKAY4kpv5WkAkq -LKvPfrdcYJfjbjI00zDa2nsDZs8gae2x5HqMPcN/1Y2+o6f18YAZ0zmszrliyE8a -osCpLyj9vmLgRp8veOa6w/zpIa59q4/r5Zc/Xw4LhA4rQv+CY1RwtbnJWElQCLvA -vOVNJBDYGKLknk7TobW0DNnAtT/JZqpOudEa+q7orYkCHAQQAQoABgUCWH5p8wAK -CRD402yRNXQF7dyaD/9fgg9aVQ7fQKPZH4SX+AVh8A30NgJh8iWcAsf1pl2Srw6z -x0upsfcUU2cT8yg435yL5Wv9b5x4Lo7Hbdo/bZ9zXVGJdmaXSS55daXgyffkjGks -hjo9aw0eAp0neWyBjIFRMuw82w79icI/UdiTbQ8u7drYrDTjCKcVu5EPHqIzfpma -uZ/U9/7Z9hhzMUNBfaDJjerbsejoD6oV2KfV3SuTVY31Yqr4Fu3DE2oWa9IWjLjA -eZ2FEDNO/siw3Vy9She0SifsVdMKWoQiI0F9zmMuAyOf3f/nu6daaQeMiByoMUVt -iHaYFRjBdzKwDSnP7R8FVU++32bn0UU2vBKMMkzXOON2oLRJ5L2VSwcwuxCm17eJ -YCvrrQSBcbiYoGp5fDnGMIS/wfhU7D9Mf3F9133AIArTtTtWydRLaR5AtWH50zB6 -Z+06EherZJ7gQstphstQb4ZON1uV6sXCfLBDMBKT52xGlK1xV+Rfbh1bIn7EdVPu -H8jHXrJKqgIE+XkEDGx27LjQCJxg5+9B2Hs1hMKOevWpl4APxCGfYy8zCTasINee -2+OAgyiKhXx2cOreI4ag462FLhmbFi0k9fKzBptfiYeyGb1mpHYy7gTU6xIVqrZo -NLukJNevpfRLjvTn1rtiOxohNdTNljEMjAi6DYWe7kzoPpQEkbl82S6m1GTLiYkC -MwQQAQgAHRYhBDX0raYj65/jo7x+9nugNcpbkBcTBQJaoDLWAAoJEHugNcpbkBcT -l4EQAJ5x2vYQTRjf6peXUyvj+rFnrWO5lVBJ9LUYDvGS+JgGTX1Gv+oxGPEKJFKG -o9Wb7En2AGZRx22CdkVB6FelBjaVT5SilLGk7uCdkzzii7vCUUOFcOX3VRY4IB+Z -H0z2LNaF/uwk30QoeyTshaucSHPVeCDWBLCoGohKzHObWaSubwhAEePBUKsvGm6V -Gw+CqbFemIfjg92JTYlWB5r8bxcalPr+kaYqOT4L/o3+wd1NwUxDwr8O3t/2KcB6 -z8KZ8/1rVjs5gzQwuKsD1ikBCtozEF7LwV6fWHVdCQ5pi6LdFSda0vcS2GKuM/8q -Hy0RRk3vQ+wYXGORsaJb7as5G46vfgud9OKGf7AX7DuD+5/3UPmCKswowkByk3Xa -05yWmzcJPeH34p0dqFL/ej7X2u9emMLOON5F26nv90YgNN1A5OGa4Nf5V9eaq84B -UdfBQWGMtV/W8B4R1TxWYgrbfIXNW4I55wPhMbU8ypst0fI/naiFD5KxiOQW576K -qkL6hTLoSRgp+sIHr8Qc66/rwQaBAmtl+FlUX6AAJ+0AfBLEqnfcA1tGb3Ed5OO4 -IbpHIrrVYLFBMntyv4F13nBNLG0RJKPTpWog8AaC3QsBfRdWWdXVjO/mfXTezAfz -3Tn/TgBaEob3u4etHQYI0AwtZ5zdVTOGg26SX3Mb/lIR11KjiQIzBBIBCAAdFiEE -gkVuwmLQjVZ8LxhHrP25OpF13KsFAlq4SjAACgkQrP25OpF13Kt4rw/7B5NfGTJX -daxE/R88cyxbDvIyGxj4eXT5uAz7gpC3CGdreejnMEUjnKsSW/SIg6jR1Xf225e/ -cYg/s7UDh2+zDRhQ1/2HXnuCQAICC576kFkc6plqZVV3EUbYtKdovO2zlksGicCE -QI7WrmcLmeuf8j3JnoSyu7WGo+5sIyACHTiUqpcMysmf8xDOp14EQZ7CkhEsJ/vI -y4ZBz+5u7Mf7t1tXrf3Aw5UjGQyPHVXqYwCz/s1glr+TkZ3P6aaF1n53MEIcfgWg -jJP1+ZcbH8aEaEthLYUBX14OHS1mok2dh8AhNrqL1xfgxJqoAzo87H+hxWSIqhie -yWDXHjAkkObqDoVXVrqngUbZAEMLn3zZaDbsut+CT1gVXRr6zhYoYzj7yjE1auSh -A6rir8UK2bG83naqn3n8Dbh9drV6w90isPffcvhgWXbdCBC0T4RqQrRwBl69f+pV -W95Y2GZ7K+IA9I6BkhHzgGTmBCEnc7sEA9VPMMxw1k5kfAyvkg3VkQenmxTsmRS1 -exMfecHBlj8IgKgamUe/SdS5993DWohetYH3BwFAdUduCr4yRUNGfrvzGWxL8hL0 -h8iE6tN4Fr0kisuGBMwxkki9g/RMgY2sgyVS6YFY8Tnb40seTD47s5oqBOu77nCq -4Px1X3mNM7OphnYrokyL3aMtypObZh94Ec6JAjMEEwEIAB0WIQTEKv98YbPkShRU -zTVXr3YtszUzIgUCWqF33gAKCRBXr3YtszUzItF+D/9kVito8nRUFx2fG0++ZOCS -Y+rdUlDrHUjv9gKZyAgdvVwckTD+iS0aMXFL/A1bHpBEFj5qJTklyyr+hnFL+Ve+ -y2HSoHHHwUR7Hk1wY546kBa8RYTcd0UZooVAgYWpDeDqyjgH0qwQwmxUANpsMg3g -IHY/0H/eOlTL4AQB/nmbj/BAofVEV76aX7H6j/MQ8E/nFwLrEIYBmGUYq1IockuD -0dsWf7YEne/X14kHZimqTvOtfo7rePIj4vgLSB3W5s+X+ZKgjUqcujyBXJhwQ2dj -MMae3ShiGAla/KKDwCC1jrXIV6N5rlCwnWfNtjVWfihqNJ+RlUDJ/W761MFk0J2i -Lj3HQTHZd/A/6FBzixnDp/2O7eQwj8o7AWHfx0ssSYXBKc+YlbrPsFk4ZO1gXh4n -r+YFNSwEzXhvC65GDWx60bRLZrSF2fZAJijOoPKhi0zjPHNdglPltcqXsOtL2xYQ -zY9lgHAsGmIw6aOMy5gDSqgwLJ0Ywg4/IYjAbcCHfvxhlRMdecjdjVwOhR/U+XWD -ikCh+l4K6Bmyf16zYx0ZfDW0gMo6sUQXYE7s2mLYm9P8+R75N7PvwLiRPXtyrJcg -NmoS0DY0mXf6933rT5GYI10A5JCLNlF40UQkPRh038BKZbJWxXWQS1SKopLnUZbb -iBBIq+senww45GQedpc+Q4kCMwQTAQoAHRYhBO2b33rWpV4jLoRSQlf/m9vMMBAJ -BQJaoCLrAAoJEFf/m9vMMBAJS8gQALM0rtD+iLEjAimQ68ybC4n4Icw8PtFw+7M5 -090LoaU7jyvP7WJBCyEOx+gmgoL9jk+Ol8uf+zigYkrtp0gZUju2a6Y0SPP2NCqk -AhO+F6KINqisR7Rg5el96fRQaTwhFVIbXvG98axSEcgWlEef2xgadTXb2f+70hw6 -bd5eF1/T2VeVcsVfAIbzxlk7WjbE4c+9dAj58ZaThEJql312gRUDyoTaVMzH4Lae -Gh3rctmHqMF62UN4GocXXQBRk2ILW5x7ovkwRhwKmjsUGWqrYOZQkiXNBigziQxd -hCm4JbUDFF5uBC4s2yCVJw8l0hEZ0e/U11piRbdbnWxmtkqcCaYpNvFy3QevuoKu -QHW3ATEZ6GCWnRIGY64dGL4Z8iNusWtVCNjbpLdPm1uHif/aD7LZZUzipV3UK3rg -9579NvHztoA4dLhZ01C+I5iysW8xLPbRRufwT6KAx4/DIDqwcFWXDpU//wjTONgI -VxyMaNku+YA5zEC5ste6zUfOEmhWu2BUVDmC8ETPk2cJJGvs2Ew8hVwRm6irC+fH -Ap3rSw3jQRsmS3iWsbsx7kBoThwiVBbpI3Oryy3gUAeTzC2Oa5xtEUaMVMLYKBal -Qib7YxiuOWG5hDzHHqnk7KSZnt2/oMkpUIpldlwGC9FArzDZStatGWtt33j+t59y -/LJNgcroiQJEBDABCgAuBQJapb6XJx0gRm9ybWVyIGUtbWFpbCBhY2NvdW50IG5v -IGxvbmdlciB2YWxpZAAKCRCibW2f4IjtWIk6D/9qsR+swOEQU9D63tSS5fczDuy7 -DXJ/qXkUOHOTbYoa3mUN++h9VRAubFyiupt0tNNMW+cRugtrJVHlEX+CtqljqAYf -vuias9iE2qu/fcBdRDfwVUdDFsGKeqiHffivAKjAPdEI97MgtLoCyKCkcnlaK/fc -ctG03RfX2IVA5rxc0s0yhylHypp/H+Z3W/OJwuIETCAKHKj7w1Yw/zUYcMwtSukw -hSOfo/uhTBsUunUMCGG3CSRGvQuL78gJcHrruI4oaP1zTOqiWe87OHJk5EA3boUv -CI3UxginKsv6ZoWGWMqKv5VVf0Qwo1BH6GsaCl4V18xT8lDZUQgX3gmQk4t4i0yB -H660JzBW9vgjtZFKZoorsXm0TbQHmFNYnN7EMtZtZY3T/1h3mbi9vJjha9CpObBq -f9hcwgHMwTYw/HLFKyUrTziiad3tmBCCMY9F3W1DZkbgFUPgSJ6tOmkcxWpYQH1X -YrZzWmC1GYgUAMibuculmHXeMXi4C0kkd5tmTEqVB2dJstVEuuPOqtacGe5/ETle -biSHoAWBm3RaB4zra0zBAFkYJ8rir0b0muWuOefZsMIa0/04oWqeNFnssNich+p8 -qPkG+rTQ3u7IgYy9BX2LW5xB5jF/sstb00FuvSYnNqCKJXJ5+py9isz5Iger/oBx -njNTTZuBLmBBGwDG2IkCNgQwAQoAIBYhBLcxqsUhsBOFkxP2dKJtbZ/giO1YBQJf -u7VQAh0gAAoJEKJtbZ/giO1YUuIQAMjAydbXcr/m6/Ihs1GBuidx87s8FkAatzt4 -rF4iCUPo4PNlfN9RTcZ4DkYC2b4YGhvR+4SFhQ3h6eLPgYrj8+axLIBuWmQtCKCQ -0xR4585q4ndc0EpsPaYDXSUk0SjTjEh2QJCzjCg7Od1i1V1YFk543AEAHbGEalL5 -O3U9JJ/mVpPtl4BP4JBz2G3hpjhfL9KXPwYanGBPapi3TZSxgM6XyeXfkhE5CYFM -Z2eJO9dzN0frs1+8fcMKThD+8mfvF6aZ/vzcPdGDuOzd4glAw2OBx0ngmrBNusEf -2v5z3qNuYFgGlbzhBiEx0jAHpN+3AHMyQodbdptX2H4v7gyuQtSDYvnv9x4njwUg -6tcsfUQy331Byo4jdJV/927G7h3vl4eN2gxuHphpYMPDoju9kRUf9H/yHh6jEWz6 -inlXCA/9xVosEkjxws2UmKDMntQ/ow4XCE/Jz32o8NhETxY6qA/3wSsU+Z2jWmc0 -HczC6sNocS5FhAxHF74d+dt/W0VO+RbJU/d+lfol+VIWWWqj6OXGE0iFaNSBLtNP -UK4HXwomj3vjN1qPG4ec6ldAdzhjjpHj0SEFVypaLVCp+EaKU90UNrUvIsuy/UGe -qA0GJADm8EbUsxlC0nglMyQH7ra1ymaJNULWONBWfzESc+52sMObbSiTKa9IQ4X5 -sa9Ngsl0tClDaHJpc3RpYW4gRGVja2VyIDxkZWNrZXJAYmxvY2tzdHJlYW0uY29t -PokCTgQTAQoAOBYhBLcxqsUhsBOFkxP2dKJtbZ/giO1YBQJfu7VzAhsDBQsJCAcD -BRUKCQgLBRYCAwEAAh4BAheAAAoJEKJtbZ/giO1YcpUQAOGGJgzghZ0x6GPErcdJ -uKU5eDfmSCoKmebiTn22MYRwgv/Vr2kvWXScQj6smYYNjCnOeod3+D6JvnIvQ2Lt -aofPVYPibFkvZ8JUAqtla9PpO2QxP0F3h1Zmxnm5UXB8lC0YxcHUzDdkLRLMa2hh -Qq3IKIbCCsr3dcPFO/Ihp5b5iE+2Hz8n001l5Mmi1krVds2y1EsqRn4PTsUlpLjj -i/x5l0rfG2Mq9jYGturtLqUA9/XCLnITql5ByzrIKURBxcAcAlnZ+BDuqb7xMJgN -VkHtZkbf3YyUFzq7XXgMjtX9cBstN0uUbAtygi/N9SAF5Zo3oxcJs7O34ycPPVD0 -5Wt5Y5vvFIrdTyrI/QADSP1fg9dSupsOirD1st+xtGd8wWQNxI6GbEx1Wgn3UUYN -UUTqge9fIgYAaTWhNhhPnHMXOqaCwknFm6ko1XkRbCei/qeXaMab2+cfuUFeoiAP -wb3L1G5k7+DLxAzdAr55OC89BjTQyDZvLpjfefosqxxTC/itgR0/IH1guoAf4YId -I/d29jSxN3perj/F9EUZzytaiJIXl6wrAs+Y5h1lkeaxtQ1uvB+XTH3Tq+fm3bzm -VHxws2x8uM9Du2IcBeZBe5Vs4LrfWKSiq0dLt7gNaUMzp4dmSLyeu7LWPjkiKFII -j96KrOYuuC4mzaKYhppzK8OFuQENBFZPgqYBCACmAhvddoJBlFoFiwpX0vVyfTM8 -4MALGHb8CvRQ90Zev3MHVzUlfRXNUhl1mfas7uW2Bm5vSxutrlOFx7Qi5jGpZF+i -ntjvcEVcwV1PDz7Uy+FZkGWJDT57qBwDAvtxRBfF+UVXQhlUDRtJaGNKGphyWBDY -EJeKM2CteDc+03cc/Y+Rnx25AveZTuxDZasK72C1VtJvI7oJ5Oi7sF5D2bKgxM4k -jgreCP6h0GasP5ZVQ7fRQT0k9foSFXw4OEjJ51QpPZ/mwrVxAZEEBeK2EcBf2BYL -UdGqomkDf0pZJ0gEhQUky1XX8i3Qw1wP61xYOcyF3cKyzCnkkddCb9yHkPvtABEB -AAGJA0QEGAECAA8FAlZPgqYCGwIFCQlmAYABKQkQom1tn+CI7VjAXSAEGQECAAYF -AlZPgqYACgkQFBbYPcTw6G0X4wf9E0XoHV/LgWW45uKDkDfqpb6PwAWKgwMuuy30 -3Gv8C03LaZ6EFvUtwUyv3RGg9efM/51UDEZoHid70BLVNTfa9tW2rGD1KOWcj3f8 -dwy+sL1F/DsJVCnKqRvUGVmj3WJRVqyPdOeEje+sk5Q9XrELmSaNVlHaNgkBNS5k -bgHQglbCz5GQAz1YfETulxTaauxHFqjxlN2LsQbK8oCbcY/S+LPAK1MOpudWEoIE -oNMnFujpEg6kZxOLs1tuerT4DVwCoM7dOh4uej8rR4SyJcB+6iQmDRAile5lJ26U -rjvSQKzspDg7fGo7mWmrXfclrkPkoatJh3sASAZCiSkWYIUu1W9cEADB9bdt856k -olZ/ldKjQzO5JExdMEnKeNkrMzzX+WpRvxxQeHbXoKNpbc5yOiZZ4i28DEFYGF6x -MxGXGfwgaPuO7qKmA3L73UmN33a95TJ2ozpoP7KzKxv5VewKpQ2hOR2m4v8h2LGe -WKBY9dvzwnDqVI9zhI/JRlD4zlZ7aj6346p3yMwCtFU4QCqBfckUjLbwao1RA9xX -K+BpGscDbFqV6n3kPJHaWLyeIMoo+bgAyef2Bj7u4ssn+2Fg2U5kl309SS64cblT -OoCV9gwSmrs1LLoIWGCjtVkG7TmfxrTXtuXMziDK2T3ElNECUy1QsjIyqdJwS0cR -fywBbgmZiUO46v4cuyuged2HkJMqSZFszIDpVnlWiqoWbU+D8VdppZRr3GnyuASF -RznTmPmOzYFkHsPFU6/YKzJ9fw905MfZtsaG8O21zOMteNJWZImTmwwfAa/BRbl0 -HtiMg44+aG0dGKyGUDZcQL6CNSk3GH9QdSmwr7NMH8g197r+SnVj/Lwq8Ab8nOEp -NThJXfqqKGxNJHt2SHaa65Z4++9oaeWYZykdcEZhPYWFaZmocz2WpIhaFsw3a2Hs -n8bquMyseO4F0h95Z7WVuEDQOWc0smwR/FipCiWrPk3d8ADf6Yn/NgKgsWc9tevd -7vQsxyyNyLacdL4Ia2wx7Ji2IEJxZLgQO4kDWwQYAQoAJgIbAhYhBLcxqsUhsBOF -kxP2dKJtbZ/giO1YBQJfu7a1BQkNLpsPASnAXSAEGQECAAYFAlZPgqYACgkQFBbY -PcTw6G0X4wf9E0XoHV/LgWW45uKDkDfqpb6PwAWKgwMuuy303Gv8C03LaZ6EFvUt -wUyv3RGg9efM/51UDEZoHid70BLVNTfa9tW2rGD1KOWcj3f8dwy+sL1F/DsJVCnK -qRvUGVmj3WJRVqyPdOeEje+sk5Q9XrELmSaNVlHaNgkBNS5kbgHQglbCz5GQAz1Y -fETulxTaauxHFqjxlN2LsQbK8oCbcY/S+LPAK1MOpudWEoIEoNMnFujpEg6kZxOL -s1tuerT4DVwCoM7dOh4uej8rR4SyJcB+6iQmDRAile5lJ26UrjvSQKzspDg7fGo7 -mWmrXfclrkPkoatJh3sASAZCiSkWYIUu1QkQom1tn+CI7VhHXw/+Od8n3AGHdyh5 -m9zhRNVpPpWGd3+uttmQ6aE/l1hFROOH6Eqo5qmZ2WHzLH9J89qTr+n7DXjYfm3s -W2LGLyi1YAGcSHfMspxyLHUM5F9+gVjdbIXAmXRHFs9zK6AhtRfqP6UGQbkutpa7 -YqfQ5BqvLD5sxlNdTUuaIQ7EyM7gqNjYekAZqNMq0YaKfTISOPQ3AIQ/oGS+9Yo7 -IYmtG9Q4gB2oVYC6VulmBiKqk1hiNApWQ7EQePFbt4F3xiwtKW0ObcN06RWl08qz -rx97U85Y1L/IeTp9sPjzeRI6I3xX0yqSgPqG/2bf0gofN1OH+5VVDS21M+lB78ys -XaPWBc6JOaVOAXeT/YMxe0j9tINuk0ZU/eyPmLypLyyvUEXdS/jdcEkgCeSkoOLm -8lwb2OfdMd+u9NpM/an/BSPRZpQlyz2fEzulGOqgFfQ90S0HYh7cfjJrzi4GyRnF -21tZsfM7Y1l4OT7XiXOtfcwSOWR2GVk8gi+SJABZW+C6xCnrH1b+eSciSXTtIYyx -fx5ZcPSNrFFn5aD/Fj5iWLOBwXWnPRvpw5O4RtWfFseRde+GZ6j+tTOXH8HUHKIy -qhenSIK6Lwb/GYdxBRpICONPIEFB7CrpihBxwLaQ9PlM8A976KGPLb6roGNfRCDj -0i0jWsCGXFE9u9WDWWA4oa4ufcmRDAK5AQ0EVk+DQwEIANX2fbJh0nzo4ALOaxFu -kRkJ+f2iFFwDuQDxW9GSbgMB/2Lx8o6XRnm+ygEPpxEAnQ8NTio3qZzgl9CLojKb -aSkfNqMxDw6wu/JMmmPII60EZgFNK+WgdyAX3Z6dobc5gZGy99zM8Wz+4gRotVCA -+IC14v1I+fhMQ2Ipk2Ajik+kJ1sDdnKKHezBW3G7HAm9q7X34XgV3yhlMQB3bg4H -0KcjFNWwd4DPJrZDaA2NJoeiKbq34sj8y4nyLsIc6lA/ok8DUo0BJusUN4CFAnEN -uQk22b6Np3ggLDvj+DGjhVQpa4pu/rscgNT975H7g35u5CBvpV39ZZpLhpuy+Tvr -cfkAEQEAAYkCJQQYAQIADwUCVk+DQwIbDAUJCWYBgAAKCRCibW2f4IjtWKIWD/0f -TtfvRmcLh7puRWib6748tVxX5ToGJ+X1GOdfMvPv4rZDpznlRoPbi29qs/aZsv5W -kH672UoTcEEwBXXEWkvQCtZC8o/28rw3K4WqiHuGoPYxjaYJBBh6f5CT6NyQE6/H -+KC6rh2Tioc5JR/Ogwwy2kc1tlbBjNc2zkFe0RVvDC2bzQRmEDTcYiAipZI98INL -lcTMIR7WU7sO06l6l0HDZM0XoxdfUzwsb+EjIDMdxBp4/kZfcnDZ/EUnXzXbI5Lw -t1yy4Vwg8O/qjCBXcN+vRb1HEa4VIRD6wjMlfysybKeQNNxV6fwLWOL5iMc/GdRW -sf0wxRNnclG7Qb2pFhn9gGh/HWGYX9P/eP0hO5dFGBMErFnJhlTe+zuYeXED0umR -FIxJ3MwSxE44yq2vyS9n+6quuAND+kzhyHb1ViD0m37ytz35fviL+cCeHF0x7Qu4 -B3MNX6cmhjcxigziEcWF6py0opKexWo8K1H+ND+eVOdzzwGxbXp/Bp5vlqATxzY3 -5jOAc6L8swgnWYanpLvZZCwMArQc9nkKgq1w+ArredC8d2X+oJBzeOIUyVf+0Bmv -tEROA1HZXie7dfVZ15JdNU0Q3/wlHyN9iqlzrKcUcvMH9SFg4BdLcZGGzWCpnD6C -fyejNswD13bQZ+E5ee/kZ8BqYuRgrn/aF3VSgwUdfIkCPAQYAQoAJgIbDBYhBLcx -qsUhsBOFkxP2dKJtbZ/giO1YBQJfu7a1BQkNLppyAAoJEKJtbZ/giO1Yn5AP/3Ta -qwcj62ePsMgsRz1+6RmHOy2guY1LfcSxNu4KvyHqQJmlkZPDaXwEOaq03y9dVPoG -Sa6WEWXh0UI6Brfa8dCt89/T/6tojfHmFrgrRcsKPLsGOHGju6ym/FLj3L5osRuM -MtFBn/6Wm7eWCmgjmp8nZ2Ku1moKogDoe8ZflTfHVmL7TeMr2XxjKEYW7FSyXDoT -UeA5nxAlO/QDj3lvfWLBeqLldK26B6wh1PE1TwDefi7+wSPBmjcDlEAw+bQGe804 -LrknD4NN6YxJIV4D0Q3eW0aVjhfEykTP3EFY+pQxs9/4lfSTzOKQSWOoI5MHdxsk -FMNT4H0X1lt6oLQivR4ndfZcgL45x/A7jrMIS7/YsqobaKkRk78jMOjAoxHibZcU -OAxpfWtG+XrGwAZy+cumLm0YovTS5OnjTeXjw9cd/3R1Qdq6sz7+OUbMf/BdSVKY -lcDCpcHBymoc3U8m5QEEb+bPwqRsbqXC20e03XrYKnZ2qZ9OcaMa+7ovwQLKmp8g -32eoErJnArJQLOA0BIQ0pYiihSf4fLWM41oO2kDIKik7kWtO+nfw5smHqIyFtBSv -ew+6tFPtdDS6KdEMeqqiMNJuRBYzZMPxiYYCLBQ/+vu/x+7MZSI4bNTfIo3RcJMQ -aaa2gbMOaPkqdHWZMmvFsIwiUBcdinMgI+C7vl/puQENBFZPg9oBCADJ8ZuYRYPY -PLHXv+4HEvatY1P4ai0eriNhJAH9fnZNaefSCAchS39hViex1GFQhL7ErpGRMBWC -fVaaw6gmIubep7inioeUF8WR3Q/23fw9TOhMK0Ro8HeEHqYuD+9yjdmke5ckrViR -A4hGn1OX+B6mS51gfzgrSOfKD5saDVy35bQ/KaOD7QimuEN8NQAkdUTJu9WrwnVK -Ub5LBvCxqAmZtjGtlmFNc4ee+qCPvks33MiYX35jlJ2g1/GZpvtMVPuaHIJqpvyP -kyAxlwt9Uawg/DjEkPbNHAuMuDTknuIHA6MrSGThyqrePITADb0QtXa6fQfZ05ML -/W2AE04BH4PjABEBAAGJAiUEGAECAA8FAlZPg9oCGyAFCQlmAYAACgkQom1tn+CI -7Vgciw/9HeJFWFlnenBzKAPwr8PkBTl9QuX3MHbiQz3JDWDo8g4F+xshgb53+StG -34fzZkSqdRdBhYhMk38ABhky8VrElcl/V1nMttikueLIDPk1sOP9wuYyR8RSqaDt -6tUA2tp6nBRiy6JewLRR8JKwup65Jw3tuyus2OhGr4JoxrHpa8bdse6f1t/ov2me -h7OaJWLZbCBs3pDHEPxI7D9Se2TvNx1Bq3XeSw2I9o8GE8z8SzVViHNbLNmJl8FL -0C8FM0TfOmvyzfcsUh36muYOQIzEwh9krWhoHqnmZAqUfl6uWFSwDMf3FU+6B69v -D8obK1xXn/fziYqvOZZ4vHtxcFAeXcrD6dyhFUnJb0fVybvDxMJ9W0yvyoLOOlAq -Apz2YmvkL7FCBUxh5eVw/KehLwCZYDQO69zcrjdi0d9YKMdgke/1k0w3CcYykPcQ -kLLq4D8dHp3tCr5e3Nxj3ROjhMlbzpRJzHyKIN908YRmF/nIhnpqss3++Um3pomx -7g6nFAF3SMhngo2phUCYVWkuGeJ5LTBKu0R8vR4MseBhrK+XN2wVs8A3JMAMRBDG -WzuvMZdn/APEooVDHvPWvTqr2bHZ5HsnvQ1oeg+Ne1fT2+E8nTmm0sq8ukq35+UQ -Oq6Wp3OY73RuR4HQeF1f7SEPzntvV4GlFSU9gfBMsg/t2MmmJMKJAjwEGAEKACYC -GyAWIQS3MarFIbAThZMT9nSibW2f4IjtWAUCX7u2tQUJDS6Z2wAKCRCibW2f4Ijt -WHb7EACamLmj7tYGpFhibogUsUM/RdMe0cQzBfkO1gKJSSNuCemjn0UTCGRWtdtn -IAF6yMBfIsO29L2yPpqtmigVfayL2b7w+pAB/1unssFfKzG6MKd/8o8i2ssVH1wz -2yJ4Vb4C9L6Par3PdjVS4u5OLmsOesJD8hsmfA69+90yHvv9tAQq4vJH446z6Z/D -7WJnA1TAwEvd3IGpxvLArdDCirnpHbQZShM+1iWR3Jawho46pyMV7EHAYVS3FuN8 -sATNmR7h8kMnQ+UY1fHTu6vlcAf1aRCsCgmBnL0BtAJC0ufXTPcKVh9DpJZibtNF -b+rarU7RoKUNyl0blnio7B9ftLCFxqPgC4Q/NCPCP+j+KGGQ5ZoivG7t2idtv/98 -oFNGo52POh+sBYs3b/ZHI0AflL1s4HAGn7xfP1e1ue68xyTUSGd0uXebMmDhTvNQ -gKpqLRO/F/UeYjIsM4kJp6Soh2LoZbtAa/rExKIVnrYk/LithJHXyXIVu0IZpdqi -CuX8cmswjFo+9zWRO7OPxVGmSShYlzrQBiXtDczb4Np34n4L/tvzpWBXAuz2Vlbm -n/XalKE0rT//SaSdHjRGbSpJLTiE+1WvdOdj71dVXW251klS99zgeSdURVRKWbfJ -jI+ZeaIuShH415MPlfMleq7cWAUk70fsH6/OIEvaQr0UEZ2kPA== -=4zi/ +lGtRn3rIZ2Z+AfGyc1+DqtrUd8lbiQEzBBABCgAdFiEEN+x9ewohfNtLTgB+f6sR +Qmfk+gQFAmFxsyMACgkQf6sRQmfk+gQEhwgAm1VQlfPtJl04Ha241D+Ls/X0J6M2 +CRyNElTJCQF/nn8p4FbdgYi74NXpz4vyHOiTuvFnFS0T5DcQIPUfCwdaFfkRGuzO +uuGXhvZChMhrRZbfx0jCJtLqbV4blIzwjvRWRgadxF8EqGQGRbSbxbFJUK7GyW5a +ZgGxhGIeP8Jo9kGwZaGoIv8iXYW5z89lojcGUXeLgUXo8n2+wV95efxbYTg/uOp0 +mEUeDUk+KB5tGzNOioKoFIZz7vM4+7oXnvXSRRp2tBZx6POMEcZKJj48Rpw4mWRp +k624UEGiS/gzh7O2mwMd0z+ETVODq0l2Ty8aS1J6pL64AvnkyJR8ZcbKzIkCOAQT +AQIAIgUCVk+ArAIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQom1tn+CI +7ViIsBAAxfE62/wBnV+/P8VoIKC6r8/k82pY0YPBrPQ7Ym8LokVPfBpDUHLNV1n5 +H1NlxId0/wrLvfG8tRIAK1vVP+4GMFXCQK7RLN+JVGdf5I8lrwVbVgA6vlG1+3S5 +MvcEKhbliSLxF4fKOdULCbRnNgDwSV6TJJhFvefY5dnVE39Z7ORQv1HxK58QmzQo +kCg4bMqiTWWyy/m+NF4KJRwpo39dpwXm1idpCy56ByQLoINjp3r/7ARFcc5P2Jko +5n55Vz+0/DPXHaBz6RPg3T5WAnHYJjqnRZ9ZGUtsGA0FirKfGD9N+TNP+jcxgvHx +rh30uZvrL/6vLl7wwhwo5XEZxL5++rizY6RWe13jz+ThKHWPF8h2GIVhpQ8ipntU +egkeOFY8bDIKMBKyJduuCgT1rQg1H1xGAJyEYt5Q+qGasA5ClVJeDc+HUG9JdDpt +cnMu7zdLaHxHeM5aM1eRT1lkiFU4z3q41A6cv4eguEa+jJUKURKPMfsW4SThK14I +U8DulagFGOW5WDZv/pyrsc6c9ztcsYCMMokshx+YrzT3rSIVwnvaEQad8FlWUdd4 +WeZm1o9pPbVKwpAscLbztfs+07bcYOIAiR+kJdXsAVPcIOfy7IkMPk0+SG1jGMwU +n1ZksDvl6dbh9ifLwzx1ppKHe0vHlUkLdyCjTM0FuHWYh5Y6MwO0KUNocmlzdGlh +biBEZWNrZXIgPGNkZWNrZXJAdGlrLmVlLmV0aHouY2g+iQI4BBMBAgAiBQJWT4IB +AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCibW2f4IjtWGoaEACJ8EIT +eJgDjXtTdTbn3SFg6ezFlsEM96HzD0AfxXJzzNXbnCkCEjUKQuGXSZJJS9eofzs3 ++DyNnW1CAgRiiTPkDmvmVvwCT3cmxWDsjrp8zrp8XTVO/77MvVzlR23Ue5x6hftz +IylOd0HcKGesWe51nfJ5nQ3N5M2Q/3ncE5VkM/vqS277G2X73PlfcA0tPbFRKlp8 +12fgoieuHvPN+AxlA1AmBseePV2W5353y6jjml6g84FR7Fdf82FAvIaZvEzeiijE +oRIxHfzyGx/tg1N1pI3pJWUf6eUh4NKQj43uWNDM3qhq5mkav4gF9LjL2pGOr38G +cMgdA6maxeyj9O4qgGPuzXKPXE05L9sv8OkOIzZU0HNT57dNtO20ENgD/USZnBlc +vTln6A9QcrXMDws+84axsGxdBXvbrGC2rcpV+iIeM1wol5TKqHZtXwU99KQxCPb1 +Qjx1dF2AtIicyhaod/k7RtFICIUETWrNX0w728g+FcJnmY9DaEdQD5xJVbVLDZOP +dn3ypWU/n/hPqqdSylACC8ehPAIX8Rvn5fcg7bfZ2dMpnPT38TfmD++RumFpxne9 +mOfvrcvpcSlWsSJtDG7CmkJOWz7oQcQPg6JKM03yBq1Zmdn3QAGgaAN3N8lVblCn +6nADerkbYFLPMTFp2eMsSrwAx+PCEQTcJpV2n4heBBARCgAGBQJYfmpYAAoJELhP +6kn9pseOb1cBAPTDaF9ANmM3cTYGtQN7uUw0Mwd+JIMAw5FW5YA3fmIBAQDgdtxZ +hVTur2SSvfkezE+7SOuipgVQ0nXHFkU/PQqHl4heBBMRCAAGBQJYCnGkAAoJEH19 +Eb9inVpnRVQBAMT41G+/HYhBJgDB3Otyr/CTGDgkH4z86hhMF9w7JMzsAQDoBUT8 +YBj/dmuouJ4jTXS/2pgYGlxvnmqrC0iWerCcXYkBmAQTAQgAghYhBDfsfXsKIXzb +S04Afn+rEUJn5PoEBQJaoCdjBYMJZgGAXhSAAAAAABUAQGJsb2NraGFzaEBiaXRj +b2luLm9yZzAwMDAwMDAwMDAwMDAwMDAwMDMxZjk2MWNkYmI0NzI5YjY3MTBlZjk4 +OTFmODNkMTA5YzgwNDg4OTU5ZTAxY2MACgkQf6sRQmfk+gS2KggAjc8ALUUggpXs +0nx2bTTypw7v1yTWEYS7ddjxtId7WvIH7T9UnUIUv7DPbQdYFE1n90OXzJMeev+z +dtelo+TtFBm6H0f9pC6LBldjcgE9XJKdKfvnoVRYBAEFT+JM8fvuTcarsCOtMaNn +2XZ7vGGGEFwVF7oxKtpSaYekaO0Hk9NQpFSc4/5RtckeEZ3E0hILRaA+CscIEkf+ +qDz3Ky6bop9PBeWXM5Fxm/qmYllAmRjJOWMa3qKTVciJb89KDpi4ZJ/N2yh3GV1Z +BBVTdpCr1gGYAvb9yAUFj48PMNYxzC0QzgIJ48CoRv8AhYAYG0ACT0O3Nm1afHom +JZ/LjUDCp4kCGwQQAQgABgUCWqF85gAKCRAXVlcy4I5eQYriD/de+LGqWqwUU/Hm +E+hN4lyxSR3Md/U6GTolU+ILCvF/RAqV1fhI5RVgusU5kkCZO74rM+6B2EgnahH+ +4JyR63oq06ciFZw+0zwCm2Z8CMbAe3E5BABDreSXd66HGKfSWMjjxu7X/RrxvDLh +E7P6XsljCKzC8vu18QXMcmiuWL90guNOx/ZNgOiAbik7ArAWawqFM3btlpJ4n0Kg +1r+ghDN3lL/by+2930JVCGGhCVUIfPSAdGYbS0kBbMAXqfTzgES+776ah9i/yt3J +nXExqdZXWl4t3S2zHWv9sk6WEXq4aZpma5p/V/3pferTOX/p0Y9ORc33Enrm7Jo3 +xb9lx91oq2FPYxQGRjrZ6GmgK9LqPpRJWX3Z6svluAP7dvVdcXFQFk+qdZzQspSA +UAmZeE1HXM5VNl6bTC1uRRjqX2jRa9ubXmBo2UeciQKMJabzlPExr1Nipopgu98M +iDAhIQc75ZpcLyTdcHJlHXOVEHuFaVEV6TzgT7db3lnuqoZ+474QbTjHVzMLxTAb +rLDoN9kh7CcxZh+0Uvlo+x3NETXnD1V5IRPSQ8DPHiuqjRIirQwrWyT4bCOI8R46 +hX0T3kTIrwqedG389jAt5SljLgeidK37mHn/G8h3zyDLuzcRkHXrvNZUVTiLQ1iA +z+kuAmfirXRNEraSJjGl/NE7YpBLiQIcBBABAgAGBQJXaLAZAAoJENkgDmzRrbjx +YZcP/0HebG97OfjkJVyDXgSJdZwK5BQbGtMaCGXmkfUvXpHtok4lIT2K5Hbt+Fno +qVt14rbUnKvSeTdXdyOP4fwABG+d54agaAxQ0H2hpJn8ixRcGLP2aNyy37utnl49 +jHNMC50gNIC0i5uJbCurxaGejugj7thvmITrShE8gWO3IXKeiCWEnjDVbd02rZAh +12YVGwdlErGgmAuXzuzrGkZf8fFoGzzE2rx/9DDo42NXbM9KEgYY7UtqvCaxE2Iq +T3fPYEgQQ3RlLvRNcTJiokIDd9/IcHnnCzjN5a9u1gFYRQSk7BI0oBPEv8cICwwt ++iNGczEH7XEpEa5ihipCthSve/sQ8GzmLvae/2aoUk1j2zR/dmLxN2y40JPZopN/ +JpAe3dwWEVNhfXAH7ViDcCd2jYxsq0GBUNJj3dvE3iMtQ9SLniE8+LpRIE7IkoZS ++Al90jSF+rZO8DeTcsqmlJ6aDH6eA5mb4ajiGAyEPVkOg4TAXKAdqW15Uuzb2u6D +GrAQNdXXqiKS2BFScGF3vtaXqNutwuNLwR5+vmbJV5ZGFMJoWYM4UbK06NoZIUCq +sq4OtthGmpgQM1/8Mk2YD/td5F/PGsDTPb6aEvGj8h6ZyRbJj04l+DM+3BkPWQV6 +2CYXQOB6arWhlgKIR6TJ5+Q/8oCVVJ21by+udDMd4QVWtSmRiQIcBBABAgAGBQJa +oCHoAAoJENMAEW4ch1o9sJAP/1wVBv1I/uLDjPoIOhiSEt50qPJMRylELV4sUGAK +8FIMuXYFWi/5pjUSoegvh+BiWPts49mTbqZwkk4YoU4twIumUsAfLj1tbZlPTNRb +c5Xh40TaduHKqC6SpVRheYVK0l2Gsgk8oD1/ooJK+pH3QO4snKXbjo1gxilKdO+i +WAF2I21QGlyuZGtElYBx5HgfjT/kfoNrL1ZWFDFHCYSQpX9lXWSNfQPhZY4OmTfa +7+rGh6sV0aKeXl6MbONuFmBUqIhCMGS9vAOUHLTlni2hD+6qWPIMIvkEDCp47Ri5 +v3nuYOKC7si7GHUQB6H2zriIg4NsrOW8pi2oDVxs9OPPNlfjfrbQ7s2gKPK/bPZ0 +w4CBjxE/ozZY8zEzSNBUxPaDCo3sfcvAv55pqEI8ltPCMS7/0WaXu8lR+BPD13bn +XhTpPRY4Go+pSY6EXPCV9gPpKrylBkxis0I4QENeMLcgGpjpvjGFcepdYXEiMJMi +rrq8vsaTNb/RSbkpM3578G2oa5RUcPE/d0Hqsfs4hDqukGA2dvjL+pX3cFNJtN72 +ZmGAXifxZ0RzhPq99vkSBT03nyGKsljEVOY731QocsguKG5XOyNAjNueKmzcXhaD +2qZ97GSwwMY8zm06P54gTIeyZKXlbVgtl+YrnOquilGsTgrOYQhO7SU+CK5QVwB2 +KrnIiQIcBBABCAAGBQJYszc9AAoJEAACNAWctQcVHNYP/15vYHzjLPJmvNH3ToTL +MNVu06IyFHqEB2ahwavaNp0/QksanD55KYeC9dpJunKEvoQ2BWIbBiGEZb0eAdGU +JrNVoXkUQwgKkhz+Lb2PRz/qwH49uHA/7qmsSxZiXpVea7zh20K5WqMi42Ihihjl +54WTUOzUUM+THB6nIur75iVE4GvUy3Syp4nZ248uM9UU0Fypmc8aVOHoKER/ob7o +1Pjg1bmlEmIXqLGC4ViR3jL9/mVLJfrM0Guy2wPwU4TMv7zaesVPLoztEOuBocb9 +MsOtyDIpamf5e+Ud09g6CnL56W5tPVc9UtesJ6EjzOdQtIrUxe4uKDcUuNKU1i60 +IHVhW0oBV+vxzIYqfT5GnR/ngJg5GsXOax3QXxj4Az4YcqF9LjEDlbChmpfPkKpK +fz942OF4rFgP7XoI6rg49jXU5nLQKNeSOi+tXfUBGGs73gHAd8gU7ehISPakW1Ay +QwEB3R08L9LQqcSWylBA/8QgML/y2tA4auWNogr41vN81KEIIIfc4pMOhj3reYKz +1TI0QtauEgyBYq7Sx1SLh5tFFaSXOj+O3wRg5nh6aAJm+/T8x674SUZU3i3WwvgW +YtvcRLWuxUHYL/WNjRpgSdPpdeIWnQcHS81rb773xqqnSXWaE03Lj84aQoQaWmVI +zXKbt81ED5smoZkf5uCNcOueiQIcBBABCgAGBQJW8xztAAoJEMDAdhMv+naV4x8P +/0/T7MsgAR8OeQ6jY4U2A7YHM9qEGySJryL4HDQSRw0ttcWMf06sqeeU1d8z8o4s +8LhzDhiCPSH0smoQfz7GmTMeB2aNExvXaYqi4QoxufqfXbsR9d5PBR6OuNBF73yf +rKyJBHOYadYkGiN48/B8US5fkKeLWpQoJysH/od9kYlN+38nHNzOFmSU+UBgtWQE +uMT8POS/xfE4YJZxnmtv9Mpi40t93QRJjZEYZe0rGRcpbqRGd7R8yGVsaItYtttN +HYaZTldqn2dNlrTvHk6D4T7a+4XwmDXa1ulDXk7kH4lKPAwAHG5ftSZcJXmY4cyq +Z94YXXhfxcTK+Y2y1gXRsg63OnbtsDfl/KHllSWnzRtmiugxToOpgwWFLDPObqW/ +bJLAcZ8nK9Kv/knIffwjpDD2Js4CH0Dyw/GQLqtcjQevhcowejXHoLeJ3EXpV4li +EOTVPxo1hR/B1XXg0aDf2SAoZN0x3WnqIFcma9dm9/8QflfSRXouUnsWApYC7+Py +vz3g2wOkW/DV7P0H0rnmq/As8bxXlK2dfOZ96eVRebwE/MDRareLxd2d3IQPxU7V +hwKMsex+w6TuWiHcB4QgOE7PwJcNPzt4LzDrXfvOjUiPn3wJO4MVhxkjXIr0ZhWX ++8GroijnnMMeHv718v+78rxhNDlVtsvKxcjvi3F0j+AUiQIcBBABCgAGBQJYeR1w +AAoJEPZDB1QSDsL0xeAQAJWRjU7tvXkl2cmqyGYNi63Jtgh5768N7vp09K9jKNPB +wutGKpLoY4OCMsEfkcFl00E1bz8B01fG7HM0huQFwd+zdPWJSMd+HunJsVy2fzYS +3FhJgmVGK6KBPGDXIKyzEb/bb8JJ53KtyGUU/v2T+AUnl+xod3gTgveuffTZTbG1 +uKH8rfoYQlKx466PSOoIKWRnXEb8iuDVq5/Lr9hXPufqgo/hs5+HJHL3YCpuEEzb +zRWK7QqTyQm1F4uJXYs/y4PsaAtwVpqyUaNWgNhp9+mM+xzTiiVkmCh0ipClnmDz +R6QN1CLZukDMtlAk4sAa845krNGHZD3muwhyuVxKMA/DeD+OWeqR3utfbR17wtJu +HwQoxGxg+il4GGz+Cyb3i6c27I0ExWdiMwdzf6B6b+EkJC4MnKpptTZve1yWLbl+ +alt4pinSC3X6afs1uC4GXadH7JzkYEmfk5JQbhWBceuaGeA2h9Pc/P2YP5n3dztR +6Ye3Z4VBiooBjiSm/laQCSosq89+t1xgl+NuMjTTMNraewNmzyBp7bHkeow9w3/V +jb6jp/XxgBnTOazOuWLITxqiwKkvKP2+YuBGny945rrD/Okhrn2rj+vllz9fDguE +DitC/4JjVHC1uclYSVAIu8C85U0kENgYouSeTtOhtbQM2cC1P8lmqk650Rr6ruit +iQIcBBABCgAGBQJYfmnzAAoJEPjTbJE1dAXt3JoP/1+CD1pVDt9Ao9kfhJf4BWHw +DfQ2AmHyJZwCx/WmXZKvDrPHS6mx9xRTZxPzKDjfnIvla/1vnHgujsdt2j9tn3Nd +UYl2ZpdJLnl1peDJ9+SMaSyGOj1rDR4CnSd5bIGMgVEy7DzbDv2Jwj9R2JNtDy7t +2tisNOMIpxW7kQ8eojN+mZq5n9T3/tn2GHMxQ0F9oMmN6tux6OgPqhXYp9XdK5NV +jfViqvgW7cMTahZr0haMuMB5nYUQM07+yLDdXL1KF7RKJ+xV0wpahCIjQX3OYy4D +I5/d/+e7p1ppB4yIHKgxRW2IdpgVGMF3MrANKc/tHwVVT77fZufRRTa8EowyTNc4 +43agtEnkvZVLBzC7EKbXt4lgK+utBIFxuJiganl8OcYwhL/B+FTsP0x/cX3XfcAg +CtO1O1bJ1EtpHkC1YfnTMHpn7ToSF6tknuBCy2mGy1Bvhk43W5XqxcJ8sEMwEpPn +bEaUrXFX5F9uHVsifsR1U+4fyMdeskqqAgT5eQQMbHbsuNAInGDn70HYezWEwo56 +9amXgA/EIZ9jLzMJNqwg157b44CDKIqFfHZw6t4jhqDjrYUuGZsWLST18rMGm1+J +h7IZvWakdjLuBNTrEhWqtmg0u6Qk16+l9EuO9OfWu2I7GiE11M2WMQyMCLoNhZ7u +TOg+lASRuXzZLqbUZMuJiQIzBBABCAAdFiEENfStpiPrn+OjvH72e6A1yluQFxMF +AlqgMtYACgkQe6A1yluQFxOXgRAAnnHa9hBNGN/ql5dTK+P6sWetY7mVUEn0tRgO +8ZL4mAZNfUa/6jEY8QokUoaj1ZvsSfYAZlHHbYJ2RUHoV6UGNpVPlKKUsaTu4J2T +POKLu8JRQ4Vw5fdVFjggH5kfTPYs1oX+7CTfRCh7JOyFq5xIc9V4INYEsKgaiErM +c5tZpK5vCEAR48FQqy8abpUbD4KpsV6Yh+OD3YlNiVYHmvxvFxqU+v6Rpio5Pgv+ +jf7B3U3BTEPCvw7e3/YpwHrPwpnz/WtWOzmDNDC4qwPWKQEK2jMQXsvBXp9YdV0J +DmmLot0VJ1rS9xLYYq4z/yofLRFGTe9D7BhcY5Gxolvtqzkbjq9+C5304oZ/sBfs +O4P7n/dQ+YIqzCjCQHKTddrTnJabNwk94ffinR2oUv96Ptfa716Yws443kXbqe/3 +RiA03UDk4Zrg1/lX15qrzgFR18FBYYy1X9bwHhHVPFZiCtt8hc1bgjnnA+ExtTzK +my3R8j+dqIUPkrGI5BbnvoqqQvqFMuhJGCn6wgevxBzrr+vBBoECa2X4WVRfoAAn +7QB8EsSqd9wDW0ZvcR3k47ghukciutVgsUEye3K/gXXecE0sbREko9OlaiDwBoLd +CwF9F1ZZ1dWM7+Z9dN7MB/PdOf9OAFoShve7h60dBgjQDC1nnN1VM4aDbpJfcxv+ +UhHXUqOJAjMEEgEIAB0WIQSCRW7CYtCNVnwvGEes/bk6kXXcqwUCWrhKMAAKCRCs +/bk6kXXcq3ivD/sHk18ZMld1rET9HzxzLFsO8jIbGPh5dPm4DPuCkLcIZ2t56Ocw +RSOcqxJb9IiDqNHVd/bbl79xiD+ztQOHb7MNGFDX/Ydee4JAAgILnvqQWRzqmWpl +VXcRRti0p2i87bOWSwaJwIRAjtauZwuZ65/yPcmehLK7tYaj7mwjIAIdOJSqlwzK +yZ/zEM6nXgRBnsKSESwn+8jLhkHP7m7sx/u3W1et/cDDlSMZDI8dVepjALP+zWCW +v5ORnc/ppoXWfncwQhx+BaCMk/X5lxsfxoRoS2EthQFfXg4dLWaiTZ2HwCE2uovX +F+DEmqgDOjzsf6HFZIiqGJ7JYNceMCSQ5uoOhVdWuqeBRtkAQwuffNloNuy634JP +WBVdGvrOFihjOPvKMTVq5KEDquKvxQrZsbzedqqfefwNuH12tXrD3SKw999y+GBZ +dt0IELRPhGpCtHAGXr1/6lVb3ljYZnsr4gD0joGSEfOAZOYEISdzuwQD1U8wzHDW +TmR8DK+SDdWRB6ebFOyZFLV7Ex95wcGWPwiAqBqZR79J1Ln33cNaiF61gfcHAUB1 +R24KvjJFQ0Z+u/MZbEvyEvSHyITq03gWvSSKy4YEzDGSSL2D9EyBjayDJVLpgVjx +OdvjSx5MPjuzmioE67vucKrg/HVfeY0zs6mGdiuiTIvdoy3Kk5tmH3gRzokCMwQT +AQgAHRYhBMQq/3xhs+RKFFTNNVevdi2zNTMiBQJaoXfeAAoJEFevdi2zNTMi0X4P +/2RWK2jydFQXHZ8bT75k4JJj6t1SUOsdSO/2ApnICB29XByRMP6JLRoxcUv8DVse +kEQWPmolOSXLKv6GcUv5V77LYdKgccfBRHseTXBjnjqQFrxFhNx3RRmihUCBhakN +4OrKOAfSrBDCbFQA2mwyDeAgdj/Qf946VMvgBAH+eZuP8ECh9URXvppfsfqP8xDw +T+cXAusQhgGYZRirUihyS4PR2xZ/tgSd79fXiQdmKapO861+jut48iPi+AtIHdbm +z5f5kqCNSpy6PIFcmHBDZ2Mwxp7dKGIYCVr8ooPAILWOtchXo3muULCdZ822NVZ+ +KGo0n5GVQMn9bvrUwWTQnaIuPcdBMdl38D/oUHOLGcOn/Y7t5DCPyjsBYd/HSyxJ +hcEpz5iVus+wWThk7WBeHiev5gU1LATNeG8LrkYNbHrRtEtmtIXZ9kAmKM6g8qGL +TOM8c12CU+W1ypew60vbFhDNj2WAcCwaYjDpo4zLmANKqDAsnRjCDj8hiMBtwId+ +/GGVEx15yN2NXA6FH9T5dYOKQKH6XgroGbJ/XrNjHRl8NbSAyjqxRBdgTuzaYtib +0/z5Hvk3s+/AuJE9e3KslyA2ahLQNjSZd/r3fetPkZgjXQDkkIs2UXjRRCQ9GHTf +wEplslbFdZBLVIqikudRltuIEEir6x6fDDjkZB52lz5DiQIzBBMBCgAdFiEE7Zvf +etalXiMuhFJCV/+b28wwEAkFAlqgIusACgkQV/+b28wwEAlLyBAAszSu0P6IsSMC +KZDrzJsLifghzDw+0XD7sznT3QuhpTuPK8/tYkELIQ7H6CaCgv2OT46Xy5/7OKBi +Su2nSBlSO7ZrpjRI8/Y0KqQCE74Xoog2qKxHtGDl6X3p9FBpPCEVUhte8b3xrFIR +yBaUR5/bGBp1NdvZ/7vSHDpt3l4XX9PZV5VyxV8AhvPGWTtaNsThz710CPnxlpOE +QmqXfXaBFQPKhNpUzMfgtp4aHety2YeowXrZQ3gahxddAFGTYgtbnHui+TBGHAqa +OxQZaqtg5lCSJc0GKDOJDF2EKbgltQMUXm4ELizbIJUnDyXSERnR79TXWmJFt1ud +bGa2SpwJpik28XLdB6+6gq5AdbcBMRnoYJadEgZjrh0YvhnyI26xa1UI2Nukt0+b +W4eJ/9oPstllTOKlXdQreuD3nv028fO2gDh0uFnTUL4jmLKxbzEs9tFG5/BPooDH +j8MgOrBwVZcOlT//CNM42AhXHIxo2S75gDnMQLmy17rNR84SaFa7YFRUOYLwRM+T +Zwkka+zYTDyFXBGbqKsL58cCnetLDeNBGyZLeJaxuzHuQGhOHCJUFukjc6vLLeBQ +B5PMLY5rnG0RRoxUwtgoFqVCJvtjGK45YbmEPMceqeTspJme3b+gySlQimV2XAYL +0UCvMNlK1q0Za23feP63n3L8sk2ByuiJAkQEMAEKAC4FAlqlvpcnHSBGb3JtZXIg +ZS1tYWlsIGFjY291bnQgbm8gbG9uZ2VyIHZhbGlkAAoJEKJtbZ/giO1YiToP/2qx +H6zA4RBT0Pre1JLl9zMO7LsNcn+peRQ4c5NtihreZQ376H1VEC5sXKK6m3S000xb +5xG6C2slUeURf4K2qWOoBh++6Jqz2ITaq799wF1EN/BVR0MWwYp6qId9+K8AqMA9 +0Qj3syC0ugLIoKRyeVor99xy0bTdF9fYhUDmvFzSzTKHKUfKmn8f5ndb84nC4gRM +IAocqPvDVjD/NRhwzC1K6TCFI5+j+6FMGxS6dQwIYbcJJEa9C4vvyAlweuu4jiho +/XNM6qJZ7zs4cmTkQDduhS8IjdTGCKcqy/pmhYZYyoq/lVV/RDCjUEfoaxoKXhXX +zFPyUNlRCBfeCZCTi3iLTIEfrrQnMFb2+CO1kUpmiiuxebRNtAeYU1ic3sQy1m1l +jdP/WHeZuL28mOFr0Kk5sGp/2FzCAczBNjD8csUrJStPOKJp3e2YEIIxj0XdbUNm +RuAVQ+BInq06aRzFalhAfVditnNaYLUZiBQAyJu5y6WYdd4xeLgLSSR3m2ZMSpUH +Z0my1US6486q1pwZ7n8ROV5uJIegBYGbdFoHjOtrTMEAWRgnyuKvRvSa5a4559mw +whrT/Tihap40Weyw2JyH6nyo+Qb6tNDe7siBjL0FfYtbnEHmMX+yy1vTQW69Jic2 +oIolcnn6nL2KzPkiB6v+gHGeM1NNm4EuYEEbAMbYiQI2BDABCgAgFiEEtzGqxSGw +E4WTE/Z0om1tn+CI7VgFAl+7tVACHSAACgkQom1tn+CI7VhS4hAAyMDJ1tdyv+br +8iGzUYG6J3HzuzwWQBq3O3isXiIJQ+jg82V831FNxngORgLZvhgaG9H7hIWFDeHp +4s+BiuPz5rEsgG5aZC0IoJDTFHjnzmrid1zQSmw9pgNdJSTRKNOMSHZAkLOMKDs5 +3WLVXVgWTnjcAQAdsYRqUvk7dT0kn+ZWk+2XgE/gkHPYbeGmOF8v0pc/BhqcYE9q +mLdNlLGAzpfJ5d+SETkJgUxnZ4k713M3R+uzX7x9wwpOEP7yZ+8Xppn+/Nw90YO4 +7N3iCUDDY4HHSeCasE26wR/a/nPeo25gWAaVvOEGITHSMAek37cAczJCh1t2m1fY +fi/uDK5C1INi+e/3HiePBSDq1yx9RDLffUHKjiN0lX/3bsbuHe+Xh43aDG4emGlg +w8OiO72RFR/0f/IeHqMRbPqKeVcID/3FWiwSSPHCzZSYoMye1D+jDhcIT8nPfajw +2ERPFjqoD/fBKxT5naNaZzQdzMLqw2hxLkWEDEcXvh35239bRU75FslT936V+iX5 +UhZZaqPo5cYTSIVo1IEu009QrgdfCiaPe+M3Wo8bh5zqV0B3OGOOkePRIQVXKlot +UKn4RopT3RQ2tS8iy7L9QZ6oDQYkAObwRtSzGULSeCUzJAfutrXKZok1QtY40FZ/ +MRJz7naww5ttKJMpr0hDhfmxr02CyXS0KUNocmlzdGlhbiBEZWNrZXIgPGRlY2tl +ckBibG9ja3N0cmVhbS5jb20+iQJOBBMBCgA4FiEEtzGqxSGwE4WTE/Z0om1tn+CI +7VgFAl+7tXMCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQom1tn+CI7Vhy +lRAA4YYmDOCFnTHoY8Stx0m4pTl4N+ZIKgqZ5uJOfbYxhHCC/9WvaS9ZdJxCPqyZ +hg2MKc56h3f4Pom+ci9DYu1qh89Vg+JsWS9nwlQCq2Vr0+k7ZDE/QXeHVmbGeblR +cHyULRjFwdTMN2QtEsxraGFCrcgohsIKyvd1w8U78iGnlvmIT7YfPyfTTWXkyaLW +StV2zbLUSypGfg9OxSWkuOOL/HmXSt8bYyr2Nga26u0upQD39cIuchOqXkHLOsgp +REHFwBwCWdn4EO6pvvEwmA1WQe1mRt/djJQXOrtdeAyO1f1wGy03S5RsC3KCL831 +IAXlmjejFwmzs7fjJw89UPTla3ljm+8Uit1PKsj9AANI/V+D11K6mw6KsPWy37G0 +Z3zBZA3EjoZsTHVaCfdRRg1RROqB718iBgBpNaE2GE+ccxc6poLCScWbqSjVeRFs +J6L+p5doxpvb5x+5QV6iIA/BvcvUbmTv4MvEDN0Cvnk4Lz0GNNDINm8umN95+iyr +HFML+K2BHT8gfWC6gB/hgh0j93b2NLE3el6uP8X0RRnPK1qIkheXrCsCz5jmHWWR +5rG1DW68H5dMfdOr5+bdvOZUfHCzbHy4z0O7YhwF5kF7lWzgut9YpKKrR0u3uA1p +QzOnh2ZIvJ67stY+OSIoUgiP3oqs5i64LibNopiGmnMrw4W5AQ0EVk+CpgEIAKYC +G912gkGUWgWLClfS9XJ9MzzgwAsYdvwK9FD3Rl6/cwdXNSV9Fc1SGXWZ9qzu5bYG +bm9LG62uU4XHtCLmMalkX6Ke2O9wRVzBXU8PPtTL4VmQZYkNPnuoHAMC+3FEF8X5 +RVdCGVQNG0loY0oamHJYENgQl4ozYK14Nz7Tdxz9j5GfHbkC95lO7ENlqwrvYLVW +0m8jugnk6LuwXkPZsqDEziSOCt4I/qHQZqw/llVDt9FBPST1+hIVfDg4SMnnVCk9 +n+bCtXEBkQQF4rYRwF/YFgtR0aqiaQN/SlknSASFBSTLVdfyLdDDXA/rXFg5zIXd +wrLMKeSR10Jv3IeQ++0AEQEAAYkDWwQYAQoAJgIbAhYhBLcxqsUhsBOFkxP2dKJt +bZ/giO1YBQJfu7a1BQkNLpsPASnAXSAEGQECAAYFAlZPgqYACgkQFBbYPcTw6G0X +4wf9E0XoHV/LgWW45uKDkDfqpb6PwAWKgwMuuy303Gv8C03LaZ6EFvUtwUyv3RGg +9efM/51UDEZoHid70BLVNTfa9tW2rGD1KOWcj3f8dwy+sL1F/DsJVCnKqRvUGVmj +3WJRVqyPdOeEje+sk5Q9XrELmSaNVlHaNgkBNS5kbgHQglbCz5GQAz1YfETulxTa +auxHFqjxlN2LsQbK8oCbcY/S+LPAK1MOpudWEoIEoNMnFujpEg6kZxOLs1tuerT4 +DVwCoM7dOh4uej8rR4SyJcB+6iQmDRAile5lJ26UrjvSQKzspDg7fGo7mWmrXfcl +rkPkoatJh3sASAZCiSkWYIUu1QkQom1tn+CI7VhHXw/+Od8n3AGHdyh5m9zhRNVp +PpWGd3+uttmQ6aE/l1hFROOH6Eqo5qmZ2WHzLH9J89qTr+n7DXjYfm3sW2LGLyi1 +YAGcSHfMspxyLHUM5F9+gVjdbIXAmXRHFs9zK6AhtRfqP6UGQbkutpa7YqfQ5Bqv +LD5sxlNdTUuaIQ7EyM7gqNjYekAZqNMq0YaKfTISOPQ3AIQ/oGS+9Yo7IYmtG9Q4 +gB2oVYC6VulmBiKqk1hiNApWQ7EQePFbt4F3xiwtKW0ObcN06RWl08qzrx97U85Y +1L/IeTp9sPjzeRI6I3xX0yqSgPqG/2bf0gofN1OH+5VVDS21M+lB78ysXaPWBc6J +OaVOAXeT/YMxe0j9tINuk0ZU/eyPmLypLyyvUEXdS/jdcEkgCeSkoOLm8lwb2Ofd +Md+u9NpM/an/BSPRZpQlyz2fEzulGOqgFfQ90S0HYh7cfjJrzi4GyRnF21tZsfM7 +Y1l4OT7XiXOtfcwSOWR2GVk8gi+SJABZW+C6xCnrH1b+eSciSXTtIYyxfx5ZcPSN +rFFn5aD/Fj5iWLOBwXWnPRvpw5O4RtWfFseRde+GZ6j+tTOXH8HUHKIyqhenSIK6 +Lwb/GYdxBRpICONPIEFB7CrpihBxwLaQ9PlM8A976KGPLb6roGNfRCDj0i0jWsCG +XFE9u9WDWWA4oa4ufcmRDAKJA1sEGAEKACYCGwIWIQS3MarFIbAThZMT9nSibW2f +4IjtWAUCY4H+2QUJFph9swEpwF0gBBkBAgAGBQJWT4KmAAoJEBQW2D3E8OhtF+MH +/RNF6B1fy4FluObig5A36qW+j8AFioMDLrst9Nxr/AtNy2mehBb1LcFMr90RoPXn +zP+dVAxGaB4ne9AS1TU32vbVtqxg9SjlnI93/HcMvrC9Rfw7CVQpyqkb1BlZo91i +UVasj3TnhI3vrJOUPV6xC5kmjVZR2jYJATUuZG4B0IJWws+RkAM9WHxE7pcU2mrs +Rxao8ZTdi7EGyvKAm3GP0vizwCtTDqbnVhKCBKDTJxbo6RIOpGcTi7Nbbnq0+A1c +AqDO3ToeLno/K0eEsiXAfuokJg0QIpXuZSdulK470kCs7KQ4O3xqO5lpq133Ja5D +5KGrSYd7AEgGQokpFmCFLtUJEKJtbZ/giO1YNscP/ivVl6G7WUeL9+y7iTuu57yZ +ICYAyNg+PRbTV2QrhgobUuC9udWiLYLzsCt9T+4vzNdd2cqgJWsnbLOLFMIpZFq4 +BczU7Zt//Bep3U3lVAUNkAGtwGNReobeVEHI8ANTKsVvwqlpymviF2i/+GH2mET1 +UaT4Sy0hXlHWfy5v+BgLYvF7JpwU6VsfbdDsrV6hfxxpBRAtGIbFKMMR8WkNAKVd +QbtaeRNvnGHUNdAY2ZB9YHU60skHNRrPoqrPIUkc4Ml/CTbLpjoo+H+2K4pIq8Qp +Xj1PCfmB34HeXjRC25LYdnc5Otey36YlL3wx6ORq2ENX8T6p50N3xK0J1m1Cokuh +twEmpXNWXHPX4KUnh1fskxmlgfPqevQkzR0N4KGO0IWsUpf8f2wgrbHpFcuDuvRO +htU0qaeheJyGwYDKnKQ025p/IypYwqVwWB3wP2fK3M1Nqd7ds0TKg6FGI10O9jYx +O3VWGJ34ts6dPoQHyTrscrSltvRsmsD6TNK72e/iDKHpe4yOa58DGpOObFZ3GxXE +bRelB78A0+wWcRMHW/ZoEjYQu0GSP65lzQTK4zmWGJfmJb+N+tgHFJf2H6OMN2Oi +pu06m6O0+dki+WkUSGUiHvoBiZv4iUYwojXQaEcb3ldpIt+x9P4UfBMGYpln8wXi +/0Nh4HRmhwPTjPb12SkvuQENBFZPg0MBCADV9n2yYdJ86OACzmsRbpEZCfn9ohRc +A7kA8VvRkm4DAf9i8fKOl0Z5vsoBD6cRAJ0PDU4qN6mc4JfQi6Iym2kpHzajMQ8O +sLvyTJpjyCOtBGYBTSvloHcgF92enaG3OYGRsvfczPFs/uIEaLVQgPiAteL9SPn4 +TENiKZNgI4pPpCdbA3Zyih3swVtxuxwJvau19+F4Fd8oZTEAd24OB9CnIxTVsHeA +zya2Q2gNjSaHoim6t+LI/MuJ8i7CHOpQP6JPA1KNASbrFDeAhQJxDbkJNtm+jad4 +ICw74/gxo4VUKWuKbv67HIDU/e+R+4N+buQgb6Vd/WWaS4absvk763H5ABEBAAGJ +AjwEGAEKACYCGwwWIQS3MarFIbAThZMT9nSibW2f4IjtWAUCX7u2tQUJDS6acgAK +CRCibW2f4IjtWJ+QD/902qsHI+tnj7DILEc9fukZhzstoLmNS33EsTbuCr8h6kCZ +pZGTw2l8BDmqtN8vXVT6BkmulhFl4dFCOga32vHQrfPf0/+raI3x5ha4K0XLCjy7 +Bjhxo7uspvxS49y+aLEbjDLRQZ/+lpu3lgpoI5qfJ2dirtZqCqIA6HvGX5U3x1Zi ++03jK9l8YyhGFuxUslw6E1HgOZ8QJTv0A495b31iwXqi5XStugesIdTxNU8A3n4u +/sEjwZo3A5RAMPm0BnvNOC65Jw+DTemMSSFeA9EN3ltGlY4XxMpEz9xBWPqUMbPf ++JX0k8zikEljqCOTB3cbJBTDU+B9F9ZbeqC0Ir0eJ3X2XIC+OcfwO46zCEu/2LKq +G2ipEZO/IzDowKMR4m2XFDgMaX1rRvl6xsAGcvnLpi5tGKL00uTp403l48PXHf90 +dUHaurM+/jlGzH/wXUlSmJXAwqXBwcpqHN1PJuUBBG/mz8KkbG6lwttHtN162Cp2 +dqmfTnGjGvu6L8ECypqfIN9nqBKyZwKyUCzgNASENKWIooUn+Hy1jONaDtpAyCop +O5FrTvp38ObJh6iMhbQUr3sPurRT7XQ0uinRDHqqojDSbkQWM2TD8YmGAiwUP/r7 +v8fuzGUiOGzU3yKN0XCTEGmmtoGzDmj5KnR1mTJrxbCMIlAXHYpzICPgu75f6YkC +PAQYAQoAJgIbDBYhBLcxqsUhsBOFkxP2dKJtbZ/giO1YBQJjgf7ZBQkWmH0WAAoJ +EKJtbZ/giO1YQZkP/1PIbDv9hN0rJsZh2gQw3QG48wPnDdz8BI7Lrus8lHMUOS71 +wpKcjUwks4f5GU0f0NBqtWJZ9AZhLBSwBf02WyBh4Ti1JFva8UKhpXk+YrsY1jDJ +G6LsfBLntD6zYZUyD0QLUTZVNSYyvVn9Cp2GKm0eEiXPoDSQFzvytNbLtQAFybke +xPWOvRtMSXh+PrTaRZe3Vrui7l/+RPYkwEybVStm6/IADjoa2lwZVwtA8F2kR7XE +9fXgDP7bna6saYeiz1UjmvRBOgMDgyg3zqRmuc4/jkcsXOkQygtbT4pB+QbZ/p9S +esQmGs0D+yElB3RQSu0N9adSCEBmvFkFRYodlS6CXGJW59MRPKyYER+4DqtGDaMv +b/tD7MgLdmwymiiM1ozoZf115SYfXVNnNqZx39GDbSsXX7O+Q2pafD3ZMAhPNfzY +iB6cuiBrAy+4xHntJfhroZVB4O52Je5cbetUENutZb8QKvp+J3Qpd8RZV1HJFseH +XH3H0u42s/Es8n6Y7RPmnoZSu3oreFXy52LvPK07vLoZX4Biu1dbrEkOVkHz7NXv +gA8CbC6gcYwlQ6Kqy1vP2+GCNyN98MNecVmBMfXj+zmX3j9oyMc8JtTsjfsqc/Cy +wJsjY1eh6lzjYCfDLKYwQFiBoKhzf61EMKhG9JpOfOvadsyA6D6gayzRJxBRuQEN +BFZPg9oBCADJ8ZuYRYPYPLHXv+4HEvatY1P4ai0eriNhJAH9fnZNaefSCAchS39h +Viex1GFQhL7ErpGRMBWCfVaaw6gmIubep7inioeUF8WR3Q/23fw9TOhMK0Ro8HeE +HqYuD+9yjdmke5ckrViRA4hGn1OX+B6mS51gfzgrSOfKD5saDVy35bQ/KaOD7Qim +uEN8NQAkdUTJu9WrwnVKUb5LBvCxqAmZtjGtlmFNc4ee+qCPvks33MiYX35jlJ2g +1/GZpvtMVPuaHIJqpvyPkyAxlwt9Uawg/DjEkPbNHAuMuDTknuIHA6MrSGThyqre +PITADb0QtXa6fQfZ05ML/W2AE04BH4PjABEBAAGJAjwEGAEKACYCGyAWIQS3MarF +IbAThZMT9nSibW2f4IjtWAUCX7u2tQUJDS6Z2wAKCRCibW2f4IjtWHb7EACamLmj +7tYGpFhibogUsUM/RdMe0cQzBfkO1gKJSSNuCemjn0UTCGRWtdtnIAF6yMBfIsO2 +9L2yPpqtmigVfayL2b7w+pAB/1unssFfKzG6MKd/8o8i2ssVH1wz2yJ4Vb4C9L6P +ar3PdjVS4u5OLmsOesJD8hsmfA69+90yHvv9tAQq4vJH446z6Z/D7WJnA1TAwEvd +3IGpxvLArdDCirnpHbQZShM+1iWR3Jawho46pyMV7EHAYVS3FuN8sATNmR7h8kMn +Q+UY1fHTu6vlcAf1aRCsCgmBnL0BtAJC0ufXTPcKVh9DpJZibtNFb+rarU7RoKUN +yl0blnio7B9ftLCFxqPgC4Q/NCPCP+j+KGGQ5ZoivG7t2idtv/98oFNGo52POh+s +BYs3b/ZHI0AflL1s4HAGn7xfP1e1ue68xyTUSGd0uXebMmDhTvNQgKpqLRO/F/Ue +YjIsM4kJp6Soh2LoZbtAa/rExKIVnrYk/LithJHXyXIVu0IZpdqiCuX8cmswjFo+ +9zWRO7OPxVGmSShYlzrQBiXtDczb4Np34n4L/tvzpWBXAuz2Vlbmn/XalKE0rT// +SaSdHjRGbSpJLTiE+1WvdOdj71dVXW251klS99zgeSdURVRKWbfJjI+ZeaIuShH4 +15MPlfMleq7cWAUk70fsH6/OIEvaQr0UEZ2kPIkCPAQYAQoAJgIbIBYhBLcxqsUh +sBOFkxP2dKJtbZ/giO1YBQJjgf7ZBQkWmHx/AAoJEKJtbZ/giO1YipwP/RuxMddi +s2bFQOrYUPsJmrQisa+uPgMJC0vDFfTga7o6ggp7vMLpwouUNx3PPBF0XI+NMUtn +iGbdseWgbDiHSitx/r8UlhWhtZ8djawsHTE6A8Dneym7FXVry7oS8p7F6QIPIRdE +bZ5snHxYh2Pff1e0/x8O8QtH74Efs9Kvjdn8C5Y6UK21kECG2DArbfiR/K6PkV6z +l6yV/yX6e8yBX538Q5h2DlV6MaxWY4TkdKM5GCZSXvpEzFtCMyvYxa73oh59EgdC +vAgW8Shwy3+JXgQZ6I13k3XB9FSwWV5+N8IV15qkLUJuQwbdo52VS/YkkULHXwXm +7HLVnSBXrwKCqM6Ycv/PVxOLsy+/Vs+ejcSGu4SCML0h6q3X29xb30lAC30oLUna +f98DSzXPpmTHL1jqmDYkQwHniC2geLmUgBOu6YxJvU/m5pZnAwmXqXyoUZiNmTuY +5u3B0Ld9qleOQGtDL/iJjNwF+nmfpwfrL+9xaa1XbZ3cBc9McHnxSGqJZKEkPsYH +gtEkkEt8h1fGbPjdpnBEefNs9Vk37e3BO0LR/LYJ+qiviGSTLNc7Hup4s3sPeMu1 +gUQ27PPDTn2POHd61BkByMmxilGRYUgXYPeIR8Fyq4PwvNBfcci7u9r9o7ycI67E +sLKHJyZzSZmi6wVDA4vXfZC2fj3AlHd7ygZ3 +=pvIX -----END PGP PUBLIC KEY BLOCK----- diff --git a/contrib/msggen/msggen/__main__.py b/contrib/msggen/msggen/__main__.py index cb221f3b6b42..5f9fdc2b8f35 100644 --- a/contrib/msggen/msggen/__main__.py +++ b/contrib/msggen/msggen/__main__.py @@ -1,37 +1,39 @@ import json import os +import argparse +from pathlib import Path from msggen.gen.grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator from msggen.gen.grpc2py import Grpc2PyGenerator from msggen.gen.rust import RustGenerator from msggen.gen.generator import GeneratorChain -from msggen.utils import repo_root, load_jsonrpc_service +from msggen.utils import load_jsonrpc_service def add_handler_gen_grpc(generator_chain: GeneratorChain, meta): """Load all mapped RPC methods, wrap them in a Service, and split them into messages. """ - fname = repo_root() / "cln-grpc" / "proto" / "node.proto" + fname = Path("cln-grpc") / "proto" / "node.proto" dest = open(fname, "w") generator_chain.add_generator(GrpcGenerator(dest, meta)) - fname = repo_root() / "cln-grpc" / "src" / "convert.rs" + fname = Path("cln-grpc") / "src" / "convert.rs" dest = open(fname, "w") generator_chain.add_generator(GrpcConverterGenerator(dest)) generator_chain.add_generator(GrpcUnconverterGenerator(dest)) - fname = repo_root() / "cln-grpc" / "src" / "server.rs" + fname = Path("cln-grpc") / "src" / "server.rs" dest = open(fname, "w") generator_chain.add_generator(GrpcServerGenerator(dest)) def add_handler_get_grpc2py(generator_chain: GeneratorChain): - fname = repo_root() / "contrib" / "pyln-testing" / "pyln" / "testing" / "grpc2py.py" + fname = Path("contrib") / "pyln-testing" / "pyln" / "testing" / "grpc2py.py" dest = open(fname, "w") generator_chain.add_generator(Grpc2PyGenerator(dest)) def add_handler_gen_rust_jsonrpc(generator_chain: GeneratorChain): - fname = repo_root() / "cln-rpc" / "src" / "model.rs" + fname = Path("cln-rpc") / "src" / "model.rs" dest = open(fname, "w") generator_chain.add_generator(RustGenerator(dest)) @@ -48,8 +50,9 @@ def write_msggen_meta(meta): os.rename(f'.msggen.json.tmp.{pid}', '.msggen.json') -def run(): - service = load_jsonrpc_service() +def run(rootdir: Path): + schemadir = rootdir / "doc" / "schemas" + service = load_jsonrpc_service(schema_dir=schemadir) meta = load_msggen_meta() generator_chain = GeneratorChain() @@ -63,4 +66,13 @@ def run(): if __name__ == "__main__": - run() + parser = argparse.ArgumentParser() + parser.add_argument( + '--rootdir', + dest='rootdir', + default='.' + ) + args = parser.parse_args() + run( + rootdir=Path(args.rootdir) + ) diff --git a/contrib/msggen/msggen/gen/grpc.py b/contrib/msggen/msggen/gen/grpc.py index b88403fb0d58..2dd6adcc6c74 100644 --- a/contrib/msggen/msggen/gen/grpc.py +++ b/contrib/msggen/msggen/gen/grpc.py @@ -210,6 +210,11 @@ def generate_message(self, message: CompositeField): if f.path in overrides: typename = overrides[f.path] self.write(f"\t{opt}{typename} {f.normalized()} = {i};\n", False) + elif isinstance(f, CompositeField): + typename = f.typename + if f.path in overrides: + typename = overrides[f.path] + self.write(f"\t{opt}{typename} {f.normalized()} = {i};\n", False) self.write(f"""}} """) @@ -256,11 +261,14 @@ def generate_composite(self, prefix, field: CompositeField): for f in field.fields: if isinstance(f, ArrayField): self.generate_array(prefix, f) + elif isinstance(f, CompositeField): + self.generate_composite(prefix, f) + pbname = self.to_camel_case(field.typename) # And now we can convert the current field: self.write(f"""\ #[allow(unused_variables)] - impl From<{prefix}::{field.typename}> for pb::{field.typename} {{ + impl From<{prefix}::{field.typename}> for pb::{pbname} {{ fn from(c: {prefix}::{field.typename}) -> Self {{ Self {{ """) @@ -321,6 +329,13 @@ def generate_composite(self, prefix, field: CompositeField): self.write(f"{name}: {rhs}, // Rule #2 for type {typ}\n", numindent=3) + elif isinstance(f, CompositeField): + rhs = "" + if f.required: + rhs = f'Some(c.{name}.into())' + else: + rhs = f'c.{name}.map(|v| v.into())' + self.write(f"{name}: {rhs},\n", numindent=3) self.write(f"""\ }} }} @@ -328,6 +343,12 @@ def generate_composite(self, prefix, field: CompositeField): """) + def to_camel_case(self, snake_str): + components = snake_str.split('_') + # We capitalize the first letter of each component except the first one + # with the 'title' method and join them together. + return components[0] + ''.join(x.title() for x in components[1:]) + def generate_requests(self, service): for meth in service.methods: req = meth.request @@ -349,8 +370,8 @@ def generate(self, service: Service) -> None: use cln_rpc::model::{responses,requests}; use crate::pb; use std::str::FromStr; - use bitcoin_hashes::sha256::Hash as Sha256; - use bitcoin_hashes::Hash; + use bitcoin::hashes::sha256::Hash as Sha256; + use bitcoin::hashes::Hash; use cln_rpc::primitives::PublicKey; """) @@ -380,12 +401,15 @@ def generate_composite(self, prefix, field: CompositeField) -> None: for f in field.fields: if isinstance(f, ArrayField): self.generate_array(prefix, f) + elif isinstance(f, CompositeField): + self.generate_composite(prefix, f) + pbname = self.to_camel_case(field.typename) # And now we can convert the current field: self.write(f"""\ #[allow(unused_variables)] - impl From for {prefix}::{field.typename} {{ - fn from(c: pb::{field.typename}) -> Self {{ + impl From for {prefix}::{field.typename} {{ + fn from(c: pb::{pbname}) -> Self {{ Self {{ """) @@ -440,11 +464,19 @@ def generate_composite(self, prefix, field: CompositeField) -> None: 'hash': f'Sha256::from_slice(&c.{name}).unwrap()', 'hash?': f'c.{name}.map(|v| Sha256::from_slice(&v).unwrap())', 'txid': f'hex::encode(&c.{name})', + 'TlvStream?': f'c.{name}.map(|s| s.into())', }.get( typ, f'c.{name}' # default to just assignment ) self.write(f"{name}: {rhs}, // Rule #1 for type {typ}\n", numindent=3) + elif isinstance(f, CompositeField): + rhs = "" + if f.required: + rhs = f'c.{name}.unwrap().into()' + else: + rhs = f'c.{name}.map(|v| v.into())' + self.write(f"{name}: {rhs},\n", numindent=3) self.write(f"""\ }} diff --git a/contrib/msggen/msggen/gen/rust.py b/contrib/msggen/msggen/gen/rust.py index 017aad1c07c3..69d3f539d09d 100644 --- a/contrib/msggen/msggen/gen/rust.py +++ b/contrib/msggen/msggen/gen/rust.py @@ -201,7 +201,16 @@ def gen_composite(c) -> Tuple[str, str]: r += "".join([f[0] for f in fields]) r += "}\n\n" - return ("", r) + + defi = "" + if c.deprecated: + defi += " #[deprecated]\n" + if c.required: + defi += f" #[serde(alias = \"{c.name.name}\")]\n pub {c.name}: {c.typename},\n" + else: + defi += f" #[serde(alias = \"{c.name.name}\", skip_serializing_if = \"Option::is_none\")]\n pub {c.name}: Option<{c.typename}>,\n" + + return defi, r class RustGenerator(IGenerator): diff --git a/contrib/msggen/msggen/model.py b/contrib/msggen/msggen/model.py index 8bad9dc8f872..8492d3831cc6 100644 --- a/contrib/msggen/msggen/model.py +++ b/contrib/msggen/msggen/model.py @@ -343,7 +343,21 @@ def __str__(self): DatastoreKeyField = ArrayField(itemtype=PrimitiveField("string", None, None), dims=1, path=None, description=None) InvoiceExposeprivatechannelsField = PrimitiveField("boolean", None, None) PayExclude = ArrayField(itemtype=PrimitiveField("string", None, None), dims=1, path=None, description=None) -RoutehintListField = PrimitiveField("RoutehintList", None, None) +RoutehintListField = PrimitiveField( + "RoutehintList", + None, + None +) + +# TlvStreams are special, they don't have preset dict-keys, rather +# they can specify `u64` keys pointing to hex payloads. So the schema +# has to rely on additionalProperties to make it work. +TlvStreamField = PrimitiveField( + "TlvStream", + None, + None +) + # Override fields with manually managed types, fieldpath -> field mapping overrides = { 'Invoice.label': InvoiceLabelField, @@ -355,6 +369,7 @@ def __str__(self): 'Invoice.exposeprivatechannels': InvoiceExposeprivatechannelsField, 'Pay.exclude': PayExclude, 'KeySend.routehints': RoutehintListField, + 'KeySend.extratlvs': TlvStreamField, } diff --git a/contrib/msggen/msggen/utils/__init__.py b/contrib/msggen/msggen/utils/__init__.py index eaa1c1aff90d..ee5ea046ee36 100644 --- a/contrib/msggen/msggen/utils/__init__.py +++ b/contrib/msggen/msggen/utils/__init__.py @@ -1 +1 @@ -from .utils import load_jsonrpc_method, load_jsonrpc_service, repo_root # noqa +from .utils import load_jsonrpc_method, load_jsonrpc_service # noqa diff --git a/contrib/msggen/msggen/utils/utils.py b/contrib/msggen/msggen/utils/utils.py index 2290b89d0091..883757b47b5e 100644 --- a/contrib/msggen/msggen/utils/utils.py +++ b/contrib/msggen/msggen/utils/utils.py @@ -1,22 +1,13 @@ -import subprocess import json from pathlib import Path from msggen.model import Method, CompositeField, Service -def repo_root(): - path = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]) - return Path(path.strip().decode('UTF-8')) - - -def load_jsonrpc_method(name, schema_dir: str = None): +def load_jsonrpc_method(name, schema_dir: Path): """Load a method based on the file naming conventions for the JSON-RPC. """ - if schema_dir is None: - base_path = (repo_root() / "doc" / "schemas").resolve() - else: - base_path = schema_dir + base_path = schema_dir req_file = base_path / f"{name.lower()}.request.json" resp_file = base_path / f"{name.lower()}.schema.json" request = CompositeField.from_js(json.load(open(req_file)), path=name) @@ -34,7 +25,7 @@ def load_jsonrpc_method(name, schema_dir: str = None): ) -def load_jsonrpc_service(schema_dir: str = None): +def load_jsonrpc_service(schema_dir: str): method_names = [ "Getinfo", "ListPeers", @@ -106,7 +97,6 @@ def load_jsonrpc_service(schema_dir: str = None): # "sendcustommsg", # "sendinvoice", # "sendonionmessage", - # "setchannelfee", "SetChannel", "SignMessage", # "unreserveinputs", diff --git a/contrib/pyln-client/pyln/client/__init__.py b/contrib/pyln-client/pyln/client/__init__.py index 399ec41417a5..f25ca52e88cd 100644 --- a/contrib/pyln-client/pyln/client/__init__.py +++ b/contrib/pyln-client/pyln/client/__init__.py @@ -2,7 +2,7 @@ from .plugin import Plugin, monkey_patch, RpcException from .gossmap import Gossmap, GossmapNode, GossmapChannel, GossmapNodeId -__version__ = "0.12.1" +__version__ = "22.11rc1" __all__ = [ "LightningRpc", diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index 507ed31858d8..d92b544f3ff2 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -163,9 +163,13 @@ def __int__(self) -> int: return self.millisatoshis def __lt__(self, other: 'Millisatoshi') -> bool: + if isinstance(other, int): + return self.millisatoshis < other return self.millisatoshis < other.millisatoshis def __le__(self, other: 'Millisatoshi') -> bool: + if isinstance(other, int): + return self.millisatoshis <= other return self.millisatoshis <= other.millisatoshis def __eq__(self, other: object) -> bool: @@ -177,9 +181,13 @@ def __eq__(self, other: object) -> bool: return False def __gt__(self, other: 'Millisatoshi') -> bool: + if isinstance(other, int): + return self.millisatoshis > other return self.millisatoshis > other.millisatoshis def __ge__(self, other: 'Millisatoshi') -> bool: + if isinstance(other, int): + return self.millisatoshis >= other return self.millisatoshis >= other.millisatoshis def __add__(self, other: 'Millisatoshi') -> 'Millisatoshi': @@ -285,6 +293,7 @@ def __init__(self, socket_path, executor=None, logger=logging, encoder_cls=json. self.executor = executor self.logger = logger self._notify = None + self._filter = None if caller_name is None: self.caller_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] else: @@ -338,7 +347,7 @@ def get_json_id(self, method, cmdprefix): this_id = f'{cmdprefix}/{this_id}' return this_id - def call(self, method, payload=None, cmdprefix=None): + def call(self, method, payload=None, cmdprefix=None, filter=None): """Generic call API: you can set cmdprefix here, or set self.cmdprefix before the call is made. @@ -379,6 +388,11 @@ def call(self, method, payload=None, cmdprefix=None): "id": this_id, } + if filter is None: + filter = self._filter + if filter is not None: + request["filter"] = filter + self._writeobj(sock, request) while True: resp, buf = self._readobj(sock, buf) @@ -435,6 +449,22 @@ def fn(message, progress, request, **kwargs): yield self._notify = old + @contextmanager + def reply_filter(self, filter): + """Filter the fields returned from am RPC call (or more than one).. + + This is a context manager and should be used like this: + + ```python + with rpc.reply_filter({"transactions": [{"outputs": [{"amount_msat": true, "type": true}]}]}): + rpc.listtransactions() + ``` + """ + old = self._filter + self._filter = filter + yield + self._filter = old + class LightningRpc(UnixDomainSocketRpc): """ @@ -581,6 +611,26 @@ def connect(self, peer_id, host=None, port=None): } return self.call("connect", payload) + def datastore(self, key, string=None, hex=None, mode=None, generation=None): + """ + Add/replace an entry in the datastore; either string or hex. + {key} can be a single string, or a sequence of strings. + {mode} defaults to 'must-create', but other options are possible: + - 'must-replace': fail it it doesn't already exist. + - 'create-or-replace': don't fail. + - 'must-append': must exist, and append to existing. + - 'create-or-append': set, or append to existing. + {generation} only succeeds if the current entry has this generation count (mode must be 'must-replace' or 'must-append'). + """ + payload = { + "key": key, + "string": string, + "hex": hex, + "mode": mode, + "generation": generation, + } + return self.call("datastore", payload) + def decodepay(self, bolt11, description=None): """ Decode {bolt11}, using {description} if necessary. @@ -591,6 +641,18 @@ def decodepay(self, bolt11, description=None): } return self.call("decodepay", payload) + def deldatastore(self, key, generation=None): + """ + Remove an existing entry from the datastore. + {key} can be a single string, or a sequence of strings. + {generation} means delete only succeeds if the current entry has this generation count. + """ + payload = { + "key": key, + "generation": generation, + } + return self.call("deldatastore", payload) + def delexpiredinvoice(self, maxexpirytime=None): """ Delete all invoices that have expired on or before the given {maxexpirytime}. @@ -921,6 +983,16 @@ def listconfigs(self, config=None): } return self.call("listconfigs", payload) + def listdatastore(self, key=None): + """ + Show entries in the heirarchical datastore, or just one from one {key}root. + {key} can be a single string, or a sequence of strings. + """ + payload = { + "key": key, + } + return self.call("listdatastore", payload) + def listforwards(self, status=None, in_channel=None, out_channel=None): """List all forwarded payments and their information matching forward {status}, {in_channel} and {out_channel}. @@ -1051,7 +1123,7 @@ def newaddr(self, addresstype=None): def pay(self, bolt11, amount_msat=None, label=None, riskfactor=None, maxfeepercent=None, retry_for=None, - maxdelay=None, exemptfee=None, localofferid=None, exclude=None, + maxdelay=None, exemptfee=None, localinvreqid=None, exclude=None, maxfee=None, description=None, msatoshi=None): """ Send payment specified by {bolt11} with {amount_msat} @@ -1070,7 +1142,7 @@ def pay(self, bolt11, amount_msat=None, label=None, riskfactor=None, "retry_for": retry_for, "maxdelay": maxdelay, "exemptfee": exemptfee, - "localofferid": localofferid, + "localinvreqid": localinvreqid, "exclude": exclude, "maxfee": maxfee, "description": description, @@ -1270,20 +1342,6 @@ def sendonion( } return self.call("sendonion", payload) - def setchannelfee(self, id, base=None, ppm=None, enforcedelay=None): - """ - Set routing fees for a channel/peer {id} (or 'all'). {base} is a value in millisatoshi - that is added as base fee to any routed payment. {ppm} is a value added proportionally - per-millionths to any routed payment volume in satoshi. {enforcedelay} is the number of seconds before enforcing this change. - """ - payload = { - "id": id, - "base": base, - "ppm": ppm, - "enforcedelay": enforcedelay, - } - return self.call("setchannelfee", payload) - def setchannel(self, id, feebase=None, feeppm=None, htlcmin=None, htlcmax=None, enforcedelay=None): """Set configuration a channel/peer {id} (or 'all'). diff --git a/contrib/pyln-client/pyln/client/plugin.py b/contrib/pyln-client/pyln/client/plugin.py index 921c9f3041d0..e53bcbeda6df 100644 --- a/contrib/pyln-client/pyln/client/plugin.py +++ b/contrib/pyln-client/pyln/client/plugin.py @@ -917,6 +917,7 @@ def _getmanifest(self, **kwargs) -> JSONType: 'subscriptions': list(self.subscriptions.keys()), 'hooks': hooks, 'dynamic': self.dynamic, + 'nonnumericids': True, 'notifications': [ {"method": name} for name in self.notification_topics ], diff --git a/contrib/pyln-client/pyproject.toml b/contrib/pyln-client/pyproject.toml index e9b742aad089..220610286e51 100644 --- a/contrib/pyln-client/pyproject.toml +++ b/contrib/pyln-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyln-client" -version = "0.12.1" +version = "22.11rc1" description = "Client library and plugin library for Core Lightning" authors = ["Christian Decker "] license = "BSD-MIT" diff --git a/contrib/pyln-client/tests/test_millisatoshi.py b/contrib/pyln-client/tests/test_millisatoshi.py index f8f877ff0360..b60c48089521 100644 --- a/contrib/pyln-client/tests/test_millisatoshi.py +++ b/contrib/pyln-client/tests/test_millisatoshi.py @@ -1,6 +1,45 @@ +import pytest from pyln.client import Millisatoshi def test_sum_radd(): result = sum([Millisatoshi(1), Millisatoshi(2), Millisatoshi(3)]) assert int(result) == 6 + + +def test_compare_int(): + # Test that we can compare msat to int numbers + assert Millisatoshi(10) == 10 + assert Millisatoshi(10) > 9 + assert Millisatoshi(10) >= 9 + assert Millisatoshi(10) < 11 + assert Millisatoshi(10) <= 11 + + # Same as above but check that the order doesn't matter + assert 10 == Millisatoshi(10) + assert 9 < Millisatoshi(10) + assert 9 <= Millisatoshi(10) + assert 11 > Millisatoshi(10) + assert 11 >= Millisatoshi(10) + + # Test that we can't accidentally compare msat to float + assert Millisatoshi(10) != 10.0 + with pytest.raises(AttributeError): + assert Millisatoshi(10) > 9.0 + with pytest.raises(AttributeError): + assert Millisatoshi(10) >= 9.0 + with pytest.raises(AttributeError): + assert Millisatoshi(10) < 11.0 + with pytest.raises(AttributeError): + assert Millisatoshi(10) <= 11.0 + + # ... and again that order does not matter + assert 10.0 != Millisatoshi(10) + with pytest.raises(AttributeError): + assert 9.0 < Millisatoshi(10) + with pytest.raises(AttributeError): + assert 9.0 <= Millisatoshi(10) + with pytest.raises(AttributeError): + assert 11.0 > Millisatoshi(10) + with pytest.raises(AttributeError): + assert 11.0 >= Millisatoshi(10) diff --git a/contrib/pyln-proto/pyln/proto/__init__.py b/contrib/pyln-proto/pyln/proto/__init__.py index f41912275101..6acf3cd64c2a 100644 --- a/contrib/pyln-proto/pyln/proto/__init__.py +++ b/contrib/pyln-proto/pyln/proto/__init__.py @@ -4,7 +4,7 @@ from .onion import OnionPayload, TlvPayload, LegacyOnionPayload from .wire import LightningConnection, LightningServerSocket -__version__ = "0.12.0" +__version__ = "22.11rc1" __all__ = [ "Invoice", diff --git a/contrib/pyln-proto/pyln/proto/message/fundamental_types.py b/contrib/pyln-proto/pyln/proto/message/fundamental_types.py index ac807e5e9c43..75aa4d640a61 100644 --- a/contrib/pyln-proto/pyln/proto/message/fundamental_types.py +++ b/contrib/pyln-proto/pyln/proto/message/fundamental_types.py @@ -297,7 +297,6 @@ def fundamental_types() -> List[FieldType]: # Extra types added in offers draft: IntegerType('utf8', 1, 'B'), FundamentalHexType('bip340sig', 64), - FundamentalHexType('point32', 32), ] diff --git a/contrib/pyln-proto/pyln/proto/wire.py b/contrib/pyln-proto/pyln/proto/wire.py index 62b5cf1b0f80..fab6e71d59d1 100644 --- a/contrib/pyln-proto/pyln/proto/wire.py +++ b/contrib/pyln-proto/pyln/proto/wire.py @@ -236,13 +236,21 @@ def read_message(self): length, = struct.unpack("!H", length) self.rn += 1 - mc = self.connection.recv(length + 16) - if len(mc) < length + 16: - raise ValueError( - "Short read reading the message: {} != {}".format( - length + 16, len(lc) + # Large messages may be split into multiple packets: + mc = b'' + toread = length + 16 + while len(mc) < length + 16: + d = self.connection.recv(toread) + if len(d) == 0: + # Not making progress anymore + raise ValueError( + "Short read reading the message: {} != {}".format( + length + 16, len(mc) + ) ) - ) + mc += d + toread -= len(d) + m = decryptWithAD(self.rk, self.nonce(self.rn), b'', mc) self.rn += 1 assert(self.rn % 2 == 0) diff --git a/contrib/pyln-proto/pyproject.toml b/contrib/pyln-proto/pyproject.toml index 407dc5ab21ee..9565e796e8d7 100644 --- a/contrib/pyln-proto/pyproject.toml +++ b/contrib/pyln-proto/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyln-proto" -version = "0.12.1" +version = "22.11rc1" description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." authors = ["Christian Decker "] license = "BSD-MIT" diff --git a/contrib/pyln-proto/tests/test_fundamental_types.py b/contrib/pyln-proto/tests/test_fundamental_types.py index 85e98d8104ae..1c22f3a21596 100644 --- a/contrib/pyln-proto/tests/test_fundamental_types.py +++ b/contrib/pyln-proto/tests/test_fundamental_types.py @@ -65,10 +65,6 @@ def test_fundamental_types(): '2122232425262728292a2b2c2d2e2f30' '3132333435363738393a3b3c3d3e3f40', bytes(range(1, 65))]], - 'point32': [['02030405060708090a0b0c0d0e0f10' - '1112131415161718191a1b1c1d1e1f20' - '21', - bytes(range(2, 34))]], } untested = set() diff --git a/contrib/pyln-testing/pyln/testing/__init__.py b/contrib/pyln-testing/pyln/testing/__init__.py index 882bcfc5cc3d..716038e11398 100644 --- a/contrib/pyln-testing/pyln/testing/__init__.py +++ b/contrib/pyln-testing/pyln/testing/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.12.1" +__version__ = "22.11rc1" __all__ = [ "__version__", diff --git a/contrib/pyln-testing/pyln/testing/fixtures.py b/contrib/pyln-testing/pyln/testing/fixtures.py index 48ea599df488..795b67ecf246 100644 --- a/contrib/pyln-testing/pyln/testing/fixtures.py +++ b/contrib/pyln-testing/pyln/testing/fixtures.py @@ -328,14 +328,6 @@ def is_32byte_hex(self, instance): """ return self.is_type(instance, "hex") and len(instance) == 64 - def is_point32(checker, instance): - """x-only BIP-340 public key""" - if not checker.is_type(instance, "hex"): - return False - if len(instance) != 64: - return False - return True - def is_signature(checker, instance): """DER encoded secp256k1 ECDSA signature""" if not checker.is_type(instance, "hex"): @@ -414,7 +406,6 @@ def is_msat_or_any(checker, instance): "txid": is_txid, "signature": is_signature, "bip340sig": is_bip340sig, - "point32": is_point32, "short_channel_id": is_short_channel_id, "short_channel_id_dir": is_short_channel_id_dir, "outpoint": is_outpoint, diff --git a/contrib/pyln-testing/pyln/testing/grpc2py.py b/contrib/pyln-testing/pyln/testing/grpc2py.py index f22259dd1919..482dc822d50c 100644 --- a/contrib/pyln-testing/pyln/testing/grpc2py.py +++ b/contrib/pyln-testing/pyln/testing/grpc2py.py @@ -339,7 +339,7 @@ def createinvoice2py(m): "paid_at": m.paid_at, # PrimitiveField in generate_composite "payment_preimage": hexlify(m.payment_preimage), # PrimitiveField in generate_composite "local_offer_id": hexlify(m.local_offer_id), # PrimitiveField in generate_composite - "payer_note": m.payer_note, # PrimitiveField in generate_composite + "invreq_payer_note": m.invreq_payer_note, # PrimitiveField in generate_composite }) @@ -384,7 +384,7 @@ def delinvoice2py(m): "status": str(m.status), # EnumField in generate_composite "expires_at": m.expires_at, # PrimitiveField in generate_composite "local_offer_id": hexlify(m.local_offer_id), # PrimitiveField in generate_composite - "payer_note": m.payer_note, # PrimitiveField in generate_composite + "invreq_payer_note": m.invreq_payer_note, # PrimitiveField in generate_composite }) @@ -428,7 +428,7 @@ def listinvoices_invoices2py(m): "bolt11": m.bolt11, # PrimitiveField in generate_composite "bolt12": m.bolt12, # PrimitiveField in generate_composite "local_offer_id": hexlify(m.local_offer_id), # PrimitiveField in generate_composite - "payer_note": m.payer_note, # PrimitiveField in generate_composite + "invreq_payer_note": m.invreq_payer_note, # PrimitiveField in generate_composite "pay_index": m.pay_index, # PrimitiveField in generate_composite "amount_received_msat": amount2msat(m.amount_received_msat), # PrimitiveField in generate_composite "paid_at": m.paid_at, # PrimitiveField in generate_composite diff --git a/contrib/pyln-testing/pyln/testing/node_pb2.py b/contrib/pyln-testing/pyln/testing/node_pb2.py index fc81eb8f583a..bb105f8bd026 100644 --- a/contrib/pyln-testing/pyln/testing/node_pb2.py +++ b/contrib/pyln-testing/pyln/testing/node_pb2.py @@ -15,7 +15,7 @@ from . import primitives_pb2 as primitives__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nnode.proto\x12\x03\x63ln\x1a\x10primitives.proto\"\x10\n\x0eGetinfoRequest\"\xae\x04\n\x0fGetinfoResponse\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05\x61lias\x18\x02 \x01(\t\x12\r\n\x05\x63olor\x18\x03 \x01(\x0c\x12\x11\n\tnum_peers\x18\x04 \x01(\r\x12\x1c\n\x14num_pending_channels\x18\x05 \x01(\r\x12\x1b\n\x13num_active_channels\x18\x06 \x01(\r\x12\x1d\n\x15num_inactive_channels\x18\x07 \x01(\r\x12\x0f\n\x07version\x18\x08 \x01(\t\x12\x15\n\rlightning_dir\x18\t \x01(\t\x12\x13\n\x0b\x62lockheight\x18\x0b \x01(\r\x12\x0f\n\x07network\x18\x0c \x01(\t\x12$\n\x17msatoshi_fees_collected\x18\x12 \x01(\x04H\x00\x88\x01\x01\x12(\n\x13\x66\x65\x65s_collected_msat\x18\r \x01(\x0b\x32\x0b.cln.Amount\x12$\n\x07\x61\x64\x64ress\x18\x0e \x03(\x0b\x32\x13.cln.GetinfoAddress\x12$\n\x07\x62inding\x18\x0f \x03(\x0b\x32\x13.cln.GetinfoBinding\x12\"\n\x15warning_bitcoind_sync\x18\x10 \x01(\tH\x01\x88\x01\x01\x12$\n\x17warning_lightningd_sync\x18\x11 \x01(\tH\x02\x88\x01\x01\x42\x1a\n\x18_msatoshi_fees_collectedB\x18\n\x16_warning_bitcoind_syncB\x1a\n\x18_warning_lightningd_sync\"S\n\x13GetinfoOur_features\x12\x0c\n\x04init\x18\x01 \x01(\x0c\x12\x0c\n\x04node\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\x0c\x12\x0f\n\x07invoice\x18\x04 \x01(\x0c\"\xd3\x01\n\x0eGetinfoAddress\x12\x39\n\titem_type\x18\x01 \x01(\x0e\x32&.cln.GetinfoAddress.GetinfoAddressType\x12\x0c\n\x04port\x18\x02 \x01(\r\x12\x14\n\x07\x61\x64\x64ress\x18\x03 \x01(\tH\x00\x88\x01\x01\"V\n\x12GetinfoAddressType\x12\x07\n\x03\x44NS\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x12\r\n\tWEBSOCKET\x10\x05\x42\n\n\x08_address\"\xfb\x01\n\x0eGetinfoBinding\x12\x39\n\titem_type\x18\x01 \x01(\x0e\x32&.cln.GetinfoBinding.GetinfoBindingType\x12\x14\n\x07\x61\x64\x64ress\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04port\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x13\n\x06socket\x18\x04 \x01(\tH\x02\x88\x01\x01\"P\n\x12GetinfoBindingType\x12\x10\n\x0cLOCAL_SOCKET\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x42\n\n\x08_addressB\x07\n\x05_portB\t\n\x07_socket\"H\n\x10ListpeersRequest\x12\x0f\n\x02id\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x12\n\x05level\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_idB\x08\n\x06_level\"7\n\x11ListpeersResponse\x12\"\n\x05peers\x18\x01 \x03(\x0b\x32\x13.cln.ListpeersPeers\"\xe2\x01\n\x0eListpeersPeers\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x11\n\tconnected\x18\x02 \x01(\x08\x12#\n\x03log\x18\x03 \x03(\x0b\x32\x16.cln.ListpeersPeersLog\x12-\n\x08\x63hannels\x18\x04 \x03(\x0b\x32\x1b.cln.ListpeersPeersChannels\x12\x0f\n\x07netaddr\x18\x05 \x03(\t\x12\x18\n\x0bremote_addr\x18\x07 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08\x66\x65\x61tures\x18\x06 \x01(\x0cH\x01\x88\x01\x01\x42\x0e\n\x0c_remote_addrB\x0b\n\t_features\"\xfd\x02\n\x11ListpeersPeersLog\x12?\n\titem_type\x18\x01 \x01(\x0e\x32,.cln.ListpeersPeersLog.ListpeersPeersLogType\x12\x18\n\x0bnum_skipped\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x11\n\x04time\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06source\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x10\n\x03log\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x14\n\x07node_id\x18\x06 \x01(\x0cH\x04\x88\x01\x01\x12\x11\n\x04\x64\x61ta\x18\x07 \x01(\x0cH\x05\x88\x01\x01\"i\n\x15ListpeersPeersLogType\x12\x0b\n\x07SKIPPED\x10\x00\x12\n\n\x06\x42ROKEN\x10\x01\x12\x0b\n\x07UNUSUAL\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\t\n\x05IO_IN\x10\x05\x12\n\n\x06IO_OUT\x10\x06\x42\x0e\n\x0c_num_skippedB\x07\n\x05_timeB\t\n\x07_sourceB\x06\n\x04_logB\n\n\x08_node_idB\x07\n\x05_data\"\x8a\x16\n\x16ListpeersPeersChannels\x12\x46\n\x05state\x18\x01 \x01(\x0e\x32\x37.cln.ListpeersPeersChannels.ListpeersPeersChannelsState\x12\x19\n\x0cscratch_txid\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x12\n\x05owner\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x1d\n\x10short_channel_id\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x17\n\nchannel_id\x18\x06 \x01(\x0cH\x03\x88\x01\x01\x12\x19\n\x0c\x66unding_txid\x18\x07 \x01(\x0cH\x04\x88\x01\x01\x12\x1b\n\x0e\x66unding_outnum\x18\x08 \x01(\rH\x05\x88\x01\x01\x12\x1c\n\x0finitial_feerate\x18\t \x01(\tH\x06\x88\x01\x01\x12\x19\n\x0clast_feerate\x18\n \x01(\tH\x07\x88\x01\x01\x12\x19\n\x0cnext_feerate\x18\x0b \x01(\tH\x08\x88\x01\x01\x12\x1a\n\rnext_fee_step\x18\x0c \x01(\rH\t\x88\x01\x01\x12\x35\n\x08inflight\x18\r \x03(\x0b\x32#.cln.ListpeersPeersChannelsInflight\x12\x15\n\x08\x63lose_to\x18\x0e \x01(\x0cH\n\x88\x01\x01\x12\x14\n\x07private\x18\x0f \x01(\x08H\x0b\x88\x01\x01\x12 \n\x06opener\x18\x10 \x01(\x0e\x32\x10.cln.ChannelSide\x12%\n\x06\x63loser\x18\x11 \x01(\x0e\x32\x10.cln.ChannelSideH\x0c\x88\x01\x01\x12\x10\n\x08\x66\x65\x61tures\x18\x12 \x03(\t\x12$\n\nto_us_msat\x18\x14 \x01(\x0b\x32\x0b.cln.AmountH\r\x88\x01\x01\x12(\n\x0emin_to_us_msat\x18\x15 \x01(\x0b\x32\x0b.cln.AmountH\x0e\x88\x01\x01\x12(\n\x0emax_to_us_msat\x18\x16 \x01(\x0b\x32\x0b.cln.AmountH\x0f\x88\x01\x01\x12$\n\ntotal_msat\x18\x17 \x01(\x0b\x32\x0b.cln.AmountH\x10\x88\x01\x01\x12\'\n\rfee_base_msat\x18\x18 \x01(\x0b\x32\x0b.cln.AmountH\x11\x88\x01\x01\x12(\n\x1b\x66\x65\x65_proportional_millionths\x18\x19 \x01(\rH\x12\x88\x01\x01\x12)\n\x0f\x64ust_limit_msat\x18\x1a \x01(\x0b\x32\x0b.cln.AmountH\x13\x88\x01\x01\x12\x30\n\x16max_total_htlc_in_msat\x18\x1b \x01(\x0b\x32\x0b.cln.AmountH\x14\x88\x01\x01\x12,\n\x12their_reserve_msat\x18\x1c \x01(\x0b\x32\x0b.cln.AmountH\x15\x88\x01\x01\x12*\n\x10our_reserve_msat\x18\x1d \x01(\x0b\x32\x0b.cln.AmountH\x16\x88\x01\x01\x12(\n\x0espendable_msat\x18\x1e \x01(\x0b\x32\x0b.cln.AmountH\x17\x88\x01\x01\x12)\n\x0freceivable_msat\x18\x1f \x01(\x0b\x32\x0b.cln.AmountH\x18\x88\x01\x01\x12.\n\x14minimum_htlc_in_msat\x18 \x01(\x0b\x32\x0b.cln.AmountH\x19\x88\x01\x01\x12/\n\x15minimum_htlc_out_msat\x18\x30 \x01(\x0b\x32\x0b.cln.AmountH\x1a\x88\x01\x01\x12/\n\x15maximum_htlc_out_msat\x18\x31 \x01(\x0b\x32\x0b.cln.AmountH\x1b\x88\x01\x01\x12 \n\x13their_to_self_delay\x18! \x01(\rH\x1c\x88\x01\x01\x12\x1e\n\x11our_to_self_delay\x18\" \x01(\rH\x1d\x88\x01\x01\x12\x1f\n\x12max_accepted_htlcs\x18# \x01(\rH\x1e\x88\x01\x01\x12\x0e\n\x06status\x18% \x03(\t\x12 \n\x13in_payments_offered\x18& \x01(\x04H\x1f\x88\x01\x01\x12)\n\x0fin_offered_msat\x18\' \x01(\x0b\x32\x0b.cln.AmountH \x88\x01\x01\x12\"\n\x15in_payments_fulfilled\x18( \x01(\x04H!\x88\x01\x01\x12+\n\x11in_fulfilled_msat\x18) \x01(\x0b\x32\x0b.cln.AmountH\"\x88\x01\x01\x12!\n\x14out_payments_offered\x18* \x01(\x04H#\x88\x01\x01\x12*\n\x10out_offered_msat\x18+ \x01(\x0b\x32\x0b.cln.AmountH$\x88\x01\x01\x12#\n\x16out_payments_fulfilled\x18, \x01(\x04H%\x88\x01\x01\x12,\n\x12out_fulfilled_msat\x18- \x01(\x0b\x32\x0b.cln.AmountH&\x88\x01\x01\x12/\n\x05htlcs\x18. \x03(\x0b\x32 .cln.ListpeersPeersChannelsHtlcs\x12\x1a\n\rclose_to_addr\x18/ \x01(\tH\'\x88\x01\x01\"\xa1\x02\n\x1bListpeersPeersChannelsState\x12\x0c\n\x08OPENINGD\x10\x00\x12\x1c\n\x18\x43HANNELD_AWAITING_LOCKIN\x10\x01\x12\x13\n\x0f\x43HANNELD_NORMAL\x10\x02\x12\x1a\n\x16\x43HANNELD_SHUTTING_DOWN\x10\x03\x12\x18\n\x14\x43LOSINGD_SIGEXCHANGE\x10\x04\x12\x15\n\x11\x43LOSINGD_COMPLETE\x10\x05\x12\x17\n\x13\x41WAITING_UNILATERAL\x10\x06\x12\x16\n\x12\x46UNDING_SPEND_SEEN\x10\x07\x12\x0b\n\x07ONCHAIN\x10\x08\x12\x17\n\x13\x44UALOPEND_OPEN_INIT\x10\t\x12\x1d\n\x19\x44UALOPEND_AWAITING_LOCKIN\x10\nB\x0f\n\r_scratch_txidB\x08\n\x06_ownerB\x13\n\x11_short_channel_idB\r\n\x0b_channel_idB\x0f\n\r_funding_txidB\x11\n\x0f_funding_outnumB\x12\n\x10_initial_feerateB\x0f\n\r_last_feerateB\x0f\n\r_next_feerateB\x10\n\x0e_next_fee_stepB\x0b\n\t_close_toB\n\n\x08_privateB\t\n\x07_closerB\r\n\x0b_to_us_msatB\x11\n\x0f_min_to_us_msatB\x11\n\x0f_max_to_us_msatB\r\n\x0b_total_msatB\x10\n\x0e_fee_base_msatB\x1e\n\x1c_fee_proportional_millionthsB\x12\n\x10_dust_limit_msatB\x19\n\x17_max_total_htlc_in_msatB\x15\n\x13_their_reserve_msatB\x13\n\x11_our_reserve_msatB\x11\n\x0f_spendable_msatB\x12\n\x10_receivable_msatB\x17\n\x15_minimum_htlc_in_msatB\x18\n\x16_minimum_htlc_out_msatB\x18\n\x16_maximum_htlc_out_msatB\x16\n\x14_their_to_self_delayB\x14\n\x12_our_to_self_delayB\x15\n\x13_max_accepted_htlcsB\x16\n\x14_in_payments_offeredB\x12\n\x10_in_offered_msatB\x18\n\x16_in_payments_fulfilledB\x14\n\x12_in_fulfilled_msatB\x17\n\x15_out_payments_offeredB\x13\n\x11_out_offered_msatB\x19\n\x17_out_payments_fulfilledB\x15\n\x13_out_fulfilled_msatB\x10\n\x0e_close_to_addr\"=\n\x1dListpeersPeersChannelsFeerate\x12\r\n\x05perkw\x18\x01 \x01(\r\x12\r\n\x05perkb\x18\x02 \x01(\r\"\xc5\x01\n\x1eListpeersPeersChannelsInflight\x12\x14\n\x0c\x66unding_txid\x18\x01 \x01(\x0c\x12\x16\n\x0e\x66unding_outnum\x18\x02 \x01(\r\x12\x0f\n\x07\x66\x65\x65rate\x18\x03 \x01(\t\x12\'\n\x12total_funding_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x10our_funding_msat\x18\x05 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0cscratch_txid\x18\x06 \x01(\x0c\"\x87\x03\n\x1dListpeersPeersChannelsFunding\x12$\n\nlocal_msat\x18\x01 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12%\n\x0bremote_msat\x18\x02 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12%\n\x0bpushed_msat\x18\x03 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12%\n\x10local_funds_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12&\n\x11remote_funds_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\'\n\rfee_paid_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\'\n\rfee_rcvd_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x42\r\n\x0b_local_msatB\x0e\n\x0c_remote_msatB\x0e\n\x0c_pushed_msatB\x10\n\x0e_fee_paid_msatB\x10\n\x0e_fee_rcvd_msat\"[\n\x1bListpeersPeersChannelsAlias\x12\x12\n\x05local\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06remote\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_localB\t\n\x07_remote\"\xd2\x02\n\x1bListpeersPeersChannelsHtlcs\x12X\n\tdirection\x18\x01 \x01(\x0e\x32\x45.cln.ListpeersPeersChannelsHtlcs.ListpeersPeersChannelsHtlcsDirection\x12\n\n\x02id\x18\x02 \x01(\x04\x12 \n\x0b\x61mount_msat\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x0e\n\x06\x65xpiry\x18\x04 \x01(\r\x12\x14\n\x0cpayment_hash\x18\x05 \x01(\x0c\x12\x1a\n\rlocal_trimmed\x18\x06 \x01(\x08H\x00\x88\x01\x01\x12\x13\n\x06status\x18\x07 \x01(\tH\x01\x88\x01\x01\"7\n$ListpeersPeersChannelsHtlcsDirection\x12\x06\n\x02IN\x10\x00\x12\x07\n\x03OUT\x10\x01\x42\x10\n\x0e_local_trimmedB\t\n\x07_status\"0\n\x10ListfundsRequest\x12\x12\n\x05spent\x18\x01 \x01(\x08H\x00\x88\x01\x01\x42\x08\n\x06_spent\"e\n\x11ListfundsResponse\x12&\n\x07outputs\x18\x01 \x03(\x0b\x32\x15.cln.ListfundsOutputs\x12(\n\x08\x63hannels\x18\x02 \x03(\x0b\x32\x16.cln.ListfundsChannels\"\xf5\x02\n\x10ListfundsOutputs\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0e\n\x06output\x18\x02 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0cscriptpubkey\x18\x04 \x01(\x0c\x12\x14\n\x07\x61\x64\x64ress\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0credeemscript\x18\x06 \x01(\x0cH\x01\x88\x01\x01\x12<\n\x06status\x18\x07 \x01(\x0e\x32,.cln.ListfundsOutputs.ListfundsOutputsStatus\x12\x10\n\x08reserved\x18\t \x01(\x08\x12\x18\n\x0b\x62lockheight\x18\x08 \x01(\rH\x02\x88\x01\x01\"C\n\x16ListfundsOutputsStatus\x12\x0f\n\x0bUNCONFIRMED\x10\x00\x12\r\n\tCONFIRMED\x10\x01\x12\t\n\x05SPENT\x10\x02\x42\n\n\x08_addressB\x0f\n\r_redeemscriptB\x0e\n\x0c_blockheight\"\x83\x02\n\x11ListfundsChannels\x12\x0f\n\x07peer_id\x18\x01 \x01(\x0c\x12$\n\x0four_amount_msat\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\x12 \n\x0b\x61mount_msat\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0c\x66unding_txid\x18\x04 \x01(\x0c\x12\x16\n\x0e\x66unding_output\x18\x05 \x01(\r\x12\x11\n\tconnected\x18\x06 \x01(\x08\x12 \n\x05state\x18\x07 \x01(\x0e\x32\x11.cln.ChannelState\x12\x1d\n\x10short_channel_id\x18\x08 \x01(\tH\x00\x88\x01\x01\x42\x13\n\x11_short_channel_id\"\xdb\x02\n\x0eSendpayRequest\x12 \n\x05route\x18\x01 \x03(\x0b\x32\x11.cln.SendpayRoute\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x12\n\x05label\x18\x03 \x01(\tH\x00\x88\x01\x01\x12%\n\x0b\x61mount_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x1b\n\x0epayment_secret\x18\x06 \x01(\x0cH\x03\x88\x01\x01\x12\x13\n\x06partid\x18\x07 \x01(\rH\x04\x88\x01\x01\x12\x19\n\x0clocalofferid\x18\x08 \x01(\x0cH\x05\x88\x01\x01\x12\x14\n\x07groupid\x18\t \x01(\x04H\x06\x88\x01\x01\x42\x08\n\x06_labelB\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\x11\n\x0f_payment_secretB\t\n\x07_partidB\x0f\n\r_localofferidB\n\n\x08_groupid\"\xd1\x04\n\x0fSendpayResponse\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x07groupid\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x32\n\x06status\x18\x04 \x01(\x0e\x32\".cln.SendpayResponse.SendpayStatus\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x06 \x01(\x0cH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\x07 \x01(\x04\x12\x19\n\x0c\x63ompleted_at\x18\x0f \x01(\x04H\x03\x88\x01\x01\x12%\n\x10\x61mount_sent_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\t \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06partid\x18\n \x01(\x04H\x05\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x0b \x01(\tH\x06\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x0c \x01(\tH\x07\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\r \x01(\x0cH\x08\x88\x01\x01\x12\x14\n\x07message\x18\x0e \x01(\tH\t\x88\x01\x01\"*\n\rSendpayStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x42\n\n\x08_groupidB\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x0f\n\r_completed_atB\x08\n\x06_labelB\t\n\x07_partidB\t\n\x07_bolt11B\t\n\x07_bolt12B\x13\n\x11_payment_preimageB\n\n\x08_message\"\\\n\x0cSendpayRoute\x12 \n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.Amount\x12\n\n\x02id\x18\x02 \x01(\x0c\x12\r\n\x05\x64\x65lay\x18\x03 \x01(\r\x12\x0f\n\x07\x63hannel\x18\x04 \x01(\t\"\x93\x01\n\x13ListchannelsRequest\x12\x1d\n\x10short_channel_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06source\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x03 \x01(\x0cH\x02\x88\x01\x01\x42\x13\n\x11_short_channel_idB\t\n\x07_sourceB\x0e\n\x0c_destination\"C\n\x14ListchannelsResponse\x12+\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x19.cln.ListchannelsChannels\"\xa0\x03\n\x14ListchannelsChannels\x12\x0e\n\x06source\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65stination\x18\x02 \x01(\x0c\x12\x18\n\x10short_channel_id\x18\x03 \x01(\t\x12\x0e\n\x06public\x18\x04 \x01(\x08\x12 \n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.Amount\x12\x15\n\rmessage_flags\x18\x06 \x01(\r\x12\x15\n\rchannel_flags\x18\x07 \x01(\r\x12\x0e\n\x06\x61\x63tive\x18\x08 \x01(\x08\x12\x13\n\x0blast_update\x18\t \x01(\r\x12\x1d\n\x15\x62\x61se_fee_millisatoshi\x18\n \x01(\r\x12\x19\n\x11\x66\x65\x65_per_millionth\x18\x0b \x01(\r\x12\r\n\x05\x64\x65lay\x18\x0c \x01(\r\x12&\n\x11htlc_minimum_msat\x18\r \x01(\x0b\x32\x0b.cln.Amount\x12+\n\x11htlc_maximum_msat\x18\x0e \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x10\n\x08\x66\x65\x61tures\x18\x0f \x01(\x0c\x42\x14\n\x12_htlc_maximum_msat\"#\n\x10\x41\x64\x64gossipRequest\x12\x0f\n\x07message\x18\x01 \x01(\x0c\"\x13\n\x11\x41\x64\x64gossipResponse\"o\n\x17\x41utocleaninvoiceRequest\x12\x17\n\nexpired_by\x18\x01 \x01(\x04H\x00\x88\x01\x01\x12\x1a\n\rcycle_seconds\x18\x02 \x01(\x04H\x01\x88\x01\x01\x42\r\n\x0b_expired_byB\x10\n\x0e_cycle_seconds\"\x81\x01\n\x18\x41utocleaninvoiceResponse\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x17\n\nexpired_by\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x1a\n\rcycle_seconds\x18\x03 \x01(\x04H\x01\x88\x01\x01\x42\r\n\x0b_expired_byB\x10\n\x0e_cycle_seconds\"U\n\x13\x43heckmessageRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\r\n\x05zbase\x18\x02 \x01(\t\x12\x13\n\x06pubkey\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\t\n\x07_pubkey\"8\n\x14\x43heckmessageResponse\x12\x10\n\x08verified\x18\x01 \x01(\x08\x12\x0e\n\x06pubkey\x18\x02 \x01(\x0c\"\xcb\x02\n\x0c\x43loseRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x1e\n\x11unilateraltimeout\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x03 \x01(\tH\x01\x88\x01\x01\x12!\n\x14\x66\x65\x65_negotiation_step\x18\x04 \x01(\tH\x02\x88\x01\x01\x12)\n\rwrong_funding\x18\x05 \x01(\x0b\x32\r.cln.OutpointH\x03\x88\x01\x01\x12\x1f\n\x12\x66orce_lease_closed\x18\x06 \x01(\x08H\x04\x88\x01\x01\x12\x1e\n\x08\x66\x65\x65range\x18\x07 \x03(\x0b\x32\x0c.cln.FeerateB\x14\n\x12_unilateraltimeoutB\x0e\n\x0c_destinationB\x17\n\x15_fee_negotiation_stepB\x10\n\x0e_wrong_fundingB\x15\n\x13_force_lease_closed\"\xab\x01\n\rCloseResponse\x12/\n\titem_type\x18\x01 \x01(\x0e\x32\x1c.cln.CloseResponse.CloseType\x12\x0f\n\x02tx\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x11\n\x04txid\x18\x03 \x01(\x0cH\x01\x88\x01\x01\"5\n\tCloseType\x12\n\n\x06MUTUAL\x10\x00\x12\x0e\n\nUNILATERAL\x10\x01\x12\x0c\n\x08UNOPENED\x10\x02\x42\x05\n\x03_txB\x07\n\x05_txid\"T\n\x0e\x43onnectRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\x04host\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04port\x18\x03 \x01(\rH\x01\x88\x01\x01\x42\x07\n\x05_hostB\x07\n\x05_port\"\x8e\x01\n\x0f\x43onnectResponse\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x10\n\x08\x66\x65\x61tures\x18\x02 \x01(\x0c\x12\x38\n\tdirection\x18\x03 \x01(\x0e\x32%.cln.ConnectResponse.ConnectDirection\"#\n\x10\x43onnectDirection\x12\x06\n\x02IN\x10\x00\x12\x07\n\x03OUT\x10\x01\"\xfb\x01\n\x0e\x43onnectAddress\x12\x39\n\titem_type\x18\x01 \x01(\x0e\x32&.cln.ConnectAddress.ConnectAddressType\x12\x13\n\x06socket\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x61\x64\x64ress\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x11\n\x04port\x18\x04 \x01(\rH\x02\x88\x01\x01\"P\n\x12\x43onnectAddressType\x12\x10\n\x0cLOCAL_SOCKET\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x42\t\n\x07_socketB\n\n\x08_addressB\x07\n\x05_port\"J\n\x14\x43reateinvoiceRequest\x12\x11\n\tinvstring\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12\x10\n\x08preimage\x18\x03 \x01(\x0c\"\xf3\x04\n\x15\x43reateinvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x06\x62olt11\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x04 \x01(\x0c\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12>\n\x06status\x18\x06 \x01(\x0e\x32..cln.CreateinvoiceResponse.CreateinvoiceStatus\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\x12\x12\n\nexpires_at\x18\x08 \x01(\x04\x12\x16\n\tpay_index\x18\t \x01(\x04H\x03\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\x14\n\x07paid_at\x18\x0b \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\x12\x1b\n\x0elocal_offer_id\x18\r \x01(\x0cH\x07\x88\x01\x01\x12\x17\n\npayer_note\x18\x0e \x01(\tH\x08\x88\x01\x01\"8\n\x13\x43reateinvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x12\n\n\x06UNPAID\x10\x02\x42\t\n\x07_bolt11B\t\n\x07_bolt12B\x0e\n\x0c_amount_msatB\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimageB\x11\n\x0f_local_offer_idB\r\n\x0b_payer_note\"\xb4\x02\n\x10\x44\x61tastoreRequest\x12\x0b\n\x03key\x18\x05 \x03(\t\x12\x13\n\x06string\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12\x36\n\x04mode\x18\x03 \x01(\x0e\x32#.cln.DatastoreRequest.DatastoreModeH\x02\x88\x01\x01\x12\x17\n\ngeneration\x18\x04 \x01(\x04H\x03\x88\x01\x01\"p\n\rDatastoreMode\x12\x0f\n\x0bMUST_CREATE\x10\x00\x12\x10\n\x0cMUST_REPLACE\x10\x01\x12\x15\n\x11\x43REATE_OR_REPLACE\x10\x02\x12\x0f\n\x0bMUST_APPEND\x10\x03\x12\x14\n\x10\x43REATE_OR_APPEND\x10\x04\x42\t\n\x07_stringB\x06\n\x04_hexB\x07\n\x05_modeB\r\n\x0b_generation\"\x82\x01\n\x11\x44\x61tastoreResponse\x12\x0b\n\x03key\x18\x05 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x06string\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\r\n\x0b_generationB\x06\n\x04_hexB\t\n\x07_string\"\x9d\x01\n\x12\x43reateonionRequest\x12\"\n\x04hops\x18\x01 \x03(\x0b\x32\x14.cln.CreateonionHops\x12\x11\n\tassocdata\x18\x02 \x01(\x0c\x12\x18\n\x0bsession_key\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x12\x17\n\nonion_size\x18\x04 \x01(\rH\x01\x88\x01\x01\x42\x0e\n\x0c_session_keyB\r\n\x0b_onion_size\"<\n\x13\x43reateonionResponse\x12\r\n\x05onion\x18\x01 \x01(\x0c\x12\x16\n\x0eshared_secrets\x18\x02 \x03(\x0c\"2\n\x0f\x43reateonionHops\x12\x0e\n\x06pubkey\x18\x01 \x01(\x0c\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"J\n\x13\x44\x65ldatastoreRequest\x12\x0b\n\x03key\x18\x03 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x42\r\n\x0b_generation\"\x85\x01\n\x14\x44\x65ldatastoreResponse\x12\x0b\n\x03key\x18\x05 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x06string\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\r\n\x0b_generationB\x06\n\x04_hexB\t\n\x07_string\"H\n\x18\x44\x65lexpiredinvoiceRequest\x12\x1a\n\rmaxexpirytime\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\x10\n\x0e_maxexpirytime\"\x1b\n\x19\x44\x65lexpiredinvoiceResponse\"\xb6\x01\n\x11\x44\x65linvoiceRequest\x12\r\n\x05label\x18\x01 \x01(\t\x12\x37\n\x06status\x18\x02 \x01(\x0e\x32\'.cln.DelinvoiceRequest.DelinvoiceStatus\x12\x15\n\x08\x64\x65sconly\x18\x03 \x01(\x08H\x00\x88\x01\x01\"5\n\x10\x44\x65linvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x12\n\n\x06UNPAID\x10\x02\x42\x0b\n\t_desconly\"\xb7\x03\n\x12\x44\x65linvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x06\x62olt11\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x03 \x01(\tH\x01\x88\x01\x01\x12%\n\x0b\x61mount_msat\x18\x04 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x06 \x01(\x0c\x12\x38\n\x06status\x18\x07 \x01(\x0e\x32(.cln.DelinvoiceResponse.DelinvoiceStatus\x12\x12\n\nexpires_at\x18\x08 \x01(\x04\x12\x1b\n\x0elocal_offer_id\x18\t \x01(\x0cH\x04\x88\x01\x01\x12\x17\n\npayer_note\x18\n \x01(\tH\x05\x88\x01\x01\"5\n\x10\x44\x65linvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x12\n\n\x06UNPAID\x10\x02\x42\t\n\x07_bolt11B\t\n\x07_bolt12B\x0e\n\x0c_amount_msatB\x0e\n\x0c_descriptionB\x11\n\x0f_local_offer_idB\r\n\x0b_payer_note\"\xb8\x02\n\x0eInvoiceRequest\x12%\n\x0b\x61mount_msat\x18\n \x01(\x0b\x32\x10.cln.AmountOrAny\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\r\n\x05label\x18\x03 \x01(\t\x12\x13\n\x06\x65xpiry\x18\x07 \x01(\x04H\x00\x88\x01\x01\x12\x11\n\tfallbacks\x18\x04 \x03(\t\x12\x15\n\x08preimage\x18\x05 \x01(\x0cH\x01\x88\x01\x01\x12\"\n\x15\x65xposeprivatechannels\x18\x08 \x01(\x08H\x02\x88\x01\x01\x12\x11\n\x04\x63ltv\x18\x06 \x01(\rH\x03\x88\x01\x01\x12\x19\n\x0c\x64\x65schashonly\x18\t \x01(\x08H\x04\x88\x01\x01\x42\t\n\x07_expiryB\x0b\n\t_preimageB\x18\n\x16_exposeprivatechannelsB\x07\n\x05_cltvB\x0f\n\r_deschashonly\"\xe7\x02\n\x0fInvoiceResponse\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x16\n\x0epayment_secret\x18\x03 \x01(\x0c\x12\x12\n\nexpires_at\x18\x04 \x01(\x04\x12\x1d\n\x10warning_capacity\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x1c\n\x0fwarning_offline\x18\x06 \x01(\tH\x01\x88\x01\x01\x12\x1d\n\x10warning_deadends\x18\x07 \x01(\tH\x02\x88\x01\x01\x12#\n\x16warning_private_unused\x18\x08 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0bwarning_mpp\x18\t \x01(\tH\x04\x88\x01\x01\x42\x13\n\x11_warning_capacityB\x12\n\x10_warning_offlineB\x13\n\x11_warning_deadendsB\x19\n\x17_warning_private_unusedB\x0e\n\x0c_warning_mpp\"#\n\x14ListdatastoreRequest\x12\x0b\n\x03key\x18\x02 \x03(\t\"G\n\x15ListdatastoreResponse\x12.\n\tdatastore\x18\x01 \x03(\x0b\x32\x1b.cln.ListdatastoreDatastore\"\x87\x01\n\x16ListdatastoreDatastore\x12\x0b\n\x03key\x18\x01 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x06string\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\r\n\x0b_generationB\x06\n\x04_hexB\t\n\x07_string\"\xa9\x01\n\x13ListinvoicesRequest\x12\x12\n\x05label\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tinvstring\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x19\n\x0cpayment_hash\x18\x03 \x01(\x0cH\x02\x88\x01\x01\x12\x15\n\x08offer_id\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x08\n\x06_labelB\x0c\n\n_invstringB\x0f\n\r_payment_hashB\x0b\n\t_offer_id\"C\n\x14ListinvoicesResponse\x12+\n\x08invoices\x18\x01 \x03(\x0b\x32\x19.cln.ListinvoicesInvoices\"\x94\x05\n\x14ListinvoicesInvoices\x12\r\n\x05label\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x44\n\x06status\x18\x04 \x01(\x0e\x32\x34.cln.ListinvoicesInvoices.ListinvoicesInvoicesStatus\x12\x12\n\nexpires_at\x18\x05 \x01(\x04\x12%\n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x08 \x01(\tH\x03\x88\x01\x01\x12\x1b\n\x0elocal_offer_id\x18\t \x01(\x0cH\x04\x88\x01\x01\x12\x17\n\npayer_note\x18\n \x01(\tH\x05\x88\x01\x01\x12\x16\n\tpay_index\x18\x0b \x01(\x04H\x06\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\x0c \x01(\x0b\x32\x0b.cln.AmountH\x07\x88\x01\x01\x12\x14\n\x07paid_at\x18\r \x01(\x04H\x08\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0e \x01(\x0cH\t\x88\x01\x01\"?\n\x1aListinvoicesInvoicesStatus\x12\n\n\x06UNPAID\x10\x00\x12\x08\n\x04PAID\x10\x01\x12\x0b\n\x07\x45XPIRED\x10\x02\x42\x0e\n\x0c_descriptionB\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\t\n\x07_bolt12B\x11\n\x0f_local_offer_idB\r\n\x0b_payer_noteB\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimage\"\xdc\x02\n\x10SendonionRequest\x12\r\n\x05onion\x18\x01 \x01(\x0c\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x12\n\x05label\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x16\n\x0eshared_secrets\x18\x05 \x03(\x0c\x12\x13\n\x06partid\x18\x06 \x01(\rH\x01\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x02\x88\x01\x01\x12%\n\x0b\x61mount_msat\x18\x0c \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\t \x01(\x0cH\x04\x88\x01\x01\x12\x19\n\x0clocalofferid\x18\n \x01(\x0cH\x05\x88\x01\x01\x12\x14\n\x07groupid\x18\x0b \x01(\x04H\x06\x88\x01\x01\x42\x08\n\x06_labelB\t\n\x07_partidB\t\n\x07_bolt11B\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x0f\n\r_localofferidB\n\n\x08_groupid\"\x8b\x04\n\x11SendonionResponse\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x36\n\x06status\x18\x03 \x01(\x0e\x32&.cln.SendonionResponse.SendonionStatus\x12%\n\x0b\x61mount_msat\x18\x04 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x05 \x01(\x0cH\x01\x88\x01\x01\x12\x12\n\ncreated_at\x18\x06 \x01(\x04\x12%\n\x10\x61mount_sent_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\t \x01(\tH\x03\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\n \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06partid\x18\r \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0b \x01(\x0cH\x06\x88\x01\x01\x12\x14\n\x07message\x18\x0c \x01(\tH\x07\x88\x01\x01\",\n\x0fSendonionStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x42\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x08\n\x06_labelB\t\n\x07_bolt11B\t\n\x07_bolt12B\t\n\x07_partidB\x13\n\x11_payment_preimageB\n\n\x08_message\"Q\n\x12SendonionFirst_hop\x12\n\n\x02id\x18\x01 \x01(\x0c\x12 \n\x0b\x61mount_msat\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\x12\r\n\x05\x64\x65lay\x18\x03 \x01(\r\"\xeb\x01\n\x13ListsendpaysRequest\x12\x13\n\x06\x62olt11\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cpayment_hash\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12@\n\x06status\x18\x03 \x01(\x0e\x32+.cln.ListsendpaysRequest.ListsendpaysStatusH\x02\x88\x01\x01\";\n\x12ListsendpaysStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x42\t\n\x07_bolt11B\x0f\n\r_payment_hashB\t\n\x07_status\"C\n\x14ListsendpaysResponse\x12+\n\x08payments\x18\x01 \x03(\x0b\x32\x19.cln.ListsendpaysPayments\"\xd4\x04\n\x14ListsendpaysPayments\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07groupid\x18\x02 \x01(\x04\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x44\n\x06status\x18\x04 \x01(\x0e\x32\x34.cln.ListsendpaysPayments.ListsendpaysPaymentsStatus\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x06 \x01(\x0cH\x01\x88\x01\x01\x12\x12\n\ncreated_at\x18\x07 \x01(\x04\x12%\n\x10\x61mount_sent_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\t \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\n \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x0e \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x0b \x01(\tH\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\x12\x17\n\nerroronion\x18\r \x01(\x0cH\x07\x88\x01\x01\"C\n\x1aListsendpaysPaymentsStatus\x12\x0b\n\x07PENDING\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x01\x12\x0c\n\x08\x43OMPLETE\x10\x02\x42\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x08\n\x06_labelB\t\n\x07_bolt11B\x0e\n\x0c_descriptionB\t\n\x07_bolt12B\x13\n\x11_payment_preimageB\r\n\x0b_erroronion\"\x19\n\x17ListtransactionsRequest\"S\n\x18ListtransactionsResponse\x12\x37\n\x0ctransactions\x18\x01 \x03(\x0b\x32!.cln.ListtransactionsTransactions\"\x9a\x02\n\x1cListtransactionsTransactions\x12\x0c\n\x04hash\x18\x01 \x01(\x0c\x12\r\n\x05rawtx\x18\x02 \x01(\x0c\x12\x13\n\x0b\x62lockheight\x18\x03 \x01(\r\x12\x0f\n\x07txindex\x18\x04 \x01(\r\x12\x14\n\x07\x63hannel\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x08locktime\x18\x07 \x01(\r\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x37\n\x06inputs\x18\t \x03(\x0b\x32\'.cln.ListtransactionsTransactionsInputs\x12\x39\n\x07outputs\x18\n \x03(\x0b\x32(.cln.ListtransactionsTransactionsOutputsB\n\n\x08_channel\"\x84\x04\n\"ListtransactionsTransactionsInputs\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\r\n\x05index\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\x66\n\titem_type\x18\x04 \x01(\x0e\x32N.cln.ListtransactionsTransactionsInputs.ListtransactionsTransactionsInputsTypeH\x00\x88\x01\x01\x12\x14\n\x07\x63hannel\x18\x05 \x01(\tH\x01\x88\x01\x01\"\x96\x02\n&ListtransactionsTransactionsInputsType\x12\n\n\x06THEIRS\x10\x00\x12\x0b\n\x07\x44\x45POSIT\x10\x01\x12\x0c\n\x08WITHDRAW\x10\x02\x12\x13\n\x0f\x43HANNEL_FUNDING\x10\x03\x12\x18\n\x14\x43HANNEL_MUTUAL_CLOSE\x10\x04\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CLOSE\x10\x05\x12\x11\n\rCHANNEL_SWEEP\x10\x06\x12\x18\n\x14\x43HANNEL_HTLC_SUCCESS\x10\x07\x12\x18\n\x14\x43HANNEL_HTLC_TIMEOUT\x10\x08\x12\x13\n\x0f\x43HANNEL_PENALTY\x10\t\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CHEAT\x10\nB\x0c\n\n_item_typeB\n\n\x08_channel\"\xa0\x04\n#ListtransactionsTransactionsOutputs\x12\r\n\x05index\x18\x01 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0cscriptPubKey\x18\x03 \x01(\x0c\x12h\n\titem_type\x18\x04 \x01(\x0e\x32P.cln.ListtransactionsTransactionsOutputs.ListtransactionsTransactionsOutputsTypeH\x00\x88\x01\x01\x12\x14\n\x07\x63hannel\x18\x05 \x01(\tH\x01\x88\x01\x01\"\x97\x02\n\'ListtransactionsTransactionsOutputsType\x12\n\n\x06THEIRS\x10\x00\x12\x0b\n\x07\x44\x45POSIT\x10\x01\x12\x0c\n\x08WITHDRAW\x10\x02\x12\x13\n\x0f\x43HANNEL_FUNDING\x10\x03\x12\x18\n\x14\x43HANNEL_MUTUAL_CLOSE\x10\x04\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CLOSE\x10\x05\x12\x11\n\rCHANNEL_SWEEP\x10\x06\x12\x18\n\x14\x43HANNEL_HTLC_SUCCESS\x10\x07\x12\x18\n\x14\x43HANNEL_HTLC_TIMEOUT\x10\x08\x12\x13\n\x0f\x43HANNEL_PENALTY\x10\t\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CHEAT\x10\nB\x0c\n\n_item_typeB\n\n\x08_channel\"\xd8\x03\n\nPayRequest\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12%\n\x0b\x61mount_msat\x18\r \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x12\n\x05label\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nriskfactor\x18\x08 \x01(\x01H\x02\x88\x01\x01\x12\x1a\n\rmaxfeepercent\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x16\n\tretry_for\x18\x05 \x01(\rH\x04\x88\x01\x01\x12\x15\n\x08maxdelay\x18\x06 \x01(\rH\x05\x88\x01\x01\x12#\n\texemptfee\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x06\x88\x01\x01\x12\x19\n\x0clocalofferid\x18\t \x01(\x0cH\x07\x88\x01\x01\x12\x0f\n\x07\x65xclude\x18\n \x03(\t\x12 \n\x06maxfee\x18\x0b \x01(\x0b\x32\x0b.cln.AmountH\x08\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x0c \x01(\tH\t\x88\x01\x01\x42\x0e\n\x0c_amount_msatB\x08\n\x06_labelB\r\n\x0b_riskfactorB\x10\n\x0e_maxfeepercentB\x0c\n\n_retry_forB\x0b\n\t_maxdelayB\x0c\n\n_exemptfeeB\x0f\n\r_localofferidB\t\n\x07_maxfeeB\x0e\n\x0c_description\"\xfb\x02\n\x0bPayResponse\x12\x18\n\x10payment_preimage\x18\x01 \x01(\x0c\x12\x18\n\x0b\x64\x65stination\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x12\n\ncreated_at\x18\x04 \x01(\x01\x12\r\n\x05parts\x18\x05 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x10\x61mount_sent_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\'\n\x1awarning_partial_completion\x18\x08 \x01(\tH\x01\x88\x01\x01\x12*\n\x06status\x18\t \x01(\x0e\x32\x1a.cln.PayResponse.PayStatus\"2\n\tPayStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x12\x0b\n\x07PENDING\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x42\x0e\n\x0c_destinationB\x1d\n\x1b_warning_partial_completion\"*\n\x10ListnodesRequest\x12\x0f\n\x02id\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x05\n\x03_id\"7\n\x11ListnodesResponse\x12\"\n\x05nodes\x18\x01 \x03(\x0b\x32\x13.cln.ListnodesNodes\"\xe1\x01\n\x0eListnodesNodes\x12\x0e\n\x06nodeid\x18\x01 \x01(\x0c\x12\x1b\n\x0elast_timestamp\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x12\n\x05\x61lias\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x12\n\x05\x63olor\x18\x04 \x01(\x0cH\x02\x88\x01\x01\x12\x15\n\x08\x66\x65\x61tures\x18\x05 \x01(\x0cH\x03\x88\x01\x01\x12/\n\taddresses\x18\x06 \x03(\x0b\x32\x1c.cln.ListnodesNodesAddressesB\x11\n\x0f_last_timestampB\x08\n\x06_aliasB\x08\n\x06_colorB\x0b\n\t_features\"\xf7\x01\n\x17ListnodesNodesAddresses\x12K\n\titem_type\x18\x01 \x01(\x0e\x32\x38.cln.ListnodesNodesAddresses.ListnodesNodesAddressesType\x12\x0c\n\x04port\x18\x02 \x01(\r\x12\x14\n\x07\x61\x64\x64ress\x18\x03 \x01(\tH\x00\x88\x01\x01\"_\n\x1bListnodesNodesAddressesType\x12\x07\n\x03\x44NS\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x12\r\n\tWEBSOCKET\x10\x05\x42\n\n\x08_address\"g\n\x15WaitanyinvoiceRequest\x12\x1a\n\rlastpay_index\x18\x01 \x01(\x04H\x00\x88\x01\x01\x12\x14\n\x07timeout\x18\x02 \x01(\x04H\x01\x88\x01\x01\x42\x10\n\x0e_lastpay_indexB\n\n\x08_timeout\"\x93\x04\n\x16WaitanyinvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12@\n\x06status\x18\x04 \x01(\x0e\x32\x30.cln.WaitanyinvoiceResponse.WaitanyinvoiceStatus\x12\x12\n\nexpires_at\x18\x05 \x01(\x04\x12%\n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x16\n\tpay_index\x18\t \x01(\x04H\x03\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\x14\n\x07paid_at\x18\x0b \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\"-\n\x14WaitanyinvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x42\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\t\n\x07_bolt12B\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimage\"#\n\x12WaitinvoiceRequest\x12\r\n\x05label\x18\x01 \x01(\t\"\x87\x04\n\x13WaitinvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12:\n\x06status\x18\x04 \x01(\x0e\x32*.cln.WaitinvoiceResponse.WaitinvoiceStatus\x12\x12\n\nexpires_at\x18\x05 \x01(\x04\x12%\n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x16\n\tpay_index\x18\t \x01(\x04H\x03\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\x14\n\x07paid_at\x18\x0b \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\"*\n\x11WaitinvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x42\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\t\n\x07_bolt12B\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimage\"\x8e\x01\n\x12WaitsendpayRequest\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x14\n\x07timeout\x18\x03 \x01(\rH\x00\x88\x01\x01\x12\x13\n\x06partid\x18\x02 \x01(\x04H\x01\x88\x01\x01\x12\x14\n\x07groupid\x18\x04 \x01(\x04H\x02\x88\x01\x01\x42\n\n\x08_timeoutB\t\n\x07_partidB\n\n\x08_groupid\"\xb2\x04\n\x13WaitsendpayResponse\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x07groupid\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12:\n\x06status\x18\x04 \x01(\x0e\x32*.cln.WaitsendpayResponse.WaitsendpayStatus\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x06 \x01(\x0cH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\x07 \x01(\x04\x12\x19\n\x0c\x63ompleted_at\x18\x0e \x01(\x01H\x03\x88\x01\x01\x12%\n\x10\x61mount_sent_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\t \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06partid\x18\n \x01(\x04H\x05\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x0b \x01(\tH\x06\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x0c \x01(\tH\x07\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\r \x01(\x0cH\x08\x88\x01\x01\"!\n\x11WaitsendpayStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x42\n\n\x08_groupidB\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x0f\n\r_completed_atB\x08\n\x06_labelB\t\n\x07_partidB\t\n\x07_bolt11B\t\n\x07_bolt12B\x13\n\x11_payment_preimage\"\x9e\x01\n\x0eNewaddrRequest\x12@\n\x0b\x61\x64\x64resstype\x18\x01 \x01(\x0e\x32&.cln.NewaddrRequest.NewaddrAddresstypeH\x00\x88\x01\x01\":\n\x12NewaddrAddresstype\x12\n\n\x06\x42\x45\x43H32\x10\x00\x12\x0f\n\x0bP2SH_SEGWIT\x10\x01\x12\x07\n\x03\x41LL\x10\x02\x42\x0e\n\x0c_addresstype\"[\n\x0fNewaddrResponse\x12\x13\n\x06\x62\x65\x63h32\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bp2sh_segwit\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_bech32B\x0e\n\x0c_p2sh_segwit\"\xca\x01\n\x0fWithdrawRequest\x12\x13\n\x0b\x64\x65stination\x18\x01 \x01(\t\x12&\n\x07satoshi\x18\x02 \x01(\x0b\x32\x10.cln.AmountOrAllH\x00\x88\x01\x01\x12\"\n\x07\x66\x65\x65rate\x18\x05 \x01(\x0b\x32\x0c.cln.FeerateH\x01\x88\x01\x01\x12\x14\n\x07minconf\x18\x03 \x01(\rH\x02\x88\x01\x01\x12\x1c\n\x05utxos\x18\x04 \x03(\x0b\x32\r.cln.OutpointB\n\n\x08_satoshiB\n\n\x08_feerateB\n\n\x08_minconf\":\n\x10WithdrawResponse\x12\n\n\x02tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\x12\x0c\n\x04psbt\x18\x03 \x01(\t\"\xcc\x02\n\x0eKeysendRequest\x12\x13\n\x0b\x64\x65stination\x18\x01 \x01(\x0c\x12 \n\x0b\x61mount_msat\x18\n \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x1a\n\rmaxfeepercent\x18\x04 \x01(\x01H\x01\x88\x01\x01\x12\x16\n\tretry_for\x18\x05 \x01(\rH\x02\x88\x01\x01\x12\x15\n\x08maxdelay\x18\x06 \x01(\rH\x03\x88\x01\x01\x12#\n\texemptfee\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12+\n\nroutehints\x18\x08 \x01(\x0b\x32\x12.cln.RoutehintListH\x05\x88\x01\x01\x42\x08\n\x06_labelB\x10\n\x0e_maxfeepercentB\x0c\n\n_retry_forB\x0b\n\t_maxdelayB\x0c\n\n_exemptfeeB\r\n\x0b_routehints\"\xf2\x02\n\x0fKeysendResponse\x12\x18\n\x10payment_preimage\x18\x01 \x01(\x0c\x12\x18\n\x0b\x64\x65stination\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x12\n\ncreated_at\x18\x04 \x01(\x01\x12\r\n\x05parts\x18\x05 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x10\x61mount_sent_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\'\n\x1awarning_partial_completion\x18\x08 \x01(\tH\x01\x88\x01\x01\x12\x32\n\x06status\x18\t \x01(\x0e\x32\".cln.KeysendResponse.KeysendStatus\"\x1d\n\rKeysendStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x42\x0e\n\x0c_destinationB\x1d\n\x1b_warning_partial_completion\"\x12\n\x10KeysendExtratlvs\"\xbc\x02\n\x0f\x46undpsbtRequest\x12!\n\x07satoshi\x18\x01 \x01(\x0b\x32\x10.cln.AmountOrAll\x12\x1d\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.Feerate\x12\x13\n\x0bstartweight\x18\x03 \x01(\r\x12\x14\n\x07minconf\x18\x04 \x01(\rH\x00\x88\x01\x01\x12\x14\n\x07reserve\x18\x05 \x01(\rH\x01\x88\x01\x01\x12\x15\n\x08locktime\x18\x06 \x01(\rH\x02\x88\x01\x01\x12\x1f\n\x12min_witness_weight\x18\x07 \x01(\rH\x03\x88\x01\x01\x12\x1d\n\x10\x65xcess_as_change\x18\x08 \x01(\x08H\x04\x88\x01\x01\x42\n\n\x08_minconfB\n\n\x08_reserveB\x0b\n\t_locktimeB\x15\n\x13_min_witness_weightB\x13\n\x11_excess_as_change\"\xd9\x01\n\x10\x46undpsbtResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x16\n\x0e\x66\x65\x65rate_per_kw\x18\x02 \x01(\r\x12\x1e\n\x16\x65stimated_final_weight\x18\x03 \x01(\r\x12 \n\x0b\x65xcess_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12\x1a\n\rchange_outnum\x18\x05 \x01(\rH\x00\x88\x01\x01\x12/\n\x0creservations\x18\x06 \x03(\x0b\x32\x19.cln.FundpsbtReservationsB\x10\n\x0e_change_outnum\"u\n\x14\x46undpsbtReservations\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0c\n\x04vout\x18\x02 \x01(\r\x12\x14\n\x0cwas_reserved\x18\x03 \x01(\x08\x12\x10\n\x08reserved\x18\x04 \x01(\x08\x12\x19\n\x11reserved_to_block\x18\x05 \x01(\r\"A\n\x0fSendpsbtRequest\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x14\n\x07reserve\x18\x02 \x01(\x08H\x00\x88\x01\x01\x42\n\n\x08_reserve\",\n\x10SendpsbtResponse\x12\n\n\x02tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\"1\n\x0fSignpsbtRequest\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x10\n\x08signonly\x18\x02 \x03(\r\"\'\n\x10SignpsbtResponse\x12\x13\n\x0bsigned_psbt\x18\x01 \x01(\t\"\xdb\x02\n\x0fUtxopsbtRequest\x12\x1c\n\x07satoshi\x18\x01 \x01(\x0b\x32\x0b.cln.Amount\x12\x1d\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.Feerate\x12\x13\n\x0bstartweight\x18\x03 \x01(\r\x12\x1c\n\x05utxos\x18\x04 \x03(\x0b\x32\r.cln.Outpoint\x12\x14\n\x07reserve\x18\x05 \x01(\rH\x00\x88\x01\x01\x12\x17\n\nreservedok\x18\x08 \x01(\x08H\x01\x88\x01\x01\x12\x15\n\x08locktime\x18\x06 \x01(\rH\x02\x88\x01\x01\x12\x1f\n\x12min_witness_weight\x18\x07 \x01(\rH\x03\x88\x01\x01\x12\x1d\n\x10\x65xcess_as_change\x18\t \x01(\x08H\x04\x88\x01\x01\x42\n\n\x08_reserveB\r\n\x0b_reservedokB\x0b\n\t_locktimeB\x15\n\x13_min_witness_weightB\x13\n\x11_excess_as_change\"\xd9\x01\n\x10UtxopsbtResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x16\n\x0e\x66\x65\x65rate_per_kw\x18\x02 \x01(\r\x12\x1e\n\x16\x65stimated_final_weight\x18\x03 \x01(\r\x12 \n\x0b\x65xcess_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12\x1a\n\rchange_outnum\x18\x05 \x01(\rH\x00\x88\x01\x01\x12/\n\x0creservations\x18\x06 \x03(\x0b\x32\x19.cln.UtxopsbtReservationsB\x10\n\x0e_change_outnum\"u\n\x14UtxopsbtReservations\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0c\n\x04vout\x18\x02 \x01(\r\x12\x14\n\x0cwas_reserved\x18\x03 \x01(\x08\x12\x10\n\x08reserved\x18\x04 \x01(\x08\x12\x19\n\x11reserved_to_block\x18\x05 \x01(\r\" \n\x10TxdiscardRequest\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\"6\n\x11TxdiscardResponse\x12\x13\n\x0bunsigned_tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\"\xa4\x01\n\x10TxprepareRequest\x12 \n\x07outputs\x18\x05 \x03(\x0b\x32\x0f.cln.OutputDesc\x12\"\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.FeerateH\x00\x88\x01\x01\x12\x14\n\x07minconf\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x1c\n\x05utxos\x18\x04 \x03(\x0b\x32\r.cln.OutpointB\n\n\x08_feerateB\n\n\x08_minconf\"D\n\x11TxprepareResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x13\n\x0bunsigned_tx\x18\x02 \x01(\x0c\x12\x0c\n\x04txid\x18\x03 \x01(\x0c\"\x1d\n\rTxsendRequest\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\"8\n\x0eTxsendResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\n\n\x02tx\x18\x02 \x01(\x0c\x12\x0c\n\x04txid\x18\x03 \x01(\x0c\"=\n\x11\x44isconnectRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x12\n\x05\x66orce\x18\x02 \x01(\x08H\x00\x88\x01\x01\x42\x08\n\x06_force\"\x14\n\x12\x44isconnectResponse\"k\n\x0f\x46\x65\x65ratesRequest\x12\x31\n\x05style\x18\x01 \x01(\x0e\x32\".cln.FeeratesRequest.FeeratesStyle\"%\n\rFeeratesStyle\x12\t\n\x05PERKB\x10\x00\x12\t\n\x05PERKW\x10\x01\"V\n\x10\x46\x65\x65ratesResponse\x12%\n\x18warning_missing_feerates\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x1b\n\x19_warning_missing_feerates\"\xc3\x02\n\rFeeratesPerkb\x12\x16\n\x0emin_acceptable\x18\x01 \x01(\r\x12\x16\n\x0emax_acceptable\x18\x02 \x01(\r\x12\x14\n\x07opening\x18\x03 \x01(\rH\x00\x88\x01\x01\x12\x19\n\x0cmutual_close\x18\x04 \x01(\rH\x01\x88\x01\x01\x12\x1d\n\x10unilateral_close\x18\x05 \x01(\rH\x02\x88\x01\x01\x12\x1a\n\rdelayed_to_us\x18\x06 \x01(\rH\x03\x88\x01\x01\x12\x1c\n\x0fhtlc_resolution\x18\x07 \x01(\rH\x04\x88\x01\x01\x12\x14\n\x07penalty\x18\x08 \x01(\rH\x05\x88\x01\x01\x42\n\n\x08_openingB\x0f\n\r_mutual_closeB\x13\n\x11_unilateral_closeB\x10\n\x0e_delayed_to_usB\x12\n\x10_htlc_resolutionB\n\n\x08_penalty\"\xc3\x02\n\rFeeratesPerkw\x12\x16\n\x0emin_acceptable\x18\x01 \x01(\r\x12\x16\n\x0emax_acceptable\x18\x02 \x01(\r\x12\x14\n\x07opening\x18\x03 \x01(\rH\x00\x88\x01\x01\x12\x19\n\x0cmutual_close\x18\x04 \x01(\rH\x01\x88\x01\x01\x12\x1d\n\x10unilateral_close\x18\x05 \x01(\rH\x02\x88\x01\x01\x12\x1a\n\rdelayed_to_us\x18\x06 \x01(\rH\x03\x88\x01\x01\x12\x1c\n\x0fhtlc_resolution\x18\x07 \x01(\rH\x04\x88\x01\x01\x12\x14\n\x07penalty\x18\x08 \x01(\rH\x05\x88\x01\x01\x42\n\n\x08_openingB\x0f\n\r_mutual_closeB\x13\n\x11_unilateral_closeB\x10\n\x0e_delayed_to_usB\x12\n\x10_htlc_resolutionB\n\n\x08_penalty\"\xc1\x01\n\x1d\x46\x65\x65ratesOnchain_fee_estimates\x12 \n\x18opening_channel_satoshis\x18\x01 \x01(\x04\x12\x1d\n\x15mutual_close_satoshis\x18\x02 \x01(\x04\x12!\n\x19unilateral_close_satoshis\x18\x03 \x01(\x04\x12\x1d\n\x15htlc_timeout_satoshis\x18\x04 \x01(\x04\x12\x1d\n\x15htlc_success_satoshis\x18\x05 \x01(\x04\"\xe5\x03\n\x12\x46undchannelRequest\x12\n\n\x02id\x18\t \x01(\x0c\x12 \n\x06\x61mount\x18\x01 \x01(\x0b\x32\x10.cln.AmountOrAll\x12\"\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.FeerateH\x00\x88\x01\x01\x12\x15\n\x08\x61nnounce\x18\x03 \x01(\x08H\x01\x88\x01\x01\x12\x14\n\x07minconf\x18\n \x01(\rH\x02\x88\x01\x01\x12#\n\tpush_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\x15\n\x08\x63lose_to\x18\x06 \x01(\tH\x04\x88\x01\x01\x12%\n\x0brequest_amt\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x05\x88\x01\x01\x12\x1a\n\rcompact_lease\x18\x08 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x05utxos\x18\x0b \x03(\x0b\x32\r.cln.Outpoint\x12\x15\n\x08mindepth\x18\x0c \x01(\rH\x07\x88\x01\x01\x12!\n\x07reserve\x18\r \x01(\x0b\x32\x0b.cln.AmountH\x08\x88\x01\x01\x42\n\n\x08_feerateB\x0b\n\t_announceB\n\n\x08_minconfB\x0c\n\n_push_msatB\x0b\n\t_close_toB\x0e\n\x0c_request_amtB\x10\n\x0e_compact_leaseB\x0b\n\t_mindepthB\n\n\x08_reserve\"\x9b\x01\n\x13\x46undchannelResponse\x12\n\n\x02tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\x12\x0e\n\x06outnum\x18\x03 \x01(\r\x12\x12\n\nchannel_id\x18\x04 \x01(\x0c\x12\x15\n\x08\x63lose_to\x18\x05 \x01(\x0cH\x00\x88\x01\x01\x12\x15\n\x08mindepth\x18\x06 \x01(\rH\x01\x88\x01\x01\x42\x0b\n\t_close_toB\x0b\n\t_mindepth\"\xec\x01\n\x0fGetrouteRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12 \n\x0b\x61mount_msat\x18\t \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\nriskfactor\x18\x03 \x01(\x04\x12\x11\n\x04\x63ltv\x18\x04 \x01(\x01H\x00\x88\x01\x01\x12\x13\n\x06\x66romid\x18\x05 \x01(\x0cH\x01\x88\x01\x01\x12\x18\n\x0b\x66uzzpercent\x18\x06 \x01(\rH\x02\x88\x01\x01\x12\x0f\n\x07\x65xclude\x18\x07 \x03(\t\x12\x14\n\x07maxhops\x18\x08 \x01(\rH\x03\x88\x01\x01\x42\x07\n\x05_cltvB\t\n\x07_fromidB\x0e\n\x0c_fuzzpercentB\n\n\x08_maxhops\"5\n\x10GetrouteResponse\x12!\n\x05route\x18\x01 \x03(\x0b\x32\x12.cln.GetrouteRoute\"\xe9\x01\n\rGetrouteRoute\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x02 \x01(\t\x12\x11\n\tdirection\x18\x03 \x01(\r\x12\x15\n\x08msatoshi\x18\x07 \x01(\x04H\x00\x88\x01\x01\x12 \n\x0b\x61mount_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12\r\n\x05\x64\x65lay\x18\x05 \x01(\r\x12\x34\n\x05style\x18\x06 \x01(\x0e\x32%.cln.GetrouteRoute.GetrouteRouteStyle\"\x1d\n\x12GetrouteRouteStyle\x12\x07\n\x03TLV\x10\x00\x42\x0b\n\t_msatoshi\"\x82\x02\n\x13ListforwardsRequest\x12@\n\x06status\x18\x01 \x01(\x0e\x32+.cln.ListforwardsRequest.ListforwardsStatusH\x00\x88\x01\x01\x12\x17\n\nin_channel\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bout_channel\x18\x03 \x01(\tH\x02\x88\x01\x01\"L\n\x12ListforwardsStatus\x12\x0b\n\x07OFFERED\x10\x00\x12\x0b\n\x07SETTLED\x10\x01\x12\x10\n\x0cLOCAL_FAILED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x42\t\n\x07_statusB\r\n\x0b_in_channelB\x0e\n\x0c_out_channel\"C\n\x14ListforwardsResponse\x12+\n\x08\x66orwards\x18\x01 \x03(\x0b\x32\x19.cln.ListforwardsForwards\"\xde\x04\n\x14ListforwardsForwards\x12\x12\n\nin_channel\x18\x01 \x01(\t\x12\x17\n\nin_htlc_id\x18\n \x01(\x04H\x00\x88\x01\x01\x12\x1c\n\x07in_msat\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\x12\x44\n\x06status\x18\x03 \x01(\x0e\x32\x34.cln.ListforwardsForwards.ListforwardsForwardsStatus\x12\x15\n\rreceived_time\x18\x04 \x01(\x01\x12\x18\n\x0bout_channel\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bout_htlc_id\x18\x0b \x01(\x04H\x02\x88\x01\x01\x12G\n\x05style\x18\t \x01(\x0e\x32\x33.cln.ListforwardsForwards.ListforwardsForwardsStyleH\x03\x88\x01\x01\x12\"\n\x08\x66\x65\x65_msat\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\"\n\x08out_msat\x18\x08 \x01(\x0b\x32\x0b.cln.AmountH\x05\x88\x01\x01\"T\n\x1aListforwardsForwardsStatus\x12\x0b\n\x07OFFERED\x10\x00\x12\x0b\n\x07SETTLED\x10\x01\x12\x10\n\x0cLOCAL_FAILED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\"0\n\x19ListforwardsForwardsStyle\x12\n\n\x06LEGACY\x10\x00\x12\x07\n\x03TLV\x10\x01\x42\r\n\x0b_in_htlc_idB\x0e\n\x0c_out_channelB\x0e\n\x0c_out_htlc_idB\x08\n\x06_styleB\x0b\n\t_fee_msatB\x0b\n\t_out_msat\"\xdb\x01\n\x0fListpaysRequest\x12\x13\n\x06\x62olt11\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cpayment_hash\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12\x38\n\x06status\x18\x03 \x01(\x0e\x32#.cln.ListpaysRequest.ListpaysStatusH\x02\x88\x01\x01\"7\n\x0eListpaysStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x42\t\n\x07_bolt11B\x0f\n\r_payment_hashB\t\n\x07_status\"3\n\x10ListpaysResponse\x12\x1f\n\x04pays\x18\x01 \x03(\x0b\x32\x11.cln.ListpaysPays\"\x87\x04\n\x0cListpaysPays\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x34\n\x06status\x18\x02 \x01(\x0e\x32$.cln.ListpaysPays.ListpaysPaysStatus\x12\x18\n\x0b\x64\x65stination\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x12\x12\n\ncreated_at\x18\x04 \x01(\x04\x12\x19\n\x0c\x63ompleted_at\x18\x0c \x01(\x04H\x01\x88\x01\x01\x12\x12\n\x05label\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x06 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x0b \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x07 \x01(\tH\x05\x88\x01\x01\x12\x15\n\x08preimage\x18\r \x01(\x0cH\x06\x88\x01\x01\x12\x1c\n\x0fnumber_of_parts\x18\x0e \x01(\x04H\x07\x88\x01\x01\x12\x17\n\nerroronion\x18\n \x01(\x0cH\x08\x88\x01\x01\";\n\x12ListpaysPaysStatus\x12\x0b\n\x07PENDING\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x01\x12\x0c\n\x08\x43OMPLETE\x10\x02\x42\x0e\n\x0c_destinationB\x0f\n\r_completed_atB\x08\n\x06_labelB\t\n\x07_bolt11B\x0e\n\x0c_descriptionB\t\n\x07_bolt12B\x0b\n\t_preimageB\x12\n\x10_number_of_partsB\r\n\x0b_erroronion\"Y\n\x0bPingRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x10\n\x03len\x18\x02 \x01(\x01H\x00\x88\x01\x01\x12\x16\n\tpongbytes\x18\x03 \x01(\x01H\x01\x88\x01\x01\x42\x06\n\x04_lenB\x0c\n\n_pongbytes\"\x1e\n\x0cPingResponse\x12\x0e\n\x06totlen\x18\x01 \x01(\r\"\xf8\x01\n\x11SetchannelRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12!\n\x07\x66\x65\x65\x62\x61se\x18\x02 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x13\n\x06\x66\x65\x65ppm\x18\x03 \x01(\rH\x01\x88\x01\x01\x12!\n\x07htlcmin\x18\x04 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12!\n\x07htlcmax\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\x19\n\x0c\x65nforcedelay\x18\x06 \x01(\rH\x04\x88\x01\x01\x42\n\n\x08_feebaseB\t\n\x07_feeppmB\n\n\x08_htlcminB\n\n\x08_htlcmaxB\x0f\n\r_enforcedelay\"?\n\x12SetchannelResponse\x12)\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x17.cln.SetchannelChannels\"\x94\x03\n\x12SetchannelChannels\x12\x0f\n\x07peer_id\x18\x01 \x01(\x0c\x12\x12\n\nchannel_id\x18\x02 \x01(\x0c\x12\x1d\n\x10short_channel_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\"\n\rfee_base_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12#\n\x1b\x66\x65\x65_proportional_millionths\x18\x05 \x01(\r\x12*\n\x15minimum_htlc_out_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12$\n\x17warning_htlcmin_too_low\x18\x07 \x01(\tH\x01\x88\x01\x01\x12*\n\x15maximum_htlc_out_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x18warning_htlcmax_too_high\x18\t \x01(\tH\x02\x88\x01\x01\x42\x13\n\x11_short_channel_idB\x1a\n\x18_warning_htlcmin_too_lowB\x1b\n\x19_warning_htlcmax_too_high\"%\n\x12SignmessageRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\"F\n\x13SignmessageResponse\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\r\n\x05recid\x18\x02 \x01(\x0c\x12\r\n\x05zbase\x18\x03 \x01(\t\"\r\n\x0bStopRequest\"\x0e\n\x0cStopResponse2\xb1\x17\n\x04Node\x12\x36\n\x07Getinfo\x12\x13.cln.GetinfoRequest\x1a\x14.cln.GetinfoResponse\"\x00\x12<\n\tListPeers\x12\x15.cln.ListpeersRequest\x1a\x16.cln.ListpeersResponse\"\x00\x12<\n\tListFunds\x12\x15.cln.ListfundsRequest\x1a\x16.cln.ListfundsResponse\"\x00\x12\x36\n\x07SendPay\x12\x13.cln.SendpayRequest\x1a\x14.cln.SendpayResponse\"\x00\x12\x45\n\x0cListChannels\x12\x18.cln.ListchannelsRequest\x1a\x19.cln.ListchannelsResponse\"\x00\x12<\n\tAddGossip\x12\x15.cln.AddgossipRequest\x1a\x16.cln.AddgossipResponse\"\x00\x12Q\n\x10\x41utoCleanInvoice\x12\x1c.cln.AutocleaninvoiceRequest\x1a\x1d.cln.AutocleaninvoiceResponse\"\x00\x12\x45\n\x0c\x43heckMessage\x12\x18.cln.CheckmessageRequest\x1a\x19.cln.CheckmessageResponse\"\x00\x12\x30\n\x05\x43lose\x12\x11.cln.CloseRequest\x1a\x12.cln.CloseResponse\"\x00\x12:\n\x0b\x43onnectPeer\x12\x13.cln.ConnectRequest\x1a\x14.cln.ConnectResponse\"\x00\x12H\n\rCreateInvoice\x12\x19.cln.CreateinvoiceRequest\x1a\x1a.cln.CreateinvoiceResponse\"\x00\x12<\n\tDatastore\x12\x15.cln.DatastoreRequest\x1a\x16.cln.DatastoreResponse\"\x00\x12\x42\n\x0b\x43reateOnion\x12\x17.cln.CreateonionRequest\x1a\x18.cln.CreateonionResponse\"\x00\x12\x45\n\x0c\x44\x65lDatastore\x12\x18.cln.DeldatastoreRequest\x1a\x19.cln.DeldatastoreResponse\"\x00\x12T\n\x11\x44\x65lExpiredInvoice\x12\x1d.cln.DelexpiredinvoiceRequest\x1a\x1e.cln.DelexpiredinvoiceResponse\"\x00\x12?\n\nDelInvoice\x12\x16.cln.DelinvoiceRequest\x1a\x17.cln.DelinvoiceResponse\"\x00\x12\x36\n\x07Invoice\x12\x13.cln.InvoiceRequest\x1a\x14.cln.InvoiceResponse\"\x00\x12H\n\rListDatastore\x12\x19.cln.ListdatastoreRequest\x1a\x1a.cln.ListdatastoreResponse\"\x00\x12\x45\n\x0cListInvoices\x12\x18.cln.ListinvoicesRequest\x1a\x19.cln.ListinvoicesResponse\"\x00\x12<\n\tSendOnion\x12\x15.cln.SendonionRequest\x1a\x16.cln.SendonionResponse\"\x00\x12\x45\n\x0cListSendPays\x12\x18.cln.ListsendpaysRequest\x1a\x19.cln.ListsendpaysResponse\"\x00\x12Q\n\x10ListTransactions\x12\x1c.cln.ListtransactionsRequest\x1a\x1d.cln.ListtransactionsResponse\"\x00\x12*\n\x03Pay\x12\x0f.cln.PayRequest\x1a\x10.cln.PayResponse\"\x00\x12<\n\tListNodes\x12\x15.cln.ListnodesRequest\x1a\x16.cln.ListnodesResponse\"\x00\x12K\n\x0eWaitAnyInvoice\x12\x1a.cln.WaitanyinvoiceRequest\x1a\x1b.cln.WaitanyinvoiceResponse\"\x00\x12\x42\n\x0bWaitInvoice\x12\x17.cln.WaitinvoiceRequest\x1a\x18.cln.WaitinvoiceResponse\"\x00\x12\x42\n\x0bWaitSendPay\x12\x17.cln.WaitsendpayRequest\x1a\x18.cln.WaitsendpayResponse\"\x00\x12\x36\n\x07NewAddr\x12\x13.cln.NewaddrRequest\x1a\x14.cln.NewaddrResponse\"\x00\x12\x39\n\x08Withdraw\x12\x14.cln.WithdrawRequest\x1a\x15.cln.WithdrawResponse\"\x00\x12\x36\n\x07KeySend\x12\x13.cln.KeysendRequest\x1a\x14.cln.KeysendResponse\"\x00\x12\x39\n\x08\x46undPsbt\x12\x14.cln.FundpsbtRequest\x1a\x15.cln.FundpsbtResponse\"\x00\x12\x39\n\x08SendPsbt\x12\x14.cln.SendpsbtRequest\x1a\x15.cln.SendpsbtResponse\"\x00\x12\x39\n\x08SignPsbt\x12\x14.cln.SignpsbtRequest\x1a\x15.cln.SignpsbtResponse\"\x00\x12\x39\n\x08UtxoPsbt\x12\x14.cln.UtxopsbtRequest\x1a\x15.cln.UtxopsbtResponse\"\x00\x12<\n\tTxDiscard\x12\x15.cln.TxdiscardRequest\x1a\x16.cln.TxdiscardResponse\"\x00\x12<\n\tTxPrepare\x12\x15.cln.TxprepareRequest\x1a\x16.cln.TxprepareResponse\"\x00\x12\x33\n\x06TxSend\x12\x12.cln.TxsendRequest\x1a\x13.cln.TxsendResponse\"\x00\x12?\n\nDisconnect\x12\x16.cln.DisconnectRequest\x1a\x17.cln.DisconnectResponse\"\x00\x12\x39\n\x08\x46\x65\x65rates\x12\x14.cln.FeeratesRequest\x1a\x15.cln.FeeratesResponse\"\x00\x12\x42\n\x0b\x46undChannel\x12\x17.cln.FundchannelRequest\x1a\x18.cln.FundchannelResponse\"\x00\x12\x39\n\x08GetRoute\x12\x14.cln.GetrouteRequest\x1a\x15.cln.GetrouteResponse\"\x00\x12\x45\n\x0cListForwards\x12\x18.cln.ListforwardsRequest\x1a\x19.cln.ListforwardsResponse\"\x00\x12\x39\n\x08ListPays\x12\x14.cln.ListpaysRequest\x1a\x15.cln.ListpaysResponse\"\x00\x12-\n\x04Ping\x12\x10.cln.PingRequest\x1a\x11.cln.PingResponse\"\x00\x12?\n\nSetChannel\x12\x16.cln.SetchannelRequest\x1a\x17.cln.SetchannelResponse\"\x00\x12\x42\n\x0bSignMessage\x12\x17.cln.SignmessageRequest\x1a\x18.cln.SignmessageResponse\"\x00\x12-\n\x04Stop\x12\x10.cln.StopRequest\x1a\x11.cln.StopResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nnode.proto\x12\x03\x63ln\x1a\x10primitives.proto\"\x10\n\x0eGetinfoRequest\"\xf4\x04\n\x0fGetinfoResponse\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\r\n\x05\x61lias\x18\x02 \x01(\t\x12\r\n\x05\x63olor\x18\x03 \x01(\x0c\x12\x11\n\tnum_peers\x18\x04 \x01(\r\x12\x1c\n\x14num_pending_channels\x18\x05 \x01(\r\x12\x1b\n\x13num_active_channels\x18\x06 \x01(\r\x12\x1d\n\x15num_inactive_channels\x18\x07 \x01(\r\x12\x0f\n\x07version\x18\x08 \x01(\t\x12\x15\n\rlightning_dir\x18\t \x01(\t\x12\x33\n\x0cour_features\x18\n \x01(\x0b\x32\x18.cln.GetinfoOur_featuresH\x00\x88\x01\x01\x12\x13\n\x0b\x62lockheight\x18\x0b \x01(\r\x12\x0f\n\x07network\x18\x0c \x01(\t\x12$\n\x17msatoshi_fees_collected\x18\x12 \x01(\x04H\x01\x88\x01\x01\x12(\n\x13\x66\x65\x65s_collected_msat\x18\r \x01(\x0b\x32\x0b.cln.Amount\x12$\n\x07\x61\x64\x64ress\x18\x0e \x03(\x0b\x32\x13.cln.GetinfoAddress\x12$\n\x07\x62inding\x18\x0f \x03(\x0b\x32\x13.cln.GetinfoBinding\x12\"\n\x15warning_bitcoind_sync\x18\x10 \x01(\tH\x02\x88\x01\x01\x12$\n\x17warning_lightningd_sync\x18\x11 \x01(\tH\x03\x88\x01\x01\x42\x0f\n\r_our_featuresB\x1a\n\x18_msatoshi_fees_collectedB\x18\n\x16_warning_bitcoind_syncB\x1a\n\x18_warning_lightningd_sync\"S\n\x13GetinfoOur_features\x12\x0c\n\x04init\x18\x01 \x01(\x0c\x12\x0c\n\x04node\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\x0c\x12\x0f\n\x07invoice\x18\x04 \x01(\x0c\"\xd3\x01\n\x0eGetinfoAddress\x12\x39\n\titem_type\x18\x01 \x01(\x0e\x32&.cln.GetinfoAddress.GetinfoAddressType\x12\x0c\n\x04port\x18\x02 \x01(\r\x12\x14\n\x07\x61\x64\x64ress\x18\x03 \x01(\tH\x00\x88\x01\x01\"V\n\x12GetinfoAddressType\x12\x07\n\x03\x44NS\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x12\r\n\tWEBSOCKET\x10\x05\x42\n\n\x08_address\"\xfb\x01\n\x0eGetinfoBinding\x12\x39\n\titem_type\x18\x01 \x01(\x0e\x32&.cln.GetinfoBinding.GetinfoBindingType\x12\x14\n\x07\x61\x64\x64ress\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04port\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x13\n\x06socket\x18\x04 \x01(\tH\x02\x88\x01\x01\"P\n\x12GetinfoBindingType\x12\x10\n\x0cLOCAL_SOCKET\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x42\n\n\x08_addressB\x07\n\x05_portB\t\n\x07_socket\"H\n\x10ListpeersRequest\x12\x0f\n\x02id\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x12\n\x05level\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_idB\x08\n\x06_level\"7\n\x11ListpeersResponse\x12\"\n\x05peers\x18\x01 \x03(\x0b\x32\x13.cln.ListpeersPeers\"\xe2\x01\n\x0eListpeersPeers\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x11\n\tconnected\x18\x02 \x01(\x08\x12#\n\x03log\x18\x03 \x03(\x0b\x32\x16.cln.ListpeersPeersLog\x12-\n\x08\x63hannels\x18\x04 \x03(\x0b\x32\x1b.cln.ListpeersPeersChannels\x12\x0f\n\x07netaddr\x18\x05 \x03(\t\x12\x18\n\x0bremote_addr\x18\x07 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08\x66\x65\x61tures\x18\x06 \x01(\x0cH\x01\x88\x01\x01\x42\x0e\n\x0c_remote_addrB\x0b\n\t_features\"\xfd\x02\n\x11ListpeersPeersLog\x12?\n\titem_type\x18\x01 \x01(\x0e\x32,.cln.ListpeersPeersLog.ListpeersPeersLogType\x12\x18\n\x0bnum_skipped\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x11\n\x04time\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06source\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x10\n\x03log\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x14\n\x07node_id\x18\x06 \x01(\x0cH\x04\x88\x01\x01\x12\x11\n\x04\x64\x61ta\x18\x07 \x01(\x0cH\x05\x88\x01\x01\"i\n\x15ListpeersPeersLogType\x12\x0b\n\x07SKIPPED\x10\x00\x12\n\n\x06\x42ROKEN\x10\x01\x12\x0b\n\x07UNUSUAL\x10\x02\x12\x08\n\x04INFO\x10\x03\x12\t\n\x05\x44\x45\x42UG\x10\x04\x12\t\n\x05IO_IN\x10\x05\x12\n\n\x06IO_OUT\x10\x06\x42\x0e\n\x0c_num_skippedB\x07\n\x05_timeB\t\n\x07_sourceB\x06\n\x04_logB\n\n\x08_node_idB\x07\n\x05_data\"\xd6\x17\n\x16ListpeersPeersChannels\x12\x46\n\x05state\x18\x01 \x01(\x0e\x32\x37.cln.ListpeersPeersChannels.ListpeersPeersChannelsState\x12\x19\n\x0cscratch_txid\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x38\n\x07\x66\x65\x65rate\x18\x03 \x01(\x0b\x32\".cln.ListpeersPeersChannelsFeerateH\x01\x88\x01\x01\x12\x12\n\x05owner\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x1d\n\x10short_channel_id\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x17\n\nchannel_id\x18\x06 \x01(\x0cH\x04\x88\x01\x01\x12\x19\n\x0c\x66unding_txid\x18\x07 \x01(\x0cH\x05\x88\x01\x01\x12\x1b\n\x0e\x66unding_outnum\x18\x08 \x01(\rH\x06\x88\x01\x01\x12\x1c\n\x0finitial_feerate\x18\t \x01(\tH\x07\x88\x01\x01\x12\x19\n\x0clast_feerate\x18\n \x01(\tH\x08\x88\x01\x01\x12\x19\n\x0cnext_feerate\x18\x0b \x01(\tH\t\x88\x01\x01\x12\x1a\n\rnext_fee_step\x18\x0c \x01(\rH\n\x88\x01\x01\x12\x35\n\x08inflight\x18\r \x03(\x0b\x32#.cln.ListpeersPeersChannelsInflight\x12\x15\n\x08\x63lose_to\x18\x0e \x01(\x0cH\x0b\x88\x01\x01\x12\x14\n\x07private\x18\x0f \x01(\x08H\x0c\x88\x01\x01\x12 \n\x06opener\x18\x10 \x01(\x0e\x32\x10.cln.ChannelSide\x12%\n\x06\x63loser\x18\x11 \x01(\x0e\x32\x10.cln.ChannelSideH\r\x88\x01\x01\x12\x10\n\x08\x66\x65\x61tures\x18\x12 \x03(\t\x12\x38\n\x07\x66unding\x18\x13 \x01(\x0b\x32\".cln.ListpeersPeersChannelsFundingH\x0e\x88\x01\x01\x12$\n\nto_us_msat\x18\x14 \x01(\x0b\x32\x0b.cln.AmountH\x0f\x88\x01\x01\x12(\n\x0emin_to_us_msat\x18\x15 \x01(\x0b\x32\x0b.cln.AmountH\x10\x88\x01\x01\x12(\n\x0emax_to_us_msat\x18\x16 \x01(\x0b\x32\x0b.cln.AmountH\x11\x88\x01\x01\x12$\n\ntotal_msat\x18\x17 \x01(\x0b\x32\x0b.cln.AmountH\x12\x88\x01\x01\x12\'\n\rfee_base_msat\x18\x18 \x01(\x0b\x32\x0b.cln.AmountH\x13\x88\x01\x01\x12(\n\x1b\x66\x65\x65_proportional_millionths\x18\x19 \x01(\rH\x14\x88\x01\x01\x12)\n\x0f\x64ust_limit_msat\x18\x1a \x01(\x0b\x32\x0b.cln.AmountH\x15\x88\x01\x01\x12\x30\n\x16max_total_htlc_in_msat\x18\x1b \x01(\x0b\x32\x0b.cln.AmountH\x16\x88\x01\x01\x12,\n\x12their_reserve_msat\x18\x1c \x01(\x0b\x32\x0b.cln.AmountH\x17\x88\x01\x01\x12*\n\x10our_reserve_msat\x18\x1d \x01(\x0b\x32\x0b.cln.AmountH\x18\x88\x01\x01\x12(\n\x0espendable_msat\x18\x1e \x01(\x0b\x32\x0b.cln.AmountH\x19\x88\x01\x01\x12)\n\x0freceivable_msat\x18\x1f \x01(\x0b\x32\x0b.cln.AmountH\x1a\x88\x01\x01\x12.\n\x14minimum_htlc_in_msat\x18 \x01(\x0b\x32\x0b.cln.AmountH\x1b\x88\x01\x01\x12/\n\x15minimum_htlc_out_msat\x18\x30 \x01(\x0b\x32\x0b.cln.AmountH\x1c\x88\x01\x01\x12/\n\x15maximum_htlc_out_msat\x18\x31 \x01(\x0b\x32\x0b.cln.AmountH\x1d\x88\x01\x01\x12 \n\x13their_to_self_delay\x18! \x01(\rH\x1e\x88\x01\x01\x12\x1e\n\x11our_to_self_delay\x18\" \x01(\rH\x1f\x88\x01\x01\x12\x1f\n\x12max_accepted_htlcs\x18# \x01(\rH \x88\x01\x01\x12\x34\n\x05\x61lias\x18\x32 \x01(\x0b\x32 .cln.ListpeersPeersChannelsAliasH!\x88\x01\x01\x12\x0e\n\x06status\x18% \x03(\t\x12 \n\x13in_payments_offered\x18& \x01(\x04H\"\x88\x01\x01\x12)\n\x0fin_offered_msat\x18\' \x01(\x0b\x32\x0b.cln.AmountH#\x88\x01\x01\x12\"\n\x15in_payments_fulfilled\x18( \x01(\x04H$\x88\x01\x01\x12+\n\x11in_fulfilled_msat\x18) \x01(\x0b\x32\x0b.cln.AmountH%\x88\x01\x01\x12!\n\x14out_payments_offered\x18* \x01(\x04H&\x88\x01\x01\x12*\n\x10out_offered_msat\x18+ \x01(\x0b\x32\x0b.cln.AmountH\'\x88\x01\x01\x12#\n\x16out_payments_fulfilled\x18, \x01(\x04H(\x88\x01\x01\x12,\n\x12out_fulfilled_msat\x18- \x01(\x0b\x32\x0b.cln.AmountH)\x88\x01\x01\x12/\n\x05htlcs\x18. \x03(\x0b\x32 .cln.ListpeersPeersChannelsHtlcs\x12\x1a\n\rclose_to_addr\x18/ \x01(\tH*\x88\x01\x01\"\xa1\x02\n\x1bListpeersPeersChannelsState\x12\x0c\n\x08OPENINGD\x10\x00\x12\x1c\n\x18\x43HANNELD_AWAITING_LOCKIN\x10\x01\x12\x13\n\x0f\x43HANNELD_NORMAL\x10\x02\x12\x1a\n\x16\x43HANNELD_SHUTTING_DOWN\x10\x03\x12\x18\n\x14\x43LOSINGD_SIGEXCHANGE\x10\x04\x12\x15\n\x11\x43LOSINGD_COMPLETE\x10\x05\x12\x17\n\x13\x41WAITING_UNILATERAL\x10\x06\x12\x16\n\x12\x46UNDING_SPEND_SEEN\x10\x07\x12\x0b\n\x07ONCHAIN\x10\x08\x12\x17\n\x13\x44UALOPEND_OPEN_INIT\x10\t\x12\x1d\n\x19\x44UALOPEND_AWAITING_LOCKIN\x10\nB\x0f\n\r_scratch_txidB\n\n\x08_feerateB\x08\n\x06_ownerB\x13\n\x11_short_channel_idB\r\n\x0b_channel_idB\x0f\n\r_funding_txidB\x11\n\x0f_funding_outnumB\x12\n\x10_initial_feerateB\x0f\n\r_last_feerateB\x0f\n\r_next_feerateB\x10\n\x0e_next_fee_stepB\x0b\n\t_close_toB\n\n\x08_privateB\t\n\x07_closerB\n\n\x08_fundingB\r\n\x0b_to_us_msatB\x11\n\x0f_min_to_us_msatB\x11\n\x0f_max_to_us_msatB\r\n\x0b_total_msatB\x10\n\x0e_fee_base_msatB\x1e\n\x1c_fee_proportional_millionthsB\x12\n\x10_dust_limit_msatB\x19\n\x17_max_total_htlc_in_msatB\x15\n\x13_their_reserve_msatB\x13\n\x11_our_reserve_msatB\x11\n\x0f_spendable_msatB\x12\n\x10_receivable_msatB\x17\n\x15_minimum_htlc_in_msatB\x18\n\x16_minimum_htlc_out_msatB\x18\n\x16_maximum_htlc_out_msatB\x16\n\x14_their_to_self_delayB\x14\n\x12_our_to_self_delayB\x15\n\x13_max_accepted_htlcsB\x08\n\x06_aliasB\x16\n\x14_in_payments_offeredB\x12\n\x10_in_offered_msatB\x18\n\x16_in_payments_fulfilledB\x14\n\x12_in_fulfilled_msatB\x17\n\x15_out_payments_offeredB\x13\n\x11_out_offered_msatB\x19\n\x17_out_payments_fulfilledB\x15\n\x13_out_fulfilled_msatB\x10\n\x0e_close_to_addr\"=\n\x1dListpeersPeersChannelsFeerate\x12\r\n\x05perkw\x18\x01 \x01(\r\x12\r\n\x05perkb\x18\x02 \x01(\r\"\xc5\x01\n\x1eListpeersPeersChannelsInflight\x12\x14\n\x0c\x66unding_txid\x18\x01 \x01(\x0c\x12\x16\n\x0e\x66unding_outnum\x18\x02 \x01(\r\x12\x0f\n\x07\x66\x65\x65rate\x18\x03 \x01(\t\x12\'\n\x12total_funding_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x10our_funding_msat\x18\x05 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0cscratch_txid\x18\x06 \x01(\x0c\"\x87\x03\n\x1dListpeersPeersChannelsFunding\x12$\n\nlocal_msat\x18\x01 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12%\n\x0bremote_msat\x18\x02 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12%\n\x0bpushed_msat\x18\x03 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12%\n\x10local_funds_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12&\n\x11remote_funds_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\'\n\rfee_paid_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\'\n\rfee_rcvd_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x42\r\n\x0b_local_msatB\x0e\n\x0c_remote_msatB\x0e\n\x0c_pushed_msatB\x10\n\x0e_fee_paid_msatB\x10\n\x0e_fee_rcvd_msat\"[\n\x1bListpeersPeersChannelsAlias\x12\x12\n\x05local\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06remote\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_localB\t\n\x07_remote\"\xd2\x02\n\x1bListpeersPeersChannelsHtlcs\x12X\n\tdirection\x18\x01 \x01(\x0e\x32\x45.cln.ListpeersPeersChannelsHtlcs.ListpeersPeersChannelsHtlcsDirection\x12\n\n\x02id\x18\x02 \x01(\x04\x12 \n\x0b\x61mount_msat\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x0e\n\x06\x65xpiry\x18\x04 \x01(\r\x12\x14\n\x0cpayment_hash\x18\x05 \x01(\x0c\x12\x1a\n\rlocal_trimmed\x18\x06 \x01(\x08H\x00\x88\x01\x01\x12\x13\n\x06status\x18\x07 \x01(\tH\x01\x88\x01\x01\"7\n$ListpeersPeersChannelsHtlcsDirection\x12\x06\n\x02IN\x10\x00\x12\x07\n\x03OUT\x10\x01\x42\x10\n\x0e_local_trimmedB\t\n\x07_status\"0\n\x10ListfundsRequest\x12\x12\n\x05spent\x18\x01 \x01(\x08H\x00\x88\x01\x01\x42\x08\n\x06_spent\"e\n\x11ListfundsResponse\x12&\n\x07outputs\x18\x01 \x03(\x0b\x32\x15.cln.ListfundsOutputs\x12(\n\x08\x63hannels\x18\x02 \x03(\x0b\x32\x16.cln.ListfundsChannels\"\x83\x03\n\x10ListfundsOutputs\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0e\n\x06output\x18\x02 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0cscriptpubkey\x18\x04 \x01(\x0c\x12\x14\n\x07\x61\x64\x64ress\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0credeemscript\x18\x06 \x01(\x0cH\x01\x88\x01\x01\x12<\n\x06status\x18\x07 \x01(\x0e\x32,.cln.ListfundsOutputs.ListfundsOutputsStatus\x12\x10\n\x08reserved\x18\t \x01(\x08\x12\x18\n\x0b\x62lockheight\x18\x08 \x01(\rH\x02\x88\x01\x01\"Q\n\x16ListfundsOutputsStatus\x12\x0f\n\x0bUNCONFIRMED\x10\x00\x12\r\n\tCONFIRMED\x10\x01\x12\t\n\x05SPENT\x10\x02\x12\x0c\n\x08IMMATURE\x10\x03\x42\n\n\x08_addressB\x0f\n\r_redeemscriptB\x0e\n\x0c_blockheight\"\x83\x02\n\x11ListfundsChannels\x12\x0f\n\x07peer_id\x18\x01 \x01(\x0c\x12$\n\x0four_amount_msat\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\x12 \n\x0b\x61mount_msat\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0c\x66unding_txid\x18\x04 \x01(\x0c\x12\x16\n\x0e\x66unding_output\x18\x05 \x01(\r\x12\x11\n\tconnected\x18\x06 \x01(\x08\x12 \n\x05state\x18\x07 \x01(\x0e\x32\x11.cln.ChannelState\x12\x1d\n\x10short_channel_id\x18\x08 \x01(\tH\x00\x88\x01\x01\x42\x13\n\x11_short_channel_id\"\xdd\x02\n\x0eSendpayRequest\x12 \n\x05route\x18\x01 \x03(\x0b\x32\x11.cln.SendpayRoute\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x12\n\x05label\x18\x03 \x01(\tH\x00\x88\x01\x01\x12%\n\x0b\x61mount_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x1b\n\x0epayment_secret\x18\x06 \x01(\x0cH\x03\x88\x01\x01\x12\x13\n\x06partid\x18\x07 \x01(\rH\x04\x88\x01\x01\x12\x1a\n\rlocalinvreqid\x18\x0b \x01(\x0cH\x05\x88\x01\x01\x12\x14\n\x07groupid\x18\t \x01(\x04H\x06\x88\x01\x01\x42\x08\n\x06_labelB\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\x11\n\x0f_payment_secretB\t\n\x07_partidB\x10\n\x0e_localinvreqidB\n\n\x08_groupid\"\xd1\x04\n\x0fSendpayResponse\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x07groupid\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x32\n\x06status\x18\x04 \x01(\x0e\x32\".cln.SendpayResponse.SendpayStatus\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x06 \x01(\x0cH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\x07 \x01(\x04\x12\x19\n\x0c\x63ompleted_at\x18\x0f \x01(\x04H\x03\x88\x01\x01\x12%\n\x10\x61mount_sent_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\t \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06partid\x18\n \x01(\x04H\x05\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x0b \x01(\tH\x06\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x0c \x01(\tH\x07\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\r \x01(\x0cH\x08\x88\x01\x01\x12\x14\n\x07message\x18\x0e \x01(\tH\t\x88\x01\x01\"*\n\rSendpayStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x42\n\n\x08_groupidB\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x0f\n\r_completed_atB\x08\n\x06_labelB\t\n\x07_partidB\t\n\x07_bolt11B\t\n\x07_bolt12B\x13\n\x11_payment_preimageB\n\n\x08_message\"\\\n\x0cSendpayRoute\x12 \n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.Amount\x12\n\n\x02id\x18\x02 \x01(\x0c\x12\r\n\x05\x64\x65lay\x18\x03 \x01(\r\x12\x0f\n\x07\x63hannel\x18\x04 \x01(\t\"\x93\x01\n\x13ListchannelsRequest\x12\x1d\n\x10short_channel_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06source\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x03 \x01(\x0cH\x02\x88\x01\x01\x42\x13\n\x11_short_channel_idB\t\n\x07_sourceB\x0e\n\x0c_destination\"C\n\x14ListchannelsResponse\x12+\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x19.cln.ListchannelsChannels\"\xa0\x03\n\x14ListchannelsChannels\x12\x0e\n\x06source\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65stination\x18\x02 \x01(\x0c\x12\x18\n\x10short_channel_id\x18\x03 \x01(\t\x12\x0e\n\x06public\x18\x04 \x01(\x08\x12 \n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.Amount\x12\x15\n\rmessage_flags\x18\x06 \x01(\r\x12\x15\n\rchannel_flags\x18\x07 \x01(\r\x12\x0e\n\x06\x61\x63tive\x18\x08 \x01(\x08\x12\x13\n\x0blast_update\x18\t \x01(\r\x12\x1d\n\x15\x62\x61se_fee_millisatoshi\x18\n \x01(\r\x12\x19\n\x11\x66\x65\x65_per_millionth\x18\x0b \x01(\r\x12\r\n\x05\x64\x65lay\x18\x0c \x01(\r\x12&\n\x11htlc_minimum_msat\x18\r \x01(\x0b\x32\x0b.cln.Amount\x12+\n\x11htlc_maximum_msat\x18\x0e \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x10\n\x08\x66\x65\x61tures\x18\x0f \x01(\x0c\x42\x14\n\x12_htlc_maximum_msat\"#\n\x10\x41\x64\x64gossipRequest\x12\x0f\n\x07message\x18\x01 \x01(\x0c\"\x13\n\x11\x41\x64\x64gossipResponse\"o\n\x17\x41utocleaninvoiceRequest\x12\x17\n\nexpired_by\x18\x01 \x01(\x04H\x00\x88\x01\x01\x12\x1a\n\rcycle_seconds\x18\x02 \x01(\x04H\x01\x88\x01\x01\x42\r\n\x0b_expired_byB\x10\n\x0e_cycle_seconds\"\x81\x01\n\x18\x41utocleaninvoiceResponse\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x17\n\nexpired_by\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x1a\n\rcycle_seconds\x18\x03 \x01(\x04H\x01\x88\x01\x01\x42\r\n\x0b_expired_byB\x10\n\x0e_cycle_seconds\"U\n\x13\x43heckmessageRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\r\n\x05zbase\x18\x02 \x01(\t\x12\x13\n\x06pubkey\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\t\n\x07_pubkey\"8\n\x14\x43heckmessageResponse\x12\x10\n\x08verified\x18\x01 \x01(\x08\x12\x0e\n\x06pubkey\x18\x02 \x01(\x0c\"\xcb\x02\n\x0c\x43loseRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x1e\n\x11unilateraltimeout\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x03 \x01(\tH\x01\x88\x01\x01\x12!\n\x14\x66\x65\x65_negotiation_step\x18\x04 \x01(\tH\x02\x88\x01\x01\x12)\n\rwrong_funding\x18\x05 \x01(\x0b\x32\r.cln.OutpointH\x03\x88\x01\x01\x12\x1f\n\x12\x66orce_lease_closed\x18\x06 \x01(\x08H\x04\x88\x01\x01\x12\x1e\n\x08\x66\x65\x65range\x18\x07 \x03(\x0b\x32\x0c.cln.FeerateB\x14\n\x12_unilateraltimeoutB\x0e\n\x0c_destinationB\x17\n\x15_fee_negotiation_stepB\x10\n\x0e_wrong_fundingB\x15\n\x13_force_lease_closed\"\xab\x01\n\rCloseResponse\x12/\n\titem_type\x18\x01 \x01(\x0e\x32\x1c.cln.CloseResponse.CloseType\x12\x0f\n\x02tx\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x11\n\x04txid\x18\x03 \x01(\x0cH\x01\x88\x01\x01\"5\n\tCloseType\x12\n\n\x06MUTUAL\x10\x00\x12\x0e\n\nUNILATERAL\x10\x01\x12\x0c\n\x08UNOPENED\x10\x02\x42\x05\n\x03_txB\x07\n\x05_txid\"T\n\x0e\x43onnectRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\x04host\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x11\n\x04port\x18\x03 \x01(\rH\x01\x88\x01\x01\x42\x07\n\x05_hostB\x07\n\x05_port\"\xb4\x01\n\x0f\x43onnectResponse\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x10\n\x08\x66\x65\x61tures\x18\x02 \x01(\x0c\x12\x38\n\tdirection\x18\x03 \x01(\x0e\x32%.cln.ConnectResponse.ConnectDirection\x12$\n\x07\x61\x64\x64ress\x18\x04 \x01(\x0b\x32\x13.cln.ConnectAddress\"#\n\x10\x43onnectDirection\x12\x06\n\x02IN\x10\x00\x12\x07\n\x03OUT\x10\x01\"\xfb\x01\n\x0e\x43onnectAddress\x12\x39\n\titem_type\x18\x01 \x01(\x0e\x32&.cln.ConnectAddress.ConnectAddressType\x12\x13\n\x06socket\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x61\x64\x64ress\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x11\n\x04port\x18\x04 \x01(\rH\x02\x88\x01\x01\"P\n\x12\x43onnectAddressType\x12\x10\n\x0cLOCAL_SOCKET\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x42\t\n\x07_socketB\n\n\x08_addressB\x07\n\x05_port\"J\n\x14\x43reateinvoiceRequest\x12\x11\n\tinvstring\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12\x10\n\x08preimage\x18\x03 \x01(\x0c\"\x81\x05\n\x15\x43reateinvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x06\x62olt11\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x04 \x01(\x0c\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12>\n\x06status\x18\x06 \x01(\x0e\x32..cln.CreateinvoiceResponse.CreateinvoiceStatus\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\x12\x12\n\nexpires_at\x18\x08 \x01(\x04\x12\x16\n\tpay_index\x18\t \x01(\x04H\x03\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\x14\n\x07paid_at\x18\x0b \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\x12\x1b\n\x0elocal_offer_id\x18\r \x01(\x0cH\x07\x88\x01\x01\x12\x1e\n\x11invreq_payer_note\x18\x0f \x01(\tH\x08\x88\x01\x01\"8\n\x13\x43reateinvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x12\n\n\x06UNPAID\x10\x02\x42\t\n\x07_bolt11B\t\n\x07_bolt12B\x0e\n\x0c_amount_msatB\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimageB\x11\n\x0f_local_offer_idB\x14\n\x12_invreq_payer_note\"\xb4\x02\n\x10\x44\x61tastoreRequest\x12\x0b\n\x03key\x18\x05 \x03(\t\x12\x13\n\x06string\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12\x36\n\x04mode\x18\x03 \x01(\x0e\x32#.cln.DatastoreRequest.DatastoreModeH\x02\x88\x01\x01\x12\x17\n\ngeneration\x18\x04 \x01(\x04H\x03\x88\x01\x01\"p\n\rDatastoreMode\x12\x0f\n\x0bMUST_CREATE\x10\x00\x12\x10\n\x0cMUST_REPLACE\x10\x01\x12\x15\n\x11\x43REATE_OR_REPLACE\x10\x02\x12\x0f\n\x0bMUST_APPEND\x10\x03\x12\x14\n\x10\x43REATE_OR_APPEND\x10\x04\x42\t\n\x07_stringB\x06\n\x04_hexB\x07\n\x05_modeB\r\n\x0b_generation\"\x82\x01\n\x11\x44\x61tastoreResponse\x12\x0b\n\x03key\x18\x05 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x06string\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\r\n\x0b_generationB\x06\n\x04_hexB\t\n\x07_string\"\x9d\x01\n\x12\x43reateonionRequest\x12\"\n\x04hops\x18\x01 \x03(\x0b\x32\x14.cln.CreateonionHops\x12\x11\n\tassocdata\x18\x02 \x01(\x0c\x12\x18\n\x0bsession_key\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x12\x17\n\nonion_size\x18\x04 \x01(\rH\x01\x88\x01\x01\x42\x0e\n\x0c_session_keyB\r\n\x0b_onion_size\"<\n\x13\x43reateonionResponse\x12\r\n\x05onion\x18\x01 \x01(\x0c\x12\x16\n\x0eshared_secrets\x18\x02 \x03(\x0c\"2\n\x0f\x43reateonionHops\x12\x0e\n\x06pubkey\x18\x01 \x01(\x0c\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"J\n\x13\x44\x65ldatastoreRequest\x12\x0b\n\x03key\x18\x03 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x42\r\n\x0b_generation\"\x85\x01\n\x14\x44\x65ldatastoreResponse\x12\x0b\n\x03key\x18\x05 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x06string\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\r\n\x0b_generationB\x06\n\x04_hexB\t\n\x07_string\"H\n\x18\x44\x65lexpiredinvoiceRequest\x12\x1a\n\rmaxexpirytime\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\x10\n\x0e_maxexpirytime\"\x1b\n\x19\x44\x65lexpiredinvoiceResponse\"\xb6\x01\n\x11\x44\x65linvoiceRequest\x12\r\n\x05label\x18\x01 \x01(\t\x12\x37\n\x06status\x18\x02 \x01(\x0e\x32\'.cln.DelinvoiceRequest.DelinvoiceStatus\x12\x15\n\x08\x64\x65sconly\x18\x03 \x01(\x08H\x00\x88\x01\x01\"5\n\x10\x44\x65linvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x12\n\n\x06UNPAID\x10\x02\x42\x0b\n\t_desconly\"\xc5\x03\n\x12\x44\x65linvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x06\x62olt11\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x03 \x01(\tH\x01\x88\x01\x01\x12%\n\x0b\x61mount_msat\x18\x04 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x06 \x01(\x0c\x12\x38\n\x06status\x18\x07 \x01(\x0e\x32(.cln.DelinvoiceResponse.DelinvoiceStatus\x12\x12\n\nexpires_at\x18\x08 \x01(\x04\x12\x1b\n\x0elocal_offer_id\x18\t \x01(\x0cH\x04\x88\x01\x01\x12\x1e\n\x11invreq_payer_note\x18\x0b \x01(\tH\x05\x88\x01\x01\"5\n\x10\x44\x65linvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x12\n\n\x06UNPAID\x10\x02\x42\t\n\x07_bolt11B\t\n\x07_bolt12B\x0e\n\x0c_amount_msatB\x0e\n\x0c_descriptionB\x11\n\x0f_local_offer_idB\x14\n\x12_invreq_payer_note\"\xb8\x02\n\x0eInvoiceRequest\x12%\n\x0b\x61mount_msat\x18\n \x01(\x0b\x32\x10.cln.AmountOrAny\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\r\n\x05label\x18\x03 \x01(\t\x12\x13\n\x06\x65xpiry\x18\x07 \x01(\x04H\x00\x88\x01\x01\x12\x11\n\tfallbacks\x18\x04 \x03(\t\x12\x15\n\x08preimage\x18\x05 \x01(\x0cH\x01\x88\x01\x01\x12\"\n\x15\x65xposeprivatechannels\x18\x08 \x01(\x08H\x02\x88\x01\x01\x12\x11\n\x04\x63ltv\x18\x06 \x01(\rH\x03\x88\x01\x01\x12\x19\n\x0c\x64\x65schashonly\x18\t \x01(\x08H\x04\x88\x01\x01\x42\t\n\x07_expiryB\x0b\n\t_preimageB\x18\n\x16_exposeprivatechannelsB\x07\n\x05_cltvB\x0f\n\r_deschashonly\"\xe7\x02\n\x0fInvoiceResponse\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x16\n\x0epayment_secret\x18\x03 \x01(\x0c\x12\x12\n\nexpires_at\x18\x04 \x01(\x04\x12\x1d\n\x10warning_capacity\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x1c\n\x0fwarning_offline\x18\x06 \x01(\tH\x01\x88\x01\x01\x12\x1d\n\x10warning_deadends\x18\x07 \x01(\tH\x02\x88\x01\x01\x12#\n\x16warning_private_unused\x18\x08 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0bwarning_mpp\x18\t \x01(\tH\x04\x88\x01\x01\x42\x13\n\x11_warning_capacityB\x12\n\x10_warning_offlineB\x13\n\x11_warning_deadendsB\x19\n\x17_warning_private_unusedB\x0e\n\x0c_warning_mpp\"#\n\x14ListdatastoreRequest\x12\x0b\n\x03key\x18\x02 \x03(\t\"G\n\x15ListdatastoreResponse\x12.\n\tdatastore\x18\x01 \x03(\x0b\x32\x1b.cln.ListdatastoreDatastore\"\x87\x01\n\x16ListdatastoreDatastore\x12\x0b\n\x03key\x18\x01 \x03(\t\x12\x17\n\ngeneration\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x03hex\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x13\n\x06string\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\r\n\x0b_generationB\x06\n\x04_hexB\t\n\x07_string\"\xa9\x01\n\x13ListinvoicesRequest\x12\x12\n\x05label\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tinvstring\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x19\n\x0cpayment_hash\x18\x03 \x01(\x0cH\x02\x88\x01\x01\x12\x15\n\x08offer_id\x18\x04 \x01(\tH\x03\x88\x01\x01\x42\x08\n\x06_labelB\x0c\n\n_invstringB\x0f\n\r_payment_hashB\x0b\n\t_offer_id\"C\n\x14ListinvoicesResponse\x12+\n\x08invoices\x18\x01 \x03(\x0b\x32\x19.cln.ListinvoicesInvoices\"\xa2\x05\n\x14ListinvoicesInvoices\x12\r\n\x05label\x18\x01 \x01(\t\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x44\n\x06status\x18\x04 \x01(\x0e\x32\x34.cln.ListinvoicesInvoices.ListinvoicesInvoicesStatus\x12\x12\n\nexpires_at\x18\x05 \x01(\x04\x12%\n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x08 \x01(\tH\x03\x88\x01\x01\x12\x1b\n\x0elocal_offer_id\x18\t \x01(\x0cH\x04\x88\x01\x01\x12\x1e\n\x11invreq_payer_note\x18\x0f \x01(\tH\x05\x88\x01\x01\x12\x16\n\tpay_index\x18\x0b \x01(\x04H\x06\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\x0c \x01(\x0b\x32\x0b.cln.AmountH\x07\x88\x01\x01\x12\x14\n\x07paid_at\x18\r \x01(\x04H\x08\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0e \x01(\x0cH\t\x88\x01\x01\"?\n\x1aListinvoicesInvoicesStatus\x12\n\n\x06UNPAID\x10\x00\x12\x08\n\x04PAID\x10\x01\x12\x0b\n\x07\x45XPIRED\x10\x02\x42\x0e\n\x0c_descriptionB\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\t\n\x07_bolt12B\x11\n\x0f_local_offer_idB\x14\n\x12_invreq_payer_noteB\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimage\"\x8a\x03\n\x10SendonionRequest\x12\r\n\x05onion\x18\x01 \x01(\x0c\x12*\n\tfirst_hop\x18\x02 \x01(\x0b\x32\x17.cln.SendonionFirst_hop\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x12\n\x05label\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x16\n\x0eshared_secrets\x18\x05 \x03(\x0c\x12\x13\n\x06partid\x18\x06 \x01(\rH\x01\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x02\x88\x01\x01\x12%\n\x0b\x61mount_msat\x18\x0c \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\t \x01(\x0cH\x04\x88\x01\x01\x12\x1a\n\rlocalinvreqid\x18\r \x01(\x0cH\x05\x88\x01\x01\x12\x14\n\x07groupid\x18\x0b \x01(\x04H\x06\x88\x01\x01\x42\x08\n\x06_labelB\t\n\x07_partidB\t\n\x07_bolt11B\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x10\n\x0e_localinvreqidB\n\n\x08_groupid\"\x8b\x04\n\x11SendonionResponse\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x36\n\x06status\x18\x03 \x01(\x0e\x32&.cln.SendonionResponse.SendonionStatus\x12%\n\x0b\x61mount_msat\x18\x04 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x05 \x01(\x0cH\x01\x88\x01\x01\x12\x12\n\ncreated_at\x18\x06 \x01(\x04\x12%\n\x10\x61mount_sent_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\t \x01(\tH\x03\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\n \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06partid\x18\r \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0b \x01(\x0cH\x06\x88\x01\x01\x12\x14\n\x07message\x18\x0c \x01(\tH\x07\x88\x01\x01\",\n\x0fSendonionStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x42\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x08\n\x06_labelB\t\n\x07_bolt11B\t\n\x07_bolt12B\t\n\x07_partidB\x13\n\x11_payment_preimageB\n\n\x08_message\"Q\n\x12SendonionFirst_hop\x12\n\n\x02id\x18\x01 \x01(\x0c\x12 \n\x0b\x61mount_msat\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\x12\r\n\x05\x64\x65lay\x18\x03 \x01(\r\"\xeb\x01\n\x13ListsendpaysRequest\x12\x13\n\x06\x62olt11\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cpayment_hash\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12@\n\x06status\x18\x03 \x01(\x0e\x32+.cln.ListsendpaysRequest.ListsendpaysStatusH\x02\x88\x01\x01\";\n\x12ListsendpaysStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x42\t\n\x07_bolt11B\x0f\n\r_payment_hashB\t\n\x07_status\"C\n\x14ListsendpaysResponse\x12+\n\x08payments\x18\x01 \x03(\x0b\x32\x19.cln.ListsendpaysPayments\"\xd4\x04\n\x14ListsendpaysPayments\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07groupid\x18\x02 \x01(\x04\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x44\n\x06status\x18\x04 \x01(\x0e\x32\x34.cln.ListsendpaysPayments.ListsendpaysPaymentsStatus\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x06 \x01(\x0cH\x01\x88\x01\x01\x12\x12\n\ncreated_at\x18\x07 \x01(\x04\x12%\n\x10\x61mount_sent_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\t \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\n \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x0e \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x0b \x01(\tH\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\x12\x17\n\nerroronion\x18\r \x01(\x0cH\x07\x88\x01\x01\"C\n\x1aListsendpaysPaymentsStatus\x12\x0b\n\x07PENDING\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x01\x12\x0c\n\x08\x43OMPLETE\x10\x02\x42\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x08\n\x06_labelB\t\n\x07_bolt11B\x0e\n\x0c_descriptionB\t\n\x07_bolt12B\x13\n\x11_payment_preimageB\r\n\x0b_erroronion\"\x19\n\x17ListtransactionsRequest\"S\n\x18ListtransactionsResponse\x12\x37\n\x0ctransactions\x18\x01 \x03(\x0b\x32!.cln.ListtransactionsTransactions\"\x9a\x02\n\x1cListtransactionsTransactions\x12\x0c\n\x04hash\x18\x01 \x01(\x0c\x12\r\n\x05rawtx\x18\x02 \x01(\x0c\x12\x13\n\x0b\x62lockheight\x18\x03 \x01(\r\x12\x0f\n\x07txindex\x18\x04 \x01(\r\x12\x14\n\x07\x63hannel\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x10\n\x08locktime\x18\x07 \x01(\r\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x37\n\x06inputs\x18\t \x03(\x0b\x32\'.cln.ListtransactionsTransactionsInputs\x12\x39\n\x07outputs\x18\n \x03(\x0b\x32(.cln.ListtransactionsTransactionsOutputsB\n\n\x08_channel\"\x84\x04\n\"ListtransactionsTransactionsInputs\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\r\n\x05index\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\x66\n\titem_type\x18\x04 \x01(\x0e\x32N.cln.ListtransactionsTransactionsInputs.ListtransactionsTransactionsInputsTypeH\x00\x88\x01\x01\x12\x14\n\x07\x63hannel\x18\x05 \x01(\tH\x01\x88\x01\x01\"\x96\x02\n&ListtransactionsTransactionsInputsType\x12\n\n\x06THEIRS\x10\x00\x12\x0b\n\x07\x44\x45POSIT\x10\x01\x12\x0c\n\x08WITHDRAW\x10\x02\x12\x13\n\x0f\x43HANNEL_FUNDING\x10\x03\x12\x18\n\x14\x43HANNEL_MUTUAL_CLOSE\x10\x04\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CLOSE\x10\x05\x12\x11\n\rCHANNEL_SWEEP\x10\x06\x12\x18\n\x14\x43HANNEL_HTLC_SUCCESS\x10\x07\x12\x18\n\x14\x43HANNEL_HTLC_TIMEOUT\x10\x08\x12\x13\n\x0f\x43HANNEL_PENALTY\x10\t\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CHEAT\x10\nB\x0c\n\n_item_typeB\n\n\x08_channel\"\xa0\x04\n#ListtransactionsTransactionsOutputs\x12\r\n\x05index\x18\x01 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12\x14\n\x0cscriptPubKey\x18\x03 \x01(\x0c\x12h\n\titem_type\x18\x04 \x01(\x0e\x32P.cln.ListtransactionsTransactionsOutputs.ListtransactionsTransactionsOutputsTypeH\x00\x88\x01\x01\x12\x14\n\x07\x63hannel\x18\x05 \x01(\tH\x01\x88\x01\x01\"\x97\x02\n\'ListtransactionsTransactionsOutputsType\x12\n\n\x06THEIRS\x10\x00\x12\x0b\n\x07\x44\x45POSIT\x10\x01\x12\x0c\n\x08WITHDRAW\x10\x02\x12\x13\n\x0f\x43HANNEL_FUNDING\x10\x03\x12\x18\n\x14\x43HANNEL_MUTUAL_CLOSE\x10\x04\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CLOSE\x10\x05\x12\x11\n\rCHANNEL_SWEEP\x10\x06\x12\x18\n\x14\x43HANNEL_HTLC_SUCCESS\x10\x07\x12\x18\n\x14\x43HANNEL_HTLC_TIMEOUT\x10\x08\x12\x13\n\x0f\x43HANNEL_PENALTY\x10\t\x12\x1c\n\x18\x43HANNEL_UNILATERAL_CHEAT\x10\nB\x0c\n\n_item_typeB\n\n\x08_channel\"\xda\x03\n\nPayRequest\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12%\n\x0b\x61mount_msat\x18\r \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x12\n\x05label\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\nriskfactor\x18\x08 \x01(\x01H\x02\x88\x01\x01\x12\x1a\n\rmaxfeepercent\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x16\n\tretry_for\x18\x05 \x01(\rH\x04\x88\x01\x01\x12\x15\n\x08maxdelay\x18\x06 \x01(\rH\x05\x88\x01\x01\x12#\n\texemptfee\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x06\x88\x01\x01\x12\x1a\n\rlocalinvreqid\x18\x0e \x01(\x0cH\x07\x88\x01\x01\x12\x0f\n\x07\x65xclude\x18\n \x03(\t\x12 \n\x06maxfee\x18\x0b \x01(\x0b\x32\x0b.cln.AmountH\x08\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x0c \x01(\tH\t\x88\x01\x01\x42\x0e\n\x0c_amount_msatB\x08\n\x06_labelB\r\n\x0b_riskfactorB\x10\n\x0e_maxfeepercentB\x0c\n\n_retry_forB\x0b\n\t_maxdelayB\x0c\n\n_exemptfeeB\x10\n\x0e_localinvreqidB\t\n\x07_maxfeeB\x0e\n\x0c_description\"\xfb\x02\n\x0bPayResponse\x12\x18\n\x10payment_preimage\x18\x01 \x01(\x0c\x12\x18\n\x0b\x64\x65stination\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x12\n\ncreated_at\x18\x04 \x01(\x01\x12\r\n\x05parts\x18\x05 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x10\x61mount_sent_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\'\n\x1awarning_partial_completion\x18\x08 \x01(\tH\x01\x88\x01\x01\x12*\n\x06status\x18\t \x01(\x0e\x32\x1a.cln.PayResponse.PayStatus\"2\n\tPayStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x12\x0b\n\x07PENDING\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x42\x0e\n\x0c_destinationB\x1d\n\x1b_warning_partial_completion\"*\n\x10ListnodesRequest\x12\x0f\n\x02id\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x05\n\x03_id\"7\n\x11ListnodesResponse\x12\"\n\x05nodes\x18\x01 \x03(\x0b\x32\x13.cln.ListnodesNodes\"\xe1\x01\n\x0eListnodesNodes\x12\x0e\n\x06nodeid\x18\x01 \x01(\x0c\x12\x1b\n\x0elast_timestamp\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x12\n\x05\x61lias\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x12\n\x05\x63olor\x18\x04 \x01(\x0cH\x02\x88\x01\x01\x12\x15\n\x08\x66\x65\x61tures\x18\x05 \x01(\x0cH\x03\x88\x01\x01\x12/\n\taddresses\x18\x06 \x03(\x0b\x32\x1c.cln.ListnodesNodesAddressesB\x11\n\x0f_last_timestampB\x08\n\x06_aliasB\x08\n\x06_colorB\x0b\n\t_features\"\xf7\x01\n\x17ListnodesNodesAddresses\x12K\n\titem_type\x18\x01 \x01(\x0e\x32\x38.cln.ListnodesNodesAddresses.ListnodesNodesAddressesType\x12\x0c\n\x04port\x18\x02 \x01(\r\x12\x14\n\x07\x61\x64\x64ress\x18\x03 \x01(\tH\x00\x88\x01\x01\"_\n\x1bListnodesNodesAddressesType\x12\x07\n\x03\x44NS\x10\x00\x12\x08\n\x04IPV4\x10\x01\x12\x08\n\x04IPV6\x10\x02\x12\t\n\x05TORV2\x10\x03\x12\t\n\x05TORV3\x10\x04\x12\r\n\tWEBSOCKET\x10\x05\x42\n\n\x08_address\"g\n\x15WaitanyinvoiceRequest\x12\x1a\n\rlastpay_index\x18\x01 \x01(\x04H\x00\x88\x01\x01\x12\x14\n\x07timeout\x18\x02 \x01(\x04H\x01\x88\x01\x01\x42\x10\n\x0e_lastpay_indexB\n\n\x08_timeout\"\x93\x04\n\x16WaitanyinvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12@\n\x06status\x18\x04 \x01(\x0e\x32\x30.cln.WaitanyinvoiceResponse.WaitanyinvoiceStatus\x12\x12\n\nexpires_at\x18\x05 \x01(\x04\x12%\n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x16\n\tpay_index\x18\t \x01(\x04H\x03\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\x14\n\x07paid_at\x18\x0b \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\"-\n\x14WaitanyinvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x42\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\t\n\x07_bolt12B\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimage\"#\n\x12WaitinvoiceRequest\x12\r\n\x05label\x18\x01 \x01(\t\"\x87\x04\n\x13WaitinvoiceResponse\x12\r\n\x05label\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12:\n\x06status\x18\x04 \x01(\x0e\x32*.cln.WaitinvoiceResponse.WaitinvoiceStatus\x12\x12\n\nexpires_at\x18\x05 \x01(\x04\x12%\n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x07 \x01(\tH\x01\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x16\n\tpay_index\x18\t \x01(\x04H\x03\x88\x01\x01\x12.\n\x14\x61mount_received_msat\x18\n \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\x14\n\x07paid_at\x18\x0b \x01(\x04H\x05\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\x0c \x01(\x0cH\x06\x88\x01\x01\"*\n\x11WaitinvoiceStatus\x12\x08\n\x04PAID\x10\x00\x12\x0b\n\x07\x45XPIRED\x10\x01\x42\x0e\n\x0c_amount_msatB\t\n\x07_bolt11B\t\n\x07_bolt12B\x0c\n\n_pay_indexB\x17\n\x15_amount_received_msatB\n\n\x08_paid_atB\x13\n\x11_payment_preimage\"\x8e\x01\n\x12WaitsendpayRequest\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x14\n\x07timeout\x18\x03 \x01(\rH\x00\x88\x01\x01\x12\x13\n\x06partid\x18\x02 \x01(\x04H\x01\x88\x01\x01\x12\x14\n\x07groupid\x18\x04 \x01(\x04H\x02\x88\x01\x01\x42\n\n\x08_timeoutB\t\n\x07_partidB\n\n\x08_groupid\"\xb2\x04\n\x13WaitsendpayResponse\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x07groupid\x18\x02 \x01(\x04H\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12:\n\x06status\x18\x04 \x01(\x0e\x32*.cln.WaitsendpayResponse.WaitsendpayStatus\x12%\n\x0b\x61mount_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x01\x88\x01\x01\x12\x18\n\x0b\x64\x65stination\x18\x06 \x01(\x0cH\x02\x88\x01\x01\x12\x12\n\ncreated_at\x18\x07 \x01(\x04\x12\x19\n\x0c\x63ompleted_at\x18\x0e \x01(\x01H\x03\x88\x01\x01\x12%\n\x10\x61mount_sent_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\t \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06partid\x18\n \x01(\x04H\x05\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x0b \x01(\tH\x06\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x0c \x01(\tH\x07\x88\x01\x01\x12\x1d\n\x10payment_preimage\x18\r \x01(\x0cH\x08\x88\x01\x01\"!\n\x11WaitsendpayStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x42\n\n\x08_groupidB\x0e\n\x0c_amount_msatB\x0e\n\x0c_destinationB\x0f\n\r_completed_atB\x08\n\x06_labelB\t\n\x07_partidB\t\n\x07_bolt11B\t\n\x07_bolt12B\x13\n\x11_payment_preimage\"\x9e\x01\n\x0eNewaddrRequest\x12@\n\x0b\x61\x64\x64resstype\x18\x01 \x01(\x0e\x32&.cln.NewaddrRequest.NewaddrAddresstypeH\x00\x88\x01\x01\":\n\x12NewaddrAddresstype\x12\n\n\x06\x42\x45\x43H32\x10\x00\x12\x0f\n\x0bP2SH_SEGWIT\x10\x01\x12\x07\n\x03\x41LL\x10\x02\x42\x0e\n\x0c_addresstype\"[\n\x0fNewaddrResponse\x12\x13\n\x06\x62\x65\x63h32\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bp2sh_segwit\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_bech32B\x0e\n\x0c_p2sh_segwit\"\xca\x01\n\x0fWithdrawRequest\x12\x13\n\x0b\x64\x65stination\x18\x01 \x01(\t\x12&\n\x07satoshi\x18\x02 \x01(\x0b\x32\x10.cln.AmountOrAllH\x00\x88\x01\x01\x12\"\n\x07\x66\x65\x65rate\x18\x05 \x01(\x0b\x32\x0c.cln.FeerateH\x01\x88\x01\x01\x12\x14\n\x07minconf\x18\x03 \x01(\rH\x02\x88\x01\x01\x12\x1c\n\x05utxos\x18\x04 \x03(\x0b\x32\r.cln.OutpointB\n\n\x08_satoshiB\n\n\x08_feerateB\n\n\x08_minconf\":\n\x10WithdrawResponse\x12\n\n\x02tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\x12\x0c\n\x04psbt\x18\x03 \x01(\t\"\x82\x03\n\x0eKeysendRequest\x12\x13\n\x0b\x64\x65stination\x18\x01 \x01(\x0c\x12 \n\x0b\x61mount_msat\x18\n \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\x05label\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x1a\n\rmaxfeepercent\x18\x04 \x01(\x01H\x01\x88\x01\x01\x12\x16\n\tretry_for\x18\x05 \x01(\rH\x02\x88\x01\x01\x12\x15\n\x08maxdelay\x18\x06 \x01(\rH\x03\x88\x01\x01\x12#\n\texemptfee\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12+\n\nroutehints\x18\x08 \x01(\x0b\x32\x12.cln.RoutehintListH\x05\x88\x01\x01\x12&\n\textratlvs\x18\t \x01(\x0b\x32\x0e.cln.TlvStreamH\x06\x88\x01\x01\x42\x08\n\x06_labelB\x10\n\x0e_maxfeepercentB\x0c\n\n_retry_forB\x0b\n\t_maxdelayB\x0c\n\n_exemptfeeB\r\n\x0b_routehintsB\x0c\n\n_extratlvs\"\xf2\x02\n\x0fKeysendResponse\x12\x18\n\x10payment_preimage\x18\x01 \x01(\x0c\x12\x18\n\x0b\x64\x65stination\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x12\n\ncreated_at\x18\x04 \x01(\x01\x12\r\n\x05parts\x18\x05 \x01(\r\x12 \n\x0b\x61mount_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x10\x61mount_sent_msat\x18\x07 \x01(\x0b\x32\x0b.cln.Amount\x12\'\n\x1awarning_partial_completion\x18\x08 \x01(\tH\x01\x88\x01\x01\x12\x32\n\x06status\x18\t \x01(\x0e\x32\".cln.KeysendResponse.KeysendStatus\"\x1d\n\rKeysendStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x42\x0e\n\x0c_destinationB\x1d\n\x1b_warning_partial_completion\"\xbc\x02\n\x0f\x46undpsbtRequest\x12!\n\x07satoshi\x18\x01 \x01(\x0b\x32\x10.cln.AmountOrAll\x12\x1d\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.Feerate\x12\x13\n\x0bstartweight\x18\x03 \x01(\r\x12\x14\n\x07minconf\x18\x04 \x01(\rH\x00\x88\x01\x01\x12\x14\n\x07reserve\x18\x05 \x01(\rH\x01\x88\x01\x01\x12\x15\n\x08locktime\x18\x06 \x01(\rH\x02\x88\x01\x01\x12\x1f\n\x12min_witness_weight\x18\x07 \x01(\rH\x03\x88\x01\x01\x12\x1d\n\x10\x65xcess_as_change\x18\x08 \x01(\x08H\x04\x88\x01\x01\x42\n\n\x08_minconfB\n\n\x08_reserveB\x0b\n\t_locktimeB\x15\n\x13_min_witness_weightB\x13\n\x11_excess_as_change\"\xd9\x01\n\x10\x46undpsbtResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x16\n\x0e\x66\x65\x65rate_per_kw\x18\x02 \x01(\r\x12\x1e\n\x16\x65stimated_final_weight\x18\x03 \x01(\r\x12 \n\x0b\x65xcess_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12\x1a\n\rchange_outnum\x18\x05 \x01(\rH\x00\x88\x01\x01\x12/\n\x0creservations\x18\x06 \x03(\x0b\x32\x19.cln.FundpsbtReservationsB\x10\n\x0e_change_outnum\"u\n\x14\x46undpsbtReservations\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0c\n\x04vout\x18\x02 \x01(\r\x12\x14\n\x0cwas_reserved\x18\x03 \x01(\x08\x12\x10\n\x08reserved\x18\x04 \x01(\x08\x12\x19\n\x11reserved_to_block\x18\x05 \x01(\r\"A\n\x0fSendpsbtRequest\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x14\n\x07reserve\x18\x02 \x01(\x08H\x00\x88\x01\x01\x42\n\n\x08_reserve\",\n\x10SendpsbtResponse\x12\n\n\x02tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\"1\n\x0fSignpsbtRequest\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x10\n\x08signonly\x18\x02 \x03(\r\"\'\n\x10SignpsbtResponse\x12\x13\n\x0bsigned_psbt\x18\x01 \x01(\t\"\xdb\x02\n\x0fUtxopsbtRequest\x12\x1c\n\x07satoshi\x18\x01 \x01(\x0b\x32\x0b.cln.Amount\x12\x1d\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.Feerate\x12\x13\n\x0bstartweight\x18\x03 \x01(\r\x12\x1c\n\x05utxos\x18\x04 \x03(\x0b\x32\r.cln.Outpoint\x12\x14\n\x07reserve\x18\x05 \x01(\rH\x00\x88\x01\x01\x12\x17\n\nreservedok\x18\x08 \x01(\x08H\x01\x88\x01\x01\x12\x15\n\x08locktime\x18\x06 \x01(\rH\x02\x88\x01\x01\x12\x1f\n\x12min_witness_weight\x18\x07 \x01(\rH\x03\x88\x01\x01\x12\x1d\n\x10\x65xcess_as_change\x18\t \x01(\x08H\x04\x88\x01\x01\x42\n\n\x08_reserveB\r\n\x0b_reservedokB\x0b\n\t_locktimeB\x15\n\x13_min_witness_weightB\x13\n\x11_excess_as_change\"\xd9\x01\n\x10UtxopsbtResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x16\n\x0e\x66\x65\x65rate_per_kw\x18\x02 \x01(\r\x12\x1e\n\x16\x65stimated_final_weight\x18\x03 \x01(\r\x12 \n\x0b\x65xcess_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12\x1a\n\rchange_outnum\x18\x05 \x01(\rH\x00\x88\x01\x01\x12/\n\x0creservations\x18\x06 \x03(\x0b\x32\x19.cln.UtxopsbtReservationsB\x10\n\x0e_change_outnum\"u\n\x14UtxopsbtReservations\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0c\n\x04vout\x18\x02 \x01(\r\x12\x14\n\x0cwas_reserved\x18\x03 \x01(\x08\x12\x10\n\x08reserved\x18\x04 \x01(\x08\x12\x19\n\x11reserved_to_block\x18\x05 \x01(\r\" \n\x10TxdiscardRequest\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\"6\n\x11TxdiscardResponse\x12\x13\n\x0bunsigned_tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\"\xa4\x01\n\x10TxprepareRequest\x12 \n\x07outputs\x18\x05 \x03(\x0b\x32\x0f.cln.OutputDesc\x12\"\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.FeerateH\x00\x88\x01\x01\x12\x14\n\x07minconf\x18\x03 \x01(\rH\x01\x88\x01\x01\x12\x1c\n\x05utxos\x18\x04 \x03(\x0b\x32\r.cln.OutpointB\n\n\x08_feerateB\n\n\x08_minconf\"D\n\x11TxprepareResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\x13\n\x0bunsigned_tx\x18\x02 \x01(\x0c\x12\x0c\n\x04txid\x18\x03 \x01(\x0c\"\x1d\n\rTxsendRequest\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\"8\n\x0eTxsendResponse\x12\x0c\n\x04psbt\x18\x01 \x01(\t\x12\n\n\x02tx\x18\x02 \x01(\x0c\x12\x0c\n\x04txid\x18\x03 \x01(\x0c\"=\n\x11\x44isconnectRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x12\n\x05\x66orce\x18\x02 \x01(\x08H\x00\x88\x01\x01\x42\x08\n\x06_force\"\x14\n\x12\x44isconnectResponse\"k\n\x0f\x46\x65\x65ratesRequest\x12\x31\n\x05style\x18\x01 \x01(\x0e\x32\".cln.FeeratesRequest.FeeratesStyle\"%\n\rFeeratesStyle\x12\t\n\x05PERKB\x10\x00\x12\t\n\x05PERKW\x10\x01\"\x9c\x02\n\x10\x46\x65\x65ratesResponse\x12%\n\x18warning_missing_feerates\x18\x01 \x01(\tH\x00\x88\x01\x01\x12&\n\x05perkb\x18\x02 \x01(\x0b\x32\x12.cln.FeeratesPerkbH\x01\x88\x01\x01\x12&\n\x05perkw\x18\x03 \x01(\x0b\x32\x12.cln.FeeratesPerkwH\x02\x88\x01\x01\x12\x46\n\x15onchain_fee_estimates\x18\x04 \x01(\x0b\x32\".cln.FeeratesOnchain_fee_estimatesH\x03\x88\x01\x01\x42\x1b\n\x19_warning_missing_feeratesB\x08\n\x06_perkbB\x08\n\x06_perkwB\x18\n\x16_onchain_fee_estimates\"\xc3\x02\n\rFeeratesPerkb\x12\x16\n\x0emin_acceptable\x18\x01 \x01(\r\x12\x16\n\x0emax_acceptable\x18\x02 \x01(\r\x12\x14\n\x07opening\x18\x03 \x01(\rH\x00\x88\x01\x01\x12\x19\n\x0cmutual_close\x18\x04 \x01(\rH\x01\x88\x01\x01\x12\x1d\n\x10unilateral_close\x18\x05 \x01(\rH\x02\x88\x01\x01\x12\x1a\n\rdelayed_to_us\x18\x06 \x01(\rH\x03\x88\x01\x01\x12\x1c\n\x0fhtlc_resolution\x18\x07 \x01(\rH\x04\x88\x01\x01\x12\x14\n\x07penalty\x18\x08 \x01(\rH\x05\x88\x01\x01\x42\n\n\x08_openingB\x0f\n\r_mutual_closeB\x13\n\x11_unilateral_closeB\x10\n\x0e_delayed_to_usB\x12\n\x10_htlc_resolutionB\n\n\x08_penalty\"\xc3\x02\n\rFeeratesPerkw\x12\x16\n\x0emin_acceptable\x18\x01 \x01(\r\x12\x16\n\x0emax_acceptable\x18\x02 \x01(\r\x12\x14\n\x07opening\x18\x03 \x01(\rH\x00\x88\x01\x01\x12\x19\n\x0cmutual_close\x18\x04 \x01(\rH\x01\x88\x01\x01\x12\x1d\n\x10unilateral_close\x18\x05 \x01(\rH\x02\x88\x01\x01\x12\x1a\n\rdelayed_to_us\x18\x06 \x01(\rH\x03\x88\x01\x01\x12\x1c\n\x0fhtlc_resolution\x18\x07 \x01(\rH\x04\x88\x01\x01\x12\x14\n\x07penalty\x18\x08 \x01(\rH\x05\x88\x01\x01\x42\n\n\x08_openingB\x0f\n\r_mutual_closeB\x13\n\x11_unilateral_closeB\x10\n\x0e_delayed_to_usB\x12\n\x10_htlc_resolutionB\n\n\x08_penalty\"\xc1\x01\n\x1d\x46\x65\x65ratesOnchain_fee_estimates\x12 \n\x18opening_channel_satoshis\x18\x01 \x01(\x04\x12\x1d\n\x15mutual_close_satoshis\x18\x02 \x01(\x04\x12!\n\x19unilateral_close_satoshis\x18\x03 \x01(\x04\x12\x1d\n\x15htlc_timeout_satoshis\x18\x04 \x01(\x04\x12\x1d\n\x15htlc_success_satoshis\x18\x05 \x01(\x04\"\xe5\x03\n\x12\x46undchannelRequest\x12\n\n\x02id\x18\t \x01(\x0c\x12 \n\x06\x61mount\x18\x01 \x01(\x0b\x32\x10.cln.AmountOrAll\x12\"\n\x07\x66\x65\x65rate\x18\x02 \x01(\x0b\x32\x0c.cln.FeerateH\x00\x88\x01\x01\x12\x15\n\x08\x61nnounce\x18\x03 \x01(\x08H\x01\x88\x01\x01\x12\x14\n\x07minconf\x18\n \x01(\rH\x02\x88\x01\x01\x12#\n\tpush_msat\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\x15\n\x08\x63lose_to\x18\x06 \x01(\tH\x04\x88\x01\x01\x12%\n\x0brequest_amt\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x05\x88\x01\x01\x12\x1a\n\rcompact_lease\x18\x08 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x05utxos\x18\x0b \x03(\x0b\x32\r.cln.Outpoint\x12\x15\n\x08mindepth\x18\x0c \x01(\rH\x07\x88\x01\x01\x12!\n\x07reserve\x18\r \x01(\x0b\x32\x0b.cln.AmountH\x08\x88\x01\x01\x42\n\n\x08_feerateB\x0b\n\t_announceB\n\n\x08_minconfB\x0c\n\n_push_msatB\x0b\n\t_close_toB\x0e\n\x0c_request_amtB\x10\n\x0e_compact_leaseB\x0b\n\t_mindepthB\n\n\x08_reserve\"\x9b\x01\n\x13\x46undchannelResponse\x12\n\n\x02tx\x18\x01 \x01(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\x12\x0e\n\x06outnum\x18\x03 \x01(\r\x12\x12\n\nchannel_id\x18\x04 \x01(\x0c\x12\x15\n\x08\x63lose_to\x18\x05 \x01(\x0cH\x00\x88\x01\x01\x12\x15\n\x08mindepth\x18\x06 \x01(\rH\x01\x88\x01\x01\x42\x0b\n\t_close_toB\x0b\n\t_mindepth\"\xec\x01\n\x0fGetrouteRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12 \n\x0b\x61mount_msat\x18\t \x01(\x0b\x32\x0b.cln.Amount\x12\x12\n\nriskfactor\x18\x03 \x01(\x04\x12\x11\n\x04\x63ltv\x18\x04 \x01(\x01H\x00\x88\x01\x01\x12\x13\n\x06\x66romid\x18\x05 \x01(\x0cH\x01\x88\x01\x01\x12\x18\n\x0b\x66uzzpercent\x18\x06 \x01(\rH\x02\x88\x01\x01\x12\x0f\n\x07\x65xclude\x18\x07 \x03(\t\x12\x14\n\x07maxhops\x18\x08 \x01(\rH\x03\x88\x01\x01\x42\x07\n\x05_cltvB\t\n\x07_fromidB\x0e\n\x0c_fuzzpercentB\n\n\x08_maxhops\"5\n\x10GetrouteResponse\x12!\n\x05route\x18\x01 \x03(\x0b\x32\x12.cln.GetrouteRoute\"\xe9\x01\n\rGetrouteRoute\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x0f\n\x07\x63hannel\x18\x02 \x01(\t\x12\x11\n\tdirection\x18\x03 \x01(\r\x12\x15\n\x08msatoshi\x18\x07 \x01(\x04H\x00\x88\x01\x01\x12 \n\x0b\x61mount_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12\r\n\x05\x64\x65lay\x18\x05 \x01(\r\x12\x34\n\x05style\x18\x06 \x01(\x0e\x32%.cln.GetrouteRoute.GetrouteRouteStyle\"\x1d\n\x12GetrouteRouteStyle\x12\x07\n\x03TLV\x10\x00\x42\x0b\n\t_msatoshi\"\x82\x02\n\x13ListforwardsRequest\x12@\n\x06status\x18\x01 \x01(\x0e\x32+.cln.ListforwardsRequest.ListforwardsStatusH\x00\x88\x01\x01\x12\x17\n\nin_channel\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bout_channel\x18\x03 \x01(\tH\x02\x88\x01\x01\"L\n\x12ListforwardsStatus\x12\x0b\n\x07OFFERED\x10\x00\x12\x0b\n\x07SETTLED\x10\x01\x12\x10\n\x0cLOCAL_FAILED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\x42\t\n\x07_statusB\r\n\x0b_in_channelB\x0e\n\x0c_out_channel\"C\n\x14ListforwardsResponse\x12+\n\x08\x66orwards\x18\x01 \x03(\x0b\x32\x19.cln.ListforwardsForwards\"\xde\x04\n\x14ListforwardsForwards\x12\x12\n\nin_channel\x18\x01 \x01(\t\x12\x17\n\nin_htlc_id\x18\n \x01(\x04H\x00\x88\x01\x01\x12\x1c\n\x07in_msat\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\x12\x44\n\x06status\x18\x03 \x01(\x0e\x32\x34.cln.ListforwardsForwards.ListforwardsForwardsStatus\x12\x15\n\rreceived_time\x18\x04 \x01(\x01\x12\x18\n\x0bout_channel\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bout_htlc_id\x18\x0b \x01(\x04H\x02\x88\x01\x01\x12G\n\x05style\x18\t \x01(\x0e\x32\x33.cln.ListforwardsForwards.ListforwardsForwardsStyleH\x03\x88\x01\x01\x12\"\n\x08\x66\x65\x65_msat\x18\x07 \x01(\x0b\x32\x0b.cln.AmountH\x04\x88\x01\x01\x12\"\n\x08out_msat\x18\x08 \x01(\x0b\x32\x0b.cln.AmountH\x05\x88\x01\x01\"T\n\x1aListforwardsForwardsStatus\x12\x0b\n\x07OFFERED\x10\x00\x12\x0b\n\x07SETTLED\x10\x01\x12\x10\n\x0cLOCAL_FAILED\x10\x02\x12\n\n\x06\x46\x41ILED\x10\x03\"0\n\x19ListforwardsForwardsStyle\x12\n\n\x06LEGACY\x10\x00\x12\x07\n\x03TLV\x10\x01\x42\r\n\x0b_in_htlc_idB\x0e\n\x0c_out_channelB\x0e\n\x0c_out_htlc_idB\x08\n\x06_styleB\x0b\n\t_fee_msatB\x0b\n\t_out_msat\"\xdb\x01\n\x0fListpaysRequest\x12\x13\n\x06\x62olt11\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cpayment_hash\x18\x02 \x01(\x0cH\x01\x88\x01\x01\x12\x38\n\x06status\x18\x03 \x01(\x0e\x32#.cln.ListpaysRequest.ListpaysStatusH\x02\x88\x01\x01\"7\n\x0eListpaysStatus\x12\x0b\n\x07PENDING\x10\x00\x12\x0c\n\x08\x43OMPLETE\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x42\t\n\x07_bolt11B\x0f\n\r_payment_hashB\t\n\x07_status\"3\n\x10ListpaysResponse\x12\x1f\n\x04pays\x18\x01 \x03(\x0b\x32\x11.cln.ListpaysPays\"\x87\x04\n\x0cListpaysPays\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x34\n\x06status\x18\x02 \x01(\x0e\x32$.cln.ListpaysPays.ListpaysPaysStatus\x12\x18\n\x0b\x64\x65stination\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x12\x12\n\ncreated_at\x18\x04 \x01(\x04\x12\x19\n\x0c\x63ompleted_at\x18\x0c \x01(\x04H\x01\x88\x01\x01\x12\x12\n\x05label\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x06\x62olt11\x18\x06 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x0b \x01(\tH\x04\x88\x01\x01\x12\x13\n\x06\x62olt12\x18\x07 \x01(\tH\x05\x88\x01\x01\x12\x15\n\x08preimage\x18\r \x01(\x0cH\x06\x88\x01\x01\x12\x1c\n\x0fnumber_of_parts\x18\x0e \x01(\x04H\x07\x88\x01\x01\x12\x17\n\nerroronion\x18\n \x01(\x0cH\x08\x88\x01\x01\";\n\x12ListpaysPaysStatus\x12\x0b\n\x07PENDING\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x01\x12\x0c\n\x08\x43OMPLETE\x10\x02\x42\x0e\n\x0c_destinationB\x0f\n\r_completed_atB\x08\n\x06_labelB\t\n\x07_bolt11B\x0e\n\x0c_descriptionB\t\n\x07_bolt12B\x0b\n\t_preimageB\x12\n\x10_number_of_partsB\r\n\x0b_erroronion\"Y\n\x0bPingRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x10\n\x03len\x18\x02 \x01(\rH\x00\x88\x01\x01\x12\x16\n\tpongbytes\x18\x03 \x01(\rH\x01\x88\x01\x01\x42\x06\n\x04_lenB\x0c\n\n_pongbytes\"\x1e\n\x0cPingResponse\x12\x0e\n\x06totlen\x18\x01 \x01(\r\"\xf8\x01\n\x11SetchannelRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12!\n\x07\x66\x65\x65\x62\x61se\x18\x02 \x01(\x0b\x32\x0b.cln.AmountH\x00\x88\x01\x01\x12\x13\n\x06\x66\x65\x65ppm\x18\x03 \x01(\rH\x01\x88\x01\x01\x12!\n\x07htlcmin\x18\x04 \x01(\x0b\x32\x0b.cln.AmountH\x02\x88\x01\x01\x12!\n\x07htlcmax\x18\x05 \x01(\x0b\x32\x0b.cln.AmountH\x03\x88\x01\x01\x12\x19\n\x0c\x65nforcedelay\x18\x06 \x01(\rH\x04\x88\x01\x01\x42\n\n\x08_feebaseB\t\n\x07_feeppmB\n\n\x08_htlcminB\n\n\x08_htlcmaxB\x0f\n\r_enforcedelay\"?\n\x12SetchannelResponse\x12)\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x17.cln.SetchannelChannels\"\x94\x03\n\x12SetchannelChannels\x12\x0f\n\x07peer_id\x18\x01 \x01(\x0c\x12\x12\n\nchannel_id\x18\x02 \x01(\x0c\x12\x1d\n\x10short_channel_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\"\n\rfee_base_msat\x18\x04 \x01(\x0b\x32\x0b.cln.Amount\x12#\n\x1b\x66\x65\x65_proportional_millionths\x18\x05 \x01(\r\x12*\n\x15minimum_htlc_out_msat\x18\x06 \x01(\x0b\x32\x0b.cln.Amount\x12$\n\x17warning_htlcmin_too_low\x18\x07 \x01(\tH\x01\x88\x01\x01\x12*\n\x15maximum_htlc_out_msat\x18\x08 \x01(\x0b\x32\x0b.cln.Amount\x12%\n\x18warning_htlcmax_too_high\x18\t \x01(\tH\x02\x88\x01\x01\x42\x13\n\x11_short_channel_idB\x1a\n\x18_warning_htlcmin_too_lowB\x1b\n\x19_warning_htlcmax_too_high\"%\n\x12SignmessageRequest\x12\x0f\n\x07message\x18\x01 \x01(\t\"F\n\x13SignmessageResponse\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\r\n\x05recid\x18\x02 \x01(\x0c\x12\r\n\x05zbase\x18\x03 \x01(\t\"\r\n\x0bStopRequest\"\x0e\n\x0cStopResponse2\xb1\x17\n\x04Node\x12\x36\n\x07Getinfo\x12\x13.cln.GetinfoRequest\x1a\x14.cln.GetinfoResponse\"\x00\x12<\n\tListPeers\x12\x15.cln.ListpeersRequest\x1a\x16.cln.ListpeersResponse\"\x00\x12<\n\tListFunds\x12\x15.cln.ListfundsRequest\x1a\x16.cln.ListfundsResponse\"\x00\x12\x36\n\x07SendPay\x12\x13.cln.SendpayRequest\x1a\x14.cln.SendpayResponse\"\x00\x12\x45\n\x0cListChannels\x12\x18.cln.ListchannelsRequest\x1a\x19.cln.ListchannelsResponse\"\x00\x12<\n\tAddGossip\x12\x15.cln.AddgossipRequest\x1a\x16.cln.AddgossipResponse\"\x00\x12Q\n\x10\x41utoCleanInvoice\x12\x1c.cln.AutocleaninvoiceRequest\x1a\x1d.cln.AutocleaninvoiceResponse\"\x00\x12\x45\n\x0c\x43heckMessage\x12\x18.cln.CheckmessageRequest\x1a\x19.cln.CheckmessageResponse\"\x00\x12\x30\n\x05\x43lose\x12\x11.cln.CloseRequest\x1a\x12.cln.CloseResponse\"\x00\x12:\n\x0b\x43onnectPeer\x12\x13.cln.ConnectRequest\x1a\x14.cln.ConnectResponse\"\x00\x12H\n\rCreateInvoice\x12\x19.cln.CreateinvoiceRequest\x1a\x1a.cln.CreateinvoiceResponse\"\x00\x12<\n\tDatastore\x12\x15.cln.DatastoreRequest\x1a\x16.cln.DatastoreResponse\"\x00\x12\x42\n\x0b\x43reateOnion\x12\x17.cln.CreateonionRequest\x1a\x18.cln.CreateonionResponse\"\x00\x12\x45\n\x0c\x44\x65lDatastore\x12\x18.cln.DeldatastoreRequest\x1a\x19.cln.DeldatastoreResponse\"\x00\x12T\n\x11\x44\x65lExpiredInvoice\x12\x1d.cln.DelexpiredinvoiceRequest\x1a\x1e.cln.DelexpiredinvoiceResponse\"\x00\x12?\n\nDelInvoice\x12\x16.cln.DelinvoiceRequest\x1a\x17.cln.DelinvoiceResponse\"\x00\x12\x36\n\x07Invoice\x12\x13.cln.InvoiceRequest\x1a\x14.cln.InvoiceResponse\"\x00\x12H\n\rListDatastore\x12\x19.cln.ListdatastoreRequest\x1a\x1a.cln.ListdatastoreResponse\"\x00\x12\x45\n\x0cListInvoices\x12\x18.cln.ListinvoicesRequest\x1a\x19.cln.ListinvoicesResponse\"\x00\x12<\n\tSendOnion\x12\x15.cln.SendonionRequest\x1a\x16.cln.SendonionResponse\"\x00\x12\x45\n\x0cListSendPays\x12\x18.cln.ListsendpaysRequest\x1a\x19.cln.ListsendpaysResponse\"\x00\x12Q\n\x10ListTransactions\x12\x1c.cln.ListtransactionsRequest\x1a\x1d.cln.ListtransactionsResponse\"\x00\x12*\n\x03Pay\x12\x0f.cln.PayRequest\x1a\x10.cln.PayResponse\"\x00\x12<\n\tListNodes\x12\x15.cln.ListnodesRequest\x1a\x16.cln.ListnodesResponse\"\x00\x12K\n\x0eWaitAnyInvoice\x12\x1a.cln.WaitanyinvoiceRequest\x1a\x1b.cln.WaitanyinvoiceResponse\"\x00\x12\x42\n\x0bWaitInvoice\x12\x17.cln.WaitinvoiceRequest\x1a\x18.cln.WaitinvoiceResponse\"\x00\x12\x42\n\x0bWaitSendPay\x12\x17.cln.WaitsendpayRequest\x1a\x18.cln.WaitsendpayResponse\"\x00\x12\x36\n\x07NewAddr\x12\x13.cln.NewaddrRequest\x1a\x14.cln.NewaddrResponse\"\x00\x12\x39\n\x08Withdraw\x12\x14.cln.WithdrawRequest\x1a\x15.cln.WithdrawResponse\"\x00\x12\x36\n\x07KeySend\x12\x13.cln.KeysendRequest\x1a\x14.cln.KeysendResponse\"\x00\x12\x39\n\x08\x46undPsbt\x12\x14.cln.FundpsbtRequest\x1a\x15.cln.FundpsbtResponse\"\x00\x12\x39\n\x08SendPsbt\x12\x14.cln.SendpsbtRequest\x1a\x15.cln.SendpsbtResponse\"\x00\x12\x39\n\x08SignPsbt\x12\x14.cln.SignpsbtRequest\x1a\x15.cln.SignpsbtResponse\"\x00\x12\x39\n\x08UtxoPsbt\x12\x14.cln.UtxopsbtRequest\x1a\x15.cln.UtxopsbtResponse\"\x00\x12<\n\tTxDiscard\x12\x15.cln.TxdiscardRequest\x1a\x16.cln.TxdiscardResponse\"\x00\x12<\n\tTxPrepare\x12\x15.cln.TxprepareRequest\x1a\x16.cln.TxprepareResponse\"\x00\x12\x33\n\x06TxSend\x12\x12.cln.TxsendRequest\x1a\x13.cln.TxsendResponse\"\x00\x12?\n\nDisconnect\x12\x16.cln.DisconnectRequest\x1a\x17.cln.DisconnectResponse\"\x00\x12\x39\n\x08\x46\x65\x65rates\x12\x14.cln.FeeratesRequest\x1a\x15.cln.FeeratesResponse\"\x00\x12\x42\n\x0b\x46undChannel\x12\x17.cln.FundchannelRequest\x1a\x18.cln.FundchannelResponse\"\x00\x12\x39\n\x08GetRoute\x12\x14.cln.GetrouteRequest\x1a\x15.cln.GetrouteResponse\"\x00\x12\x45\n\x0cListForwards\x12\x18.cln.ListforwardsRequest\x1a\x19.cln.ListforwardsResponse\"\x00\x12\x39\n\x08ListPays\x12\x14.cln.ListpaysRequest\x1a\x15.cln.ListpaysResponse\"\x00\x12-\n\x04Ping\x12\x10.cln.PingRequest\x1a\x11.cln.PingResponse\"\x00\x12?\n\nSetChannel\x12\x16.cln.SetchannelRequest\x1a\x17.cln.SetchannelResponse\"\x00\x12\x42\n\x0bSignMessage\x12\x17.cln.SignmessageRequest\x1a\x18.cln.SignmessageResponse\"\x00\x12-\n\x04Stop\x12\x10.cln.StopRequest\x1a\x11.cln.StopResponse\"\x00\x62\x06proto3') @@ -105,7 +105,6 @@ _WITHDRAWRESPONSE = DESCRIPTOR.message_types_by_name['WithdrawResponse'] _KEYSENDREQUEST = DESCRIPTOR.message_types_by_name['KeysendRequest'] _KEYSENDRESPONSE = DESCRIPTOR.message_types_by_name['KeysendResponse'] -_KEYSENDEXTRATLVS = DESCRIPTOR.message_types_by_name['KeysendExtratlvs'] _FUNDPSBTREQUEST = DESCRIPTOR.message_types_by_name['FundpsbtRequest'] _FUNDPSBTRESPONSE = DESCRIPTOR.message_types_by_name['FundpsbtResponse'] _FUNDPSBTRESERVATIONS = DESCRIPTOR.message_types_by_name['FundpsbtReservations'] @@ -785,13 +784,6 @@ }) _sym_db.RegisterMessage(KeysendResponse) -KeysendExtratlvs = _reflection.GeneratedProtocolMessageType('KeysendExtratlvs', (_message.Message,), { - 'DESCRIPTOR' : _KEYSENDEXTRATLVS, - '__module__' : 'node_pb2' - # @@protoc_insertion_point(class_scope:cln.KeysendExtratlvs) - }) -_sym_db.RegisterMessage(KeysendExtratlvs) - FundpsbtRequest = _reflection.GeneratedProtocolMessageType('FundpsbtRequest', (_message.Message,), { 'DESCRIPTOR' : _FUNDPSBTREQUEST, '__module__' : 'node_pb2' @@ -1100,331 +1092,329 @@ _GETINFOREQUEST._serialized_start=37 _GETINFOREQUEST._serialized_end=53 _GETINFORESPONSE._serialized_start=56 - _GETINFORESPONSE._serialized_end=614 - _GETINFOOUR_FEATURES._serialized_start=616 - _GETINFOOUR_FEATURES._serialized_end=699 - _GETINFOADDRESS._serialized_start=702 - _GETINFOADDRESS._serialized_end=913 - _GETINFOADDRESS_GETINFOADDRESSTYPE._serialized_start=815 - _GETINFOADDRESS_GETINFOADDRESSTYPE._serialized_end=901 - _GETINFOBINDING._serialized_start=916 - _GETINFOBINDING._serialized_end=1167 - _GETINFOBINDING_GETINFOBINDINGTYPE._serialized_start=1055 - _GETINFOBINDING_GETINFOBINDINGTYPE._serialized_end=1135 - _LISTPEERSREQUEST._serialized_start=1169 - _LISTPEERSREQUEST._serialized_end=1241 - _LISTPEERSRESPONSE._serialized_start=1243 - _LISTPEERSRESPONSE._serialized_end=1298 - _LISTPEERSPEERS._serialized_start=1301 - _LISTPEERSPEERS._serialized_end=1527 - _LISTPEERSPEERSLOG._serialized_start=1530 - _LISTPEERSPEERSLOG._serialized_end=1911 - _LISTPEERSPEERSLOG_LISTPEERSPEERSLOGTYPE._serialized_start=1741 - _LISTPEERSPEERSLOG_LISTPEERSPEERSLOGTYPE._serialized_end=1846 - _LISTPEERSPEERSCHANNELS._serialized_start=1914 - _LISTPEERSPEERSCHANNELS._serialized_end=4740 - _LISTPEERSPEERSCHANNELS_LISTPEERSPEERSCHANNELSSTATE._serialized_start=3644 - _LISTPEERSPEERSCHANNELS_LISTPEERSPEERSCHANNELSSTATE._serialized_end=3933 - _LISTPEERSPEERSCHANNELSFEERATE._serialized_start=4742 - _LISTPEERSPEERSCHANNELSFEERATE._serialized_end=4803 - _LISTPEERSPEERSCHANNELSINFLIGHT._serialized_start=4806 - _LISTPEERSPEERSCHANNELSINFLIGHT._serialized_end=5003 - _LISTPEERSPEERSCHANNELSFUNDING._serialized_start=5006 - _LISTPEERSPEERSCHANNELSFUNDING._serialized_end=5397 - _LISTPEERSPEERSCHANNELSALIAS._serialized_start=5399 - _LISTPEERSPEERSCHANNELSALIAS._serialized_end=5490 - _LISTPEERSPEERSCHANNELSHTLCS._serialized_start=5493 - _LISTPEERSPEERSCHANNELSHTLCS._serialized_end=5831 - _LISTPEERSPEERSCHANNELSHTLCS_LISTPEERSPEERSCHANNELSHTLCSDIRECTION._serialized_start=5747 - _LISTPEERSPEERSCHANNELSHTLCS_LISTPEERSPEERSCHANNELSHTLCSDIRECTION._serialized_end=5802 - _LISTFUNDSREQUEST._serialized_start=5833 - _LISTFUNDSREQUEST._serialized_end=5881 - _LISTFUNDSRESPONSE._serialized_start=5883 - _LISTFUNDSRESPONSE._serialized_end=5984 - _LISTFUNDSOUTPUTS._serialized_start=5987 - _LISTFUNDSOUTPUTS._serialized_end=6360 - _LISTFUNDSOUTPUTS_LISTFUNDSOUTPUTSSTATUS._serialized_start=6248 - _LISTFUNDSOUTPUTS_LISTFUNDSOUTPUTSSTATUS._serialized_end=6315 - _LISTFUNDSCHANNELS._serialized_start=6363 - _LISTFUNDSCHANNELS._serialized_end=6622 - _SENDPAYREQUEST._serialized_start=6625 - _SENDPAYREQUEST._serialized_end=6972 - _SENDPAYRESPONSE._serialized_start=6975 - _SENDPAYRESPONSE._serialized_end=7568 - _SENDPAYRESPONSE_SENDPAYSTATUS._serialized_start=7389 - _SENDPAYRESPONSE_SENDPAYSTATUS._serialized_end=7431 - _SENDPAYROUTE._serialized_start=7570 - _SENDPAYROUTE._serialized_end=7662 - _LISTCHANNELSREQUEST._serialized_start=7665 - _LISTCHANNELSREQUEST._serialized_end=7812 - _LISTCHANNELSRESPONSE._serialized_start=7814 - _LISTCHANNELSRESPONSE._serialized_end=7881 - _LISTCHANNELSCHANNELS._serialized_start=7884 - _LISTCHANNELSCHANNELS._serialized_end=8300 - _ADDGOSSIPREQUEST._serialized_start=8302 - _ADDGOSSIPREQUEST._serialized_end=8337 - _ADDGOSSIPRESPONSE._serialized_start=8339 - _ADDGOSSIPRESPONSE._serialized_end=8358 - _AUTOCLEANINVOICEREQUEST._serialized_start=8360 - _AUTOCLEANINVOICEREQUEST._serialized_end=8471 - _AUTOCLEANINVOICERESPONSE._serialized_start=8474 - _AUTOCLEANINVOICERESPONSE._serialized_end=8603 - _CHECKMESSAGEREQUEST._serialized_start=8605 - _CHECKMESSAGEREQUEST._serialized_end=8690 - _CHECKMESSAGERESPONSE._serialized_start=8692 - _CHECKMESSAGERESPONSE._serialized_end=8748 - _CLOSEREQUEST._serialized_start=8751 - _CLOSEREQUEST._serialized_end=9082 - _CLOSERESPONSE._serialized_start=9085 - _CLOSERESPONSE._serialized_end=9256 - _CLOSERESPONSE_CLOSETYPE._serialized_start=9187 - _CLOSERESPONSE_CLOSETYPE._serialized_end=9240 - _CONNECTREQUEST._serialized_start=9258 - _CONNECTREQUEST._serialized_end=9342 - _CONNECTRESPONSE._serialized_start=9345 - _CONNECTRESPONSE._serialized_end=9487 - _CONNECTRESPONSE_CONNECTDIRECTION._serialized_start=9452 - _CONNECTRESPONSE_CONNECTDIRECTION._serialized_end=9487 - _CONNECTADDRESS._serialized_start=9490 - _CONNECTADDRESS._serialized_end=9741 - _CONNECTADDRESS_CONNECTADDRESSTYPE._serialized_start=9629 - _CONNECTADDRESS_CONNECTADDRESSTYPE._serialized_end=9709 - _CREATEINVOICEREQUEST._serialized_start=9743 - _CREATEINVOICEREQUEST._serialized_end=9817 - _CREATEINVOICERESPONSE._serialized_start=9820 - _CREATEINVOICERESPONSE._serialized_end=10447 - _CREATEINVOICERESPONSE_CREATEINVOICESTATUS._serialized_start=10247 - _CREATEINVOICERESPONSE_CREATEINVOICESTATUS._serialized_end=10303 - _DATASTOREREQUEST._serialized_start=10450 - _DATASTOREREQUEST._serialized_end=10758 - _DATASTOREREQUEST_DATASTOREMODE._serialized_start=10603 - _DATASTOREREQUEST_DATASTOREMODE._serialized_end=10715 - _DATASTORERESPONSE._serialized_start=10761 - _DATASTORERESPONSE._serialized_end=10891 - _CREATEONIONREQUEST._serialized_start=10894 - _CREATEONIONREQUEST._serialized_end=11051 - _CREATEONIONRESPONSE._serialized_start=11053 - _CREATEONIONRESPONSE._serialized_end=11113 - _CREATEONIONHOPS._serialized_start=11115 - _CREATEONIONHOPS._serialized_end=11165 - _DELDATASTOREREQUEST._serialized_start=11167 - _DELDATASTOREREQUEST._serialized_end=11241 - _DELDATASTORERESPONSE._serialized_start=11244 - _DELDATASTORERESPONSE._serialized_end=11377 - _DELEXPIREDINVOICEREQUEST._serialized_start=11379 - _DELEXPIREDINVOICEREQUEST._serialized_end=11451 - _DELEXPIREDINVOICERESPONSE._serialized_start=11453 - _DELEXPIREDINVOICERESPONSE._serialized_end=11480 - _DELINVOICEREQUEST._serialized_start=11483 - _DELINVOICEREQUEST._serialized_end=11665 - _DELINVOICEREQUEST_DELINVOICESTATUS._serialized_start=11599 - _DELINVOICEREQUEST_DELINVOICESTATUS._serialized_end=11652 - _DELINVOICERESPONSE._serialized_start=11668 - _DELINVOICERESPONSE._serialized_end=12107 - _DELINVOICERESPONSE_DELINVOICESTATUS._serialized_start=11599 - _DELINVOICERESPONSE_DELINVOICESTATUS._serialized_end=11652 - _INVOICEREQUEST._serialized_start=12110 - _INVOICEREQUEST._serialized_end=12422 - _INVOICERESPONSE._serialized_start=12425 - _INVOICERESPONSE._serialized_end=12784 - _LISTDATASTOREREQUEST._serialized_start=12786 - _LISTDATASTOREREQUEST._serialized_end=12821 - _LISTDATASTORERESPONSE._serialized_start=12823 - _LISTDATASTORERESPONSE._serialized_end=12894 - _LISTDATASTOREDATASTORE._serialized_start=12897 - _LISTDATASTOREDATASTORE._serialized_end=13032 - _LISTINVOICESREQUEST._serialized_start=13035 - _LISTINVOICESREQUEST._serialized_end=13204 - _LISTINVOICESRESPONSE._serialized_start=13206 - _LISTINVOICESRESPONSE._serialized_end=13273 - _LISTINVOICESINVOICES._serialized_start=13276 - _LISTINVOICESINVOICES._serialized_end=13936 - _LISTINVOICESINVOICES_LISTINVOICESINVOICESSTATUS._serialized_start=13713 - _LISTINVOICESINVOICES_LISTINVOICESINVOICESSTATUS._serialized_end=13776 - _SENDONIONREQUEST._serialized_start=13939 - _SENDONIONREQUEST._serialized_end=14287 - _SENDONIONRESPONSE._serialized_start=14290 - _SENDONIONRESPONSE._serialized_end=14813 - _SENDONIONRESPONSE_SENDONIONSTATUS._serialized_start=14661 - _SENDONIONRESPONSE_SENDONIONSTATUS._serialized_end=14705 - _SENDONIONFIRST_HOP._serialized_start=14815 - _SENDONIONFIRST_HOP._serialized_end=14896 - _LISTSENDPAYSREQUEST._serialized_start=14899 - _LISTSENDPAYSREQUEST._serialized_end=15134 - _LISTSENDPAYSREQUEST_LISTSENDPAYSSTATUS._serialized_start=15036 - _LISTSENDPAYSREQUEST_LISTSENDPAYSSTATUS._serialized_end=15095 - _LISTSENDPAYSRESPONSE._serialized_start=15136 - _LISTSENDPAYSRESPONSE._serialized_end=15203 - _LISTSENDPAYSPAYMENTS._serialized_start=15206 - _LISTSENDPAYSPAYMENTS._serialized_end=15802 - _LISTSENDPAYSPAYMENTS_LISTSENDPAYSPAYMENTSSTATUS._serialized_start=15619 - _LISTSENDPAYSPAYMENTS_LISTSENDPAYSPAYMENTSSTATUS._serialized_end=15686 - _LISTTRANSACTIONSREQUEST._serialized_start=15804 - _LISTTRANSACTIONSREQUEST._serialized_end=15829 - _LISTTRANSACTIONSRESPONSE._serialized_start=15831 - _LISTTRANSACTIONSRESPONSE._serialized_end=15914 - _LISTTRANSACTIONSTRANSACTIONS._serialized_start=15917 - _LISTTRANSACTIONSTRANSACTIONS._serialized_end=16199 - _LISTTRANSACTIONSTRANSACTIONSINPUTS._serialized_start=16202 - _LISTTRANSACTIONSTRANSACTIONSINPUTS._serialized_end=16718 - _LISTTRANSACTIONSTRANSACTIONSINPUTS_LISTTRANSACTIONSTRANSACTIONSINPUTSTYPE._serialized_start=16414 - _LISTTRANSACTIONSTRANSACTIONSINPUTS_LISTTRANSACTIONSTRANSACTIONSINPUTSTYPE._serialized_end=16692 - _LISTTRANSACTIONSTRANSACTIONSOUTPUTS._serialized_start=16721 - _LISTTRANSACTIONSTRANSACTIONSOUTPUTS._serialized_end=17265 - _LISTTRANSACTIONSTRANSACTIONSOUTPUTS_LISTTRANSACTIONSTRANSACTIONSOUTPUTSTYPE._serialized_start=16960 - _LISTTRANSACTIONSTRANSACTIONSOUTPUTS_LISTTRANSACTIONSTRANSACTIONSOUTPUTSTYPE._serialized_end=17239 - _PAYREQUEST._serialized_start=17268 - _PAYREQUEST._serialized_end=17740 - _PAYRESPONSE._serialized_start=17743 - _PAYRESPONSE._serialized_end=18122 - _PAYRESPONSE_PAYSTATUS._serialized_start=18025 - _PAYRESPONSE_PAYSTATUS._serialized_end=18075 - _LISTNODESREQUEST._serialized_start=18124 - _LISTNODESREQUEST._serialized_end=18166 - _LISTNODESRESPONSE._serialized_start=18168 - _LISTNODESRESPONSE._serialized_end=18223 - _LISTNODESNODES._serialized_start=18226 - _LISTNODESNODES._serialized_end=18451 - _LISTNODESNODESADDRESSES._serialized_start=18454 - _LISTNODESNODESADDRESSES._serialized_end=18701 - _LISTNODESNODESADDRESSES_LISTNODESNODESADDRESSESTYPE._serialized_start=18594 - _LISTNODESNODESADDRESSES_LISTNODESNODESADDRESSESTYPE._serialized_end=18689 - _WAITANYINVOICEREQUEST._serialized_start=18703 - _WAITANYINVOICEREQUEST._serialized_end=18806 - _WAITANYINVOICERESPONSE._serialized_start=18809 - _WAITANYINVOICERESPONSE._serialized_end=19340 - _WAITANYINVOICERESPONSE_WAITANYINVOICESTATUS._serialized_start=19185 - _WAITANYINVOICERESPONSE_WAITANYINVOICESTATUS._serialized_end=19230 - _WAITINVOICEREQUEST._serialized_start=19342 - _WAITINVOICEREQUEST._serialized_end=19377 - _WAITINVOICERESPONSE._serialized_start=19380 - _WAITINVOICERESPONSE._serialized_end=19899 - _WAITINVOICERESPONSE_WAITINVOICESTATUS._serialized_start=19747 - _WAITINVOICERESPONSE_WAITINVOICESTATUS._serialized_end=19789 - _WAITSENDPAYREQUEST._serialized_start=19902 - _WAITSENDPAYREQUEST._serialized_end=20044 - _WAITSENDPAYRESPONSE._serialized_start=20047 - _WAITSENDPAYRESPONSE._serialized_end=20609 - _WAITSENDPAYRESPONSE_WAITSENDPAYSTATUS._serialized_start=20451 - _WAITSENDPAYRESPONSE_WAITSENDPAYSTATUS._serialized_end=20484 - _NEWADDRREQUEST._serialized_start=20612 - _NEWADDRREQUEST._serialized_end=20770 - _NEWADDRREQUEST_NEWADDRADDRESSTYPE._serialized_start=20696 - _NEWADDRREQUEST_NEWADDRADDRESSTYPE._serialized_end=20754 - _NEWADDRRESPONSE._serialized_start=20772 - _NEWADDRRESPONSE._serialized_end=20863 - _WITHDRAWREQUEST._serialized_start=20866 - _WITHDRAWREQUEST._serialized_end=21068 - _WITHDRAWRESPONSE._serialized_start=21070 - _WITHDRAWRESPONSE._serialized_end=21128 - _KEYSENDREQUEST._serialized_start=21131 - _KEYSENDREQUEST._serialized_end=21463 - _KEYSENDRESPONSE._serialized_start=21466 - _KEYSENDRESPONSE._serialized_end=21836 - _KEYSENDRESPONSE_KEYSENDSTATUS._serialized_start=21760 - _KEYSENDRESPONSE_KEYSENDSTATUS._serialized_end=21789 - _KEYSENDEXTRATLVS._serialized_start=21838 - _KEYSENDEXTRATLVS._serialized_end=21856 - _FUNDPSBTREQUEST._serialized_start=21859 - _FUNDPSBTREQUEST._serialized_end=22175 - _FUNDPSBTRESPONSE._serialized_start=22178 - _FUNDPSBTRESPONSE._serialized_end=22395 - _FUNDPSBTRESERVATIONS._serialized_start=22397 - _FUNDPSBTRESERVATIONS._serialized_end=22514 - _SENDPSBTREQUEST._serialized_start=22516 - _SENDPSBTREQUEST._serialized_end=22581 - _SENDPSBTRESPONSE._serialized_start=22583 - _SENDPSBTRESPONSE._serialized_end=22627 - _SIGNPSBTREQUEST._serialized_start=22629 - _SIGNPSBTREQUEST._serialized_end=22678 - _SIGNPSBTRESPONSE._serialized_start=22680 - _SIGNPSBTRESPONSE._serialized_end=22719 - _UTXOPSBTREQUEST._serialized_start=22722 - _UTXOPSBTREQUEST._serialized_end=23069 - _UTXOPSBTRESPONSE._serialized_start=23072 - _UTXOPSBTRESPONSE._serialized_end=23289 - _UTXOPSBTRESERVATIONS._serialized_start=23291 - _UTXOPSBTRESERVATIONS._serialized_end=23408 - _TXDISCARDREQUEST._serialized_start=23410 - _TXDISCARDREQUEST._serialized_end=23442 - _TXDISCARDRESPONSE._serialized_start=23444 - _TXDISCARDRESPONSE._serialized_end=23498 - _TXPREPAREREQUEST._serialized_start=23501 - _TXPREPAREREQUEST._serialized_end=23665 - _TXPREPARERESPONSE._serialized_start=23667 - _TXPREPARERESPONSE._serialized_end=23735 - _TXSENDREQUEST._serialized_start=23737 - _TXSENDREQUEST._serialized_end=23766 - _TXSENDRESPONSE._serialized_start=23768 - _TXSENDRESPONSE._serialized_end=23824 - _DISCONNECTREQUEST._serialized_start=23826 - _DISCONNECTREQUEST._serialized_end=23887 - _DISCONNECTRESPONSE._serialized_start=23889 - _DISCONNECTRESPONSE._serialized_end=23909 - _FEERATESREQUEST._serialized_start=23911 - _FEERATESREQUEST._serialized_end=24018 - _FEERATESREQUEST_FEERATESSTYLE._serialized_start=23981 - _FEERATESREQUEST_FEERATESSTYLE._serialized_end=24018 - _FEERATESRESPONSE._serialized_start=24020 - _FEERATESRESPONSE._serialized_end=24106 - _FEERATESPERKB._serialized_start=24109 - _FEERATESPERKB._serialized_end=24432 - _FEERATESPERKW._serialized_start=24435 - _FEERATESPERKW._serialized_end=24758 - _FEERATESONCHAIN_FEE_ESTIMATES._serialized_start=24761 - _FEERATESONCHAIN_FEE_ESTIMATES._serialized_end=24954 - _FUNDCHANNELREQUEST._serialized_start=24957 - _FUNDCHANNELREQUEST._serialized_end=25442 - _FUNDCHANNELRESPONSE._serialized_start=25445 - _FUNDCHANNELRESPONSE._serialized_end=25600 - _GETROUTEREQUEST._serialized_start=25603 - _GETROUTEREQUEST._serialized_end=25839 - _GETROUTERESPONSE._serialized_start=25841 - _GETROUTERESPONSE._serialized_end=25894 - _GETROUTEROUTE._serialized_start=25897 - _GETROUTEROUTE._serialized_end=26130 - _GETROUTEROUTE_GETROUTEROUTESTYLE._serialized_start=26088 - _GETROUTEROUTE_GETROUTEROUTESTYLE._serialized_end=26117 - _LISTFORWARDSREQUEST._serialized_start=26133 - _LISTFORWARDSREQUEST._serialized_end=26391 - _LISTFORWARDSREQUEST_LISTFORWARDSSTATUS._serialized_start=26273 - _LISTFORWARDSREQUEST_LISTFORWARDSSTATUS._serialized_end=26349 - _LISTFORWARDSRESPONSE._serialized_start=26393 - _LISTFORWARDSRESPONSE._serialized_end=26460 - _LISTFORWARDSFORWARDS._serialized_start=26463 - _LISTFORWARDSFORWARDS._serialized_end=27069 - _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTATUS._serialized_start=26852 - _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTATUS._serialized_end=26936 - _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTYLE._serialized_start=26938 - _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTYLE._serialized_end=26986 - _LISTPAYSREQUEST._serialized_start=27072 - _LISTPAYSREQUEST._serialized_end=27291 - _LISTPAYSREQUEST_LISTPAYSSTATUS._serialized_start=27197 - _LISTPAYSREQUEST_LISTPAYSSTATUS._serialized_end=27252 - _LISTPAYSRESPONSE._serialized_start=27293 - _LISTPAYSRESPONSE._serialized_end=27344 - _LISTPAYSPAYS._serialized_start=27347 - _LISTPAYSPAYS._serialized_end=27866 - _LISTPAYSPAYS_LISTPAYSPAYSSTATUS._serialized_start=27678 - _LISTPAYSPAYS_LISTPAYSPAYSSTATUS._serialized_end=27737 - _PINGREQUEST._serialized_start=27868 - _PINGREQUEST._serialized_end=27957 - _PINGRESPONSE._serialized_start=27959 - _PINGRESPONSE._serialized_end=27989 - _SETCHANNELREQUEST._serialized_start=27992 - _SETCHANNELREQUEST._serialized_end=28240 - _SETCHANNELRESPONSE._serialized_start=28242 - _SETCHANNELRESPONSE._serialized_end=28305 - _SETCHANNELCHANNELS._serialized_start=28308 - _SETCHANNELCHANNELS._serialized_end=28712 - _SIGNMESSAGEREQUEST._serialized_start=28714 - _SIGNMESSAGEREQUEST._serialized_end=28751 - _SIGNMESSAGERESPONSE._serialized_start=28753 - _SIGNMESSAGERESPONSE._serialized_end=28823 - _STOPREQUEST._serialized_start=28825 - _STOPREQUEST._serialized_end=28838 - _STOPRESPONSE._serialized_start=28840 - _STOPRESPONSE._serialized_end=28854 - _NODE._serialized_start=28857 - _NODE._serialized_end=31850 + _GETINFORESPONSE._serialized_end=684 + _GETINFOOUR_FEATURES._serialized_start=686 + _GETINFOOUR_FEATURES._serialized_end=769 + _GETINFOADDRESS._serialized_start=772 + _GETINFOADDRESS._serialized_end=983 + _GETINFOADDRESS_GETINFOADDRESSTYPE._serialized_start=885 + _GETINFOADDRESS_GETINFOADDRESSTYPE._serialized_end=971 + _GETINFOBINDING._serialized_start=986 + _GETINFOBINDING._serialized_end=1237 + _GETINFOBINDING_GETINFOBINDINGTYPE._serialized_start=1125 + _GETINFOBINDING_GETINFOBINDINGTYPE._serialized_end=1205 + _LISTPEERSREQUEST._serialized_start=1239 + _LISTPEERSREQUEST._serialized_end=1311 + _LISTPEERSRESPONSE._serialized_start=1313 + _LISTPEERSRESPONSE._serialized_end=1368 + _LISTPEERSPEERS._serialized_start=1371 + _LISTPEERSPEERS._serialized_end=1597 + _LISTPEERSPEERSLOG._serialized_start=1600 + _LISTPEERSPEERSLOG._serialized_end=1981 + _LISTPEERSPEERSLOG_LISTPEERSPEERSLOGTYPE._serialized_start=1811 + _LISTPEERSPEERSLOG_LISTPEERSPEERSLOGTYPE._serialized_end=1916 + _LISTPEERSPEERSCHANNELS._serialized_start=1984 + _LISTPEERSPEERSCHANNELS._serialized_end=5014 + _LISTPEERSPEERSCHANNELS_LISTPEERSPEERSCHANNELSSTATE._serialized_start=3884 + _LISTPEERSPEERSCHANNELS_LISTPEERSPEERSCHANNELSSTATE._serialized_end=4173 + _LISTPEERSPEERSCHANNELSFEERATE._serialized_start=5016 + _LISTPEERSPEERSCHANNELSFEERATE._serialized_end=5077 + _LISTPEERSPEERSCHANNELSINFLIGHT._serialized_start=5080 + _LISTPEERSPEERSCHANNELSINFLIGHT._serialized_end=5277 + _LISTPEERSPEERSCHANNELSFUNDING._serialized_start=5280 + _LISTPEERSPEERSCHANNELSFUNDING._serialized_end=5671 + _LISTPEERSPEERSCHANNELSALIAS._serialized_start=5673 + _LISTPEERSPEERSCHANNELSALIAS._serialized_end=5764 + _LISTPEERSPEERSCHANNELSHTLCS._serialized_start=5767 + _LISTPEERSPEERSCHANNELSHTLCS._serialized_end=6105 + _LISTPEERSPEERSCHANNELSHTLCS_LISTPEERSPEERSCHANNELSHTLCSDIRECTION._serialized_start=6021 + _LISTPEERSPEERSCHANNELSHTLCS_LISTPEERSPEERSCHANNELSHTLCSDIRECTION._serialized_end=6076 + _LISTFUNDSREQUEST._serialized_start=6107 + _LISTFUNDSREQUEST._serialized_end=6155 + _LISTFUNDSRESPONSE._serialized_start=6157 + _LISTFUNDSRESPONSE._serialized_end=6258 + _LISTFUNDSOUTPUTS._serialized_start=6261 + _LISTFUNDSOUTPUTS._serialized_end=6648 + _LISTFUNDSOUTPUTS_LISTFUNDSOUTPUTSSTATUS._serialized_start=6522 + _LISTFUNDSOUTPUTS_LISTFUNDSOUTPUTSSTATUS._serialized_end=6603 + _LISTFUNDSCHANNELS._serialized_start=6651 + _LISTFUNDSCHANNELS._serialized_end=6910 + _SENDPAYREQUEST._serialized_start=6913 + _SENDPAYREQUEST._serialized_end=7262 + _SENDPAYRESPONSE._serialized_start=7265 + _SENDPAYRESPONSE._serialized_end=7858 + _SENDPAYRESPONSE_SENDPAYSTATUS._serialized_start=7679 + _SENDPAYRESPONSE_SENDPAYSTATUS._serialized_end=7721 + _SENDPAYROUTE._serialized_start=7860 + _SENDPAYROUTE._serialized_end=7952 + _LISTCHANNELSREQUEST._serialized_start=7955 + _LISTCHANNELSREQUEST._serialized_end=8102 + _LISTCHANNELSRESPONSE._serialized_start=8104 + _LISTCHANNELSRESPONSE._serialized_end=8171 + _LISTCHANNELSCHANNELS._serialized_start=8174 + _LISTCHANNELSCHANNELS._serialized_end=8590 + _ADDGOSSIPREQUEST._serialized_start=8592 + _ADDGOSSIPREQUEST._serialized_end=8627 + _ADDGOSSIPRESPONSE._serialized_start=8629 + _ADDGOSSIPRESPONSE._serialized_end=8648 + _AUTOCLEANINVOICEREQUEST._serialized_start=8650 + _AUTOCLEANINVOICEREQUEST._serialized_end=8761 + _AUTOCLEANINVOICERESPONSE._serialized_start=8764 + _AUTOCLEANINVOICERESPONSE._serialized_end=8893 + _CHECKMESSAGEREQUEST._serialized_start=8895 + _CHECKMESSAGEREQUEST._serialized_end=8980 + _CHECKMESSAGERESPONSE._serialized_start=8982 + _CHECKMESSAGERESPONSE._serialized_end=9038 + _CLOSEREQUEST._serialized_start=9041 + _CLOSEREQUEST._serialized_end=9372 + _CLOSERESPONSE._serialized_start=9375 + _CLOSERESPONSE._serialized_end=9546 + _CLOSERESPONSE_CLOSETYPE._serialized_start=9477 + _CLOSERESPONSE_CLOSETYPE._serialized_end=9530 + _CONNECTREQUEST._serialized_start=9548 + _CONNECTREQUEST._serialized_end=9632 + _CONNECTRESPONSE._serialized_start=9635 + _CONNECTRESPONSE._serialized_end=9815 + _CONNECTRESPONSE_CONNECTDIRECTION._serialized_start=9780 + _CONNECTRESPONSE_CONNECTDIRECTION._serialized_end=9815 + _CONNECTADDRESS._serialized_start=9818 + _CONNECTADDRESS._serialized_end=10069 + _CONNECTADDRESS_CONNECTADDRESSTYPE._serialized_start=9957 + _CONNECTADDRESS_CONNECTADDRESSTYPE._serialized_end=10037 + _CREATEINVOICEREQUEST._serialized_start=10071 + _CREATEINVOICEREQUEST._serialized_end=10145 + _CREATEINVOICERESPONSE._serialized_start=10148 + _CREATEINVOICERESPONSE._serialized_end=10789 + _CREATEINVOICERESPONSE_CREATEINVOICESTATUS._serialized_start=10582 + _CREATEINVOICERESPONSE_CREATEINVOICESTATUS._serialized_end=10638 + _DATASTOREREQUEST._serialized_start=10792 + _DATASTOREREQUEST._serialized_end=11100 + _DATASTOREREQUEST_DATASTOREMODE._serialized_start=10945 + _DATASTOREREQUEST_DATASTOREMODE._serialized_end=11057 + _DATASTORERESPONSE._serialized_start=11103 + _DATASTORERESPONSE._serialized_end=11233 + _CREATEONIONREQUEST._serialized_start=11236 + _CREATEONIONREQUEST._serialized_end=11393 + _CREATEONIONRESPONSE._serialized_start=11395 + _CREATEONIONRESPONSE._serialized_end=11455 + _CREATEONIONHOPS._serialized_start=11457 + _CREATEONIONHOPS._serialized_end=11507 + _DELDATASTOREREQUEST._serialized_start=11509 + _DELDATASTOREREQUEST._serialized_end=11583 + _DELDATASTORERESPONSE._serialized_start=11586 + _DELDATASTORERESPONSE._serialized_end=11719 + _DELEXPIREDINVOICEREQUEST._serialized_start=11721 + _DELEXPIREDINVOICEREQUEST._serialized_end=11793 + _DELEXPIREDINVOICERESPONSE._serialized_start=11795 + _DELEXPIREDINVOICERESPONSE._serialized_end=11822 + _DELINVOICEREQUEST._serialized_start=11825 + _DELINVOICEREQUEST._serialized_end=12007 + _DELINVOICEREQUEST_DELINVOICESTATUS._serialized_start=11941 + _DELINVOICEREQUEST_DELINVOICESTATUS._serialized_end=11994 + _DELINVOICERESPONSE._serialized_start=12010 + _DELINVOICERESPONSE._serialized_end=12463 + _DELINVOICERESPONSE_DELINVOICESTATUS._serialized_start=11941 + _DELINVOICERESPONSE_DELINVOICESTATUS._serialized_end=11994 + _INVOICEREQUEST._serialized_start=12466 + _INVOICEREQUEST._serialized_end=12778 + _INVOICERESPONSE._serialized_start=12781 + _INVOICERESPONSE._serialized_end=13140 + _LISTDATASTOREREQUEST._serialized_start=13142 + _LISTDATASTOREREQUEST._serialized_end=13177 + _LISTDATASTORERESPONSE._serialized_start=13179 + _LISTDATASTORERESPONSE._serialized_end=13250 + _LISTDATASTOREDATASTORE._serialized_start=13253 + _LISTDATASTOREDATASTORE._serialized_end=13388 + _LISTINVOICESREQUEST._serialized_start=13391 + _LISTINVOICESREQUEST._serialized_end=13560 + _LISTINVOICESRESPONSE._serialized_start=13562 + _LISTINVOICESRESPONSE._serialized_end=13629 + _LISTINVOICESINVOICES._serialized_start=13632 + _LISTINVOICESINVOICES._serialized_end=14306 + _LISTINVOICESINVOICES_LISTINVOICESINVOICESSTATUS._serialized_start=14076 + _LISTINVOICESINVOICES_LISTINVOICESINVOICESSTATUS._serialized_end=14139 + _SENDONIONREQUEST._serialized_start=14309 + _SENDONIONREQUEST._serialized_end=14703 + _SENDONIONRESPONSE._serialized_start=14706 + _SENDONIONRESPONSE._serialized_end=15229 + _SENDONIONRESPONSE_SENDONIONSTATUS._serialized_start=15077 + _SENDONIONRESPONSE_SENDONIONSTATUS._serialized_end=15121 + _SENDONIONFIRST_HOP._serialized_start=15231 + _SENDONIONFIRST_HOP._serialized_end=15312 + _LISTSENDPAYSREQUEST._serialized_start=15315 + _LISTSENDPAYSREQUEST._serialized_end=15550 + _LISTSENDPAYSREQUEST_LISTSENDPAYSSTATUS._serialized_start=15452 + _LISTSENDPAYSREQUEST_LISTSENDPAYSSTATUS._serialized_end=15511 + _LISTSENDPAYSRESPONSE._serialized_start=15552 + _LISTSENDPAYSRESPONSE._serialized_end=15619 + _LISTSENDPAYSPAYMENTS._serialized_start=15622 + _LISTSENDPAYSPAYMENTS._serialized_end=16218 + _LISTSENDPAYSPAYMENTS_LISTSENDPAYSPAYMENTSSTATUS._serialized_start=16035 + _LISTSENDPAYSPAYMENTS_LISTSENDPAYSPAYMENTSSTATUS._serialized_end=16102 + _LISTTRANSACTIONSREQUEST._serialized_start=16220 + _LISTTRANSACTIONSREQUEST._serialized_end=16245 + _LISTTRANSACTIONSRESPONSE._serialized_start=16247 + _LISTTRANSACTIONSRESPONSE._serialized_end=16330 + _LISTTRANSACTIONSTRANSACTIONS._serialized_start=16333 + _LISTTRANSACTIONSTRANSACTIONS._serialized_end=16615 + _LISTTRANSACTIONSTRANSACTIONSINPUTS._serialized_start=16618 + _LISTTRANSACTIONSTRANSACTIONSINPUTS._serialized_end=17134 + _LISTTRANSACTIONSTRANSACTIONSINPUTS_LISTTRANSACTIONSTRANSACTIONSINPUTSTYPE._serialized_start=16830 + _LISTTRANSACTIONSTRANSACTIONSINPUTS_LISTTRANSACTIONSTRANSACTIONSINPUTSTYPE._serialized_end=17108 + _LISTTRANSACTIONSTRANSACTIONSOUTPUTS._serialized_start=17137 + _LISTTRANSACTIONSTRANSACTIONSOUTPUTS._serialized_end=17681 + _LISTTRANSACTIONSTRANSACTIONSOUTPUTS_LISTTRANSACTIONSTRANSACTIONSOUTPUTSTYPE._serialized_start=17376 + _LISTTRANSACTIONSTRANSACTIONSOUTPUTS_LISTTRANSACTIONSTRANSACTIONSOUTPUTSTYPE._serialized_end=17655 + _PAYREQUEST._serialized_start=17684 + _PAYREQUEST._serialized_end=18158 + _PAYRESPONSE._serialized_start=18161 + _PAYRESPONSE._serialized_end=18540 + _PAYRESPONSE_PAYSTATUS._serialized_start=18443 + _PAYRESPONSE_PAYSTATUS._serialized_end=18493 + _LISTNODESREQUEST._serialized_start=18542 + _LISTNODESREQUEST._serialized_end=18584 + _LISTNODESRESPONSE._serialized_start=18586 + _LISTNODESRESPONSE._serialized_end=18641 + _LISTNODESNODES._serialized_start=18644 + _LISTNODESNODES._serialized_end=18869 + _LISTNODESNODESADDRESSES._serialized_start=18872 + _LISTNODESNODESADDRESSES._serialized_end=19119 + _LISTNODESNODESADDRESSES_LISTNODESNODESADDRESSESTYPE._serialized_start=19012 + _LISTNODESNODESADDRESSES_LISTNODESNODESADDRESSESTYPE._serialized_end=19107 + _WAITANYINVOICEREQUEST._serialized_start=19121 + _WAITANYINVOICEREQUEST._serialized_end=19224 + _WAITANYINVOICERESPONSE._serialized_start=19227 + _WAITANYINVOICERESPONSE._serialized_end=19758 + _WAITANYINVOICERESPONSE_WAITANYINVOICESTATUS._serialized_start=19603 + _WAITANYINVOICERESPONSE_WAITANYINVOICESTATUS._serialized_end=19648 + _WAITINVOICEREQUEST._serialized_start=19760 + _WAITINVOICEREQUEST._serialized_end=19795 + _WAITINVOICERESPONSE._serialized_start=19798 + _WAITINVOICERESPONSE._serialized_end=20317 + _WAITINVOICERESPONSE_WAITINVOICESTATUS._serialized_start=20165 + _WAITINVOICERESPONSE_WAITINVOICESTATUS._serialized_end=20207 + _WAITSENDPAYREQUEST._serialized_start=20320 + _WAITSENDPAYREQUEST._serialized_end=20462 + _WAITSENDPAYRESPONSE._serialized_start=20465 + _WAITSENDPAYRESPONSE._serialized_end=21027 + _WAITSENDPAYRESPONSE_WAITSENDPAYSTATUS._serialized_start=20869 + _WAITSENDPAYRESPONSE_WAITSENDPAYSTATUS._serialized_end=20902 + _NEWADDRREQUEST._serialized_start=21030 + _NEWADDRREQUEST._serialized_end=21188 + _NEWADDRREQUEST_NEWADDRADDRESSTYPE._serialized_start=21114 + _NEWADDRREQUEST_NEWADDRADDRESSTYPE._serialized_end=21172 + _NEWADDRRESPONSE._serialized_start=21190 + _NEWADDRRESPONSE._serialized_end=21281 + _WITHDRAWREQUEST._serialized_start=21284 + _WITHDRAWREQUEST._serialized_end=21486 + _WITHDRAWRESPONSE._serialized_start=21488 + _WITHDRAWRESPONSE._serialized_end=21546 + _KEYSENDREQUEST._serialized_start=21549 + _KEYSENDREQUEST._serialized_end=21935 + _KEYSENDRESPONSE._serialized_start=21938 + _KEYSENDRESPONSE._serialized_end=22308 + _KEYSENDRESPONSE_KEYSENDSTATUS._serialized_start=22232 + _KEYSENDRESPONSE_KEYSENDSTATUS._serialized_end=22261 + _FUNDPSBTREQUEST._serialized_start=22311 + _FUNDPSBTREQUEST._serialized_end=22627 + _FUNDPSBTRESPONSE._serialized_start=22630 + _FUNDPSBTRESPONSE._serialized_end=22847 + _FUNDPSBTRESERVATIONS._serialized_start=22849 + _FUNDPSBTRESERVATIONS._serialized_end=22966 + _SENDPSBTREQUEST._serialized_start=22968 + _SENDPSBTREQUEST._serialized_end=23033 + _SENDPSBTRESPONSE._serialized_start=23035 + _SENDPSBTRESPONSE._serialized_end=23079 + _SIGNPSBTREQUEST._serialized_start=23081 + _SIGNPSBTREQUEST._serialized_end=23130 + _SIGNPSBTRESPONSE._serialized_start=23132 + _SIGNPSBTRESPONSE._serialized_end=23171 + _UTXOPSBTREQUEST._serialized_start=23174 + _UTXOPSBTREQUEST._serialized_end=23521 + _UTXOPSBTRESPONSE._serialized_start=23524 + _UTXOPSBTRESPONSE._serialized_end=23741 + _UTXOPSBTRESERVATIONS._serialized_start=23743 + _UTXOPSBTRESERVATIONS._serialized_end=23860 + _TXDISCARDREQUEST._serialized_start=23862 + _TXDISCARDREQUEST._serialized_end=23894 + _TXDISCARDRESPONSE._serialized_start=23896 + _TXDISCARDRESPONSE._serialized_end=23950 + _TXPREPAREREQUEST._serialized_start=23953 + _TXPREPAREREQUEST._serialized_end=24117 + _TXPREPARERESPONSE._serialized_start=24119 + _TXPREPARERESPONSE._serialized_end=24187 + _TXSENDREQUEST._serialized_start=24189 + _TXSENDREQUEST._serialized_end=24218 + _TXSENDRESPONSE._serialized_start=24220 + _TXSENDRESPONSE._serialized_end=24276 + _DISCONNECTREQUEST._serialized_start=24278 + _DISCONNECTREQUEST._serialized_end=24339 + _DISCONNECTRESPONSE._serialized_start=24341 + _DISCONNECTRESPONSE._serialized_end=24361 + _FEERATESREQUEST._serialized_start=24363 + _FEERATESREQUEST._serialized_end=24470 + _FEERATESREQUEST_FEERATESSTYLE._serialized_start=24433 + _FEERATESREQUEST_FEERATESSTYLE._serialized_end=24470 + _FEERATESRESPONSE._serialized_start=24473 + _FEERATESRESPONSE._serialized_end=24757 + _FEERATESPERKB._serialized_start=24760 + _FEERATESPERKB._serialized_end=25083 + _FEERATESPERKW._serialized_start=25086 + _FEERATESPERKW._serialized_end=25409 + _FEERATESONCHAIN_FEE_ESTIMATES._serialized_start=25412 + _FEERATESONCHAIN_FEE_ESTIMATES._serialized_end=25605 + _FUNDCHANNELREQUEST._serialized_start=25608 + _FUNDCHANNELREQUEST._serialized_end=26093 + _FUNDCHANNELRESPONSE._serialized_start=26096 + _FUNDCHANNELRESPONSE._serialized_end=26251 + _GETROUTEREQUEST._serialized_start=26254 + _GETROUTEREQUEST._serialized_end=26490 + _GETROUTERESPONSE._serialized_start=26492 + _GETROUTERESPONSE._serialized_end=26545 + _GETROUTEROUTE._serialized_start=26548 + _GETROUTEROUTE._serialized_end=26781 + _GETROUTEROUTE_GETROUTEROUTESTYLE._serialized_start=26739 + _GETROUTEROUTE_GETROUTEROUTESTYLE._serialized_end=26768 + _LISTFORWARDSREQUEST._serialized_start=26784 + _LISTFORWARDSREQUEST._serialized_end=27042 + _LISTFORWARDSREQUEST_LISTFORWARDSSTATUS._serialized_start=26924 + _LISTFORWARDSREQUEST_LISTFORWARDSSTATUS._serialized_end=27000 + _LISTFORWARDSRESPONSE._serialized_start=27044 + _LISTFORWARDSRESPONSE._serialized_end=27111 + _LISTFORWARDSFORWARDS._serialized_start=27114 + _LISTFORWARDSFORWARDS._serialized_end=27720 + _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTATUS._serialized_start=27503 + _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTATUS._serialized_end=27587 + _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTYLE._serialized_start=27589 + _LISTFORWARDSFORWARDS_LISTFORWARDSFORWARDSSTYLE._serialized_end=27637 + _LISTPAYSREQUEST._serialized_start=27723 + _LISTPAYSREQUEST._serialized_end=27942 + _LISTPAYSREQUEST_LISTPAYSSTATUS._serialized_start=27848 + _LISTPAYSREQUEST_LISTPAYSSTATUS._serialized_end=27903 + _LISTPAYSRESPONSE._serialized_start=27944 + _LISTPAYSRESPONSE._serialized_end=27995 + _LISTPAYSPAYS._serialized_start=27998 + _LISTPAYSPAYS._serialized_end=28517 + _LISTPAYSPAYS_LISTPAYSPAYSSTATUS._serialized_start=28329 + _LISTPAYSPAYS_LISTPAYSPAYSSTATUS._serialized_end=28388 + _PINGREQUEST._serialized_start=28519 + _PINGREQUEST._serialized_end=28608 + _PINGRESPONSE._serialized_start=28610 + _PINGRESPONSE._serialized_end=28640 + _SETCHANNELREQUEST._serialized_start=28643 + _SETCHANNELREQUEST._serialized_end=28891 + _SETCHANNELRESPONSE._serialized_start=28893 + _SETCHANNELRESPONSE._serialized_end=28956 + _SETCHANNELCHANNELS._serialized_start=28959 + _SETCHANNELCHANNELS._serialized_end=29363 + _SIGNMESSAGEREQUEST._serialized_start=29365 + _SIGNMESSAGEREQUEST._serialized_end=29402 + _SIGNMESSAGERESPONSE._serialized_start=29404 + _SIGNMESSAGERESPONSE._serialized_end=29474 + _STOPREQUEST._serialized_start=29476 + _STOPREQUEST._serialized_end=29489 + _STOPRESPONSE._serialized_start=29491 + _STOPRESPONSE._serialized_end=29505 + _NODE._serialized_start=29508 + _NODE._serialized_end=32501 # @@protoc_insertion_point(module_scope) diff --git a/contrib/pyln-testing/pyln/testing/primitives_pb2.py b/contrib/pyln-testing/pyln/testing/primitives_pb2.py index bc180d942093..48421bdf21cc 100644 --- a/contrib/pyln-testing/pyln/testing/primitives_pb2.py +++ b/contrib/pyln-testing/pyln/testing/primitives_pb2.py @@ -15,7 +15,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10primitives.proto\x12\x03\x63ln\"\x16\n\x06\x41mount\x12\x0c\n\x04msat\x18\x01 \x01(\x04\"D\n\x0b\x41mountOrAll\x12\x1d\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x0b.cln.AmountH\x00\x12\r\n\x03\x61ll\x18\x02 \x01(\x08H\x00\x42\x07\n\x05value\"D\n\x0b\x41mountOrAny\x12\x1d\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x0b.cln.AmountH\x00\x12\r\n\x03\x61ny\x18\x02 \x01(\x08H\x00\x42\x07\n\x05value\"\x19\n\x17\x43hannelStateChangeCause\"(\n\x08Outpoint\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0e\n\x06outnum\x18\x02 \x01(\r\"h\n\x07\x46\x65\x65rate\x12\x0e\n\x04slow\x18\x01 \x01(\x08H\x00\x12\x10\n\x06normal\x18\x02 \x01(\x08H\x00\x12\x10\n\x06urgent\x18\x03 \x01(\x08H\x00\x12\x0f\n\x05perkb\x18\x04 \x01(\rH\x00\x12\x0f\n\x05perkw\x18\x05 \x01(\rH\x00\x42\x07\n\x05style\":\n\nOutputDesc\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x1b\n\x06\x61mount\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\"t\n\x08RouteHop\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x18\n\x10short_channel_id\x18\x02 \x01(\t\x12\x1c\n\x07\x66\x65\x65\x62\x61se\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x0f\n\x07\x66\x65\x65prop\x18\x04 \x01(\r\x12\x13\n\x0b\x65xpirydelta\x18\x05 \x01(\r\"(\n\tRoutehint\x12\x1b\n\x04hops\x18\x01 \x03(\x0b\x32\r.cln.RouteHop\".\n\rRoutehintList\x12\x1d\n\x05hints\x18\x02 \x03(\x0b\x32\x0e.cln.Routehint*\x1e\n\x0b\x43hannelSide\x12\x06\n\x02IN\x10\x00\x12\x07\n\x03OUT\x10\x01*\x84\x02\n\x0c\x43hannelState\x12\x0c\n\x08Openingd\x10\x00\x12\x1a\n\x16\x43hanneldAwaitingLockin\x10\x01\x12\x12\n\x0e\x43hanneldNormal\x10\x02\x12\x18\n\x14\x43hanneldShuttingDown\x10\x03\x12\x17\n\x13\x43losingdSigexchange\x10\x04\x12\x14\n\x10\x43losingdComplete\x10\x05\x12\x16\n\x12\x41waitingUnilateral\x10\x06\x12\x14\n\x10\x46undingSpendSeen\x10\x07\x12\x0b\n\x07Onchain\x10\x08\x12\x15\n\x11\x44ualopendOpenInit\x10\t\x12\x1b\n\x17\x44ualopendAwaitingLockin\x10\nb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10primitives.proto\x12\x03\x63ln\"\x16\n\x06\x41mount\x12\x0c\n\x04msat\x18\x01 \x01(\x04\"D\n\x0b\x41mountOrAll\x12\x1d\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x0b.cln.AmountH\x00\x12\r\n\x03\x61ll\x18\x02 \x01(\x08H\x00\x42\x07\n\x05value\"D\n\x0b\x41mountOrAny\x12\x1d\n\x06\x61mount\x18\x01 \x01(\x0b\x32\x0b.cln.AmountH\x00\x12\r\n\x03\x61ny\x18\x02 \x01(\x08H\x00\x42\x07\n\x05value\"\x19\n\x17\x43hannelStateChangeCause\"(\n\x08Outpoint\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\x0e\n\x06outnum\x18\x02 \x01(\r\"h\n\x07\x46\x65\x65rate\x12\x0e\n\x04slow\x18\x01 \x01(\x08H\x00\x12\x10\n\x06normal\x18\x02 \x01(\x08H\x00\x12\x10\n\x06urgent\x18\x03 \x01(\x08H\x00\x12\x0f\n\x05perkb\x18\x04 \x01(\rH\x00\x12\x0f\n\x05perkw\x18\x05 \x01(\rH\x00\x42\x07\n\x05style\":\n\nOutputDesc\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x1b\n\x06\x61mount\x18\x02 \x01(\x0b\x32\x0b.cln.Amount\"t\n\x08RouteHop\x12\n\n\x02id\x18\x01 \x01(\x0c\x12\x18\n\x10short_channel_id\x18\x02 \x01(\t\x12\x1c\n\x07\x66\x65\x65\x62\x61se\x18\x03 \x01(\x0b\x32\x0b.cln.Amount\x12\x0f\n\x07\x66\x65\x65prop\x18\x04 \x01(\r\x12\x13\n\x0b\x65xpirydelta\x18\x05 \x01(\r\"(\n\tRoutehint\x12\x1b\n\x04hops\x18\x01 \x03(\x0b\x32\r.cln.RouteHop\".\n\rRoutehintList\x12\x1d\n\x05hints\x18\x02 \x03(\x0b\x32\x0e.cln.Routehint\"\'\n\x08TlvEntry\x12\x0c\n\x04type\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c\"+\n\tTlvStream\x12\x1e\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\r.cln.TlvEntry*\x1e\n\x0b\x43hannelSide\x12\x06\n\x02IN\x10\x00\x12\x07\n\x03OUT\x10\x01*\x84\x02\n\x0c\x43hannelState\x12\x0c\n\x08Openingd\x10\x00\x12\x1a\n\x16\x43hanneldAwaitingLockin\x10\x01\x12\x12\n\x0e\x43hanneldNormal\x10\x02\x12\x18\n\x14\x43hanneldShuttingDown\x10\x03\x12\x17\n\x13\x43losingdSigexchange\x10\x04\x12\x14\n\x10\x43losingdComplete\x10\x05\x12\x16\n\x12\x41waitingUnilateral\x10\x06\x12\x14\n\x10\x46undingSpendSeen\x10\x07\x12\x0b\n\x07Onchain\x10\x08\x12\x15\n\x11\x44ualopendOpenInit\x10\t\x12\x1b\n\x17\x44ualopendAwaitingLockin\x10\nb\x06proto3') _CHANNELSIDE = DESCRIPTOR.enum_types_by_name['ChannelSide'] ChannelSide = enum_type_wrapper.EnumTypeWrapper(_CHANNELSIDE) @@ -46,6 +46,8 @@ _ROUTEHOP = DESCRIPTOR.message_types_by_name['RouteHop'] _ROUTEHINT = DESCRIPTOR.message_types_by_name['Routehint'] _ROUTEHINTLIST = DESCRIPTOR.message_types_by_name['RoutehintList'] +_TLVENTRY = DESCRIPTOR.message_types_by_name['TlvEntry'] +_TLVSTREAM = DESCRIPTOR.message_types_by_name['TlvStream'] Amount = _reflection.GeneratedProtocolMessageType('Amount', (_message.Message,), { 'DESCRIPTOR' : _AMOUNT, '__module__' : 'primitives_pb2' @@ -116,13 +118,27 @@ }) _sym_db.RegisterMessage(RoutehintList) +TlvEntry = _reflection.GeneratedProtocolMessageType('TlvEntry', (_message.Message,), { + 'DESCRIPTOR' : _TLVENTRY, + '__module__' : 'primitives_pb2' + # @@protoc_insertion_point(class_scope:cln.TlvEntry) + }) +_sym_db.RegisterMessage(TlvEntry) + +TlvStream = _reflection.GeneratedProtocolMessageType('TlvStream', (_message.Message,), { + 'DESCRIPTOR' : _TLVSTREAM, + '__module__' : 'primitives_pb2' + # @@protoc_insertion_point(class_scope:cln.TlvStream) + }) +_sym_db.RegisterMessage(TlvStream) + if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _CHANNELSIDE._serialized_start=632 - _CHANNELSIDE._serialized_end=662 - _CHANNELSTATE._serialized_start=665 - _CHANNELSTATE._serialized_end=925 + _CHANNELSIDE._serialized_start=718 + _CHANNELSIDE._serialized_end=748 + _CHANNELSTATE._serialized_start=751 + _CHANNELSTATE._serialized_end=1011 _AMOUNT._serialized_start=25 _AMOUNT._serialized_end=47 _AMOUNTORALL._serialized_start=49 @@ -143,4 +159,8 @@ _ROUTEHINT._serialized_end=582 _ROUTEHINTLIST._serialized_start=584 _ROUTEHINTLIST._serialized_end=630 + _TLVENTRY._serialized_start=632 + _TLVENTRY._serialized_end=671 + _TLVSTREAM._serialized_start=673 + _TLVSTREAM._serialized_end=716 # @@protoc_insertion_point(module_scope) diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py index 927f6e8ceba8..0e1f653de2a5 100644 --- a/contrib/pyln-testing/pyln/testing/utils.py +++ b/contrib/pyln-testing/pyln/testing/utils.py @@ -90,7 +90,7 @@ def wait_for(success, timeout=TIMEOUT): while not success(): time_left = start_time + timeout - time.time() if time_left <= 0: - raise ValueError("Timeout while waiting for {}", success) + raise ValueError("Timeout while waiting for {}".format(success)) time.sleep(min(interval, time_left)) interval *= 2 if interval > 5: @@ -413,7 +413,11 @@ def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=None): '-nolisten', '-txindex', '-nowallet', - '-addresstype=bech32' + '-addresstype=bech32', + '-debug=mempool', + '-debug=mempoolrej', + '-debug=rpc', + '-debug=validation', ] # For up to and including 0.16.1, this needs to be in main section. BITCOIND_CONFIG['rpcport'] = rpcport @@ -678,13 +682,14 @@ def __init__(self, socket_path, executor=None, logger=logging, self.jsonschemas = jsonschemas self.check_request_schemas = True - def call(self, method, payload=None, cmdprefix=None): + def call(self, method, payload=None, cmdprefix=None, filter=None): id = self.get_json_id(method, cmdprefix) schemas = self.jsonschemas.get(method) self.logger.debug(json.dumps({ "id": id, "method": method, - "params": payload + "params": payload, + "filter": filter, }, indent=2)) # We only check payloads which are dicts, which is what we @@ -698,13 +703,14 @@ def call(self, method, payload=None, cmdprefix=None): testpayload[k] = v schemas[0].validate(testpayload) - res = LightningRpc.call(self, method, payload, cmdprefix) + res = LightningRpc.call(self, method, payload, cmdprefix, filter) self.logger.debug(json.dumps({ "id": id, "result": res }, indent=2)) - if schemas and schemas[1]: + # FIXME: if filter set, just remove "required" from schemas? + if schemas and schemas[1] and filter is None and self._filter is None: schemas[1].validate(res) return res diff --git a/contrib/pyln-testing/pyproject.toml b/contrib/pyln-testing/pyproject.toml index 98e95d64b3e4..549c96de5ab6 100644 --- a/contrib/pyln-testing/pyproject.toml +++ b/contrib/pyln-testing/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyln-testing" -version = "0.12.1" +version = "22.11rc1" description = "Test your Core Lightning integration, plugins or whatever you want" authors = ["Christian Decker "] license = "BSD-MIT" diff --git a/devtools/Makefile b/devtools/Makefile index a18548c666fc..0d5e3cf9a90c 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -14,6 +14,8 @@ ALL_PROGRAMS += $(DEVTOOLS) DEVTOOLS_COMMON_OBJS := \ common/amount.o \ common/autodata.o \ + common/blinding.o \ + common/blindedpath.o \ common/coin_mvt.o \ common/base32.o \ common/bech32.o \ @@ -69,7 +71,7 @@ devtools/create-gossipstore.o: gossipd/gossip_store_wiregen.h devtools/onion.c: ccan/config.h -devtools/onion: $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(BITCOIN_OBJS) common/onion.o common/onionreply.o wire/fromwire.o wire/towire.o devtools/onion.o common/sphinx.o +devtools/onion: $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(BITCOIN_OBJS) common/onion_decode.o common/onion_encode.o common/onionreply.o wire/fromwire.o wire/towire.o devtools/onion.o common/sphinx.o devtools/gossipwith: $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o wire/peer$(EXP)_wiregen.o devtools/gossipwith.o common/cryptomsg.o common/cryptomsg.o diff --git a/devtools/blockreplace.py b/devtools/blockreplace.py new file mode 100644 index 000000000000..72d92bcd0fb9 --- /dev/null +++ b/devtools/blockreplace.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +# A rather simple script to replace a block of text, delimited by +# markers, with new contents from stdin. Importantly the markers are +# left in the file so future runs can update the file without +# requiring a separate template. The markers are currently for +# reStructuredText only, but more can be added. + +from enum import Enum +import argparse +import os +import sys +import textwrap + + +class Language(str, Enum): + md = 'md' + rst = 'rst' + c = 'c' + + +comment_style = { + Language.md: ( + "", + "", + ), + Language.rst: ( + ".. block_start {blockname}", + ".. block_end {blockname}", + ), + Language.c: ( + "/* block_start {blockname} */", + "/* block_end {blockname} */", + ), +} + + +def replace(filename, blockname, language, content): + start, stop = comment_style[language] + + tempfile = f"{filename}.tmp" + + with open(filename, 'r') as i, open(tempfile, 'w') as o: + lines = i.readlines() + # Read lines up to the marker + while lines != []: + l = lines.pop(0) + o.write(l) + if l.strip() == start.format(blockname=blockname): + break + + o.write(content) + + # Skip lines until we get the end marker + while lines != []: + l = lines.pop(0) + if l.strip() == stop.format(blockname=blockname): + o.write(l) + break + + # Now flush the rest of the file + for l in lines: + o.write(l) + + # Move the temp file over the old one for an atomic replacement + os.rename(tempfile, filename) + + +def main(args): + parser = argparse.ArgumentParser( + prog='blockreplace' + ) + parser.add_argument('filename') + parser.add_argument('blockname') + parser.add_argument('--language', type=Language) + parser.add_argument('--indent', dest="indent", default="") + args = parser.parse_args() + content = sys.stdin.read() + content = textwrap.indent(content, args.indent) + + replace(args.filename, args.blockname, args.language, content) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/devtools/bolt12-cli.c b/devtools/bolt12-cli.c index 9b082f847c4d..9d815cff8f49 100644 --- a/devtools/bolt12-cli.c +++ b/devtools/bolt12-cli.c @@ -65,36 +65,47 @@ static bool must_str(bool expected, const char *complaint, const char *fieldname #define must_not_have(obj, field) \ must_str((obj)->field == NULL, "Unnecessary", stringify(field)) -static void print_chains(const struct bitcoin_blkid *chains) +static void print_offer_chains(const struct bitcoin_blkid *chains) { - printf("chains:"); + printf("offer_chains:"); for (size_t i = 0; i < tal_count(chains); i++) { printf(" %s", type_to_string(tmpctx, struct bitcoin_blkid, &chains[i])); } printf("\n"); } -static void print_chain(const struct bitcoin_blkid *chain) +static void print_hex(const char *fieldname, const u8 *bin) { - printf("chain: %s\n", + printf("%s: %s\n", fieldname, tal_hex(tmpctx, bin)); +} + + +static void print_invreq_chain(const struct bitcoin_blkid *chain) +{ + printf("invreq_chain: %s\n", type_to_string(tmpctx, struct bitcoin_blkid, chain)); } -static bool print_amount(const struct bitcoin_blkid *chains, - const char *iso4217, u64 amount) +static bool print_offer_amount(const struct bitcoin_blkid *chains, + const char *iso4217, u64 amount) { const char *currency; unsigned int minor_unit; bool ok = true; /* BOLT-offers #12: - * - if the currency for `amount` is that of the first entry in `chains`: - * - MUST specify `amount` in multiples of the minimum - * lightning-payable unit (e.g. milli-satoshis for bitcoin). + * - if a specific minimum `offer_amount` is required for successful payment: + * - MUST set `offer_amount` to the amount expected (per item). + * - if the currency for `offer_amount` is that of all entries in `chains`: + * - MUST specify `amount` in multiples of the minimum lightning-payable unit + * (e.g. milli-satoshis for bitcoin). + * - otherwise: + * - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code. + * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712 + * exponent (e.g. USD cents). * - otherwise: - * - MUST specify `iso4217` as an ISO 4712 three-letter code. - * - MUST specify `amount` in the currency unit adjusted by the - * ISO 4712 exponent (e.g. USD cents). + * - MUST NOT set `offer_amount` + * - MUST NOT set `offer_currency` */ if (!iso4217) { if (tal_count(chains) == 0) @@ -128,12 +139,12 @@ static bool print_amount(const struct bitcoin_blkid *chains, } if (!minor_unit) - printf("amount: %"PRIu64"%s\n", amount, currency); + printf("offer_amount: %"PRIu64"%s\n", amount, currency); else { u64 minor_div = 1; for (size_t i = 0; i < minor_unit; i++) minor_div *= 10; - printf("amount: %"PRIu64".%.*"PRIu64"%s\n", + printf("offer_amount: %"PRIu64".%.*"PRIu64"%s\n", amount / minor_div, minor_unit, amount % minor_div, currency); } @@ -141,36 +152,30 @@ static bool print_amount(const struct bitcoin_blkid *chains, return ok; } -static void print_description(const char *description) -{ - printf("description: %.*s\n", - (int)tal_bytelen(description), description); -} - -static void print_issuer(const char *issuer) +static bool print_utf8(const char *fieldname, const char *description) { - printf("issuer: %.*s\n", (int)tal_bytelen(issuer), issuer); + bool valid = utf8_check(description, tal_bytelen(description)); + printf("%s: %.*s%s\n", fieldname, + (int)tal_bytelen(description), description, + valid ? "" : "(INVALID UTF-8)"); + return valid; } -static void print_node_id(const struct point32 *node_id) +static void print_node_id(const char *fieldname, const struct pubkey *node_id) { - printf("node_id: %s\n", type_to_string(tmpctx, struct point32, node_id)); -} - -static void print_quantity_min(u64 min) -{ - printf("quantity_min: %"PRIu64"\n", min); + printf("%s: %s\n", + fieldname, type_to_string(tmpctx, struct pubkey, node_id)); } -static void print_quantity_max(u64 max) +static void print_u64(const char *fieldname, u64 max) { - printf("quantity_max: %"PRIu64"\n", max); + printf("%s: %"PRIu64"\n", fieldname, max); } -static bool print_recurrance(const struct tlv_offer_recurrence *recurrence, - const struct tlv_offer_recurrence_paywindow *paywindow, +static bool print_recurrance(const struct recurrence *recurrence, + const struct recurrence_paywindow *paywindow, const u32 *limit, - const struct tlv_offer_recurrence_base *base) + const struct recurrence_base *base) { const char *unit; bool ok = true; @@ -217,7 +222,7 @@ static bool print_recurrance(const struct tlv_offer_recurrence *recurrence, unit = ""; ok = false; } - printf("recurrence: every %u %s", recurrence->period, unit); + printf("offer_recurrence: every %u %s", recurrence->period, unit); if (limit) printf(" limit %u", *limit); if (base) { @@ -238,15 +243,15 @@ static bool print_recurrance(const struct tlv_offer_recurrence *recurrence, return ok; } -static void print_absolute_expiry(u64 expiry) +static void print_abstime(const char *fieldname, u64 expiry) { - printf("absolute_expiry: %"PRIu64" (%s)\n", + printf("%s: %"PRIu64" (%s)\n", fieldname, expiry, fmt_time(tmpctx, expiry)); } -static void print_features(const u8 *features) +static void print_features(const char *fieldname, const u8 *features) { - printf("features:"); + printf("%s:", fieldname); for (size_t i = 0; i < tal_bytelen(features) * CHAR_BIT; i++) { if (feature_is_set(features, i)) printf(" %zu", i); @@ -254,23 +259,26 @@ static void print_features(const u8 *features) printf("\n"); } -static bool print_blindedpaths(struct blinded_path **paths, +static bool print_blindedpaths(const char *fieldname, + struct blinded_path **paths, struct blinded_payinfo **blindedpay) { size_t bp_idx = 0; for (size_t i = 0; i < tal_count(paths); i++) { - struct onionmsg_path **p = paths[i]->path; - printf("blindedpath %zu/%zu: blinding %s", + struct onionmsg_hop **p = paths[i]->path; + printf("%s %zu/%zu: blinding %s", + fieldname, i, tal_count(paths), type_to_string(tmpctx, struct pubkey, &paths[i]->blinding)); - printf("blindedpath %zu/%zu: path ", + printf("%s %zu/%zu: path ", + fieldname, i, tal_count(paths)); for (size_t j = 0; j < tal_count(p); j++) { printf(" %s:%s", type_to_string(tmpctx, struct pubkey, - &p[j]->node_id), + &p[j]->blinded_node_id), tal_hex(tmpctx, p[j]->encrypted_recipient_data)); if (blindedpay) { if (bp_idx < tal_count(blindedpay)) @@ -293,21 +301,10 @@ static bool print_blindedpaths(struct blinded_path **paths, return true; } -static void print_send_invoice(void) -{ - printf("send_invoice\n"); -} - -static void print_refund_for(const struct sha256 *payment_hash) -{ - printf("refund_for: %s\n", - type_to_string(tmpctx, struct sha256, payment_hash)); -} - static bool print_signature(const char *messagename, const char *fieldname, const struct tlv_field *fields, - const struct point32 *node_id, + const struct pubkey *node_id, const struct bip340sig *sig) { struct sha256 m, shash; @@ -318,11 +315,7 @@ static bool print_signature(const char *messagename, merkle_tlv(fields, &m); sighash_from_merkle(messagename, fieldname, &m, &shash); - if (secp256k1_schnorrsig_verify(secp256k1_ctx, - sig->u8, - shash.u.u8, - sizeof(shash.u.u8), - &node_id->pubkey) != 1) { + if (!check_schnorr_sig(&shash, &node_id->pubkey, sig)) { fprintf(stderr, "%s: INVALID\n", fieldname); return false; } @@ -332,21 +325,10 @@ static bool print_signature(const char *messagename, return true; } -static void print_offer_id(const struct sha256 *offer_id) -{ - printf("offer_id: %s\n", - type_to_string(tmpctx, struct sha256, offer_id)); -} - -static void print_quantity(u64 q) -{ - printf("quantity: %"PRIu64"\n", q); -} - static void print_recurrence_counter(const u32 *recurrence_counter, const u32 *recurrence_start) { - printf("recurrence_counter: %u", *recurrence_counter); + printf("invreq_recurrence_counter: %u", *recurrence_counter); if (recurrence_start) printf(" (start +%u)", *recurrence_start); printf("\n"); @@ -360,44 +342,17 @@ static bool print_recurrence_counter_with_base(const u32 *recurrence_counter, fprintf(stderr, "Missing recurrence_base\n"); return false; } - printf("recurrence_counter: %u", *recurrence_counter); + printf("invreq_recurrence_counter: %u", *recurrence_counter); if (recurrence_start) printf(" (start +%u)", *recurrence_start); printf(" (base %"PRIu64")\n", *recurrence_base); return true; } -static void print_payer_key(const struct point32 *payer_key, - const u8 *payer_info) -{ - printf("payer_key: %s", - type_to_string(tmpctx, struct point32, payer_key)); - if (payer_info) - printf(" (payer_info %s)", tal_hex(tmpctx, payer_info)); - printf("\n"); -} - -static void print_payer_note(const char *payer_note) +static void print_hash(const char *fieldname, const struct sha256 *hash) { - printf("payer_note: %.*s\n", - (int)tal_bytelen(payer_note), payer_note); -} - -static void print_created_at(u64 timestamp) -{ - printf("created_at: %"PRIu64" (%s)\n", - timestamp, fmt_time(tmpctx, timestamp)); -} - -static void print_payment_hash(const struct sha256 *payment_hash) -{ - printf("payment_hash: %s\n", - type_to_string(tmpctx, struct sha256, payment_hash)); -} - -static void print_cltv(u32 cltv) -{ - printf("min_final_cltv_expiry: %u\n", cltv); + printf("%s: %s\n", + fieldname, type_to_string(tmpctx, struct sha256, hash)); } static void print_relative_expiry(u64 *created_at, u32 *relative) @@ -407,19 +362,19 @@ static void print_relative_expiry(u64 *created_at, u32 *relative) return; /* BOLT-offers #12: - * - if `relative_expiry` is present: + * - if `invoice_relative_expiry` is present: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus `seconds_from_creation`. + * is greater than `invoice_created_at` plus `seconds_from_creation`. * - otherwise: * - MUST reject the invoice if the current time since 1970-01-01 UTC - * is greater than `created_at` plus 7200. + * is greater than `invoice_created_at` plus 7200. */ if (!relative) - printf("relative_expiry: %u (%s) (default)\n", + printf("invoice_relative_expiry: %u (%s) (default)\n", BOLT12_DEFAULT_REL_EXPIRY, fmt_time(tmpctx, *created_at + BOLT12_DEFAULT_REL_EXPIRY)); else - printf("relative_expiry: %u (%s)\n", *relative, + printf("invoice_relative_expiry: %u (%s)\n", *relative, fmt_time(tmpctx, *created_at + *relative)); } @@ -427,12 +382,17 @@ static void print_fallbacks(struct fallback_address **fallbacks) { for (size_t i = 0; i < tal_count(fallbacks); i++) { /* FIXME: format properly! */ - printf("fallback: %u %s\n", + printf("invocice_fallbacks: %u %s\n", fallbacks[i]->version, tal_hex(tmpctx, fallbacks[i]->address)); } } +static void print_msat(const char *fieldname, u64 amount) +{ + printf("%s: %s\n", fieldname, fmt_amount_msat(tmpctx, amount_msat(amount))); +} + static bool print_extra_fields(const struct tlv_field *fields) { bool ok = true; @@ -551,166 +511,199 @@ int main(int argc, char *argv[]) } if (streq(hrp, "lno")) { + struct sha256 offer_id; const struct tlv_offer *offer = offer_decode(ctx, argv[2], strlen(argv[2]), NULL, NULL, &fail); if (!offer) errx(ERROR_BAD_DECODE, "Bad offer: %s", fail); - if (offer->send_invoice) - print_send_invoice(); - if (offer->chains) - print_chains(offer->chains); - if (offer->refund_for) - print_refund_for(offer->refund_for); - if (offer->amount) - well_formed &= print_amount(offer->chains, - offer->currency, - *offer->amount); - if (must_have(offer, description)) - print_description(offer->description); - if (offer->issuer) - print_issuer(offer->issuer); - if (must_have(offer, node_id)) - print_node_id(offer->node_id); - if (offer->quantity_min) - print_quantity_min(*offer->quantity_min); - if (offer->quantity_max) - print_quantity_max(*offer->quantity_max); - if (offer->recurrence) - well_formed &= print_recurrance(offer->recurrence, - offer->recurrence_paywindow, - offer->recurrence_limit, - offer->recurrence_base); - if (offer->absolute_expiry) - print_absolute_expiry(*offer->absolute_expiry); - if (offer->features) - print_features(offer->features); - if (offer->paths) - print_blindedpaths(offer->paths, NULL); - if (offer->signature && offer->node_id) - well_formed &= print_signature("offer", "signature", - offer->fields, - offer->node_id, - offer->signature); + offer_offer_id(offer, &offer_id); + print_hash("offer_id", &offer_id); + if (offer->offer_chains) + print_offer_chains(offer->offer_chains); + if (offer->offer_amount) + well_formed &= print_offer_amount(offer->offer_chains, + offer->offer_currency, + *offer->offer_amount); + if (must_have(offer, offer_description)) + well_formed &= print_utf8("offer_description", offer->offer_description); + if (offer->offer_features) + print_features("offer_features", offer->offer_features); + if (offer->offer_absolute_expiry) + print_abstime("offer_absolute_expiry", *offer->offer_absolute_expiry); + if (offer->offer_paths) + print_blindedpaths("offer_paths", offer->offer_paths, NULL); + if (offer->offer_issuer) + well_formed &= print_utf8("offer_issuer", offer->offer_issuer); + if (offer->offer_quantity_max) + print_u64("offer_quantity_max", *offer->offer_quantity_max); + if (must_have(offer, offer_node_id)) + print_node_id("offer_node_id", offer->offer_node_id); + if (offer->offer_recurrence) + well_formed &= print_recurrance(offer->offer_recurrence, + offer->offer_recurrence_paywindow, + offer->offer_recurrence_limit, + offer->offer_recurrence_base); if (!print_extra_fields(offer->fields)) well_formed = false; } else if (streq(hrp, "lnr")) { + struct sha256 offer_id, invreq_id; const struct tlv_invoice_request *invreq = invrequest_decode(ctx, argv[2], strlen(argv[2]), NULL, NULL, &fail); if (!invreq) - errx(ERROR_BAD_DECODE, "Bad invoice_request: %s", fail); - - if (invreq->chain) - print_chain(invreq->chain); - if (must_have(invreq, payer_key)) - print_payer_key(invreq->payer_key, invreq->payer_info); - if (invreq->payer_note) - print_payer_note(invreq->payer_note); - if (must_have(invreq, offer_id)) - print_offer_id(invreq->offer_id); - if (must_have(invreq, amount)) - well_formed &= print_amount(invreq->chain, - NULL, - *invreq->amount); - if (invreq->features) - print_features(invreq->features); - if (invreq->quantity) - print_quantity(*invreq->quantity); - if (must_have(invreq, signature)) { - if (!print_signature("invoice_request", - "signature", - invreq->fields, - invreq->payer_key, - invreq->signature)) { - /* FIXME: We temporarily allow the old "payer_signature" name */ - well_formed &= print_signature("invoice_request", - "payer_signature", - invreq->fields, - invreq->payer_key, - invreq->signature); - } + errx(ERROR_BAD_DECODE, "Bad invreq: %s", fail); + + if (invreq->offer_node_id) { + invreq_offer_id(invreq, &offer_id); + print_hash("offer_id", &offer_id); } - if (invreq->recurrence_counter) { - print_recurrence_counter(invreq->recurrence_counter, - invreq->recurrence_start); + invreq_invreq_id(invreq, &invreq_id); + print_hash("invreq_id", &invreq_id); + + /* FIXME: We can do more intra-field checking! */ + if (must_have(invreq, invreq_metadata)) + print_hex("invreq_metadata", invreq->invreq_metadata); + if (invreq->offer_chains) + print_offer_chains(invreq->offer_chains); + if (invreq->offer_amount) + well_formed &= print_offer_amount(invreq->offer_chains, + invreq->offer_currency, + *invreq->offer_amount); + if (must_have(invreq, offer_description)) + well_formed &= print_utf8("offer_description", invreq->offer_description); + if (invreq->offer_features) + print_features("offer_features", invreq->offer_features); + if (invreq->offer_absolute_expiry) + print_abstime("offer_absolute_expiry", *invreq->offer_absolute_expiry); + if (must_have(invreq, offer_paths)) + print_blindedpaths("offer_paths", invreq->offer_paths, NULL); + if (invreq->offer_issuer) + well_formed &= print_utf8("offer_issuer", invreq->offer_issuer); + if (invreq->offer_quantity_max) + print_u64("offer_quantity_max", *invreq->offer_quantity_max); + if (invreq->offer_node_id) + print_node_id("offer_node_id", invreq->offer_node_id); + if (invreq->offer_recurrence) + well_formed &= print_recurrance(invreq->offer_recurrence, + invreq->offer_recurrence_paywindow, + invreq->offer_recurrence_limit, + invreq->offer_recurrence_base); + if (invreq->invreq_chain) + print_invreq_chain(invreq->invreq_chain); + if (invreq->invreq_amount) + print_msat("invreq_amount", *invreq->invreq_amount); + if (invreq->invreq_features) + print_features("invreq_features", invreq->invreq_features); + if (invreq->invreq_quantity) + print_u64("invreq_quantity", *invreq->invreq_quantity); + if (must_have(invreq, invreq_payer_id)) + print_node_id("invreq_payer_id", invreq->invreq_payer_id); + if (invreq->invreq_payer_note) + well_formed &= print_utf8("invreq_payer_note", invreq->invreq_payer_note); + if (invreq->invreq_recurrence_counter) { + print_recurrence_counter(invreq->invreq_recurrence_counter, + invreq->invreq_recurrence_start); } else { - must_not_have(invreq, recurrence_start); + must_not_have(invreq, invreq_recurrence_start); + } + if (must_have(invreq, signature)) { + well_formed = print_signature("invoice_request", + "signature", + invreq->fields, + invreq->invreq_payer_id, + invreq->signature); } if (!print_extra_fields(invreq->fields)) well_formed = false; } else if (streq(hrp, "lni")) { + struct sha256 offer_id, invreq_id; const struct tlv_invoice *invoice = invoice_decode(ctx, argv[2], strlen(argv[2]), NULL, NULL, &fail); if (!invoice) errx(ERROR_BAD_DECODE, "Bad invoice: %s", fail); - if (invoice->chain) - print_chain(invoice->chain); - - if (invoice->offer_id) { - print_offer_id(invoice->offer_id); - } - if (must_have(invoice, amount)) - well_formed &= print_amount(invoice->chain, - NULL, - *invoice->amount); - if (must_have(invoice, description)) - print_description(invoice->description); - if (invoice->features) - print_features(invoice->features); - if (invoice->paths) { - must_have(invoice, blindedpay); - well_formed &= print_blindedpaths(invoice->paths, - invoice->blindedpay); - } else - must_not_have(invoice, blindedpay); - if (invoice->issuer) - print_issuer(invoice->issuer); - if (must_have(invoice, node_id)) - print_node_id(invoice->node_id); - if (invoice->quantity) - print_quantity(*invoice->quantity); - if (invoice->refund_for) { - print_refund_for(invoice->refund_for); - if (must_have(invoice, refund_signature)) - well_formed &= print_signature("invoice", - "refund_signature", - invoice->fields, - invoice->payer_key, - invoice->refund_signature); - } else { - must_not_have(invoice, refund_signature); + if (invoice->invreq_payer_id) { + if (invoice->offer_node_id) { + invoice_offer_id(invoice, &offer_id); + print_hash("offer_id", &offer_id); + } + invoice_invreq_id(invoice, &invreq_id); + print_hash("invreq_id", &invreq_id); } - if (invoice->recurrence_counter) { - well_formed &= - print_recurrence_counter_with_base(invoice->recurrence_counter, - invoice->recurrence_start, - invoice->recurrence_basetime); + + /* FIXME: We can do more intra-field checking! */ + if (must_have(invoice, invreq_metadata)) + print_hex("invreq_metadata", invoice->invreq_metadata); + if (invoice->offer_chains) + print_offer_chains(invoice->offer_chains); + if (invoice->offer_amount) + well_formed &= print_offer_amount(invoice->offer_chains, + invoice->offer_currency, + *invoice->offer_amount); + if (must_have(invoice, offer_description)) + well_formed &= print_utf8("offer_description", invoice->offer_description); + if (invoice->offer_features) + print_features("offer_features", invoice->offer_features); + if (invoice->offer_absolute_expiry) + print_abstime("offer_absolute_expiry", *invoice->offer_absolute_expiry); + if (must_have(invoice, offer_paths)) + print_blindedpaths("offer_paths", invoice->offer_paths, NULL); + if (invoice->offer_issuer) + well_formed &= print_utf8("offer_issuer", invoice->offer_issuer); + if (invoice->offer_quantity_max) + print_u64("offer_quantity_max", *invoice->offer_quantity_max); + if (invoice->offer_node_id) + print_node_id("offer_node_id", invoice->offer_node_id); + if (invoice->offer_recurrence) + well_formed &= print_recurrance(invoice->offer_recurrence, + invoice->offer_recurrence_paywindow, + invoice->offer_recurrence_limit, + invoice->offer_recurrence_base); + if (invoice->invreq_chain) + print_invreq_chain(invoice->invreq_chain); + if (invoice->invreq_amount) + print_msat("invreq_amount", *invoice->invreq_amount); + if (invoice->invreq_features) + print_features("invreq_features", invoice->invreq_features); + if (invoice->invreq_quantity) + print_u64("invreq_quantity", *invoice->invreq_quantity); + if (must_have(invoice, invreq_payer_id)) + print_node_id("invreq_payer_id", invoice->invreq_payer_id); + if (invoice->invreq_payer_note) + well_formed &= print_utf8("invreq_payer_note", invoice->invreq_payer_note); + if (invoice->invreq_recurrence_counter) { + well_formed &= print_recurrence_counter_with_base(invoice->invreq_recurrence_counter, + invoice->invreq_recurrence_start, + invoice->invoice_recurrence_basetime); } else { - must_not_have(invoice, recurrence_start); - must_not_have(invoice, recurrence_basetime); + must_not_have(invoice, invreq_recurrence_start); } - if (must_have(invoice, payer_key)) - print_payer_key(invoice->payer_key, invoice->payer_info); - if (must_have(invoice, created_at)) - print_created_at(*invoice->created_at); - if (invoice->payer_note) - print_payer_note(invoice->payer_note); - print_relative_expiry(invoice->created_at, - invoice->relative_expiry); - if (must_have(invoice, payment_hash)) - print_payment_hash(invoice->payment_hash); - if (must_have(invoice, cltv)) - print_cltv(*invoice->cltv); - if (invoice->fallbacks) - print_fallbacks(invoice->fallbacks); + if (must_have(invoice, invoice_paths)) + print_blindedpaths("invoice_paths", + invoice->invoice_paths, + invoice->invoice_blindedpay); + if (must_have(invoice, invoice_created_at)) + print_abstime("invoice_created_at", + *invoice->invoice_created_at); + print_relative_expiry(invoice->invoice_created_at, + invoice->invoice_relative_expiry); + if (must_have(invoice, invoice_payment_hash)) + print_hash("invoice_payment_hash", invoice->invoice_payment_hash); + if (must_have(invoice, invoice_amount)) + print_msat("invoice_amount", *invoice->invoice_amount); + if (invoice->invoice_fallbacks) + print_fallbacks(invoice->invoice_fallbacks); + if (invoice->invoice_features) + print_features("invoice_features", invoice->invoice_features); + if (must_have(invoice, invoice_node_id)) + print_node_id("invoice_node_id", invoice->invoice_node_id); if (must_have(invoice, signature)) well_formed &= print_signature("invoice", "signature", invoice->fields, - invoice->node_id, + invoice->invoice_node_id, invoice->signature); if (!print_extra_fields(invoice->fields)) well_formed = false; diff --git a/devtools/mkfunding.c b/devtools/mkfunding.c index 878212de086a..d31ddb4ebe25 100644 --- a/devtools/mkfunding.c +++ b/devtools/mkfunding.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -86,7 +87,7 @@ int main(int argc, char *argv[]) struct bitcoin_txid txid; u8 **witnesses; - setup_locale(); + common_setup(argv[0]); chainparams = chainparams_for_network("bitcoin"); secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | @@ -143,6 +144,9 @@ int main(int argc, char *argv[]) type_to_string(NULL, struct amount_sat, &input.amount), type_to_string(NULL, struct amount_sat, &fee)); + /* Find the P2WPKH script from input pubkey */ + input.scriptPubkey = scriptpubkey_p2wpkh(NULL, &inputkey); + /* No change output, so we don't need a bip32 base. */ tx = funding_tx(NULL, &input, funding_amount, &funding_localkey, &funding_remotekey); @@ -168,6 +172,7 @@ int main(int argc, char *argv[]) type_to_string(NULL, struct bitcoin_txid, &txid)); printf("tx: %s\n", tal_hex(NULL, linearize_tx(NULL, tx))); + common_shutdown(); return 0; } diff --git a/devtools/onion.c b/devtools/onion.c index 58c76c85197b..b08a6f25bd94 100644 --- a/devtools/onion.c +++ b/devtools/onion.c @@ -7,7 +7,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -79,15 +80,12 @@ static void do_generate(int argc, char **argv, sphinx_add_hop_has_length(sp, &path[i], take(onion_final_hop(NULL, amt, i, amt, - NULL, NULL, NULL, NULL))); else sphinx_add_hop_has_length(sp, &path[i], take(onion_nonfinal_hop(NULL, &scid, - amt, i, - NULL, - NULL))); + amt, i))); } } diff --git a/devtools/print_wire.c b/devtools/print_wire.c index 742b72de6e32..9b1792f1525b 100644 --- a/devtools/print_wire.c +++ b/devtools/print_wire.c @@ -321,7 +321,6 @@ PRINTWIRE_STRUCT_TYPE_TO_STRING(bitcoin_blkid) PRINTWIRE_STRUCT_TYPE_TO_STRING(bitcoin_txid) PRINTWIRE_STRUCT_TYPE_TO_STRING(channel_id) PRINTWIRE_STRUCT_TYPE_TO_STRING(node_id) -PRINTWIRE_STRUCT_TYPE_TO_STRING(point32) PRINTWIRE_STRUCT_TYPE_TO_STRING(preimage) PRINTWIRE_STRUCT_TYPE_TO_STRING(pubkey) PRINTWIRE_STRUCT_TYPE_TO_STRING(sha256) diff --git a/devtools/print_wire.h b/devtools/print_wire.h index 3165e0e03abc..74607d1a550c 100644 --- a/devtools/print_wire.h +++ b/devtools/print_wire.h @@ -35,7 +35,6 @@ bool printwire_bitcoin_txid(const char *fieldname, const u8 **cursor, size_t *pl bool printwire_channel_id(const char *fieldname, const u8 **cursor, size_t *plen); bool printwire_amount_sat(const char *fieldname, const u8 **cursor, size_t *plen); bool printwire_amount_msat(const char *fieldname, const u8 **cursor, size_t *plen); -bool printwire_point32(const char *fieldname, const u8 **cursor, size_t *plen); bool printwire_preimage(const char *fieldname, const u8 **cursor, size_t *plen); bool printwire_pubkey(const char *fieldname, const u8 **cursor, size_t *plen); bool printwire_node_id(const char *fieldname, const u8 **cursor, size_t *plen); diff --git a/devtools/reduce-includes.sh b/devtools/reduce-includes.sh index c71cf32b4de7..81e4f8770c3e 100755 --- a/devtools/reduce-includes.sh +++ b/devtools/reduce-includes.sh @@ -17,12 +17,11 @@ for file; do grep -F -v "$LINE" "$file" > "$file".c if $CCMD /tmp/out.$$.o "$file".c 2>/dev/null; then - # shellcheck disable=SC2039 - echo -n "-$LINE" + printf "%s" "-$LINE" mv "$file".c "$file" else - # shellcheck disable=SC2039 - echo -n "." + # shellcheck disable=SC2039,SC3037 + printf "." rm -f "$file".c i=$((i + 1)) fi diff --git a/devtools/sql-rewrite.py b/devtools/sql-rewrite.py index 7ae209403c3b..03c358a643c7 100755 --- a/devtools/sql-rewrite.py +++ b/devtools/sql-rewrite.py @@ -75,6 +75,7 @@ def rewrite_single(self, q): typemapping = { r'BLOB': 'BYTEA', + r'_ROWID_': '(((ctid::text::point)[0]::bigint << 32) | (ctid::text::point)[1]::bigint)', # Yeah, I know... r'CURRENT_TIMESTAMP\(\)': "EXTRACT(epoch FROM now())", } diff --git a/doc/FUZZING.md b/doc/FUZZING.md index 7bd9c9ed912e..1be23dfcd4ed 100644 --- a/doc/FUZZING.md +++ b/doc/FUZZING.md @@ -65,5 +65,7 @@ In order to write a new target: repeatedly with mutated data. - read about [what makes a good fuzz target](https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md). -A simple example is [`fuzz-addr`](tests/fuzz/fuzz-addr.c). It setups the chainparams and +A simple example is [`fuzz-addr`][tests/fuzz/fuzz-addr.c]. It setups the chainparams and context (wally, tmpctx, ..) in `init()` then bruteforces the bech32 encoder in `run()`. + +[tests/fuzz/fuzz-addr.c]: https://github.com/ElementsProject/lightning/blob/master/tests/fuzz/fuzz-addr.c diff --git a/doc/INSTALL.md b/doc/INSTALL.md index 3d8817c16ee7..5a306ae8fc5c 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -59,7 +59,7 @@ Clone lightning: Checkout a release tag: - git checkout v0.11.2 + git checkout v22.11.1 For development or running tests, get additional dependencies: @@ -70,7 +70,7 @@ If you can't install `lowdown`, a version will be built in-tree. If you want to build the Rust plugins (currently, cln-grpc): - sudo apt-get install -y cargo rustfmt + sudo apt-get install -y cargo rustfmt protobuf-compiler There are two ways to build core lightning, and this depends on how you want use it. @@ -142,7 +142,7 @@ $ cd lightning Checkout a release tag: ``` -$ git checkout v0.11.2 +$ git checkout v22.11.1 ``` Build and install lightning: @@ -287,7 +287,7 @@ Clone lightning: Checkout a release tag: - $ git checkout v0.11.2 + $ git checkout v22.11.1 Build lightning: @@ -305,6 +305,16 @@ need to include `testnet=1` ./lightningd/lightningd & ./cli/lightning-cli help + +To install the built binaries into your system, you'll need to run `make install`: + + make install + +On an M1 mac you may need to use this command instead: + + sudo PATH="/usr/local/opt:$PATH" LIBRARY_PATH=/opt/homebrew/lib CPATH=/opt/homebrew/include make install + + To Build on Arch Linux --------------------- @@ -401,9 +411,9 @@ Obtain and install cross-compiled versions of sqlite3, gmp and zlib: Download and build zlib: - wget https://zlib.net/zlib-1.2.12.tar.gz - tar xvf zlib-1.2.12.tar.gz - cd zlib-1.2.12 + wget https://zlib.net/fossils/zlib-1.2.13.tar.gz + tar xvf zlib-1.2.13.tar.gz + cd zlib-1.2.13 ./configure --prefix=$QEMU_LD_PREFIX make make install diff --git a/doc/Makefile b/doc/Makefile index 527b47bca268..4bc98106db3c 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -7,7 +7,9 @@ doc-wrongdir: MANPAGES := doc/lightning-cli.1 \ doc/lightningd.8 \ doc/lightningd-config.5 \ + doc/lightningd-rpc.7 \ doc/lightning-addgossip.7 \ + doc/lightning-autoclean-once.7 \ doc/lightning-autoclean-status.7 \ doc/lightning-batching.7 \ doc/lightning-bkpr-channelsapy.7 \ @@ -79,7 +81,6 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-sendonionmessage.7 \ doc/lightning-sendpay.7 \ doc/lightning-setchannel.7 \ - doc/lightning-setchannelfee.7 \ doc/lightning-sendcustommsg.7 \ doc/lightning-signmessage.7 \ doc/lightning-staticbackup.7 \ @@ -102,7 +103,8 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-listnodes.7 \ doc/lightning-listconfigs.7 \ doc/lightning-help.7 \ - doc/lightning-getlog.7 + doc/lightning-getlog.7 \ + doc/reckless.7 doc-all: $(MANPAGES) doc/index.rst @@ -181,6 +183,7 @@ check: check-manpages check-manpages: all-programs check-config-docs default-targets @tools/check-manpage.sh cli/lightning-cli doc/lightning-cli.1.md @tools/check-manpage.sh "lightningd/lightningd --lightning-dir=/tmp/" doc/lightningd-config.5.md + @awk '/^$$/ { do { getline } while ($$0 ~ /^( {4,}|\t)/) } /^\s*```/ { do { getline } while ($$0 !~ /^\s*```/) } /^([^`_\\]|`([^`\\]|\\.)*`|\b_|_\b|\\.)*\B_\B/ { print "" ; print "Unescaped underscore at " FILENAME ":" NR ":" ; print ; ret = 1 } ENDFILE { NR = 0 } END { exit ret }' doc/*.[0-9].md # Makes sure that fields mentioned in schema are in man page, and vice versa. check-config-docs: @@ -194,4 +197,9 @@ doc-clean: $(RM) doc/deployable-lightning.{aux,bbl,blg,dvi,log,out,tex} doc/index.rst: $(MANPAGES:=.md) - @$(call VERBOSE, "genidx $@",(grep -v "^ lightning.*\.[0-9]\.md>$$" $@; for m in $$(cd doc && ls lightningd*.[0-9].md lightning-*.[0-9].md); do echo " $${m%.[0-9].md} <$$m>"; done |$(SORT)) > $@.tmp.$$$$ && mv $@.tmp.$$$$ $@) + @$(call VERBOSE, "genidx $@", \ + find doc -maxdepth 1 -name '*\.[0-9]\.md' | \ + cut -b 5- | LC_ALL=C sort | \ + sed 's/\(.*\)\.\(.*\).*\.md/\1 <\1.\2.md>/' | \ + python3 devtools/blockreplace.py doc/index.rst manpages --language=rst --indent " " \ + ) diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index d00867a7763e..b98576a3b198 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -7,7 +7,9 @@ variety of ways: - **Command line option passthrough** allows plugins to register their own command line options that are exposed through `lightningd` so - that only the main process needs to be configured[^options]. + that only the main process needs to be configured. Option values are not + remembered when a plugin is stopped or killed, but can be passed as parameters + to [`plugin start`][lightning-plugin]. - **JSON-RPC command passthrough** adds a way for plugins to add their own commands to the JSON-RPC interface. - **Event stream subscriptions** provide plugins with a push-based @@ -23,13 +25,10 @@ server and `lightningd` acting as client. The plugin file needs to be executable (e.g. use `chmod a+x plugin_name`) A `helloworld.py` example plugin based on [pyln-client][pyln-client] -can be found [here](../contrib/plugins/helloworld.py). There is also a -[repository](https://github.com/lightningd/plugins) with a collection of +can be found [here][contrib/plugins]. +There is also a [repository](https://github.com/lightningd/plugins) with a collection of actively maintained plugins and finally, `lightningd`'s own internal -[tests](../tests) can be a useful (and most reliable) resource. - -[^options]: Only for plugins that start when `lightningd` starts, option - values are not remembered when a plugin is stopped or killed. +[tests][tests] can be a useful (and most reliable) resource. ### Warning @@ -136,6 +135,7 @@ example: "method": "mycustomnotification" } ], + "nonnumericids": true, "dynamic": true } ``` @@ -158,6 +158,13 @@ you plan on removing them: this will disable them if the user sets `allow-deprecated-apis` to false (which every developer should do, right?). +The `nonnumericids` indicates that the plugin can handle +string JSON request `id` fields: prior to v22.11 lightningd used numbers +for these, and the change to strings broke some plugins. If not set, +then strings will be used once this feature is removed after v23.05. +See [the lightning-rpc documentation][lightning-rpc.7.md] for how to handle +JSON `id` fields! + The `dynamic` indicates if the plugin can be managed after `lightningd` has been started using the [plugin][lightning-plugin] JSON-RPC command. Critical plugins that should not be stopped should set it to false. Plugin `options` can be passed to dynamic plugins as argument to the `plugin` command . @@ -472,6 +479,7 @@ old and new channel states, the type of `cause` and a `message`. "peer_id": "03bc9337c7a28bb784d67742ebedd30a93bacdf7e4ca16436ef3798000242b2251", "channel_id": "a2d0851832f0e30a0cf778a826d72f077ca86b69f72677e0267f23f63a0599b4", "short_channel_id" : "561820x1020x1", + "timestamp":"2023-01-05T18:27:12.145Z", "old_state": "CHANNELD_NORMAL", "new_state": "CHANNELD_SHUTTING_DOWN", "cause" : "remote", @@ -1650,23 +1658,22 @@ type prefix, since Core Lightning does not know how to parse the message. Because this is a chained hook, the daemon expects the result to be `{'result': 'continue'}`. It will fail if something else is returned. -### `onion_message_blinded` and `onion_message_ourpath` +### `onion_message_recv` and `onion_message_recv_secret` **(WARNING: experimental-offers only)** These two hooks are almost identical, in that they are called when an onion message is received. -`onion_message_blinded` is used for unsolicited messages (where the +`onion_message_recv` is used for unsolicited messages (where the source knows that it is sending to this node), and -`onion_message_ourpath` is used for messages which use a blinded path -we supplied (where the source doesn't know that this node is the -destination). The latter hook will have a `our_alias` field, the +`onion_message_recv_secret` is used for messages which use a blinded path +we supplied. The latter hook will have a `pathsecret` field, the former never will. These hooks are separate, because replies MUST be ignored unless they -use the correct path (i.e. `onion_message_ourpath`, with the expected -`our_alias`). This avoids the source trying to probe for responses +use the correct path (i.e. `onion_message_recv_secret`, with the expected +`pathsecret`). This avoids the source trying to probe for responses without using the designated delivery path. The payload for a call follows this format: @@ -1674,7 +1681,7 @@ The payload for a call follows this format: ```json { "onion_message": { - "our_alias": "02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f", + "pathsecret": "0000000000000000000000000000000000000000000000000000000000000000", "reply_first_node": "02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f", "reply_blinding": "02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f", "reply_path": [ {"id": "02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f", @@ -1691,7 +1698,6 @@ The payload for a call follows this format: All fields shown here are optional. We suggest just returning `{'result': 'continue'}`; any other result -Signed-off-by: Rusty Russell will cause the message not to be handed to any other hooks. ## Bitcoin backend @@ -1777,9 +1783,12 @@ The plugin must broadcast it and respond with the following fields: [bolt4-failure-messages]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md#failure-messages [bolt4-failure-onion]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md#returning-errors [bolt2-open-channel]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-open_channel-message -[sendcustommsg]: lightning-sendcustommsg.7.html +[sendcustommsg]: lightning-sendcustommsg.7.md [oddok]: https://github.com/lightning/bolts/blob/master/00-introduction.md#its-ok-to-be-odd -[spec]: [https://github.com/lightning/bolts] +[spec]: https://github.com/lightning/bolts [bolt9]: https://github.com/lightning/bolts/blob/master/09-features.md [lightning-plugin]: lightning-plugin.7.md -[pyln-client]: ../contrib/pyln-client +[pyln-client]: https://github.com/ElementsProject/lightning/tree/master/contrib/pyln-client +[contrib/plugins]: https://github.com/ElementsProject/lightning/tree/master/contrib/plugins +[tests]: https://github.com/ElementsProject/lightning/tree/master/tests +[lightning-rpc.7.md]: lightningd-rpc.7.md diff --git a/doc/REPRODUCIBLE.md b/doc/REPRODUCIBLE.md index b92fb5c63e2b..8bbbbaf5e703 100644 --- a/doc/REPRODUCIBLE.md +++ b/doc/REPRODUCIBLE.md @@ -69,13 +69,17 @@ the non-updated repos). The following table lists the codenames of distributions that we currently support: -| Distribution Version | Codename | -|:---------------------|:---------| -| Ubuntu 18.04 | bionic | -| Ubuntu 20.04 | focal | -| Ubuntu 22.04 | jammy | - -Depending on your host OS release you migh not have `debootstrap` +- Ubuntu 18.06: + - Distribution Version: 18.04 + - Codename: bionic +- Ubuntu 20.04: + - Distribution Version: 20.04 + - Codename: focal +- Ubuntu 22.04: + - Distribution Version: 22.04 + - Codename: jammy + +Depending on your host OS release you might not have `debootstrap` manifests for versions newer than your host OS. Due to this we run the `debootstrap` commands in a container of the latest version itself: diff --git a/doc/TOR.md b/doc/TOR.md index af5efb01653f..9edb89ec8cd1 100644 --- a/doc/TOR.md +++ b/doc/TOR.md @@ -281,7 +281,7 @@ to add *two* lines in your lightningd config file: 1. A local address which lightningd can tell Tor to connect to when connections come in, e.g. `bind-addr=127.0.0.1:9735`. -2. After that, a `addr=statictor:127.0.0.1:9051` to tell +2. After that, a `addr=statictor:127.0.0.1:9051` to tell Core Lightning to set up and announce a Tor onion address (and tell Tor to send connections to our real address, above). diff --git a/doc/conf.py b/doc/conf.py index 5ae4f97be47e..a3ba6734353d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -76,7 +76,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/doc/index.rst b/doc/index.rst index ad6b847f939b..68498827ae7c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -29,7 +29,9 @@ Core Lightning Documentation :maxdepth: 1 :caption: Manpages + .. block_start manpages lightning-addgossip + lightning-autoclean-once lightning-autoclean-status lightning-batching lightning-bkpr-channelsapy @@ -42,8 +44,8 @@ Core Lightning Documentation lightning-checkmessage lightning-cli lightning-close - lightning-commando lightning-commando-rune + lightning-commando lightning-connect lightning-createinvoice lightning-createonion @@ -111,7 +113,6 @@ Core Lightning Documentation lightning-sendpay lightning-sendpsbt lightning-setchannel - lightning-setchannelfee lightning-signmessage lightning-signpsbt lightning-staticbackup @@ -126,5 +127,8 @@ Core Lightning Documentation lightning-waitinvoice lightning-waitsendpay lightning-withdraw - lightningd lightningd-config + lightningd-rpc + lightningd + reckless +.. block_end manpages diff --git a/doc/lightning-addgossip.7.md b/doc/lightning-addgossip.7.md index 72c662975342..e5ad29508b56 100644 --- a/doc/lightning-addgossip.7.md +++ b/doc/lightning-addgossip.7.md @@ -16,7 +16,7 @@ update its internal state using the gossip message. Note that currently some paths will still silently reject the gossip: it is best effort. -This is particularly used by plugins which may receive channel_update +This is particularly used by plugins which may receive channel\_update messages within error replies. RETURN VALUE @@ -42,4 +42,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:326e5801f65998e13e909d8b682e9fbc9824f3a43aa7da1d76b871882e52f293) +[comment]: # ( SHA256STAMP:6ab8038cbad395e5a65a52fe66948740ad360c123e42c28d5879f5f03369b744) diff --git a/doc/lightning-autoclean-once.7.md b/doc/lightning-autoclean-once.7.md new file mode 100644 index 000000000000..77f4983b0b88 --- /dev/null +++ b/doc/lightning-autoclean-once.7.md @@ -0,0 +1,70 @@ +lightning-autoclean-once -- A single deletion of old invoices/payments/forwards +=============================================================================== + +SYNOPSIS +-------- + +**autoclean-once** *subsystem* *age* + +DESCRIPTION +----------- + +The **autoclean-once** RPC command tell the `autoclean` plugin to do a +single sweep to delete old entries. This is a manual alternative (or +addition) to the various `autoclean-...-age` parameters which +cause autoclean to run once per hour: see lightningd-config(5). + +The *subsystem*s currently supported are: + +* `failedforwards`: routed payments which did not succeed (`failed` or `local_failed` in listforwards `status`). +* `succeededforwards`: routed payments which succeeded (`settled` in listforwards `status`). +* `failedpays`: payment attempts which did not succeed (`failed` in listpays `status`). +* `succededpays`: payment attempts which succeeded (`complete` in listpays `status`). +* `expiredinvoices`: invoices which were not paid (and cannot be) (`expired` in listinvoices `status`). +* `paidinvoices`: invoices which were paid (`paid` in listinvoices `status). + +*age* is a non-zero number in seconds. + +RETURN VALUE +------------ + +[comment]: # (GENERATE-FROM-SCHEMA-START) +On success, an object containing **autoclean** is returned. It is an object containing: + +- **succeededforwards** (object, optional): + - **cleaned** (u64): total number of deletions done this run + - **uncleaned** (u64): the total number of entries *not* deleted this run +- **failedforwards** (object, optional): + - **cleaned** (u64): total number of deletions done this run + - **uncleaned** (u64): the total number of entries *not* deleted this run +- **succeededpays** (object, optional): + - **cleaned** (u64): total number of deletions done this run + - **uncleaned** (u64): the total number of entries *not* deleted this run +- **failedpays** (object, optional): + - **cleaned** (u64): total number of deletions done this run + - **uncleaned** (u64): the total number of entries *not* deleted this run +- **paidinvoices** (object, optional): + - **cleaned** (u64): total number of deletions done this run + - **uncleaned** (u64): the total number of entries *not* deleted this run +- **expiredinvoices** (object, optional): + - **cleaned** (u64): total number of deletions done this run + - **uncleaned** (u64): the total number of entries *not* deleted this run + +[comment]: # (GENERATE-FROM-SCHEMA-END) + +AUTHOR +------ + +Rusty Russell <> is mainly responsible. + +SEE ALSO +-------- + +lightningd-config(5), lightning-autoclean-status(7) + +RESOURCES +--------- + +Main web site: + +[comment]: # ( SHA256STAMP:9853f639595b1fd8d04e41cf7fe8de9bb90d1cb132c70dd4f8db8a7cf6f1233b) diff --git a/doc/lightning-autoclean-status.7.md b/doc/lightning-autoclean-status.7.md index ca22f2675fbe..6c71bb1a95db 100644 --- a/doc/lightning-autoclean-status.7.md +++ b/doc/lightning-autoclean-status.7.md @@ -91,4 +91,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:9431024693a7c26f9519ef24bdfb8b5c26902bdc0631d427f89c9e49ecd88e13) +[comment]: # ( SHA256STAMP:151fc6cdfd277cac7e6f18e98384b40a6cc1c2a3eb2d0f1e3c26442aa0e9e8d4) diff --git a/doc/lightning-batching.7.md b/doc/lightning-batching.7.md index d69d8b290130..2f95d475586f 100644 --- a/doc/lightning-batching.7.md +++ b/doc/lightning-batching.7.md @@ -52,4 +52,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:326e5801f65998e13e909d8b682e9fbc9824f3a43aa7da1d76b871882e52f293) + +[comment]: # ( SHA256STAMP:6ab8038cbad395e5a65a52fe66948740ad360c123e42c28d5879f5f03369b744) diff --git a/doc/lightning-bkpr-channelsapy.7.md b/doc/lightning-bkpr-channelsapy.7.md index 80554c9d035e..0011b28ce2ea 100644 --- a/doc/lightning-bkpr-channelsapy.7.md +++ b/doc/lightning-bkpr-channelsapy.7.md @@ -4,7 +4,7 @@ lightning-bkpr-channelsapy -- Command to list stats on channel earnings SYNOPSIS -------- -**bkpr-channelsapy** \[*start_time*\] \[*end_time*\] +**bkpr-channelsapy** \[*start\_time*\] \[*end\_time*\] DESCRIPTION ----------- @@ -12,9 +12,9 @@ DESCRIPTION The **bkpr-channelsapy** RPC command lists stats on routing income, leasing income, and various calculated APYs for channel routed funds. -The **start_time** is a UNIX timestamp (in seconds) that filters events after the provided timestamp. Defaults to zero. +The **start\_time** is a UNIX timestamp (in seconds) that filters events after the provided timestamp. Defaults to zero. -The **end_time** is a UNIX timestamp (in seconds) that filters events up to and at the provided timestamp. Defaults to max-int. +The **end\_time** is a UNIX timestamp (in seconds) that filters events up to and at the provided timestamp. Defaults to max-int. RETURN VALUE @@ -23,14 +23,14 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object containing **channels\_apy** is returned. It is an array of objects, where each object contains: -- **account** (string): The account name. If the account is a channel, the channel_id. The 'net' entry is the rollup of all channel accounts +- **account** (string): The account name. If the account is a channel, the channel\_id. The 'net' entry is the rollup of all channel accounts - **routed\_out\_msat** (msat): Sats routed (outbound) - **routed\_in\_msat** (msat): Sats routed (inbound) - **lease\_fee\_paid\_msat** (msat): Sats paid for leasing inbound (liquidity ads) - **lease\_fee\_earned\_msat** (msat): Sats earned for leasing outbound (liquidity ads) - **pushed\_out\_msat** (msat): Sats pushed to peer at open - **pushed\_in\_msat** (msat): Sats pushed in from peer at open -- **our\_start\_balance\_msat** (msat): Starting balance in channel at funding. Note that if our start ballance is zero, any _initial field will be omitted (can't divide by zero) +- **our\_start\_balance\_msat** (msat): Starting balance in channel at funding. Note that if our start balance is zero, any \_initial field will be omitted (can't divide by zero) - **channel\_start\_balance\_msat** (msat): Total starting balance at funding - **fees\_out\_msat** (msat): Fees earned on routed outbound - **utilization\_out** (string): Sats routed outbound / total start balance @@ -65,4 +65,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:8ec833f8261ab8b559f0d645d6da45322b388905413ef262d95f5039d533fdc8) +[comment]: # ( SHA256STAMP:05c9260f9ba49e3c3333ec7d4b0e671f81b8f8cd32cde87ea46c532b54ae7e54) diff --git a/doc/lightning-bkpr-dumpincomecsv.7.md b/doc/lightning-bkpr-dumpincomecsv.7.md index 17b3ae4cb670..440cad07ecb3 100644 --- a/doc/lightning-bkpr-dumpincomecsv.7.md +++ b/doc/lightning-bkpr-dumpincomecsv.7.md @@ -4,19 +4,19 @@ lightning-bkpr-dumpincomecsv -- Command to emit a CSV of income events SYNOPSIS -------- -**bkpr-dumpincomecsv** *csv_format* \[*csv_file*\] \[*consolidate_fees*\] \[*start_time*\] \[*end_time*\] +**bkpr-dumpincomecsv** *csv\_format* \[*csv\_file*\] \[*consolidate\_fees*\] \[*start\_time*\] \[*end\_time*\] DESCRIPTION ----------- -The **bkpr-dumpincomcsv** RPC command writes a CSV file to disk at *csv_file* +The **bkpr-dumpincomcsv** RPC command writes a CSV file to disk at *csv\_file* location. This is a formatted output of the **listincome** RPC command. -**csv_format** is which CSV format to use. See RETURN VALUE for options. +**csv\_format** is which CSV format to use. See RETURN VALUE for options. -**csv_file** is the on-disk destination of the generated CSV file. +**csv\_file** is the on-disk destination of the generated CSV file. -If **consolidate_fees** is true, we emit a single, consolidated event for +If **consolidate\_fees** is true, we emit a single, consolidated event for any onchain-fees for a txid and account. Otherwise, events for every update to the onchain fee calculation for this account and txid will be printed. Defaults to true. Note that this means that the events emitted are @@ -24,9 +24,9 @@ non-stable, i.e. calling **dumpincomecsv** twice may result in different onchain fee events being emitted, depending on how much information we've logged for that transaction. -The **start_time** is a UNIX timestamp (in seconds) that filters events after the provided timestamp. Defaults to zero. +The **start\_time** is a UNIX timestamp (in seconds) that filters events after the provided timestamp. Defaults to zero. -The **end_time** is a UNIX timestamp (in seconds) that filters events up to and at the provided timestamp. Defaults to max-int. +The **end\_time** is a UNIX timestamp (in seconds) that filters events up to and at the provided timestamp. Defaults to max-int. RETURN VALUE @@ -57,4 +57,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:1375c000d025b6cb72daa3b2ea64ec3212ae1aa5552c0d87918fd869d2fc5a0b) +[comment]: # ( SHA256STAMP:8c27ebf6e36fb26051ec724d44497d765454cac071d287586d2b0490e690c01c) diff --git a/doc/lightning-bkpr-inspect.7.md b/doc/lightning-bkpr-inspect.7.md index 0fd01cf2b642..a5119cd5bef9 100644 --- a/doc/lightning-bkpr-inspect.7.md +++ b/doc/lightning-bkpr-inspect.7.md @@ -52,4 +52,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:ea50ea813e46669b522ebd466619ac6f7a4be5ae38b4f976a7db70a3c01b7fae) +[comment]: # ( SHA256STAMP:1fc6c84962d2c670b3555dbdb7ffdddf33c4f5c445f3cfbab474a6c017ead06b) diff --git a/doc/lightning-bkpr-listaccountevents.7.md b/doc/lightning-bkpr-listaccountevents.7.md index e8c3f4030d17..cb09197f37ff 100644 --- a/doc/lightning-bkpr-listaccountevents.7.md +++ b/doc/lightning-bkpr-listaccountevents.7.md @@ -14,7 +14,7 @@ The **bkpr-listaccountevents** RPC command is a list of all bookkeeping events t If the optional parameter **account** is set, we only emit events for the specified account, if exists. -Note that the type **onchain_fees** that are emitted are of opposite credit/debit than as they appear in **listincome**, as **listincome** shows all events from the perspective of the node, whereas **listaccountevents** just dumps the event data as we've got it. Onchain fees are updated/recorded as we get more information about input and output spends -- the total onchain fees that were recorded for a transaction for an account can be found by summing all onchain fee events and taking the difference between the **credit_msat** and **debit_msat** for these events. We do this so that successive calls to **listaccountevents** always +Note that the type **onchain\_fees** that are emitted are of opposite credit/debit than as they appear in **listincome**, as **listincome** shows all events from the perspective of the node, whereas **listaccountevents** just dumps the event data as we've got it. Onchain fees are updated/recorded as we get more information about input and output spends -- the total onchain fees that were recorded for a transaction for an account can be found by summing all onchain fee events and taking the difference between the **credit\_msat** and **debit\_msat** for these events. We do this so that successive calls to **listaccountevents** always produce the same list of events -- no previously emitted event will be subsequently updated, rather we add a new event to the list. @@ -25,13 +25,13 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object containing **events** is returned. It is an array of objects, where each object contains: -- **account** (string): The account name. If the account is a channel, the channel_id -- **type** (string): Coin movement type (one of "onchain_fee", "chain", "channel") +- **account** (string): The account name. If the account is a channel, the channel\_id +- **type** (string): Coin movement type (one of "onchain\_fee", "chain", "channel") - **tag** (string): Description of movement - **credit\_msat** (msat): Amount credited - **debit\_msat** (msat): Amount debited - **currency** (string): human-readable bech32 part for this coin type -- **timestamp** (u32): Timestamp this event was recorded by the node. For consolidated events such as onchain_fees, the most recent timestamp +- **timestamp** (u32): Timestamp this event was recorded by the node. For consolidated events such as onchain\_fees, the most recent timestamp If **type** is "chain": @@ -42,7 +42,7 @@ If **type** is "chain": - **txid** (txid, optional): The txid of the transaction that created this event - **description** (string, optional): The description of this event -If **type** is "onchain_fee": +If **type** is "onchain\_fee": - **txid** (txid): The txid of the transaction that created this event @@ -71,4 +71,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:1ac0919bf29ebc37a92283d15a9ffa06f0f46be5fb55920b335d0c43e02a6ee4) +[comment]: # ( SHA256STAMP:2326473627193f2e7d2a1688d046106996a43602ccad64df16913e89bf67b3e8) diff --git a/doc/lightning-bkpr-listbalances.7.md b/doc/lightning-bkpr-listbalances.7.md index 21cad1db129e..851e6efff42d 100644 --- a/doc/lightning-bkpr-listbalances.7.md +++ b/doc/lightning-bkpr-listbalances.7.md @@ -21,7 +21,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object containing **accounts** is returned. It is an array of objects, where each object contains: -- **account** (string): The account name. If the account is a channel, the channel_id +- **account** (string): The account name. If the account is a channel, the channel\_id - **balances** (array of objects): - **balance\_msat** (msat): Current account balance - **coin\_type** (string): coin type, same as HRP for bech32 @@ -53,4 +53,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:2801e5f237043c6f85d35e2f4a5f69aab5d1cb6a9fcbea9ead1da2daa93265c8) +[comment]: # ( SHA256STAMP:ece6c722354576bd5de4d116547546129cdb22e950ce5e9fde3c4d7466bd255c) diff --git a/doc/lightning-bkpr-listincome.7.md b/doc/lightning-bkpr-listincome.7.md index 77378193f8d7..b56cca89246b 100644 --- a/doc/lightning-bkpr-listincome.7.md +++ b/doc/lightning-bkpr-listincome.7.md @@ -4,23 +4,23 @@ lightning-bkpr-listincome -- Command for listing all income impacting events SYNOPSIS -------- -**bkpr-listincome** \[*consolidate_fees*\] \[*start_time*\] \[*end_time*\] +**bkpr-listincome** \[*consolidate\_fees*\] \[*start\_time*\] \[*end\_time*\] DESCRIPTION ----------- The **bkpr-listincome** RPC command is a list of all income impacting events that the bookkeeper plugin has recorded for this node. -If **consolidate_fees** is true, we emit a single, consolidated event for +If **consolidate\_fees** is true, we emit a single, consolidated event for any onchain-fees for a txid and account. Otherwise, events for every update to the onchain fee calculation for this account and txid will be printed. Defaults to true. Note that this means that the events emitted are non-stable, i.e. calling **listincome** twice may result in different onchain fee events being emitted, depending on how much information we've logged for that transaction. -The **start_time** is a UNIX timestamp (in seconds) that filters events after the provided timestamp. Defaults to zero. +The **start\_time** is a UNIX timestamp (in seconds) that filters events after the provided timestamp. Defaults to zero. -The **end_time** is a UNIX timestamp (in seconds) that filters events up to and at the provided timestamp. Defaults to max-int. +The **end\_time** is a UNIX timestamp (in seconds) that filters events up to and at the provided timestamp. Defaults to max-int. RETURN VALUE ------------ @@ -28,12 +28,12 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object containing **income\_events** is returned. It is an array of objects, where each object contains: -- **account** (string): The account name. If the account is a channel, the channel_id +- **account** (string): The account name. If the account is a channel, the channel\_id - **tag** (string): Type of income event - **credit\_msat** (msat): Amount earned (income) - **debit\_msat** (msat): Amount spent (expenses) - **currency** (string): human-readable bech32 part for this coin type -- **timestamp** (u32): Timestamp this event was recorded by the node. For consolidated events such as onchain_fees, the most recent timestamp +- **timestamp** (u32): Timestamp this event was recorded by the node. For consolidated events such as onchain\_fees, the most recent timestamp - **description** (string, optional): More information about this event. If a `invoice` type, typically the bolt11/bolt12 description - **outpoint** (string, optional): The txid:outnum for this event, if applicable - **txid** (txid, optional): The txid of the transaction that created this event, if applicable @@ -57,4 +57,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:24a4784f87f26283e8849e525d51b376d3e69ae20c0941cfd745a7d07af8032a) +[comment]: # ( SHA256STAMP:8d35ccd4a389f70dc69bda4b66bd834bd48198191565fe37d4af8caa165a8108) diff --git a/doc/lightning-check.7.md b/doc/lightning-check.7.md index c859c61cbd67..e35c187074ea 100644 --- a/doc/lightning-check.7.md +++ b/doc/lightning-check.7.md @@ -26,7 +26,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **command\_to\_check** (string): the *command_to_check* argument +- **command\_to\_check** (string): the *command\_to\_check* argument [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -41,4 +41,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:22c1ad9baf37cb8c7c4b587047d40ef23f0af3d821feaf1aab6d365d724b08fe) +[comment]: # ( SHA256STAMP:0a799d16e3f191b6c5dbea039ba32c6824718b326a1178b1f4948461c8ba6a0b) diff --git a/doc/lightning-checkmessage.7.md b/doc/lightning-checkmessage.7.md index 6d5c3af4a163..da5b0c378808 100644 --- a/doc/lightning-checkmessage.7.md +++ b/doc/lightning-checkmessage.7.md @@ -50,4 +50,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:28b7c05443a785461a0134e3c2761a2e2d698cb71044f4d895d15ac7f2ee4316) +[comment]: # ( SHA256STAMP:7a8b174b98e2e339e0001de740d19ac83042617eaad9b160fa5a8a3525ce7bc4) diff --git a/doc/lightning-cli.1.md b/doc/lightning-cli.1.md index 8981c238a81e..29d2845c4435 100644 --- a/doc/lightning-cli.1.md +++ b/doc/lightning-cli.1.md @@ -71,6 +71,10 @@ print out notifications of *LEVEL* or above (one of `io`, `debug`, `info` (the default), `unusual` or `broken`: they are prefixed with `# `. +* **--filter**/**-l**=*JSON* + + This hands lightningd *JSON* as a filter, which controls what will be output, e.g. `'--filter={"help":[{"command":true}]}'`. See lightningd-rpc(7) for more details on how to specify filters. + * **--help**/**-h** Pretty-print summary of options to standard output and exit. The format can diff --git a/doc/lightning-close.7.md b/doc/lightning-close.7.md index ab767266e290..fdf3b171dc43 100644 --- a/doc/lightning-close.7.md +++ b/doc/lightning-close.7.md @@ -4,7 +4,7 @@ lightning-close -- Command for closing channels with direct peers SYNOPSIS -------- -**close** *id* [*unilateraltimeout*] [*destination*] [*fee_negotiation_step*] [*wrong_funding*] [*force_lease_closed*] [\*feerange\*] +**close** *id* [*unilateraltimeout*] [*destination*] [*fee\_negotiation\_step*] [*wrong\_funding*] [*force\_lease\_closed*] [\*feerange\*] DESCRIPTION ----------- @@ -31,7 +31,7 @@ the peer hasn't offered the `option_shutdown_anysegwit` feature, then taproot addresses (or other v1+ segwit) are not allowed. Tell your friends to upgrade! -The *fee_negotiation_step* parameter controls how closing fee +The *fee\_negotiation\_step* parameter controls how closing fee negotiation is performed assuming the peer proposes a fee that is different than our estimate. (Note that modern peers use the quick-close protocol which does not allow negotiation: see *feerange* instead). @@ -50,8 +50,8 @@ we quickly accept the peer's proposal. The default is "50%". -*wrong_funding* can only be specified if both sides have offered -the "shutdown_wrong_funding" feature (enabled by the +*wrong\_funding* can only be specified if both sides have offered +the "shutdown\_wrong\_funding" feature (enabled by the **experimental-shutdown-wrong-funding** option): it must be a transaction id followed by a colon then the output number. Instead of negotiating a shutdown to spend the expected funding transaction, the @@ -59,13 +59,13 @@ shutdown transaction will spend this output instead. This is only allowed if this peer opened the channel and the channel is unused: it can rescue openings which have been manually miscreated. -*force_lease_closed* if the channel has funds leased to the peer -(option_will_fund), we prevent initiation of a mutual close +*force\_lease\_closed* if the channel has funds leased to the peer +(option\_will\_fund), we prevent initiation of a mutual close unless this flag is passed in. Defaults to false. *feerange* is an optional array [ *min*, *max* ], indicating the minimum and maximum feerates to offer: the peer will obey these if it -supports the quick-close protocol. *slow* and *unilateral_close* are +supports the quick-close protocol. *slow* and *unilateral\_close* are the defaults. Rates are one of the strings *urgent* (aim for next block), *normal* @@ -135,4 +135,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:6a438338ae697732f0100f9e1566b9b8d189778cdb05681305e060487d68663e) +[comment]: # ( SHA256STAMP:f323b7998a41c28a6c398b8e4ebbefcd227b7624dcf7c03373b518bc55211dd6) diff --git a/doc/lightning-commando-rune.7.md b/doc/lightning-commando-rune.7.md index 04b84b0a7035..f78e0b7f19c2 100644 --- a/doc/lightning-commando-rune.7.md +++ b/doc/lightning-commando-rune.7.md @@ -30,7 +30,7 @@ is listpeers", or "method is listpeers OR time is before 2023". Alternatives us being run: * time: the current UNIX time, e.g. "time<1656759180". -* id: the node_id of the peer, e.g. "id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605". +* id: the node\_id of the peer, e.g. "id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605". * method: the command being run, e.g. "method=withdraw". * rate: the rate limit, per minute, e.g. "rate=60". * pnum: the number of parameters. e.g. "pnum<2". @@ -218,4 +218,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:08ded3c93fea629f414a96f12ac02de1000743a487ec8989ba1510a59861ccc1) +[comment]: # ( SHA256STAMP:5f4371a060861ca04019948242803f8b6254627f9993a866ec6e119d8a14cef6) diff --git a/doc/lightning-commando.7.md b/doc/lightning-commando.7.md index 871083f12083..52ab1f94936a 100644 --- a/doc/lightning-commando.7.md +++ b/doc/lightning-commando.7.md @@ -4,13 +4,13 @@ lightning-commando -- Command to Send a Command to a Remote Peer SYNOPSIS -------- -**commando** *peer_id* *method* [*params*] [*rune*] +**commando** *peer\_id* *method* [*params*] [*rune*] DESCRIPTION ----------- The **commando** RPC command is a homage to bad 80s movies. It also -sends a directly-connected *peer_id* a custom message, containing a +sends a directly-connected *peer\_id* a custom message, containing a request to run *method* (with an optional dictionary of *params*); generally the peer will only allow you to run a command if it has provided you with a *rune* which allows it. diff --git a/doc/lightning-connect.7.md b/doc/lightning-connect.7.md index ab6752fa303b..189b3da8cafc 100644 --- a/doc/lightning-connect.7.md +++ b/doc/lightning-connect.7.md @@ -104,4 +104,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:581c6243302c8fa5c9234de97e1f6af842bbfee544850c55281924721b46432f) +[comment]: # ( SHA256STAMP:b9f59ec875e50da2251c3e9e6166e62cfc473a46e29eece96705f34c27841782) diff --git a/doc/lightning-createinvoice.7.md b/doc/lightning-createinvoice.7.md index 477f971fbafa..587d20022529 100644 --- a/doc/lightning-createinvoice.7.md +++ b/doc/lightning-createinvoice.7.md @@ -34,7 +34,7 @@ RETURN VALUE On success, an object is returned, containing: - **label** (string): the label for the invoice -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): Whether it has been paid, or can no longer be paid (one of "paid", "expired", "unpaid") - **description** (string): Description extracted from **bolt11** or **bolt12** - **expires\_at** (u64): UNIX timestamp of when invoice expires (or expired) @@ -44,9 +44,9 @@ On success, an object is returned, containing: - **pay\_index** (u64, optional): Incrementing id for when this was paid (**status** *paid* only) - **amount\_received\_msat** (msat, optional): Amount actually received (**status** *paid* only) - **paid\_at** (u64, optional): UNIX timestamp of when invoice was paid (**status** *paid* only) -- **payment\_preimage** (secret, optional): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) +- **payment\_preimage** (secret, optional): the proof of payment: SHA256 of this **payment\_hash** (always 64 characters) - **local\_offer\_id** (hex, optional): the *id* of our offer which created this invoice (**experimental-offers** only). (always 64 characters) -- **payer\_note** (string, optional): the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only). +- **invreq\_payer\_note** (string, optional): the optional *invreq\_payer\_note* from invoice\_request which created this invoice (**experimental-offers** only). [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -75,4 +75,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:9fc2c7cb6e5980774a768dd9ddfe81e254c084554c159e6b07e92e703dc10595) +[comment]: # ( SHA256STAMP:8f1ca1ec7fafd96f38d6ffaa0b9b1d0ac0a5ec5d535c45b8b4cb08257468a6b2) diff --git a/doc/lightning-createonion.7.md b/doc/lightning-createonion.7.md index bbeace8f2055..e3615360ac02 100644 --- a/doc/lightning-createonion.7.md +++ b/doc/lightning-createonion.7.md @@ -4,7 +4,7 @@ lightning-createonion -- Low-level command to create a custom onion SYNOPSIS -------- -**createonion** *hops* *assocdata* [*session_key*] [*onion_size*] +**createonion** *hops* *assocdata* [*session\_key*] [*onion\_size*] DESCRIPTION ----------- @@ -75,13 +75,13 @@ The *assocdata* parameter specifies the associated data that the onion should commit to. If the onion is to be used to send a payment later it MUST match the `payment_hash` of the payment in order to be valid. -The optional *session_key* parameter can be used to specify a secret that is +The optional *session\_key* parameter can be used to specify a secret that is used to generate the shared secrets used to encrypt the onion for each hop. It should only be used for testing or if a specific shared secret is important. If not specified it will be securely generated internally, and the shared secrets will be returned. -The optional *onion_size* parameter specifies a size different from the default +The optional *onion\_size* parameter specifies a size different from the default payment onion (1300 bytes). May be used for custom protocols like trampoline routing. @@ -91,7 +91,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **onion** (hex): the onion packet (*onion_size* bytes) +- **onion** (hex): the onion packet (*onion\_size* bytes) - **shared\_secrets** (array of secrets): one shared secret for each node in the *hops* parameter: - the shared secret with this hop (always 64 characters) @@ -132,4 +132,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:c8bd0abd35904cb009b95a7d345be4cc254cff2a427dcf04679a64a6e62dce1e) +[comment]: # ( SHA256STAMP:da11700fff0b97851d2c649b3717a3b51c606a930b84dbdd24fb3367dd7de8cb) diff --git a/doc/lightning-datastore.7.md b/doc/lightning-datastore.7.md index 81c7409f790b..052798f8ceeb 100644 --- a/doc/lightning-datastore.7.md +++ b/doc/lightning-datastore.7.md @@ -66,4 +66,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:cb5bccd7efd8438c61b909bda419e0300993b2b2267cb335c1f91d12bd402b3e) +[comment]: # ( SHA256STAMP:65c6c1cb555e5042c3d5d7ad4f9577ec75838d497349c8caee7e186486e09d04) diff --git a/doc/lightning-decode.7.md b/doc/lightning-decode.7.md index 53e23c4ea9fb..58fd9b259c62 100644 --- a/doc/lightning-decode.7.md +++ b/doc/lightning-decode.7.md @@ -24,130 +24,221 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **type** (string): what kind of object it decoded to (one of "bolt12 offer", "bolt12 invoice", "bolt12 invoice_request", "bolt11 invoice", "rune") +- **type** (string): what kind of object it decoded to (one of "bolt12 offer", "bolt12 invoice", "bolt12 invoice\_request", "bolt11 invoice", "rune") - **valid** (boolean): if this is false, you *MUST* not use the result except for diagnostics! If **type** is "bolt12 offer", and **valid** is *true*: - - **offer\_id** (hex): the id of this offer (merkle hash of non-signature fields) (always 64 characters) - - **node\_id** (point32): x-only public key of the offering node - - **description** (string): the description of the purpose of the offer - - **signature** (bip340sig, optional): BIP-340 signature of the *node_id* on this offer - - **chains** (array of hexs, optional): which blockchains this offer is for (missing implies bitcoin mainnet only): + - **offer\_id** (hex): the id we use to identify this offer (always 64 characters) + - **offer\_description** (string): the description of the purpose of the offer + - **offer\_node\_id** (pubkey): public key of the offering node + - **offer\_chains** (array of hexs, optional): which blockchains this offer is for (missing implies bitcoin mainnet only): - the genesis blockhash (always 64 characters) - - **currency** (string, optional): ISO 4217 code of the currency (missing implies Bitcoin) (always 3 characters) - - **minor\_unit** (u32, optional): the number of decimal places to apply to amount (if currency known) - - **amount** (u64, optional): the amount in the *currency* adjusted by *minor_unit*, if any - - **amount\_msat** (msat, optional): the amount in bitcoin (if specified, and no *currency*) - - **send\_invoice** (boolean, optional): present if this is a send_invoice offer (always *true*) - - **refund\_for** (hex, optional): the *payment_preimage* of invoice this is a refund for (always 64 characters) - - **vendor** (string, optional): the name of the vendor for this offer - - **features** (hex, optional): the array of feature bits for this offer - - **absolute\_expiry** (u64, optional): UNIX timestamp of when this offer expires - - **paths** (array of objects, optional): Paths to the destination: + - **offer\_metadata** (hex, optional): any metadata the creater of the offer includes + - **offer\_currency** (string, optional): ISO 4217 code of the currency (missing implies Bitcoin) (always 3 characters) + - **currency\_minor\_unit** (u32, optional): the number of decimal places to apply to amount (if currency known) + - **offer\_amount** (u64, optional): the amount in the `offer_currency` adjusted by `currency_minor_unit`, if any + - **offer\_amount\_msat** (msat, optional): the amount in bitcoin (if specified, and no `offer_currency`) + - **offer\_issuer** (string, optional): the description of the creator of the offer + - **offer\_features** (hex, optional): the feature bits of the offer + - **offer\_absolute\_expiry** (u64, optional): UNIX timestamp of when this offer expires + - **offer\_quantity\_max** (u64, optional): the maximum quantity (or, if 0, means any quantity) + - **offer\_paths** (array of objects, optional): Paths to the destination: + - **first\_node\_id** (pubkey): the (presumably well-known) public key of the start of the path - **blinding** (pubkey): blinding factor for this path - **path** (array of objects): an individual path: - - **node\_id** (pubkey): node_id of the hop + - **blinded\_node\_id** (pubkey): node\_id of the hop - **encrypted\_recipient\_data** (hex): encrypted TLV entry for this hop - - **quantity\_min** (u64, optional): the minimum quantity - - **quantity\_max** (u64, optional): the maximum quantity - - **recurrence** (object, optional): how often to this offer should be used: + - **offer\_recurrence** (object, optional): how often to this offer should be used: - **time\_unit** (u32): the BOLT12 time unit - - **period** (u32): how many *time_unit* per payment period - - **time\_unit\_name** (string, optional): the name of *time_unit* (if valid) + - **period** (u32): how many `time_unit` per payment period + - **time\_unit\_name** (string, optional): the name of `time_unit` (if valid) - **basetime** (u64, optional): period starts at this UNIX timestamp - - **start\_any\_period** (u64, optional): you can start at any period (only if **basetime** present) + - **start\_any\_period** (u64, optional): you can start at any period (only if `basetime` present) - **limit** (u32, optional): maximum period number for recurrence - **paywindow** (object, optional): when within a period will payment be accepted (default is prior and during the period): - **seconds\_before** (u32): seconds prior to period start - **seconds\_after** (u32): seconds after to period start - **proportional\_amount** (boolean, optional): amount should be scaled if payed after period start (always *true*) + - **unknown\_offer\_tlvs** (array of objects, optional): Any extra fields we didn't know how to parse: + - **type** (u64): The type + - **length** (u64): The length + - **value** (hex): The value - the following warnings are possible: - - **warning\_offer\_unknown\_currency**: The currency code is unknown (so no **minor_unit**) + - **warning\_unknown\_offer\_currency**: The currency code is unknown (so no `currency_minor_unit`) If **type** is "bolt12 offer", and **valid** is *false*: - the following warnings are possible: - - **warning\_offer\_missing\_description**: No **description** + - **warning\_missing\_offer\_node\_id**: `offer_node_id` is not present + - **warning\_invalid\_offer\_description**: `offer_description` is not valid UTF8 + - **warning\_missing\_offer\_description**: `offer_description` is not present + - **warning\_invalid\_offer\_currency**: `offer_currency_code` is not valid UTF8 + - **warning\_invalid\_offer\_issuer**: `offer_issuer` is not valid UTF8 + +If **type** is "bolt12 invoice\_request", and **valid** is *true*: + + - **offer\_description** (string): the description of the purpose of the offer + - **offer\_node\_id** (pubkey): public key of the offering node + - **invreq\_metadata** (hex): the payer-provided blob to derive invreq\_payer\_id + - **invreq\_payer\_id** (hex): the payer-provided key + - **signature** (bip340sig): BIP-340 signature of the `invreq_payer_id` on this invoice\_request + - **offer\_id** (hex, optional): the id we use to identify this offer (always 64 characters) + - **offer\_chains** (array of hexs, optional): which blockchains this offer is for (missing implies bitcoin mainnet only): + - the genesis blockhash (always 64 characters) + - **offer\_metadata** (hex, optional): any metadata the creator of the offer includes + - **offer\_currency** (string, optional): ISO 4217 code of the currency (missing implies Bitcoin) (always 3 characters) + - **currency\_minor\_unit** (u32, optional): the number of decimal places to apply to amount (if currency known) + - **offer\_amount** (u64, optional): the amount in the `offer_currency` adjusted by `currency_minor_unit`, if any + - **offer\_amount\_msat** (msat, optional): the amount in bitcoin (if specified, and no `offer_currency`) + - **offer\_issuer** (string, optional): the description of the creator of the offer + - **offer\_features** (hex, optional): the feature bits of the offer + - **offer\_absolute\_expiry** (u64, optional): UNIX timestamp of when this offer expires + - **offer\_quantity\_max** (u64, optional): the maximum quantity (or, if 0, means any quantity) + - **offer\_paths** (array of objects, optional): Paths to the destination: + - **first\_node\_id** (pubkey): the (presumably well-known) public key of the start of the path + - **blinding** (pubkey): blinding factor for this path + - **path** (array of objects): an individual path: + - **blinded\_node\_id** (pubkey): node\_id of the hop + - **encrypted\_recipient\_data** (hex): encrypted TLV entry for this hop + - **offer\_recurrence** (object, optional): how often to this offer should be used: + - **time\_unit** (u32): the BOLT12 time unit + - **period** (u32): how many `time_unit` per payment period + - **time\_unit\_name** (string, optional): the name of `time_unit` (if valid) + - **basetime** (u64, optional): period starts at this UNIX timestamp + - **start\_any\_period** (u64, optional): you can start at any period (only if `basetime` present) + - **limit** (u32, optional): maximum period number for recurrence + - **paywindow** (object, optional): when within a period will payment be accepted (default is prior and during the period): + - **seconds\_before** (u32): seconds prior to period start + - **seconds\_after** (u32): seconds after to period start + - **proportional\_amount** (boolean, optional): amount should be scaled if payed after period start (always *true*) + - **invreq\_chain** (hex, optional): which blockchain this offer is for (missing implies bitcoin mainnet only) (always 64 characters) + - **invreq\_amount\_msat** (msat, optional): the amount the invoice should be for + - **invreq\_features** (hex, optional): the feature bits of the invoice\_request + - **invreq\_quantity** (u64, optional): the number of items to invoice for + - **invreq\_payer\_note** (string, optional): a note attached by the payer + - **invreq\_recurrence\_counter** (u32, optional): which number request this is for the same invoice + - **invreq\_recurrence\_start** (u32, optional): when we're requesting to start an invoice at a non-zero period + - **unknown\_invoice\_request\_tlvs** (array of objects, optional): Any extra fields we didn't know how to parse: + - **type** (u64): The type + - **length** (u64): The length + - **value** (hex): The value + - the following warnings are possible: + - **warning\_unknown\_offer\_currency**: The currency code is unknown (so no `currency_minor_unit`) + +If **type** is "bolt12 invoice\_request", and **valid** is *false*: + + - the following warnings are possible: + - **warning\_invalid\_offer\_description**: `offer_description` is not valid UTF8 + - **warning\_missing\_offer\_description**: `offer_description` is not present + - **warning\_invalid\_offer\_currency**: `offer_currency_code` is not valid UTF8 + - **warning\_invalid\_offer\_issuer**: `offer_issuer` is not valid UTF8 + - **warning\_missing\_invreq\_metadata**: `invreq_metadata` is not present + - **warning\_missing\_invreq\_payer\_id**: `invreq_payer_id` is not present + - **warning\_invalid\_invreq\_payer\_note**: `invreq_payer_note` is not valid UTF8 + - **warning\_missing\_invoice\_request\_signature**: `signature` is not present + - **warning\_invalid\_invoice\_request\_signature**: Incorrect `signature` If **type** is "bolt12 invoice", and **valid** is *true*: - - **node\_id** (point32): x-only public key of the offering node - - **signature** (bip340sig): BIP-340 signature of the *node_id* on this offer - - **amount\_msat** (msat): the amount in bitcoin - - **description** (string): the description of the purpose of the offer - - **created\_at** (u64): the UNIX timestamp of invoice creation - - **payment\_hash** (hex): the hash of the *payment_preimage* (always 64 characters) - - **relative\_expiry** (u32): the number of seconds after *created_at* when this expires - - **min\_final\_cltv\_expiry** (u32): the number of blocks required by destination - - **offer\_id** (hex, optional): the id of this offer (merkle hash of non-signature fields) (always 64 characters) - - **chain** (hex, optional): which blockchain this invoice is for (missing implies bitcoin mainnet only) (always 64 characters) - - **send\_invoice** (boolean, optional): present if this offer was a send_invoice offer (always *true*) - - **refund\_for** (hex, optional): the *payment_preimage* of invoice this is a refund for (always 64 characters) - - **vendor** (string, optional): the name of the vendor for this offer - - **features** (hex, optional): the array of feature bits for this offer - - **paths** (array of objects, optional): Paths to the destination: + - **offer\_description** (string): the description of the purpose of the offer + - **offer\_node\_id** (pubkey): public key of the offering node + - **invreq\_metadata** (hex): the payer-provided blob to derive invreq\_payer\_id + - **invreq\_payer\_id** (hex): the payer-provided key + - **invoice\_paths** (array of objects): Paths to pay the destination: + - **first\_node\_id** (pubkey): the (presumably well-known) public key of the start of the path - **blinding** (pubkey): blinding factor for this path - **path** (array of objects): an individual path: - - **node\_id** (pubkey): node_id of the hop + - **blinded\_node\_id** (pubkey): node\_id of the hop - **encrypted\_recipient\_data** (hex): encrypted TLV entry for this hop - - **quantity** (u64, optional): the quantity ordered - - **recurrence\_counter** (u32, optional): the 0-based counter for a recurring payment - - **recurrence\_start** (u32, optional): the optional start period for a recurring payment - - **recurrence\_basetime** (u32, optional): the UNIX timestamp of the first recurrence period start - - **payer\_key** (point32, optional): the transient key which identifies the payer - - **payer\_info** (hex, optional): the payer-provided blob to derive payer_key - - **fallbacks** (array of objects, optional): onchain addresses: + - **fee\_base\_msat** (msat, optional): basefee for path + - **fee\_proportional\_millionths** (u32, optional): proportional fee for path + - **cltv\_expiry\_delta** (u32, optional): CLTV delta for path + - **features** (hex, optional): features allowed for path + - **invoice\_created\_at** (u64): the UNIX timestamp of invoice creation + - **invoice\_payment\_hash** (hex): the hash of the *payment\_preimage* (always 64 characters) + - **invoice\_amount\_msat** (msat): the amount required to fulfill invoice + - **signature** (bip340sig): BIP-340 signature of the `offer_node_id` on this invoice + - **offer\_id** (hex, optional): the id we use to identify this offer (always 64 characters) + - **offer\_chains** (array of hexs, optional): which blockchains this offer is for (missing implies bitcoin mainnet only): + - the genesis blockhash (always 64 characters) + - **offer\_metadata** (hex, optional): any metadata the creator of the offer includes + - **offer\_currency** (string, optional): ISO 4217 code of the currency (missing implies Bitcoin) (always 3 characters) + - **currency\_minor\_unit** (u32, optional): the number of decimal places to apply to amount (if currency known) + - **offer\_amount** (u64, optional): the amount in the `offer_currency` adjusted by `currency_minor_unit`, if any + - **offer\_amount\_msat** (msat, optional): the amount in bitcoin (if specified, and no `offer_currency`) + - **offer\_issuer** (string, optional): the description of the creator of the offer + - **offer\_features** (hex, optional): the feature bits of the offer + - **offer\_absolute\_expiry** (u64, optional): UNIX timestamp of when this offer expires + - **offer\_quantity\_max** (u64, optional): the maximum quantity (or, if 0, means any quantity) + - **offer\_paths** (array of objects, optional): Paths to the destination: + - **first\_node\_id** (pubkey): the (presumably well-known) public key of the start of the path + - **blinding** (pubkey): blinding factor for this path + - **path** (array of objects): an individual path: + - **blinded\_node\_id** (pubkey): node\_id of the hop + - **encrypted\_recipient\_data** (hex): encrypted TLV entry for this hop + - **offer\_recurrence** (object, optional): how often to this offer should be used: + - **time\_unit** (u32): the BOLT12 time unit + - **period** (u32): how many `time_unit` per payment period + - **time\_unit\_name** (string, optional): the name of `time_unit` (if valid) + - **basetime** (u64, optional): period starts at this UNIX timestamp + - **start\_any\_period** (u64, optional): you can start at any period (only if `basetime` present) + - **limit** (u32, optional): maximum period number for recurrence + - **paywindow** (object, optional): when within a period will payment be accepted (default is prior and during the period): + - **seconds\_before** (u32): seconds prior to period start + - **seconds\_after** (u32): seconds after to period start + - **proportional\_amount** (boolean, optional): amount should be scaled if payed after period start (always *true*) + - **invreq\_chain** (hex, optional): which blockchain this offer is for (missing implies bitcoin mainnet only) (always 64 characters) + - **invreq\_amount\_msat** (msat, optional): the amount the invoice should be for + - **invreq\_features** (hex, optional): the feature bits of the invoice\_request + - **invreq\_quantity** (u64, optional): the number of items to invoice for + - **invreq\_payer\_note** (string, optional): a note attached by the payer + - **invreq\_recurrence\_counter** (u32, optional): which number request this is for the same invoice + - **invreq\_recurrence\_start** (u32, optional): when we're requesting to start an invoice at a non-zero period + - **invoice\_relative\_expiry** (u32, optional): the number of seconds after *invoice\_created\_at* when this expires + - **invoice\_fallbacks** (array of objects, optional): onchain addresses: - **version** (u8): Segwit address version - **hex** (hex): Raw encoded segwit address - **address** (string, optional): bech32 segwit address - - **refund\_signature** (bip340sig, optional): the payer key signature to get a refund + - **invoice\_features** (hex, optional): the feature bits of the invoice + - **invoice\_node\_id** (pubkey, optional): the id to pay (usually the same as offer\_node\_id) + - **invoice\_recurrence\_basetime** (u64, optional): the UNIX timestamp to base the invoice periods on + - **unknown\_invoice\_tlvs** (array of objects, optional): Any extra fields we didn't know how to parse: + - **type** (u64): The type + - **length** (u64): The length + - **value** (hex): The value + - the following warnings are possible: + - **warning\_unknown\_offer\_currency**: The currency code is unknown (so no `currency_minor_unit`) If **type** is "bolt12 invoice", and **valid** is *false*: - **fallbacks** (array of objects, optional): - the following warnings are possible: - - **warning\_invoice\_fallbacks\_version\_invalid**: **version** is > 16 - - the following warnings are possible: - - **warning\_invoice\_missing\_amount**: **amount_msat* missing - - **warning\_invoice\_missing\_description**: No **description** - - **warning\_invoice\_missing\_blinded\_payinfo**: Has **paths** without payinfo - - **warning\_invoice\_invalid\_blinded\_payinfo**: Does not have exactly one payinfo for each of **paths** - - **warning\_invoice\_missing\_recurrence\_basetime**: Has **recurrence_counter** without **recurrence_basetime** - - **warning\_invoice\_missing\_created\_at**: Missing **created_at** - - **warning\_invoice\_missing\_payment\_hash**: Missing **payment_hash** - - **warning\_invoice\_refund\_signature\_missing\_payer\_key**: Missing **payer_key** for refund_signature - - **warning\_invoice\_refund\_signature\_invalid**: **refund_signature** incorrect - - **warning\_invoice\_refund\_missing\_signature**: No **refund_signature** - -If **type** is "bolt12 invoice_request", and **valid** is *true*: - - - **offer\_id** (hex): the id of the offer this is requesting (merkle hash of non-signature fields) (always 64 characters) - - **payer\_key** (point32): the transient key which identifies the payer - - **chain** (hex, optional): which blockchain this invoice_request is for (missing implies bitcoin mainnet only) (always 64 characters) - - **amount\_msat** (msat, optional): the amount in bitcoin - - **features** (hex, optional): the array of feature bits for this offer - - **quantity** (u64, optional): the quantity ordered - - **recurrence\_counter** (u32, optional): the 0-based counter for a recurring payment - - **recurrence\_start** (u32, optional): the optional start period for a recurring payment - - **payer\_info** (hex, optional): the payer-provided blob to derive payer_key - - **recurrence\_signature** (bip340sig, optional): the payer key signature - -If **type** is "bolt12 invoice_request", and **valid** is *false*: - + - **warning\_invoice\_fallbacks\_version\_invalid**: `version` is > 16 - the following warnings are possible: - - **warning\_invoice\_request\_missing\_offer\_id**: No **offer_id** - - **warning\_invoice\_request\_missing\_payer\_key**: No **payer_key** - - **warning\_invoice\_request\_missing\_recurrence\_signature**: No **recurrence_signature** - - **warning\_invoice\_request\_invalid\_recurrence\_signature**: **recurrence_signature** incorrect + - **warning\_invalid\_offer\_description**: `offer_description` is not valid UTF8 + - **warning\_missing\_offer\_description**: `offer_description` is not present + - **warning\_invalid\_offer\_currency**: `offer_currency_code` is not valid UTF8 + - **warning\_invalid\_offer\_issuer**: `offer_issuer` is not valid UTF8 + - **warning\_missing\_invreq\_metadata**: `invreq_metadata` is not present + - **warning\_invalid\_invreq\_payer\_note**: `invreq_payer_note` is not valid UTF8 + - **warning\_missing\_invoice\_paths**: `invoice_paths` is not present + - **warning\_missing\_invoice\_blindedpay**: `invoice_blindedpay` is not present + - **warning\_missing\_invoice\_created\_at**: `invoice_created_at` is not present + - **warning\_missing\_invoice\_payment\_hash**: `invoice_payment_hash` is not present + - **warning\_missing\_invoice\_amount**: `invoice_amount` is not present + - **warning\_missing\_invoice\_recurrence\_basetime**: `invoice_recurrence_basetime` is not present + - **warning\_missing\_invoice\_node\_id**: `invoice_node_id` is not present + - **warning\_missing\_invoice\_signature**: `signature` is not present + - **warning\_invalid\_invoice\_signature**: Incorrect `signature` If **type** is "bolt11 invoice", and **valid** is *true*: - **currency** (string): the BIP173 name for the currency - **created\_at** (u64): the UNIX-style timestamp of the invoice - - **expiry** (u64): the number of seconds this is valid after *timestamp* + - **expiry** (u64): the number of seconds this is valid after `created_at` - **payee** (pubkey): the public key of the recipient - - **payment\_hash** (hex): the hash of the *payment_preimage* (always 64 characters) + - **payment\_hash** (hex): the hash of the *payment\_preimage* (always 64 characters) - **signature** (signature): signature of the *payee* on this invoice - **min\_final\_cltv\_expiry** (u32): the minimum CLTV delay for the final node - **amount\_msat** (msat, optional): Amount the invoice asked for @@ -155,7 +246,7 @@ If **type** is "bolt11 invoice", and **valid** is *true*: - **description\_hash** (hex, optional): the hash of the description, in place of *description* (always 64 characters) - **payment\_secret** (hex, optional): the secret to hand to the payee node (always 64 characters) - **features** (hex, optional): the features bitmap for this invoice - - **payment\_metadata** (hex, optional): the payment_metadata to put in the payment + - **payment\_metadata** (hex, optional): the payment\_metadata to put in the payment - **fallbacks** (array of objects, optional): onchain addresses: - **type** (string): the address type (if known) (one of "P2PKH", "P2SH", "P2WPKH", "P2WSH") - **hex** (hex): Raw encoded address @@ -201,9 +292,9 @@ SEE ALSO lightning-pay(7), lightning-offer(7), lightning-offerout(7), lightning-fetchinvoice(7), lightning-sendinvoice(7), lightning-commando-rune(7) -[BOLT #11](https://github.com/lightningnetwork/bolts/blob/master/11-payment-encoding.md). +[BOLT #11](https://github.com/lightning/bolts/blob/master/11-payment-encoding.md) -[BOLT #12](https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md). +[BOLT #12](https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md) (experimental, [bolt](https://github.com/lightning/bolts) #798) RESOURCES @@ -211,4 +302,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:610b6f9d61e79b503ff81cc164f79ea90883c6d10f5e7b28555aefabfd6e17bb) +[comment]: # ( SHA256STAMP:7920e365fe0f41fc2aa99c9e99af7c0666da229310ce50c2c2728c973069b2a7) diff --git a/doc/lightning-decodepay.7.md b/doc/lightning-decodepay.7.md index 9ba5a8b3a2db..f142a1c6407f 100644 --- a/doc/lightning-decodepay.7.md +++ b/doc/lightning-decodepay.7.md @@ -22,7 +22,7 @@ On success, an object is returned, containing: - **created\_at** (u64): the UNIX-style timestamp of the invoice - **expiry** (u64): the number of seconds this is valid after *timestamp* - **payee** (pubkey): the public key of the recipient -- **payment\_hash** (hex): the hash of the *payment_preimage* (always 64 characters) +- **payment\_hash** (hex): the hash of the *payment\_preimage* (always 64 characters) - **signature** (signature): signature of the *payee* on this invoice - **min\_final\_cltv\_expiry** (u32): the minimum CLTV delay for the final node - **amount\_msat** (msat, optional): Amount the invoice asked for @@ -30,7 +30,7 @@ On success, an object is returned, containing: - **description\_hash** (hex, optional): the hash of the description, in place of *description* (always 64 characters) - **payment\_secret** (hex, optional): the secret to hand to the payee node (always 64 characters) - **features** (hex, optional): the features bitmap for this invoice -- **payment\_metadata** (hex, optional): the payment_metadata to put in the payment +- **payment\_metadata** (hex, optional): the payment\_metadata to put in the payment - **fallbacks** (array of objects, optional): onchain addresses: - **type** (string): the address type (if known) (one of "P2PKH", "P2SH", "P2WPKH", "P2WSH") - **hex** (hex): Raw encoded address @@ -71,4 +71,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:c98fd8cac46b5446ff2d01ede6b082f64a83d4b5745d06e410af3e1dd91be8e2) +[comment]: # ( SHA256STAMP:a1163f7535526b8f9bcea137c6cd5d270e0730d2d58f8c8487794415273dd489) diff --git a/doc/lightning-deldatastore.7.md b/doc/lightning-deldatastore.7.md index c991b49a11e3..9651d2157108 100644 --- a/doc/lightning-deldatastore.7.md +++ b/doc/lightning-deldatastore.7.md @@ -49,4 +49,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:262227d8b4aaee5cad954afa4335b29bceabd787f346d1e5a990614edac7f5e7) +[comment]: # ( SHA256STAMP:0d9d6e4336f6317ca85628b76f2aa40a5172b54333a1a3931e1284d9a803f61b) diff --git a/doc/lightning-delexpiredinvoice.7.md b/doc/lightning-delexpiredinvoice.7.md index e2a9c3c45924..bfda28704d0f 100644 --- a/doc/lightning-delexpiredinvoice.7.md +++ b/doc/lightning-delexpiredinvoice.7.md @@ -38,4 +38,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:01526643e128e75057c668cd5dd36e79f075ca847bc692629e1c773b6c940ae6) +[comment]: # ( SHA256STAMP:aa572f59f54ad8ca0d2c43d41574e9068c7d5dc371927638b7c8a0c1c3b6e496) diff --git a/doc/lightning-delforward.7.md b/doc/lightning-delforward.7.md index c8ee5e2f33e9..c5ab8cddacb0 100644 --- a/doc/lightning-delforward.7.md +++ b/doc/lightning-delforward.7.md @@ -4,13 +4,13 @@ lightning-delforward -- Command for removing a forwarding entry SYNOPSIS -------- -**delforward** *in_channel* *in_htlc_id* *status* +**delforward** *in\_channel* *in\_htlc\_id* *status* DESCRIPTION ----------- The **delforward** RPC command removes a single forward from **listforwards**, -using the uniquely-identifying *in_channel* and *in_htlc_id* (and, as a sanity +using the uniquely-identifying *in\_channel* and *in\_htlc\_id* (and, as a sanity check, the *status*) given by that command. This command is mainly used by the *autoclean* plugin (see lightningd-config(7)), @@ -20,10 +20,10 @@ has no effect on the running of your node. You cannot delete forwards which have status *offered* (i.e. are currently active). -Note: for **listforwards** entries without an *in_htlc_id* entry (no +Note: for **listforwards** entries without an *in\_htlc\_id* entry (no longer created in v22.11, but can exist from older versions), a value of 18446744073709551615 can be used, but then it will delete *all* -entries without *in_htlc_id* for this *in_channel* and *status*. +entries without *in\_htlc\_id* for this *in\_channel* and *status*. RETURN VALUE ------------ @@ -55,4 +55,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:200de829c6635242cb2dd8ec0650c2fa8f5fcbf413f4a704884516df80492fcb) +[comment]: # ( SHA256STAMP:4aff9673290966c7b09e65672da5dc8ef4d2601d3d1681009b329a4f8ceb9af6) diff --git a/doc/lightning-delinvoice.7.md b/doc/lightning-delinvoice.7.md index 71f34b78d03e..4c33f2d0ad58 100644 --- a/doc/lightning-delinvoice.7.md +++ b/doc/lightning-delinvoice.7.md @@ -28,7 +28,7 @@ Note: The return is the same as an object from lightning-listinvoice(7). On success, an object is returned, containing: - **label** (string): Unique label given at creation time -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): State of invoice (one of "paid", "expired", "unpaid") - **expires\_at** (u64): UNIX timestamp when invoice expires (or expired) - **bolt11** (string, optional): BOLT11 string @@ -39,14 +39,14 @@ On success, an object is returned, containing: If **bolt12** is present: - **local\_offer\_id** (hex, optional): offer for which this invoice was created - - **payer\_note** (string, optional): the optional *payer_note* from invoice_request which created this invoice + - **invreq\_payer\_note** (string, optional): the optional *invreq\_payer\_note* from invoice\_request which created this invoice If **status** is "paid": - **pay\_index** (u64): unique index for this invoice payment - **amount\_received\_msat** (msat): how much was actually received - **paid\_at** (u64): UNIX timestamp of when payment was received - - **payment\_preimage** (secret): SHA256 of this is the *payment_hash* offered in the invoice (always 64 characters) + - **payment\_preimage** (secret): SHA256 of this is the *payment\_hash* offered in the invoice (always 64 characters) [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -59,7 +59,7 @@ The following errors may be reported: - 905: An invoice with that label does not exist. - 906: The invoice *status* does not match the parameter. An error object will be returned as error *data*, containing - *current_status* and *expected_status* fields. + *current\_status* and *expected\_status* fields. This is most likely due to the *status* of the invoice changing just before this command is invoked. - 908: The invoice already has no description, and *desconly* was set. @@ -81,4 +81,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:d754daa61ddb65009fced566338af35ffb23069593f4741e6d8f6f138f60bb4f) +[comment]: # ( SHA256STAMP:c21dd851c40769c1b79489ccaf364c4647a67bf6cd1a34dd96a8574016d66d96) diff --git a/doc/lightning-delpay.7.md b/doc/lightning-delpay.7.md index 4568ebfbb9c0..f1fe76b7c43f 100644 --- a/doc/lightning-delpay.7.md +++ b/doc/lightning-delpay.7.md @@ -12,7 +12,7 @@ DESCRIPTION The **delpay** RPC command deletes a payment with the given `payment_hash` if its status is either `complete` or `failed`. Deleting a `pending` payment is an error. If *partid* and *groupid* are not specified, all payment parts are deleted. - *payment\_hash*: The unique identifier of a payment. -- *status*: Expected status of the payment. +- *status*: Expected status of the payment. - *partid*: Specific partid to delete (must be paired with *groupid*) - *groupid*: Specific groupid to delete (must be paired with *partid*) @@ -42,7 +42,7 @@ payments will be returned -- one payment object for each partid. On success, an object containing **payments** is returned. It is an array of objects, where each object contains: - **id** (u64): unique ID for this payment attempt -- **payment\_hash** (hex): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hex): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): status of the payment (one of "pending", "failed", "complete") - **amount\_sent\_msat** (msat): the amount we actually sent, including fees - **created\_at** (u64): the UNIX timestamp showing when this payment was initiated @@ -50,7 +50,7 @@ On success, an object containing **payments** is returned. It is an array of ob - **destination** (pubkey, optional): the final destination of the payment if known - **amount\_msat** (msat, optional): the amount the destination received, if known - **completed\_at** (u64, optional): the UNIX timestamp showing when this payment was completed -- **groupid** (u64, optional): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment_hash +- **groupid** (u64, optional): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment\_hash - **payment\_preimage** (hex, optional): proof of payment (always 64 characters) - **label** (string, optional): the label, if given to sendpay - **bolt11** (string, optional): the bolt11 string (if pay supplied one) @@ -106,4 +106,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:1ce2241eeae759ed5566342fb7810e62fa2c618f2465314f17376ebe9b6d24f8) + +[comment]: # ( SHA256STAMP:6f1e5f66278e49d10d5556abfabbab6a178f0dbd518b669ce93a32e6763dd458) diff --git a/doc/lightning-disableoffer.7.md b/doc/lightning-disableoffer.7.md index 2af2c73060b6..646624c70ff0 100644 --- a/doc/lightning-disableoffer.7.md +++ b/doc/lightning-disableoffer.7.md @@ -41,7 +41,6 @@ On success, an object is returned, containing: - **active** (boolean): Whether the offer can produce invoices/payments (always *false*) - **single\_use** (boolean): Whether the offer is disabled after first successful use - **bolt12** (string): The bolt12 string representing this offer -- **bolt12\_unsigned** (string): The bolt12 string representing this offer, without signature - **used** (boolean): Whether the offer has had an invoice paid / payment made - **label** (string, optional): The label provided when offer was created @@ -75,4 +74,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:27200ba49d493cbbb1ea84736ccfaeb05a92c69dab34f48cd3d5bbf46ffc2d64) + +[comment]: # ( SHA256STAMP:f6896438745837d5f1f5999553b397660eded7b22e5d0765ce5feaa3fc14e48e) diff --git a/doc/lightning-disconnect.7.md b/doc/lightning-disconnect.7.md index 68b5f5277277..cae3b959cacb 100644 --- a/doc/lightning-disconnect.7.md +++ b/doc/lightning-disconnect.7.md @@ -59,4 +59,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:326e5801f65998e13e909d8b682e9fbc9824f3a43aa7da1d76b871882e52f293) +[comment]: # ( SHA256STAMP:6ab8038cbad395e5a65a52fe66948740ad360c123e42c28d5879f5f03369b744) diff --git a/doc/lightning-feerates.7.md b/doc/lightning-feerates.7.md index ff3c6ee31bd0..23e1ebc6712a 100644 --- a/doc/lightning-feerates.7.md +++ b/doc/lightning-feerates.7.md @@ -52,7 +52,7 @@ On success, an object is returned, containing: - **max\_acceptable** (u32): The largest feerate we will accept from remote negotiations. If a peer attempts to set the feerate higher than this we will unilaterally close the channel (or simply forget it if it's not open yet). - **opening** (u32, optional): Default feerate for lightning-fundchannel(7) and lightning-withdraw(7) - **mutual\_close** (u32, optional): Feerate to aim for in cooperative shutdown. Note that since mutual close is a **negotiation**, the actual feerate used in mutual close will be somewhere between this and the corresponding mutual close feerate of the peer. - - **unilateral\_close** (u32, optional): Feerate for commitment_transaction in a live channel which we originally funded + - **unilateral\_close** (u32, optional): Feerate for commitment\_transaction in a live channel which we originally funded - **delayed\_to\_us** (u32, optional): Feerate for returning unilateral close funds to our wallet - **htlc\_resolution** (u32, optional): Feerate for returning unilateral close HTLC outputs to our wallet - **penalty** (u32, optional): Feerate to start at when penalizing a cheat attempt @@ -61,7 +61,7 @@ On success, an object is returned, containing: - **max\_acceptable** (u32): The largest feerate we will accept from remote negotiations. If a peer attempts to set the feerate higher than this we will unilaterally close the channel (or simply forget it if it's not open yet). - **opening** (u32, optional): Default feerate for lightning-fundchannel(7) and lightning-withdraw(7) - **mutual\_close** (u32, optional): Feerate to aim for in cooperative shutdown. Note that since mutual close is a **negotiation**, the actual feerate used in mutual close will be somewhere between this and the corresponding mutual close feerate of the peer. - - **unilateral\_close** (u32, optional): Feerate for commitment_transaction in a live channel which we originally funded + - **unilateral\_close** (u32, optional): Feerate for commitment\_transaction in a live channel which we originally funded - **delayed\_to\_us** (u32, optional): Feerate for returning unilateral close funds to our wallet - **htlc\_resolution** (u32, optional): Feerate for returning unilateral close HTLC outputs to our wallet - **penalty** (u32, optional): Feerate to start at when penalizing a cheat attempt @@ -114,11 +114,11 @@ SEE ALSO -------- lightning-parsefeerate(7), lightning-fundchannel(7), lightning-withdraw(7), -lightning-txprepare(7), lightning-fundchannel_start(7). +lightning-txprepare(7), lightning-fundchannel\_start(7). RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:d448abe4c00efb8cb68edf6f8316f130ed45a26223b151ac0647bf5b69aec4fd) +[comment]: # ( SHA256STAMP:c4fbacd9a36de4ed8307deae74f49e40a158435d726aee02f5c37f7a31a71400) diff --git a/doc/lightning-fetchinvoice.7.md b/doc/lightning-fetchinvoice.7.md index 9f219f141ca9..bc40c806514a 100644 --- a/doc/lightning-fetchinvoice.7.md +++ b/doc/lightning-fetchinvoice.7.md @@ -6,7 +6,7 @@ SYNOPSIS **(WARNING: experimental-offers only)** -**fetchinvoice** *offer* [*msatoshi*] [*quantity*] [*recurrence_counter*] [*recurrence_start*] [*recurrence_label*] [*timeout*] [*payer_note*] +**fetchinvoice** *offer* [*amount\_msat*] [*quantity*] [*recurrence\_counter*] [*recurrence\_start*] [*recurrence\_label*] [*timeout*] [*payer\_note*] DESCRIPTION ----------- @@ -19,31 +19,31 @@ If **fetchinvoice-noconnect** is not specified in the configuation, it will connect to the destination in the (currently common!) case where it cannot find a route which supports `option_onion_messages`. -The offer must not contain *send_invoice*; see lightning-sendinvoice(7). +The offer must not contain *send\_invoice*; see lightning-sendinvoice(7). -*msatoshi* is required if the *offer* does not specify +*amount\_msat* is required if the *offer* does not specify an amount at all, otherwise it is not allowed. *quantity* is is required if the *offer* specifies -*quantity_min* or *quantity_max*, otherwise it is not allowed. +*quantity\_min* or *quantity\_max*, otherwise it is not allowed. -*recurrence_counter* is required if the *offer* +*recurrence\_counter* is required if the *offer* specifies *recurrence*, otherwise it is not allowed. -*recurrence_counter* should first be set to 0, and incremented for +*recurrence\_counter* should first be set to 0, and incremented for each successive invoice in a given series. -*recurrence_start* is required if the *offer* -specifies *recurrence_base* with *start_any_period* set, otherwise it +*recurrence\_start* is required if the *offer* +specifies *recurrence\_base* with *start\_any\_period* set, otherwise it is not allowed. It indicates what period number to start at. -*recurrence_label* is required if *recurrence_counter* is set, and +*recurrence\_label* is required if *recurrence\_counter* is set, and otherwise is not allowed. It must be the same as prior fetchinvoice calls for the same recurrence, as it is used to link them together. *timeout* is an optional timeout; if we don't get a reply before this we fail (default, 60 seconds). -*payer_note* is an optional payer note to include in the fetched invoice. +*payer\_note* is an optional payer note to include in the fetched invoice. RETURN VALUE ------------ @@ -58,7 +58,7 @@ On success, an object is returned, containing: - **vendor\_removed** (string, optional): The *vendor* from the offer, which is missing in the invoice - **vendor** (string, optional): a completely replaced *vendor* field - **amount\_msat** (msat, optional): the amount, if different from the offer amount multiplied by any *quantity* (or the offer had no amount, or was not in BTC). -- **next\_period** (object, optional): Only for recurring invoices if the next period is under the *recurrence_limit*: +- **next\_period** (object, optional): Only for recurring invoices if the next period is under the *recurrence\_limit*: - **counter** (u64): the index of the next period to fetchinvoice - **starttime** (u64): UNIX timestamp that the next period starts - **endtime** (u64): UNIX timestamp that the next period ends @@ -89,4 +89,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:18164ef676c71c8d3abde89d974b3c74bd7fdb43356a737f937b2fb060795a47) +[comment]: # ( SHA256STAMP:59b33634070b62e711cae7457bfb08874851e4e001512feaefc5ddac1a5b3b5b) diff --git a/doc/lightning-fundchannel.7.md b/doc/lightning-fundchannel.7.md index 3f9e80566769..9ea163664a63 100644 --- a/doc/lightning-fundchannel.7.md +++ b/doc/lightning-fundchannel.7.md @@ -5,7 +5,8 @@ SYNOPSIS -------- **fundchannel** *id* *amount* [*feerate*] [*announce*] [*minconf*] -[*utxos*] [*push_msat*] [*close_to*] [*request_amt*] [*compact_lease*] +[*utxos*] [*push\_msat*] [*close\_to*] [*request\_amt*] [*compact\_lease*] +[*reserve*] DESCRIPTION ----------- @@ -55,23 +56,28 @@ outputs should have. Default is 1. *utxos* specifies the utxos to be used to fund the channel, as an array of "txid:vout". -*push_msat* is the amount of millisatoshis to push to the channel peer at +*push\_msat* is the amount of millisatoshis to push to the channel peer at open. Note that this is a gift to the peer -- these satoshis are added to the initial balance of the peer at channel start and are largely unrecoverable once pushed. -*close_to* is a Bitcoin address to which the channel funds should be sent to +*close\_to* is a Bitcoin address to which the channel funds should be sent to on close. Only valid if both peers have negotiated `option_upfront_shutdown_script`. Returns `close_to` set to closing script iff is negotiated. -*request_amt* is an amount of liquidity you'd like to lease from the peer. +*request\_amt* is an amount of liquidity you'd like to lease from the peer. If peer supports `option_will_fund`, indicates to them to include this -much liquidity into the channel. Must also pass in *compact_lease*. +much liquidity into the channel. Must also pass in *compact\_lease*. -*compact_lease* is a compact represenation of the peer's expected +*compact\_lease* is a compact represenation of the peer's expected channel lease terms. If the peer's terms don't match this set, we will fail to open the channel. +*reserve* is the amount we want the peer to maintain on its side of the channel. +Default is 1% of the funding amount. It can be a whole number, a whole number +ending in *sat*, a whole number ending in *000msat*, or a number with 1 to 8 +decimal places ending in *btc*. + This example shows how to use lightning-cli to open new channel with peer 03f...fc1 from one whole utxo bcc1...39c:0 @@ -88,8 +94,8 @@ On success, an object is returned, containing: - **tx** (hex): The raw transaction which funded the channel - **txid** (txid): The txid of the transaction which funded the channel - **outnum** (u32): The 0-based output index showing which output funded the channel -- **channel\_id** (hex): The channel_id of the resulting channel (always 64 characters) -- **close\_to** (hex, optional): The raw scriptPubkey which mutual close will go to; only present if *close_to* parameter was specified and peer supports `option_upfront_shutdown_script` +- **channel\_id** (hex): The channel\_id of the resulting channel (always 64 characters) +- **close\_to** (hex, optional): The raw scriptPubkey which mutual close will go to; only present if *close\_to* parameter was specified and peer supports `option_upfront_shutdown_script` - **mindepth** (u32, optional): Number of confirmations before we consider the channel active. [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -115,4 +121,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:bca36e910b93b86fc42c2d047e703e9760250757cbf09d8cacdf4e3fe1a1f605) +[comment]: # ( SHA256STAMP:ed7d5aa730bf6b87b3f7072272b984539ca991670c13f85a0da8d4d1333549ae) diff --git a/doc/lightning-fundchannel_cancel.7.md b/doc/lightning-fundchannel_cancel.7.md index 2a2ce70e596e..7909c2f0fda1 100644 --- a/doc/lightning-fundchannel_cancel.7.md +++ b/doc/lightning-fundchannel_cancel.7.md @@ -60,4 +60,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:a6b67ae1ecd3bfe5c41e17710342ce32e93675775653bfcffc5b7413c6a15726) +[comment]: # ( SHA256STAMP:d8a18db4d75c051ec89cd2a52add52aea04e78608c625270238c992b977ec173) diff --git a/doc/lightning-fundchannel_complete.7.md b/doc/lightning-fundchannel_complete.7.md index 55d74f9716a8..f63679929528 100644 --- a/doc/lightning-fundchannel_complete.7.md +++ b/doc/lightning-fundchannel_complete.7.md @@ -29,7 +29,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **channel\_id** (hex): The channel_id of the resulting channel (always 64 characters) +- **channel\_id** (hex): The channel\_id of the resulting channel (always 64 characters) - **commitments\_secured** (boolean): Indication that channel is safe to use (always *true*) [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -62,4 +62,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:6852cb54595920fa692b6c0a816b44efa7623a3fd12af90602a137f7de0fc57c) +[comment]: # ( SHA256STAMP:a1853aa4288c0ee50956328f02e86b580de0dffc8b73e204c8f5daaa79c51a0c) diff --git a/doc/lightning-fundchannel_start.7.md b/doc/lightning-fundchannel_start.7.md index 66c51000374e..551c4e168421 100644 --- a/doc/lightning-fundchannel_start.7.md +++ b/doc/lightning-fundchannel_start.7.md @@ -4,7 +4,7 @@ lightning-fundchannel\_start -- Command for initiating channel establishment for SYNOPSIS -------- -**fundchannel\_start** *id* *amount* [*feerate* *announce* *close_to* *push_msat*] +**fundchannel\_start** *id* *amount* [*feerate* *announce* *close\_to* *push\_msat*] DESCRIPTION ----------- @@ -23,11 +23,11 @@ commitment transactions: see **fundchannel**. *announce* whether or not to announce this channel. -*close_to* is a Bitcoin address to which the channel funds should be sent to +*close\_to* is a Bitcoin address to which the channel funds should be sent to on close. Only valid if both peers have negotiated `option_upfront_shutdown_script`. Returns `close_to` set to closing script iff is negotiated. -*push_msat* is the amount of millisatoshis to push to the channel peer at +*push\_msat* is the amount of millisatoshis to push to the channel peer at open. Note that this is a gift to the peer -- these satoshis are added to the initial balance of the peer at channel start and are largely unrecoverable once pushed. @@ -46,7 +46,7 @@ On success, an object is returned, containing: - **funding\_address** (string): The address to send funding to for the channel. DO NOT SEND COINS TO THIS ADDRESS YET. - **scriptpubkey** (hex): The raw scriptPubkey for the address -- **close\_to** (hex, optional): The raw scriptPubkey which mutual close will go to; only present if *close_to* parameter was specified and peer supports `option_upfront_shutdown_script` +- **close\_to** (hex, optional): The raw scriptPubkey which mutual close will go to; only present if *close\_to* parameter was specified and peer supports `option_upfront_shutdown_script` - **mindepth** (u32, optional): Number of confirmations before we consider the channel active. The following warnings may also be returned: @@ -85,4 +85,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:b054bc55f69cc1f23f78f342974a8476eab84146bbcf57ab30095e8eba3ed849) +[comment]: # ( SHA256STAMP:ca4ad15c25dc588980dea11be9d3f73c9da3688a5e81bfc13660eabe93cbec13) diff --git a/doc/lightning-funderupdate.7.md b/doc/lightning-funderupdate.7.md index b02300ca4c52..865f8d5428a8 100644 --- a/doc/lightning-funderupdate.7.md +++ b/doc/lightning-funderupdate.7.md @@ -4,7 +4,7 @@ lightning-funderupdate -- Command for adjusting node funding v2 channels SYNOPSIS -------- -**funderupdate** [*policy*] [*policy_mod*] [*leases_only*] [*min_their_funding_msat*] [*max_their_funding_msat*] [*per_channel_min_msat*] [*per_channel_max_msat*] [*reserve_tank_msat*] [*fuzz_percent*] [*fund_probability*] [*lease_fee_base_msat*] [*lease_fee_basis*] [*funding_weight*] [*channel_fee_max_base_msat*] [*channel_fee_max_proportional_thousandths*] [*compact_lease*] +**funderupdate** [*policy*] [*policy\_mod*] [*leases\_only*] [*min\_their\_funding\_msat*] [*max\_their\_funding\_msat*] [*per\_channel\_min\_msat*] [*per\_channel\_max\_msat*] [*reserve\_tank\_msat*] [*fuzz\_percent*] [*fund\_probability*] [*lease\_fee\_base\_msat*] [*lease\_fee\_basis*] [*funding\_weight*] [*channel\_fee\_max\_base\_msat*] [*channel\_fee\_max\_proportional\_thousandths*] [*compact\_lease*] NOTE: Must have --experimental-dual-fund enabled for these settings to take effect. @@ -14,55 +14,55 @@ DESCRIPTION For channel open requests using -*policy*, *policy_mod* is the policy the funder plugin will use to decide +*policy*, *policy\_mod* is the policy the funder plugin will use to decide how much capital to commit to a v2 open channel request. There are three policy options, detailed below: `match`, `available`, and `fixed`. -The *policy_mod* is the number or 'modification' to apply to the policy. +The *policy\_mod* is the number or 'modification' to apply to the policy. Default is (fixed, 0sats). -* `match` -- Contribute *policy_mod* percent of their requested funds. - Valid *policy_mod* values are 0 to 200. If this is a channel lease +* `match` -- Contribute *policy\_mod* percent of their requested funds. + Valid *policy\_mod* values are 0 to 200. If this is a channel lease request, we match based on their requested funds. If it is not a - channel lease request (and *lease_only* is false), then we match + channel lease request (and *lease\_only* is false), then we match their funding amount. Note: any lease match less than 100 will likely fail, as clients will not accept a lease less than their request. -* `available` -- Contribute *policy_mod* percent of our available - node wallet funds. Valid *policy_mod* values are 0 to 100. -* `fixed` -- Contributes a fixed *policy_mod* sats to v2 channel open requests. +* `available` -- Contribute *policy\_mod* percent of our available + node wallet funds. Valid *policy\_mod* values are 0 to 100. +* `fixed` -- Contributes a fixed *policy\_mod* sats to v2 channel open requests. Note: to maximize channel leases, best policy setting is (match, 100). -*leases_only* will only contribute funds to `option_will_fund` requests +*leases\_only* will only contribute funds to `option_will_fund` requests which pay to lease funds. Defaults to false, will fund any v2 open request using *policy* even if it's they're not seeking to lease funds. Note that `option_will_fund` commits funds for 4032 blocks (~1mo). Must also set -*lease_fee_base_msat*, *lease_fee_basis*, *funding_weight*, -*channel_fee_max_base_msat*, and *channel_fee_max_proportional_thousandths* +*lease\_fee\_base\_msat*, *lease\_fee\_basis*, *funding\_weight*, +*channel\_fee\_max\_base\_msat*, and *channel\_fee\_max\_proportional\_thousandths* to advertise available channel leases. -*min_their_funding_msat* is the minimum funding sats that we require in order +*min\_their\_funding\_msat* is the minimum funding sats that we require in order to activate our contribution policy to the v2 open. Defaults to 10k sats. -*max_their_funding_msat* is the maximum funding sats that we will consider +*max\_their\_funding\_msat* is the maximum funding sats that we will consider to activate our contribution policy to the v2 open. Any channel open above this will not be funded. Defaults to no max (`UINT_MAX`). -*per_channel_min_msat* is the minimum amount that we will contribute to a +*per\_channel\_min\_msat* is the minimum amount that we will contribute to a channel open. Defaults to 10k sats. -*per_channel_max_msat* is the maximum amount that we will contribute to a +*per\_channel\_max\_msat* is the maximum amount that we will contribute to a channel open. Defaults to no max (`UINT_MAX`). -*reserve_tank_msat* is the amount of sats to leave available in the node wallet. +*reserve\_tank\_msat* is the amount of sats to leave available in the node wallet. Defaults to zero sats. -*fuzz_percent* is a percentage to fuzz the resulting contribution amount by. +*fuzz\_percent* is a percentage to fuzz the resulting contribution amount by. Valid values are 0 to 100. Note that turning this on with (match, 100) policy will randomly fail `option_will_fund` leases, as most clients expect an exact or greater match of their `requested_funds`. Defaults to 0% (no fuzz). -*fund_probability* is the percent of v2 channel open requests to apply our +*fund\_probability* is the percent of v2 channel open requests to apply our policy to. Valid values are integers from 0 (fund 0% of all open requests) to 100 (fund every request). Useful for randomizing opens that receive funds. Defaults to 100. @@ -71,33 +71,33 @@ Setting any of the next 5 options will activate channel leases for this node, and advertise these values via the lightning gossip network. If any one is set, the other values will be the default. -*lease_fee_base_msat* is the flat fee for a channel lease. Node will +*lease\_fee\_base\_msat* is the flat fee for a channel lease. Node will receive this much extra added to their channel balance, paid by the opening node. Defaults to 2k sats. Note that the minimum is 1sat. -*lease_fee_basis* is a basis fee that's calculated as 1/10k of the total +*lease\_fee\_basis* is a basis fee that's calculated as 1/10k of the total requested funds the peer is asking for. Node will receive the total of -*lease_fee_basis* times requested funds / 10k satoshis added to their channel +*lease\_fee\_basis* times requested funds / 10k satoshis added to their channel balance, paid by the opening node. Default is 0.65% (65 basis points) -*funding_weight* is used to calculate the fee the peer will compensate your +*funding\_weight* is used to calculate the fee the peer will compensate your node for its contributing inputs to the funding transaction. The total fee is calculated as the `open_channel2`.`funding_feerate_perkw` times this -*funding_weight* divided by 1000. Node will have this funding fee added +*funding\_weight* divided by 1000. Node will have this funding fee added to their channel balance, paid by the opening node. Default is 2 inputs + 1 P2WPKH output. -*channel_fee_max_base_msat* is a commitment to a maximum +*channel\_fee\_max\_base\_msat* is a commitment to a maximum `channel_fee_base_msat` that your node will charge for routing payments over this leased channel during the lease duration. Default is 5k sats. -*channel_fee_max_proportional_thousandths* is a commitment to a maximum +*channel\_fee\_max\_proportional\_thousandths* is a commitment to a maximum `channel_fee_proportional_millionths` that your node will charge for routing payments over this leased channel during the lease duration. Note that it's denominated in 'thousandths'. A setting of `1` is equal to 1k ppm; `5` is 5k ppm, etc. Default is 100 (100k ppm). -*compact_lease* is a compact description of the channel lease params. When +*compact\_lease* is a compact description of the channel lease params. When opening a channel, passed in to `fundchannel` to indicate the terms we expect from the peer. @@ -109,7 +109,7 @@ On success, an object is returned, containing: - **summary** (string): Summary of the current funding policy e.g. (match 100) - **policy** (string): Policy funder plugin will use to decide how much captial to commit to a v2 open channel request (one of "match", "available", "fixed") -- **policy\_mod** (u32): The *policy_mod* is the number or 'modification' to apply to the policy. +- **policy\_mod** (u32): The *policy\_mod* is the number or 'modification' to apply to the policy. - **leases\_only** (boolean): Only contribute funds to `option_will_fund` lease requests. - **min\_their\_funding\_msat** (msat): The minimum funding sats that we require from peer to activate our funding policy. - **max\_their\_funding\_msat** (msat): The maximum funding sats that we'll allow from peer to activate our funding policy. @@ -121,8 +121,8 @@ On success, an object is returned, containing: - **lease\_fee\_base\_msat** (msat, optional): Flat fee to charge for a channel lease. - **lease\_fee\_basis** (u32, optional): Proportional fee to charge for a channel lease, calculated as 1/10,000th of requested funds. - **funding\_weight** (u32, optional): Transaction weight the channel opener will pay us for a leased funding transaction. -- **channel\_fee\_max\_base\_msat** (msat, optional): Maximum channel_fee_base_msat we'll charge for routing funds leased on this channel. -- **channel\_fee\_max\_proportional\_thousandths** (u32, optional): Maximum channel_fee_proportional_millitionths we'll charge for routing funds leased on this channel, in thousandths. +- **channel\_fee\_max\_base\_msat** (msat, optional): Maximum channel\_fee\_base\_msat we'll charge for routing funds leased on this channel. +- **channel\_fee\_max\_proportional\_thousandths** (u32, optional): Maximum channel\_fee\_proportional\_millitionths we'll charge for routing funds leased on this channel, in thousandths. - **compact\_lease** (hex, optional): Compact description of the channel lease parameters. [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -147,4 +147,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:03b74bb9d03466181e3f643f718a07388e722e4a6ea1fbb30350e22a7fc491c3) +[comment]: # ( SHA256STAMP:13eef3ba929ea98506f6ed3d042072be6f70fd01d503ca5f7b49480dea7af627) diff --git a/doc/lightning-fundpsbt.7.md b/doc/lightning-fundpsbt.7.md index 827bbf6fffcf..5b7728a27b57 100644 --- a/doc/lightning-fundpsbt.7.md +++ b/doc/lightning-fundpsbt.7.md @@ -4,7 +4,7 @@ lightning-fundpsbt -- Command to populate PSBT inputs from the wallet SYNOPSIS -------- -**fundpsbt** *satoshi* *feerate* *startweight* [*minconf*] [*reserve*] [*locktime*] [*min_witness_weight*] [*excess_as_change*] +**fundpsbt** *satoshi* *feerate* *startweight* [*minconf*] [*reserve*] [*locktime*] [*min\_witness\_weight*] [*excess\_as\_change*] DESCRIPTION ----------- @@ -40,11 +40,11 @@ If *reserve* if not zero, then *reserveinputs* is called (successfully, with *locktime* is an optional locktime: if not set, it is set to a recent block height. -*min_witness_weight* is an optional minimum weight to use for a UTXO's +*min\_witness\_weight* is an optional minimum weight to use for a UTXO's witness. If the actual witness weight is greater than the provided minimum, the actual witness weight will be used. -*excess_as_change* is an optional boolean to flag to add a change output +*excess\_as\_change* is an optional boolean to flag to add a change output for the excess sats. EXAMPLE USAGE @@ -57,15 +57,15 @@ known outputs of the transaction (typically (9 + scriptlen) * 4). For a simple P2WPKH it's a 22 byte scriptpubkey, so that's 124 weight. It calls "*fundpsbt* 100000sat slow 166", which succeeds, and returns -the *psbt* and *feerate_per_kw* it used, the *estimated_final_weight* -and any *excess_msat*. +the *psbt* and *feerate\_per\_kw* it used, the *estimated\_final\_weight* +and any *excess\_msat*. -If *excess_msat* is greater than the cost of adding a change output, +If *excess\_msat* is greater than the cost of adding a change output, the caller adds a change output randomly to position 0 or 1 in the -PSBT. Say *feerate_per_kw* is 253, and the change output is a P2WPKH +PSBT. Say *feerate\_per\_kw* is 253, and the change output is a P2WPKH (weight 124), the cost is around 31 sats. With the dust limit disallowing payments below 546 satoshis, we would only create a change output -if *excess_msat* was greater or equal to 31 + 546. +if *excess\_msat* was greater or equal to 31 + 546. RETURN VALUE ------------ @@ -76,8 +76,8 @@ On success, an object is returned, containing: - **psbt** (string): Unsigned PSBT which fulfills the parameters given - **feerate\_per\_kw** (u32): The feerate used to create the PSBT, in satoshis-per-kiloweight - **estimated\_final\_weight** (u32): The estimated weight of the transaction once fully signed -- **excess\_msat** (msat): The amount above *satoshi* which is available. This could be zero, or dust; it will be zero if *change_outnum* is also returned -- **change\_outnum** (u32, optional): The 0-based output number where change was placed (only if parameter *excess_as_change* was true and there was sufficient funds) +- **excess\_msat** (msat): The amount above *satoshi* which is available. This could be zero, or dust; it will be zero if *change\_outnum* is also returned +- **change\_outnum** (u32, optional): The 0-based output number where change was placed (only if parameter *excess\_as\_change* was true and there was sufficient funds) - **reservations** (array of objects, optional): If *reserve* was true or a non-zero number, just as per lightning-reserveinputs(7): - **txid** (txid): The txid of the transaction - **vout** (u32): The 0-based output number @@ -87,10 +87,10 @@ On success, an object is returned, containing: [comment]: # (GENERATE-FROM-SCHEMA-END) -If *excess_as_change* is true and the excess is enough to cover +If *excess\_as\_change* is true and the excess is enough to cover an additional output above the `dust_limit`, then an output is -added to the PSBT for the excess amount. The *excess_msat* will -be zero. A *change_outnum* will be returned with the index of +added to the PSBT for the excess amount. The *excess\_msat* will +be zero. A *change\_outnum* will be returned with the index of the change output. On error the returned object will contain `code` and `message` properties, @@ -115,4 +115,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:0f290582f49c6103258b7f781a9e7fa4075ec6c05335a459a91da0b6fd58c68d) +[comment]: # ( SHA256STAMP:35947e2b2c402a87c4bad3a5a90443bfe5db44d71cb515541074abfc4dc3f24d) diff --git a/doc/lightning-getinfo.7.md b/doc/lightning-getinfo.7.md index 99cc81369b36..fdda23de90e2 100644 --- a/doc/lightning-getinfo.7.md +++ b/doc/lightning-getinfo.7.md @@ -41,9 +41,9 @@ On success, an object is returned, containing: - **network** (string): represents the type of network on the node are working (e.g: `bitcoin`, `testnet`, or `regtest`) - **fees\_collected\_msat** (msat): Total routing fees collected by this node - **our\_features** (object, optional): Our BOLT #9 feature bits (as hexstring) for various contexts: - - **init** (hex): features (incl. globalfeatures) in our init message, these also restrict what we offer in open_channel or accept in accept_channel - - **node** (hex): features in our node_announcement message - - **channel** (hex): negotiated channel features we (as channel initiator) publish in the channel_announcement message + - **init** (hex): features (incl. globalfeatures) in our init message, these also restrict what we offer in open\_channel or accept in accept\_channel + - **node** (hex): features in our node\_announcement message + - **channel** (hex): negotiated channel features we (as channel initiator) publish in the channel\_announcement message - **invoice** (hex): features in our BOLT11 invoices - **address** (array of objects, optional): The addresses we announce to the world: - **type** (string): Type of connection (one of "dns", "ipv4", "ipv6", "torv2", "torv3", "websocket") @@ -131,4 +131,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:0e54af449933b833f2e74bab9fde46096a79d69b4d958a548c7c0b7cc5654e99) + +[comment]: # ( SHA256STAMP:ce2b96e2e97cf6bc1e311d6125253dfbed900f13c001f518e9a0b4f202a0e1d6) diff --git a/doc/lightning-getlog.7.md b/doc/lightning-getlog.7.md index d4c3942fc06e..725cd231bfd3 100644 --- a/doc/lightning-getlog.7.md +++ b/doc/lightning-getlog.7.md @@ -33,9 +33,9 @@ On success, an object is returned, containing: - **created\_at** (string): UNIX timestamp with 9 decimal places, when logging was initialized - **bytes\_used** (u32): The number of bytes used by logging records -- **bytes\_max** (u32): The bytes_used values at which records will be trimmed +- **bytes\_max** (u32): The bytes\_used values at which records will be trimmed - **log** (array of objects): - - **type** (string) (one of "SKIPPED", "BROKEN", "UNUSUAL", "INFO", "DEBUG", "IO_IN", "IO_OUT") + - **type** (string) (one of "SKIPPED", "BROKEN", "UNUSUAL", "INFO", "DEBUG", "IO\_IN", "IO\_OUT") If **type** is "SKIPPED": @@ -43,14 +43,14 @@ On success, an object is returned, containing: If **type** is "BROKEN", "UNUSUAL", "INFO" or "DEBUG": - - **time** (string): UNIX timestamp with 9 decimal places after **created_at** + - **time** (string): UNIX timestamp with 9 decimal places after **created\_at** - **source** (string): The particular logbook this was found in - **log** (string): The actual log message - **node\_id** (pubkey, optional): The peer this is associated with - If **type** is "IO_IN" or "IO_OUT": + If **type** is "IO\_IN" or "IO\_OUT": - - **time** (string): Seconds after **created_at**, with 9 decimal places + - **time** (string): Seconds after **created\_at**, with 9 decimal places - **source** (string): The particular logbook this was found in - **log** (string): The associated log message - **data** (hex): The IO which occurred @@ -94,4 +94,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:0f6e346c57e59aa8ebe0aee9bcb7ded6f66776752e55c4c125f4a80d98cf90fd) + +[comment]: # ( SHA256STAMP:6b925456a06076ba98a04df3ff6931d2cd5d09ccec82829301428493ff824e34) diff --git a/doc/lightning-getroute.7.md b/doc/lightning-getroute.7.md index 128afe20af9d..cbb6cb86c9d0 100644 --- a/doc/lightning-getroute.7.md +++ b/doc/lightning-getroute.7.md @@ -4,17 +4,17 @@ lightning-getroute -- Command for routing a payment (low-level) SYNOPSIS -------- -**getroute** *id* *msatoshi* *riskfactor* [*cltv*] [*fromid*] +**getroute** *id* *amount\_msat* *riskfactor* [*cltv*] [*fromid*] [*fuzzpercent*] [*exclude*] [*maxhops*] DESCRIPTION ----------- The **getroute** RPC command attempts to find the best route for the -payment of *msatoshi* to lightning node *id*, such that the payment will +payment of *amount\_msat* to lightning node *id*, such that the payment will arrive at *id* with *cltv*-blocks to spare (default 9). -*msatoshi* is in millisatoshi precision; it can be a whole number, or a +*amount\_msat* is in millisatoshi precision; it can be a whole number, or a whole number ending in *msat* or *sat*, or a number with three decimal places ending in *sat*, or a number with 1 to 11 decimal places ending in *btc*. @@ -290,7 +290,7 @@ On success, an object containing **route** is returned. It is an array of objec [comment]: # (GENERATE-FROM-SCHEMA-END) The final *id* will be the destination *id* given in the input. The -difference between the first *msatoshi* minus the *msatoshi* given in +difference between the first *amount\_msat* minus the *amount\_msat* given in the input is the fee (assuming the first hop is free). The first *delay* is the very worst case timeout for the payment failure, in blocks. @@ -310,4 +310,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:e592a238b3701399c1e8de45cb7186b9714742daefa2f33287019f860c1cc24d) +[comment]: # ( SHA256STAMP:7456e80dc70830703bd8fd05d4047f721592bc3c1b2c51fbcb54ce0d87167380) diff --git a/doc/lightning-help.7.md b/doc/lightning-help.7.md index bf46d269b220..8344efbae3dd 100644 --- a/doc/lightning-help.7.md +++ b/doc/lightning-help.7.md @@ -68,4 +68,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:48f9ef4d1d73aa13ebd1ffa37107111c35c1a197bbcf00f52c5149847ca57ac1) + +[comment]: # ( SHA256STAMP:64c5aa03469d6cb3e00e46141a5f11e499a0cc74a13b5600b26aa6dd1539346f) diff --git a/doc/lightning-hsmtool.8.md b/doc/lightning-hsmtool.8.md index 22463c06f99f..01c3e9f0d303 100644 --- a/doc/lightning-hsmtool.8.md +++ b/doc/lightning-hsmtool.8.md @@ -53,17 +53,17 @@ ever had. Specify *password* if the `hsm_secret` is encrypted. **generatehsm** *hsm\_secret\_path* - Generates a new hsm_secret using BIP39. + Generates a new hsm\_secret using BIP39. **checkhsm** *hsm\_secret\_path* - Checks that hsm_secret matchs a BIP39 pass phrase. + Checks that hsm\_secret matches a BIP39 passphrase. -**dumponchaindescriptors** *hsm_secret* \[*password*\] \[*network*\] +**dumponchaindescriptors** *hsm\_secret* \[*password*\] \[*network*\] Dump output descriptors for our onchain wallet. The descriptors can be used by external services to be able to generate addresses for our onchain wallet. (for example on `bitcoind` using the `importmulti` or `importdescriptors` RPC calls) -We need the path to the hsm_secret containing the wallet seed, and an optional +We need the path to the hsm\_secret containing the wallet seed, and an optional (skip using `""`) password if it was encrypted. To generate descriptors using testnet master keys, you may specify *testnet* as the last parameter. By default, mainnet-encoded keys are generated. diff --git a/doc/lightning-invoice.7.md b/doc/lightning-invoice.7.md index 218374d4ad49..e3a89125cf2c 100644 --- a/doc/lightning-invoice.7.md +++ b/doc/lightning-invoice.7.md @@ -4,7 +4,7 @@ lightning-invoice -- Command for accepting payments SYNOPSIS -------- -**invoice** *amount_msat* *label* *description* [*expiry*] +**invoice** *amount\_msat* *label* *description* [*expiry*] [*fallbacks*] [*preimage*] [*exposeprivatechannels*] [*cltv*] [*deschashonly*] DESCRIPTION @@ -16,7 +16,7 @@ lightning daemon can use to pay this invoice. This token includes a *route hint* description of an incoming channel with capacity to pay the invoice, if any exists. -The *amount_msat* parameter can be the string "any", which creates an +The *amount\_msat* parameter can be the string "any", which creates an invoice that can be paid with any amount. Otherwise it is a positive value in millisatoshi precision; it can be a whole number, or a whole number ending in *msat* or *sat*, or a number with three decimal places ending @@ -58,13 +58,13 @@ as a route hint candidate; if *false*, never. If it is a short channel id will be considered candidates, even if they are public or dead-ends. The route hint is selected from the set of incoming channels of which: -peer's balance minus their reserves is at least *msatoshi*, state is +peer's balance minus their reserves is at least *amount\_msat*, state is normal, the peer is connected and not a dead end (i.e. has at least one other public channel). The selection uses some randomness to prevent probing, but favors channels that become more balanced after the payment. -If specified, *cltv* sets the *min_final_cltv_expiry* for the invoice. +If specified, *cltv* sets the *min\_final\_cltv\_expiry* for the invoice. Otherwise, it's set to the parameter **cltv-final**. If *deschashonly* is true (default false), then the bolt11 returned @@ -79,8 +79,8 @@ RETURN VALUE On success, an object is returned, containing: - **bolt11** (string): the bolt11 string -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) -- **payment\_secret** (secret): the *payment_secret* to place in the onion (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) +- **payment\_secret** (secret): the *payment\_secret* to place in the onion (always 64 characters) - **expires\_at** (u64): UNIX timestamp of when invoice expires The following warnings may also be returned: @@ -119,4 +119,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:4dd2b9d74116f77ad09ad4162ba8438db79e79d1aa99b23e2c993d754327649d) +[comment]: # ( SHA256STAMP:e3b07ce2a4cbe9198d5a65df1e49b628a8a7e857770e004a1d84c41c67601712) diff --git a/doc/lightning-keysend.7.md b/doc/lightning-keysend.7.md index 794934cacb0a..cb8bd2a10fa9 100644 --- a/doc/lightning-keysend.7.md +++ b/doc/lightning-keysend.7.md @@ -4,7 +4,7 @@ lightning-keysend -- Send funds to a node without an invoice SYNOPSIS -------- -**keysend** *destination* *msatoshi* [*label*] [*maxfeepercent*] [*retry\_for*] [*maxdelay*] [*exemptfee*] [*extratlvs*] +**keysend** *destination* *amount\_msat* [*label*] [*maxfeepercent*] [*retry\_for*] [*maxdelay*] [*exemptfee*] [*extratlvs*] DESCRIPTION ----------- @@ -23,7 +23,7 @@ sending a payment. Please ensure that this matches your use-case when using `keysend`. `destination` is the 33 byte, hex-encoded, node ID of the node that the payment should go to. -`msatoshi` is in millisatoshi precision; it can be a whole number, or a whole number with suffix `msat` or `sat`, or a three decimal point number with suffix `sat`, or an 1 to 11 decimal point number suffixed by `btc`. +`amount\_msat` is in millisatoshi precision; it can be a whole number, or a whole number with suffix `msat` or `sat`, or a three decimal point number with suffix `sat`, or an 1 to 11 decimal point number suffixed by `btc`. The `label` field is used to attach a label to payments, and is returned in lightning-listpays(7) and lightning-listsendpays(7). The `maxfeepercent` limits the money paid in fees as percentage of the total amount that is to be transferred, and defaults to *0.5*. @@ -33,7 +33,7 @@ Setting `exemptfee` allows the `maxfeepercent` check to be skipped on fees that The response will occur when the payment fails or succeeds. Unlike lightning-pay(7), issuing the same `keysend` commands multiple times will result in multiple payments being sent. -Until *retry_for* seconds passes (default: 60), the command will keep finding routes and retrying the payment. +Until *retry\_for* seconds passes (default: 60), the command will keep finding routes and retrying the payment. However, a payment may be delayed for up to `maxdelay` blocks by another node; clients should be prepared for this worst case. *extratlvs* is an optional dictionary of additional fields to insert into the final tlv. The format is 'fieldnumber': 'hexstring'. @@ -70,8 +70,8 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment\_hash** (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **created\_at** (number): the UNIX timestamp showing when this payment was initiated - **parts** (u32): how many attempts this took - **amount\_msat** (msat): Amount the recipient received @@ -99,7 +99,7 @@ A routing failure object has the fields below: - `erring_node`: The hex string of the pubkey id of the node that reported the error. - `erring_channel`: The short channel ID of the channel that has the error, or *0:0:0* if the destination node raised the error. - `failcode`: The failure code, as per BOLT \#4. -- `channel_update`. The hex string of the *channel_update* message received from the remote node. Only present if error is from the remote node and the *failcode* has the `UPDATE` bit set, as per BOLT \#4. +- `channel_update`. The hex string of the *channel\_update* message received from the remote node. Only present if error is from the remote node and the *failcode* has the `UPDATE` bit set, as per BOLT \#4. AUTHOR @@ -118,4 +118,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:d208bd6f3e78b039a4790b8de599ffd819aa169c59430ac487fd7030cd3fe640) +[comment]: # ( SHA256STAMP:b6a047c09d40be10ed9027ca0f38332a57bfe7a232fa66fa5a669cf76e2731cd) diff --git a/doc/lightning-listchannels.7.md b/doc/lightning-listchannels.7.md index 49af830d0dab..7be7d4f1b9d9 100644 --- a/doc/lightning-listchannels.7.md +++ b/doc/lightning-listchannels.7.md @@ -41,7 +41,7 @@ On success, an object containing **channels** is returned. It is an array of ob - **message\_flags** (u8): as defined by BOLT #7 - **channel\_flags** (u8): as defined by BOLT #7 - **active** (boolean): true unless source has disabled it, or it's a local channel and the peer is disconnected or it's still opening or closing -- **last\_update** (u32): UNIX timestamp on the last channel_update from *source* +- **last\_update** (u32): UNIX timestamp on the last channel\_update from *source* - **base\_fee\_millisatoshi** (u32): Base fee changed by *source* to use this channel - **fee\_per\_millionth** (u32): Proportional fee changed by *source* to use this channel, in parts-per-million - **delay** (u32): The number of blocks delay required by *source* to use this channel @@ -79,4 +79,4 @@ Lightning RFC site - BOLT \#7: -[comment]: # ( SHA256STAMP:baf45b77bd2ba22e245e007b57d8e5f70d06cbf9cebf7ed1431da6a0cf6f367a) +[comment]: # ( SHA256STAMP:693b8297d390522cd68a27b607194567cebb7bf021f769c82d430afced9d0029) diff --git a/doc/lightning-listconfigs.7.md b/doc/lightning-listconfigs.7.md index d6f65b12891e..bbc7e59e41d1 100644 --- a/doc/lightning-listconfigs.7.md +++ b/doc/lightning-listconfigs.7.md @@ -98,9 +98,10 @@ On success, an object is returned, containing: - **force-feerates** (string, optional): force-feerate configuration setting, if any - **subdaemon** (string, optional): `subdaemon` fields from config or cmdline if any (can be more than one) - **fetchinvoice-noconnect** (boolean, optional): `fetchinvoice-noconnect` fields from config or cmdline, or default -- **accept-htlc-tlv-types** (string, optional): `accept-extra-tlvs-type` fields from config or cmdline, or not present +- **accept-htlc-tlv-types** (string, optional): `accept-htlc-tlv-types` fields from config or cmdline, or not present - **tor-service-password** (string, optional): `tor-service-password` field from config or cmdline, if any - **dev-allowdustreserve** (boolean, optional): Whether we allow setting dust reserves +- **announce-addr-dns** (boolean, optional): Whether we put DNS entries into node\_announcement [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -218,4 +219,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:5871ac751654339ed65ab905d61f0bc3afbb8576a33a5c4e9a73d2084f438582) + +[comment]: # ( SHA256STAMP:4a72e74f8551a9ded7ad9a23044198c985530b72d9a8974bb4ef68b3ee37b8da) diff --git a/doc/lightning-listdatastore.7.md b/doc/lightning-listdatastore.7.md index e3c6e0928d8d..2f0b91c8251f 100644 --- a/doc/lightning-listdatastore.7.md +++ b/doc/lightning-listdatastore.7.md @@ -47,4 +47,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:699f7121a6f1aac9ea8afe39f4bac0e696e97754579d544a141fe2a0e404305f) +[comment]: # ( SHA256STAMP:ccb9085c7ad0757e324e4e74d5a22009153f2a9f40f4e926c15fc918ab2bab4f) diff --git a/doc/lightning-listforwards.7.md b/doc/lightning-listforwards.7.md index 87c5b723c899..7fa489a96bac 100644 --- a/doc/lightning-listforwards.7.md +++ b/doc/lightning-listforwards.7.md @@ -4,7 +4,7 @@ lightning-listforwards -- Command showing all htlcs and their information SYNOPSIS -------- -**listforwards** [*status*] [*in_channel*] [*out_channel*] +**listforwards** [*status*] [*in\_channel*] [*out\_channel*] DESCRIPTION ----------- @@ -13,9 +13,9 @@ The **listforwards** RPC command displays all htlcs that have been attempted to be forwarded by the Core Lightning node. If *status* is specified, then only the forwards with the given status are returned. -*status* can be either *offered* or *settled* or *failed* or *local_failed* +*status* can be either *offered* or *settled* or *failed* or *local\_failed* -If *in_channel* or *out_channel* is specified, then only the matching forwards +If *in\_channel* or *out\_channel* is specified, then only the matching forwards on the given in/out channel are returned. RETURN VALUE @@ -26,23 +26,23 @@ On success, an object containing **forwards** is returned. It is an array of ob - **in\_channel** (short\_channel\_id): the channel that received the HTLC - **in\_msat** (msat): the value of the incoming HTLC -- **status** (string): still ongoing, completed, failed locally, or failed after forwarding (one of "offered", "settled", "local_failed", "failed") +- **status** (string): still ongoing, completed, failed locally, or failed after forwarding (one of "offered", "settled", "local\_failed", "failed") - **received\_time** (number): the UNIX timestamp when this was received - **in\_htlc\_id** (u64, optional): the unique HTLC id the sender gave this (not present if incoming channel was closed before ugprade to v22.11) - **out\_channel** (short\_channel\_id, optional): the channel that the HTLC (trying to) forward to -- **out\_htlc\_id** (u64, optional): the unique HTLC id we gave this when sending (may be missing even if out_channel is present, for old forwards before v22.11) +- **out\_htlc\_id** (u64, optional): the unique HTLC id we gave this when sending (may be missing even if out\_channel is present, for old forwards before v22.11) - **style** (string, optional): Either a legacy onion format or a modern tlv format (one of "legacy", "tlv") If **out\_msat** is present: - **fee\_msat** (msat): the amount this paid in fees - - **out\_msat** (msat): the amount we sent out the *out_channel* + - **out\_msat** (msat): the amount we sent out the *out\_channel* If **status** is "settled" or "failed": - **resolved\_time** (number): the UNIX timestamp when this was resolved -If **status** is "local_failed" or "failed": +If **status** is "local\_failed" or "failed": - **failcode** (u32, optional): the numeric onion code returned - **failreason** (string, optional): the name of the onion code returned @@ -64,4 +64,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:15bf997ae8e93ab28b0084d9cc45fc80fb18b2bcf705f690f77617f0b66b069d) +[comment]: # ( SHA256STAMP:2627ce6a1e4877810e690a40fda2145292ce15f0b1393d3b35b4c54b599b044e) diff --git a/doc/lightning-listfunds.7.md b/doc/lightning-listfunds.7.md index 7c8b31292761..165e3f66a01c 100644 --- a/doc/lightning-listfunds.7.md +++ b/doc/lightning-listfunds.7.md @@ -27,7 +27,7 @@ On success, an object is returned, containing: - **output** (u32): the index within *txid* - **amount\_msat** (msat): the amount of the output - **scriptpubkey** (hex): the scriptPubkey of the output - - **status** (string) (one of "unconfirmed", "confirmed", "spent") + - **status** (string) (one of "unconfirmed", "confirmed", "spent", "immature") - **reserved** (boolean): whether this UTXO is currently reserved for an in-flight tx - **address** (string, optional): the bitcoin address of the output - **redeemscript** (hex, optional): the redeemscript, only if it's p2sh-wrapped @@ -46,13 +46,13 @@ On success, an object is returned, containing: - **funding\_txid** (txid): funding transaction id - **funding\_output** (u32): the 0-based index of the output in the funding transaction - **connected** (boolean): whether the channel peer is connected - - **state** (string): the channel state, in particular "CHANNELD_NORMAL" means the channel can be used normally (one of "OPENINGD", "CHANNELD_AWAITING_LOCKIN", "CHANNELD_NORMAL", "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "AWAITING_UNILATERAL", "FUNDING_SPEND_SEEN", "ONCHAIN", "DUALOPEND_OPEN_INIT", "DUALOPEND_AWAITING_LOCKIN") + - **state** (string): the channel state, in particular "CHANNELD\_NORMAL" means the channel can be used normally (one of "OPENINGD", "CHANNELD\_AWAITING\_LOCKIN", "CHANNELD\_NORMAL", "CHANNELD\_SHUTTING\_DOWN", "CLOSINGD\_SIGEXCHANGE", "CLOSINGD\_COMPLETE", "AWAITING\_UNILATERAL", "FUNDING\_SPEND\_SEEN", "ONCHAIN", "DUALOPEND\_OPEN\_INIT", "DUALOPEND\_AWAITING\_LOCKIN") - If **state** is "CHANNELD_NORMAL": + If **state** is "CHANNELD\_NORMAL": - **short\_channel\_id** (short\_channel\_id): short channel id of channel - If **state** is "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "AWAITING_UNILATERAL", "FUNDING_SPEND_SEEN" or "ONCHAIN": + If **state** is "CHANNELD\_SHUTTING\_DOWN", "CLOSINGD\_SIGEXCHANGE", "CLOSINGD\_COMPLETE", "AWAITING\_UNILATERAL", "FUNDING\_SPEND\_SEEN" or "ONCHAIN": - **short\_channel\_id** (short\_channel\_id, optional): short channel id of channel (only if funding reached lockin depth before closing) @@ -73,4 +73,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:e5c1f54c8a5008a30648e0fe5883132759fcdabd72bd7e8a00bedc360363e85e) +[comment]: # ( SHA256STAMP:5c118dc7780049bcd320aa16d301bf778552fe6ae42c9d598a3926ab0c14694d) diff --git a/doc/lightning-listhtlcs.7.md b/doc/lightning-listhtlcs.7.md index 6c8f265c563e..541b97331d7c 100644 --- a/doc/lightning-listhtlcs.7.md +++ b/doc/lightning-listhtlcs.7.md @@ -27,7 +27,7 @@ On success, an object containing **htlcs** is returned. It is an array of objec - **amount\_msat** (msat): the value of the HTLC - **direction** (string): out if we offered this to the peer, in if they offered it (one of "out", "in") - **payment\_hash** (hex): payment hash sought by HTLC (always 64 characters) -- **state** (string): The first 10 states are for `in`, the next 10 are for `out`. (one of "SENT_ADD_HTLC", "SENT_ADD_COMMIT", "RCVD_ADD_REVOCATION", "RCVD_ADD_ACK_COMMIT", "SENT_ADD_ACK_REVOCATION", "RCVD_REMOVE_HTLC", "RCVD_REMOVE_COMMIT", "SENT_REMOVE_REVOCATION", "SENT_REMOVE_ACK_COMMIT", "RCVD_REMOVE_ACK_REVOCATION", "RCVD_ADD_HTLC", "RCVD_ADD_COMMIT", "SENT_ADD_REVOCATION", "SENT_ADD_ACK_COMMIT", "RCVD_ADD_ACK_REVOCATION", "SENT_REMOVE_HTLC", "SENT_REMOVE_COMMIT", "RCVD_REMOVE_REVOCATION", "RCVD_REMOVE_ACK_COMMIT", "SENT_REMOVE_ACK_REVOCATION") +- **state** (string): The first 10 states are for `in`, the next 10 are for `out`. (one of "SENT\_ADD\_HTLC", "SENT\_ADD\_COMMIT", "RCVD\_ADD\_REVOCATION", "RCVD\_ADD\_ACK\_COMMIT", "SENT\_ADD\_ACK\_REVOCATION", "RCVD\_REMOVE\_HTLC", "RCVD\_REMOVE\_COMMIT", "SENT\_REMOVE\_REVOCATION", "SENT\_REMOVE\_ACK\_COMMIT", "RCVD\_REMOVE\_ACK\_REVOCATION", "RCVD\_ADD\_HTLC", "RCVD\_ADD\_COMMIT", "SENT\_ADD\_REVOCATION", "SENT\_ADD\_ACK\_COMMIT", "RCVD\_ADD\_ACK\_REVOCATION", "SENT\_REMOVE\_HTLC", "SENT\_REMOVE\_COMMIT", "RCVD\_REMOVE\_REVOCATION", "RCVD\_REMOVE\_ACK\_COMMIT", "SENT\_REMOVE\_ACK\_REVOCATION") [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -46,4 +46,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:6ef16f6e1f54522435130d99f224ca41a38fb3c5bc26886ccdaddc69f1abb946) +[comment]: # ( SHA256STAMP:444e5aefafe607226d36b80adfebef7bf0b9173dbb28bbfcc7f78aaed0eac682) diff --git a/doc/lightning-listinvoices.7.md b/doc/lightning-listinvoices.7.md index 1af9600ce8ce..4b8438f3c322 100644 --- a/doc/lightning-listinvoices.7.md +++ b/doc/lightning-listinvoices.7.md @@ -4,7 +4,7 @@ lightning-listinvoices -- Command for querying invoice status SYNOPSIS -------- -**listinvoices** [*label*] [*invstring*] [*payment_hash*] [*offer_id*] +**listinvoices** [*label*] [*invstring*] [*payment\_hash*] [*offer\_id*] DESCRIPTION ----------- @@ -24,7 +24,7 @@ RETURN VALUE On success, an object containing **invoices** is returned. It is an array of objects, where each object contains: - **label** (string): unique label supplied at invoice creation -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): Whether it's paid, unpaid or unpayable (one of "unpaid", "paid", "expired") - **expires\_at** (u64): UNIX timestamp of when it will become / became unpayable - **description** (string, optional): description used in the invoice @@ -32,12 +32,12 @@ On success, an object containing **invoices** is returned. It is an array of ob - **bolt11** (string, optional): the BOLT11 string (always present unless *bolt12* is) - **bolt12** (string, optional): the BOLT12 string (always present unless *bolt11* is) - **local\_offer\_id** (hex, optional): the *id* of our offer which created this invoice (**experimental-offers** only). (always 64 characters) -- **payer\_note** (string, optional): the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only). +- **invreq\_payer\_note** (string, optional): the optional *invreq\_payer\_note* from invoice\_request which created this invoice (**experimental-offers** only). If **status** is "paid": - **pay\_index** (u64): Unique incrementing index for this payment - - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount_msat*, since clients may overpay) + - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount\_msat*, since clients may overpay) - **paid\_at** (u64): UNIX timestamp of when it was paid - **payment\_preimage** (secret): proof of payment (always 64 characters) @@ -58,4 +58,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:58de6b2fa9e3e796689618bf92a78dac66bb6cfe941d099abd73da3e41bfa60e) +[comment]: # ( SHA256STAMP:67af32ecf6319aec4376074b0f0a1b42cf111cbb3acec0108d7f3607dc441252) diff --git a/doc/lightning-listnodes.7.md b/doc/lightning-listnodes.7.md index 63c5bf4af464..bae31753efbc 100644 --- a/doc/lightning-listnodes.7.md +++ b/doc/lightning-listnodes.7.md @@ -30,7 +30,7 @@ RETURN VALUE On success, an object containing **nodes** is returned. It is an array of objects, where each object contains: - **nodeid** (pubkey): the public key of the node -- **last\_timestamp** (u32, optional): A node_announcement has been received for this node (UNIX timestamp) +- **last\_timestamp** (u32, optional): A node\_announcement has been received for this node (UNIX timestamp) If **last\_timestamp** is present: @@ -52,8 +52,8 @@ If **option\_will\_fund** is present: - **lease\_fee\_basis** (u32): the proportional fee in basis points (parts per 10,000) for a lease - **funding\_weight** (u32): the onchain weight you'll have to pay for a lease - **channel\_fee\_max\_base\_msat** (msat): the maximum base routing fee this node will charge during the lease - - **channel\_fee\_max\_proportional\_thousandths** (u32): the maximum proportional routing fee this node will charge during the lease (in thousandths, not millionths like channel_update) - - **compact\_lease** (hex): the lease as represented in the node_announcement + - **channel\_fee\_max\_proportional\_thousandths** (u32): the maximum proportional routing fee this node will charge during the lease (in thousandths, not millionths like channel\_update) + - **compact\_lease** (hex): the lease as represented in the node\_announcement [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -99,4 +99,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:7f1378c1376ade1c9912c8eef3ebc77b13cbc5194ee813f8f1b4e0061338e0bb) + +[comment]: # ( SHA256STAMP:030d48d2a5fc02cb26fc2a35125116085eb67d0afc39066259adacc433a3d38b) diff --git a/doc/lightning-listoffers.7.md b/doc/lightning-listoffers.7.md index 1852270a2106..216b4085a1b7 100644 --- a/doc/lightning-listoffers.7.md +++ b/doc/lightning-listoffers.7.md @@ -5,13 +5,13 @@ SYNOPSIS -------- **(WARNING: experimental-offers only)** -**listoffers** [*offer_id*] [*active_only*] +**listoffers** [*offer\_id*] [*active\_only*] DESCRIPTION ----------- The **listoffers** RPC command list all offers, or with `offer_id`, -only the offer with that offer_id (if it exists). If `active_only` is +only the offer with that offer\_id (if it exists). If `active_only` is set and is true, only offers with `active` true are returned. EXAMPLE JSON REQUEST @@ -36,7 +36,6 @@ On success, an object containing **offers** is returned. It is an array of obje - **active** (boolean): whether this can still be used - **single\_use** (boolean): whether this expires as soon as it's paid - **bolt12** (string): the bolt12 encoding of the offer -- **bolt12\_unsigned** (string): the bolt12 encoding of the offer, without signature - **used** (boolean): True if an associated invoice has been paid - **label** (string, optional): the (optional) user-specified label @@ -81,4 +80,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:ac5b79c1f9b76add7eb08b9940180f2200049509df627cccc1dc892efa488778) + +[comment]: # ( SHA256STAMP:088d6fef8790bc9151b07f9b974568ce612c7fea8f52fdcaaf52b32e4ef8d5f2) diff --git a/doc/lightning-listpays.7.md b/doc/lightning-listpays.7.md index 2a334a7af260..e7ff59c006c4 100644 --- a/doc/lightning-listpays.7.md +++ b/doc/lightning-listpays.7.md @@ -4,13 +4,13 @@ lightning-listpays -- Command for querying payment status SYNOPSIS -------- -**listpays** [*bolt11*] [*payment_hash*] [*status*] +**listpays** [*bolt11*] [*payment\_hash*] [*status*] DESCRIPTION ----------- The **listpay** RPC command gets the status of all *pay* commands, or a -single one if either *bolt11* or *payment_hash* was specified. +single one if either *bolt11* or *payment\_hash* was specified. It is possible filter the payments also by *status*. RETURN VALUE @@ -19,7 +19,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object containing **pays** is returned. It is an array of objects, where each object contains: -- **payment\_hash** (hex): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hex): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): status of the payment (one of "pending", "failed", "complete") - **created\_at** (u64): the UNIX timestamp showing when this payment was initiated - **destination** (pubkey, optional): the final destination of the payment if known @@ -40,7 +40,7 @@ If **status** is "failed": [comment]: # (GENERATE-FROM-SCHEMA-END) -The returned array is ordered by increasing **created_at** fields. +The returned array is ordered by increasing **created\_at** fields. AUTHOR ------ @@ -57,4 +57,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:1175415c0f9398e1087d68dd75266bf894249053a4e57f16b8ee16cf5ffa414f) +[comment]: # ( SHA256STAMP:ee5242e7cf0a7c1385ab26885436b723b916f0d4e17080323876781e8c2aee76) diff --git a/doc/lightning-listpeers.7.md b/doc/lightning-listpeers.7.md index 4a40341efbc9..6838779303a1 100644 --- a/doc/lightning-listpeers.7.md +++ b/doc/lightning-listpeers.7.md @@ -44,17 +44,17 @@ On success, an object containing **peers** is returned. It is an array of objec - **id** (pubkey): the public key of the peer - **connected** (boolean): True if the peer is currently connected - **channels** (array of objects): - - **state** (string): the channel state, in particular "CHANNELD_NORMAL" means the channel can be used normally (one of "OPENINGD", "CHANNELD_AWAITING_LOCKIN", "CHANNELD_NORMAL", "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "AWAITING_UNILATERAL", "FUNDING_SPEND_SEEN", "ONCHAIN", "DUALOPEND_OPEN_INIT", "DUALOPEND_AWAITING_LOCKIN") + - **state** (string): the channel state, in particular "CHANNELD\_NORMAL" means the channel can be used normally (one of "OPENINGD", "CHANNELD\_AWAITING\_LOCKIN", "CHANNELD\_NORMAL", "CHANNELD\_SHUTTING\_DOWN", "CLOSINGD\_SIGEXCHANGE", "CLOSINGD\_COMPLETE", "AWAITING\_UNILATERAL", "FUNDING\_SPEND\_SEEN", "ONCHAIN", "DUALOPEND\_OPEN\_INIT", "DUALOPEND\_AWAITING\_LOCKIN") - **opener** (string): Who initiated the channel (one of "local", "remote") - **features** (array of strings): - - BOLT #9 features which apply to this channel (one of "option_static_remotekey", "option_anchor_outputs", "option_zeroconf") + - BOLT #9 features which apply to this channel (one of "option\_static\_remotekey", "option\_anchor\_outputs", "option\_zeroconf") - **scratch\_txid** (txid, optional): The txid we would use if we went onchain now - **feerate** (object, optional): Feerates for the current tx: - **perkw** (u32): Feerate per 1000 weight (i.e kSipa) - **perkb** (u32): Feerate per 1000 virtual bytes - **owner** (string, optional): The current subdaemon controlling this connection - - **short\_channel\_id** (short\_channel\_id, optional): The short_channel_id (once locked in) - - **channel\_id** (hash, optional): The full channel_id (always 64 characters) + - **short\_channel\_id** (short\_channel\_id, optional): The short\_channel\_id (once locked in) + - **channel\_id** (hash, optional): The full channel\_id (always 64 characters) - **funding\_txid** (txid, optional): ID of the funding transaction - **funding\_outnum** (u32, optional): The 0-based output number of the funding transaction which opens the channel - **initial\_feerate** (string, optional): For inflight opens, the first feerate used to initiate the channel open @@ -102,8 +102,8 @@ On success, an object containing **peers** is returned. It is an array of objec - **remote** (short\_channel\_id, optional): An alias assigned by the remote node to this channel, usable in routehints and invoices - **state\_changes** (array of objects, optional): Prior state changes: - **timestamp** (string): UTC timestamp of form YYYY-mm-ddTHH:MM:SS.%03dZ - - **old\_state** (string): Previous state (one of "OPENINGD", "CHANNELD_AWAITING_LOCKIN", "CHANNELD_NORMAL", "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "AWAITING_UNILATERAL", "FUNDING_SPEND_SEEN", "ONCHAIN", "DUALOPEND_OPEN_INIT", "DUALOPEND_AWAITING_LOCKIN") - - **new\_state** (string): New state (one of "OPENINGD", "CHANNELD_AWAITING_LOCKIN", "CHANNELD_NORMAL", "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "AWAITING_UNILATERAL", "FUNDING_SPEND_SEEN", "ONCHAIN", "DUALOPEND_OPEN_INIT", "DUALOPEND_AWAITING_LOCKIN") + - **old\_state** (string): Previous state (one of "OPENINGD", "CHANNELD\_AWAITING\_LOCKIN", "CHANNELD\_NORMAL", "CHANNELD\_SHUTTING\_DOWN", "CLOSINGD\_SIGEXCHANGE", "CLOSINGD\_COMPLETE", "AWAITING\_UNILATERAL", "FUNDING\_SPEND\_SEEN", "ONCHAIN", "DUALOPEND\_OPEN\_INIT", "DUALOPEND\_AWAITING\_LOCKIN") + - **new\_state** (string): New state (one of "OPENINGD", "CHANNELD\_AWAITING\_LOCKIN", "CHANNELD\_NORMAL", "CHANNELD\_SHUTTING\_DOWN", "CLOSINGD\_SIGEXCHANGE", "CLOSINGD\_COMPLETE", "AWAITING\_UNILATERAL", "FUNDING\_SPEND\_SEEN", "ONCHAIN", "DUALOPEND\_OPEN\_INIT", "DUALOPEND\_AWAITING\_LOCKIN") - **cause** (string): What caused the change (one of "unknown", "local", "user", "remote", "protocol", "onchain") - **message** (string): Human-readable explanation - **status** (array of strings, optional): @@ -121,17 +121,17 @@ On success, an object containing **peers** is returned. It is an array of objec - **id** (u64): Unique ID for this htlc on this channel in this direction - **amount\_msat** (msat): Amount send/received for this HTLC - **expiry** (u32): Block this HTLC expires at - - **payment\_hash** (hash): the hash of the payment_preimage which will prove payment (always 64 characters) + - **payment\_hash** (hash): the hash of the payment\_preimage which will prove payment (always 64 characters) - **local\_trimmed** (boolean, optional): if this is too small to enforce onchain (always *true*) - **status** (string, optional): set if this HTLC is currently waiting on a hook (and shows what plugin) If **direction** is "out": - - **state** (string): Status of the HTLC (one of "SENT_ADD_HTLC", "SENT_ADD_COMMIT", "RCVD_ADD_REVOCATION", "RCVD_ADD_ACK_COMMIT", "SENT_ADD_ACK_REVOCATION", "RCVD_REMOVE_HTLC", "RCVD_REMOVE_COMMIT", "SENT_REMOVE_REVOCATION", "SENT_REMOVE_ACK_COMMIT", "RCVD_REMOVE_ACK_REVOCATION") + - **state** (string): Status of the HTLC (one of "SENT\_ADD\_HTLC", "SENT\_ADD\_COMMIT", "RCVD\_ADD\_REVOCATION", "RCVD\_ADD\_ACK\_COMMIT", "SENT\_ADD\_ACK\_REVOCATION", "RCVD\_REMOVE\_HTLC", "RCVD\_REMOVE\_COMMIT", "SENT\_REMOVE\_REVOCATION", "SENT\_REMOVE\_ACK\_COMMIT", "RCVD\_REMOVE\_ACK\_REVOCATION") If **direction** is "in": - - **state** (string): Status of the HTLC (one of "RCVD_ADD_HTLC", "RCVD_ADD_COMMIT", "SENT_ADD_REVOCATION", "SENT_ADD_ACK_COMMIT", "RCVD_ADD_ACK_REVOCATION", "SENT_REMOVE_HTLC", "SENT_REMOVE_COMMIT", "RCVD_REMOVE_REVOCATION", "RCVD_REMOVE_ACK_COMMIT", "SENT_REMOVE_ACK_REVOCATION") + - **state** (string): Status of the HTLC (one of "RCVD\_ADD\_HTLC", "RCVD\_ADD\_COMMIT", "SENT\_ADD\_REVOCATION", "SENT\_ADD\_ACK\_COMMIT", "RCVD\_ADD\_ACK\_REVOCATION", "SENT\_REMOVE\_HTLC", "SENT\_REMOVE\_COMMIT", "RCVD\_REMOVE\_REVOCATION", "RCVD\_REMOVE\_ACK\_COMMIT", "SENT\_REMOVE\_ACK\_REVOCATION") If **close\_to** is present: @@ -143,7 +143,7 @@ On success, an object containing **peers** is returned. It is an array of objec If **short\_channel\_id** is present: - - **direction** (u32): 0 if we're the lesser node_id, 1 if we're the greater + - **direction** (u32): 0 if we're the lesser node\_id, 1 if we're the greater If **inflight** is present: @@ -151,7 +151,7 @@ On success, an object containing **peers** is returned. It is an array of objec - **last\_feerate** (string): The feerate for the latest funding transaction in per-1000-weight, with "kpw" appended - **next\_feerate** (string): The minimum feerate for the next funding transaction in per-1000-weight, with "kpw" appended - **log** (array of objects, optional): if *level* is specified, logs for this peer: - - **type** (string) (one of "SKIPPED", "BROKEN", "UNUSUAL", "INFO", "DEBUG", "IO_IN", "IO_OUT") + - **type** (string) (one of "SKIPPED", "BROKEN", "UNUSUAL", "INFO", "DEBUG", "IO\_IN", "IO\_OUT") If **type** is "SKIPPED": @@ -164,7 +164,7 @@ On success, an object containing **peers** is returned. It is an array of objec - **log** (string): The actual log message - **node\_id** (pubkey): The peer this is associated with - If **type** is "IO_IN" or "IO_OUT": + If **type** is "IO\_IN" or "IO\_OUT": - **time** (string): UNIX timestamp with 9 decimal places - **source** (string): The particular logbook this was found in @@ -227,7 +227,7 @@ The objects in the *channels* array will have at least these fields: peer, or a theft attempt). * `"CLOSED"`: The channel closure has been confirmed deeply. The channel will eventually be removed from this array. -* *state_changes*: An array of objects describing prior state change events. +* *state\_changes*: An array of objects describing prior state change events. * *opener*: A string `"local"` or `"remote`" describing which side opened this channel. * *closer*: A string `"local"` or `"remote`" describing which side @@ -243,9 +243,9 @@ The objects in the *channels* array will have at least these fields: a number followed by a string unit. * *total\_msat*: A string describing the total capacity of the channel; a number followed by a string unit. -* *fee_base_msat*: The fixed routing fee we charge for forwards going out over +* *fee\_base\_msat*: The fixed routing fee we charge for forwards going out over this channel, regardless of payment size. -* *fee_proportional_millionths*: The proportional routing fees in ppm (parts- +* *fee\_proportional\_millionths*: The proportional routing fees in ppm (parts- per-millionths) we charge for forwards going out over this channel. * *features*: An array of feature names supported by this channel. @@ -325,7 +325,7 @@ state, or in various circumstances: your funds, if you close unilaterally. * *max\_accepted\_htlcs*: The maximum number of HTLCs you will accept on this channel. -* *in\_payments_offered*: The number of incoming HTLCs offered over this +* *in\_payments\_offered*: The number of incoming HTLCs offered over this channel. * *in\_offered\_msat*: A string describing the total amount of all incoming HTLCs offered over this channel; @@ -345,9 +345,9 @@ state, or in various circumstances: * *out\_fulfilled\_msat*: A string describing the total amount of all outgoing HTLCs offered *and successfully claimed* over this channel; a number followed by a string unit. -* *scratch_txid*: The txid of the latest transaction (what we would sign and +* *scratch\_txid*: The txid of the latest transaction (what we would sign and send to chain if the channel were to fail now). -* *last_tx_fee*: The fee on that latest transaction. +* *last\_tx\_fee*: The fee on that latest transaction. * *feerate*: An object containing the latest feerate as both *perkw* and *perkb*. * *htlcs*: An array of objects describing the HTLCs currently in-flight in the channel. @@ -399,4 +399,4 @@ Main web site: Lightning RFC site (BOLT \#9): -[comment]: # ( SHA256STAMP:108f43815e3475b88fd9b6a4a8f868e9d729c5d7616e0b0cc2c14f8922f54955) +[comment]: # ( SHA256STAMP:faff728119e12d98202be265991e8b2c17dfa1a611bc52586c662fe8bfdccf53) diff --git a/doc/lightning-listsendpays.7.md b/doc/lightning-listsendpays.7.md index fb575eb59a55..a5d4a6d5b008 100644 --- a/doc/lightning-listsendpays.7.md +++ b/doc/lightning-listsendpays.7.md @@ -26,8 +26,8 @@ Note that the returned array is ordered by increasing *id*. On success, an object containing **payments** is returned. It is an array of objects, where each object contains: - **id** (u64): unique ID for this payment attempt -- **groupid** (u64): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment_hash -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **groupid** (u64): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment\_hash +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): status of the payment (one of "pending", "failed", "complete") - **created\_at** (u64): the UNIX timestamp showing when this payment was initiated - **amount\_sent\_msat** (msat): The amount sent @@ -40,7 +40,7 @@ On success, an object containing **payments** is returned. It is an array of ob If **status** is "complete": - - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) + - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment\_hash** (always 64 characters) If **status** is "failed": @@ -64,4 +64,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:eddbf227775b367fbea5d90dfc1d06bc87b9301e4b862b0d755592432ef58f89) +[comment]: # ( SHA256STAMP:bd2975d79d2000a8f390da4744c79a924b4fba8268830d086d5024defe8ac274) diff --git a/doc/lightning-listtransactions.7.md b/doc/lightning-listtransactions.7.md index b43e763ff441..23751d0c9e8f 100644 --- a/doc/lightning-listtransactions.7.md +++ b/doc/lightning-listtransactions.7.md @@ -37,20 +37,20 @@ On success, an object containing **transactions** is returned. It is an array o - **txid** (txid): the transaction id spent - **index** (u32): the output spent - **sequence** (u32): the nSequence value - - **type** (string, optional): the purpose of this input (*EXPERIMENTAL_FEATURES* only) (one of "theirs", "deposit", "withdraw", "channel_funding", "channel_mutual_close", "channel_unilateral_close", "channel_sweep", "channel_htlc_success", "channel_htlc_timeout", "channel_penalty", "channel_unilateral_cheat") - - **channel** (short\_channel\_id, optional): the channel this input is associated with (*EXPERIMENTAL_FEATURES* only) + - **type** (string, optional): the purpose of this input (*EXPERIMENTAL\_FEATURES* only) (one of "theirs", "deposit", "withdraw", "channel\_funding", "channel\_mutual\_close", "channel\_unilateral\_close", "channel\_sweep", "channel\_htlc\_success", "channel\_htlc\_timeout", "channel\_penalty", "channel\_unilateral\_cheat") + - **channel** (short\_channel\_id, optional): the channel this input is associated with (*EXPERIMENTAL\_FEATURES* only) - **outputs** (array of objects): Each output, in order: - **index** (u32): the 0-based output number - **amount\_msat** (msat): the amount of the output - **scriptPubKey** (hex): the scriptPubKey - - **type** (string, optional): the purpose of this output (*EXPERIMENTAL_FEATURES* only) (one of "theirs", "deposit", "withdraw", "channel_funding", "channel_mutual_close", "channel_unilateral_close", "channel_sweep", "channel_htlc_success", "channel_htlc_timeout", "channel_penalty", "channel_unilateral_cheat") - - **channel** (short\_channel\_id, optional): the channel this output is associated with (*EXPERIMENTAL_FEATURES* only) + - **type** (string, optional): the purpose of this output (*EXPERIMENTAL\_FEATURES* only) (one of "theirs", "deposit", "withdraw", "channel\_funding", "channel\_mutual\_close", "channel\_unilateral\_close", "channel\_sweep", "channel\_htlc\_success", "channel\_htlc\_timeout", "channel\_penalty", "channel\_unilateral\_cheat") + - **channel** (short\_channel\_id, optional): the channel this output is associated with (*EXPERIMENTAL\_FEATURES* only) - **type** (array of strings, optional): - - Reason we care about this transaction (*EXPERIMENTAL_FEATURES* only) (one of "theirs", "deposit", "withdraw", "channel_funding", "channel_mutual_close", "channel_unilateral_close", "channel_sweep", "channel_htlc_success", "channel_htlc_timeout", "channel_penalty", "channel_unilateral_cheat") -- **channel** (short\_channel\_id, optional): the channel this transaction is associated with (*EXPERIMENTAL_FEATURES* only) + - Reason we care about this transaction (*EXPERIMENTAL\_FEATURES* only) (one of "theirs", "deposit", "withdraw", "channel\_funding", "channel\_mutual\_close", "channel\_unilateral\_close", "channel\_sweep", "channel\_htlc\_success", "channel\_htlc\_timeout", "channel\_penalty", "channel\_unilateral\_cheat") +- **channel** (short\_channel\_id, optional): the channel this transaction is associated with (*EXPERIMENTAL\_FEATURES* only) [comment]: # (GENERATE-FROM-SCHEMA-END) - + On failure, one of the following error codes may be returned: - -32602: Error in given parameters. @@ -105,4 +105,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:f7c39908eaa1a2561597c8f97658b873953daab0a68ed2e9b68e434a55d55efe) + +[comment]: # ( SHA256STAMP:1a1afbcbcdbd19df28020d48c581dfff6ed4f5beaf557e1423edb6828eb78a07) diff --git a/doc/lightning-makesecret.7.md b/doc/lightning-makesecret.7.md index fc54dd514e5a..517389c35369 100644 --- a/doc/lightning-makesecret.7.md +++ b/doc/lightning-makesecret.7.md @@ -9,7 +9,7 @@ SYNOPSIS DESCRIPTION ----------- -The **makesecret** RPC command derives a secret key from the HSM_secret. +The **makesecret** RPC command derives a secret key from the HSM\_secret. One of *hex* or *string* must be specified: *hex* can be any hex data, *string* is a UTF-8 string interpreted literally. @@ -20,7 +20,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **secret** (secret): the pseudorandom key derived from HSM_secret (always 64 characters) +- **secret** (secret): the pseudorandom key derived from HSM\_secret (always 64 characters) [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -38,4 +38,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:0ef7c3e2172219fa647d1c447cb82daa7857c6c53a27fd191bff83f59ce6b9f7) +[comment]: # ( SHA256STAMP:5560433bde5292bad74eab0b688d8e6baa0a51562670a4f486d41b4eb2103ca8) diff --git a/doc/lightning-multifundchannel.7.md b/doc/lightning-multifundchannel.7.md index 54f827af5326..045cbc22d741 100644 --- a/doc/lightning-multifundchannel.7.md +++ b/doc/lightning-multifundchannel.7.md @@ -4,7 +4,7 @@ lightning-multifundchannel -- Command for establishing many lightning channels SYNOPSIS -------- -**multifundchannel** *destinations* [*feerate*] [*minconf*] [*utxos*] [*minchannels*] [*commitment_feerate*] +**multifundchannel** *destinations* [*feerate*] [*minconf*] [*utxos*] [*minchannels*] [*commitment\_feerate*] DESCRIPTION ----------- @@ -47,22 +47,26 @@ Readiness is indicated by **listpeers** reporting a *state* of node. This is a gift to the peer, and you do not get a proof-of-payment out of this. -* *close_to* is a Bitcoin address to which the channel funds should be sent to +* *close\_to* is a Bitcoin address to which the channel funds should be sent to on close. Only valid if both peers have negotiated `option_upfront_shutdown_script`. Returns `close_to` set to closing script iff is negotiated. -* *request_amt* is the amount of liquidity you'd like to lease from peer. +* *request\_amt* is the amount of liquidity you'd like to lease from peer. If peer supports `option_will_fund`, indicates to them to include this - much liquidity into the channel. Must also pass in *compact_lease*. -* *compact_lease* is a compact represenation of the peer's expected + much liquidity into the channel. Must also pass in *compact\_lease*. +* *compact\_lease* is a compact represenation of the peer's expected channel lease terms. If the peer's terms don't match this set, we will fail to open the channel to this destination. +* *reserve* is the amount we want the peer to maintain on its side of the + channel. Default is 1% of the funding amount. It can be a whole number, a + whole number ending in *sat*, a whole number ending in *000msat*, or a number + with 1 to 8 decimal places ending in *btc*. There must be at least one entry in *destinations*; it cannot be an empty array. *feerate* is an optional feerate used for the opening transaction and, if -*commitment_feerate* is not set, as the initial feerate for +*commitment\_feerate* is not set, as the initial feerate for commitment and HTLC transactions. It can be one of the strings *urgent* (aim for next block), *normal* (next 4 blocks or so) or *slow* (next 100 blocks or so) to use lightningd's internal @@ -84,7 +88,7 @@ this many peers remain (must not be zero). The **multifundchannel** command will only fail if too many peers fail the funding process. -*commitment_feerate* is the initial feerate for commitment and HTLC +*commitment\_feerate* is the initial feerate for commitment and HTLC transactions. See *feerate* for valid values. RETURN VALUE @@ -105,11 +109,11 @@ On success, an object is returned, containing: - **channel\_ids** (array of objects): - **id** (pubkey): The peer we opened the channel with - **outnum** (u32): The 0-based output index showing which output funded the channel - - **channel\_id** (hex): The channel_id of the resulting channel (always 64 characters) - - **close\_to** (hex, optional): The raw scriptPubkey which mutual close will go to; only present if *close_to* parameter was specified and peer supports `option_upfront_shutdown_script` + - **channel\_id** (hex): The channel\_id of the resulting channel (always 64 characters) + - **close\_to** (hex, optional): The raw scriptPubkey which mutual close will go to; only present if *close\_to* parameter was specified and peer supports `option_upfront_shutdown_script` - **failed** (array of objects, optional): any peers we failed to open with (if *minchannels* was specified less than the number of destinations): - **id** (pubkey): The peer we failed to open the channel with - - **method** (string): What stage we failed at (one of "connect", "openchannel_init", "fundchannel_start", "fundchannel_complete") + - **method** (string): What stage we failed at (one of "connect", "openchannel\_init", "fundchannel\_start", "fundchannel\_complete") - **error** (object): - **code** (integer): JSON error code from failing stage - **message** (string): Message from stage @@ -159,4 +163,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:a507d57bbf36455924497c8354f41e225bc16f63f12fe01b4f7c4af37f0c6960) + +[comment]: # ( SHA256STAMP:0dc2b563ed6995f65388a52b01e8882a167ead3c1d3b3dc985486cd8b4dfe157) diff --git a/doc/lightning-multiwithdraw.7.md b/doc/lightning-multiwithdraw.7.md index c090387d74ee..59560201d1bd 100644 --- a/doc/lightning-multiwithdraw.7.md +++ b/doc/lightning-multiwithdraw.7.md @@ -72,4 +72,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:6c0054088c17481dedbedb6a5ed4be7f09ce8783780707432907508ebf4bbd7a) + +[comment]: # ( SHA256STAMP:632868a585b9150a80ccc4ba173d90a8beebab8e604c06f1ccdc4493604152e3) diff --git a/doc/lightning-newaddr.7.md b/doc/lightning-newaddr.7.md index 1c47a13b4129..8c2902c98935 100644 --- a/doc/lightning-newaddr.7.md +++ b/doc/lightning-newaddr.7.md @@ -58,4 +58,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:e9650b5f1f4374007c8fde63dae2ac9981c952ed8074aabade39fcc0ebe21333) +[comment]: # ( SHA256STAMP:2178e43f4b90a07f1d31679f86e7e5b1bc5239333ba64652614f03847c869fd4) diff --git a/doc/lightning-notifications.7.md b/doc/lightning-notifications.7.md index 3940d12ab91f..1027b301ec7b 100644 --- a/doc/lightning-notifications.7.md +++ b/doc/lightning-notifications.7.md @@ -102,4 +102,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:326e5801f65998e13e909d8b682e9fbc9824f3a43aa7da1d76b871882e52f293) + +[comment]: # ( SHA256STAMP:6ab8038cbad395e5a65a52fe66948740ad360c123e42c28d5879f5f03369b744) diff --git a/doc/lightning-offer.7.md b/doc/lightning-offer.7.md index 26e689b923ba..ffccb36465d2 100644 --- a/doc/lightning-offer.7.md +++ b/doc/lightning-offer.7.md @@ -6,14 +6,14 @@ SYNOPSIS **(WARNING: experimental-offers only)** -**offer** *amount* *description* [*issuer*] [*label*] [*quantity_min*] [*quantity_max*] [*absolute_expiry*] [*recurrence*] [*recurrence_base*] [*recurrence_paywindow*] [*recurrence_limit*] [*single_use*] +**offer** *amount* *description* [*issuer*] [*label*] [*quantity\_max*] [*absolute\_expiry*] [*recurrence*] [*recurrence\_base*] [*recurrence\_paywindow*] [*recurrence\_limit*] [*single\_use*] DESCRIPTION ----------- The **offer** RPC command creates an offer (or returns an existing one), which is a precursor to creating one or more invoices. It -automatically enables the processing of an incoming invoice_request, +automatically enables the processing of an incoming invoice\_request, and issuing of invoices. Note that it creates two variants of the offer: a signed and an @@ -43,12 +43,13 @@ reflects who is issuing this offer (i.e. you) if appropriate. The *label* field is an internal-use name for the offer, which can be any UTF-8 string. -The present of *quantity_min* or *quantity_max* indicates that the -invoice can specify more than one of the items within this (inclusive) -range. The *amount* for the invoice will need to be multiplied -accordingly. These are encoded in the offer. +The presence of *quantity\_max* indicates that the +invoice can specify more than one of the items up (and including) +this maximum: 0 is a special value meaning "no maximuim". +The *amount* for the invoice will need to be multiplied +accordingly. This is encoded in the offer. -The *absolute_expiry* is optionally the time the offer is valid until, +The *absolute\_expiry* is optionally the time the offer is valid until, in seconds since the first day of 1970 UTC. If not set, the offer remains valid (though it can be deactivated by the issuer of course). This is encoded in the offer. @@ -60,7 +61,7 @@ without the trailing "s" are also permitted). This is encoded in the offer. The semantics of recurrence is fairly predictable, but fully documented in BOLT 12. e.g. "4weeks". -*recurrence_base* is an optional time in seconds since the first day +*recurrence\_base* is an optional time in seconds since the first day of 1970 UTC, optionally with a "@" prefix. This indicates when the first period begins; without this, the recurrence periods start from the first invoice. The "@" prefix means that the invoice must start @@ -68,7 +69,7 @@ by paying the first period; otherwise it is permitted to start at any period. This is encoded in the offer. e.g. "@1609459200" indicates you must start paying on the 1st January 2021. -*recurrence_paywindow* is an optional argument of form +*recurrence\_paywindow* is an optional argument of form '-time+time[%]'. The first time is the number of seconds before the start of a period in which an invoice and payment is valid, the second time is the number of seconds after the start of the period. For @@ -79,15 +80,15 @@ by the time remaining in the period. If this is not specified, the default is that payment is allowed during the current and previous periods. This is encoded in the offer. -*recurrence_limit* is an optional argument to indicate the maximum +*recurrence\_limit* is an optional argument to indicate the maximum period which exists. eg. "12" means there are 13 periods, from 0 to 12 inclusive. This is encoded in the offer. -*refund_for* is the payment_preimage of a previous (paid) invoice. -This implies *send_invoice* and *single_use*. This is encoded in the +*refund\_for* is the payment\_preimage of a previous (paid) invoice. +This implies *send\_invoice* and *single\_use*. This is encoded in the offer. -*single_use* (default false) indicates that the offer is only valid +*single\_use* (default false) indicates that the offer is only valid once; we may issue multiple invoices, but as soon as one is paid all other invoices will be expired (i.e. only one person can pay this offer). @@ -99,9 +100,8 @@ On success, an object is returned, containing: - **offer\_id** (hex): the id of this offer (merkle hash of non-signature fields) (always 64 characters) - **active** (boolean): whether this can still be used (always *true*) -- **single\_use** (boolean): whether this expires as soon as it's paid (reflects the *single_use* parameter) +- **single\_use** (boolean): whether this expires as soon as it's paid (reflects the *single\_use* parameter) - **bolt12** (string): the bolt12 encoding of the offer -- **bolt12\_unsigned** (string): the bolt12 encoding of the offer, without a signature - **used** (boolean): True if an associated invoice has been paid - **created** (boolean): false if the offer already existed - **label** (string, optional): the (optional) user-specified label @@ -118,7 +118,7 @@ if it's not active then this call fails. The following error codes may occur: - -1: Catchall nonspecific error. -- 1000: Offer with this offer_id already exists (but is not active). +- 1000: Offer with this offer\_id already exists (but is not active). AUTHOR ------ @@ -135,4 +135,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:aa7544c07d3d84963e43500a367ceb62ebab8f5ae26de1dd39bb087f928dcaee) +[comment]: # ( SHA256STAMP:a9cd6cc9f41fefc87c060ee979599f55154a11fc3a9b5dca046cea3e9c2385c2) diff --git a/doc/lightning-offerout.7.md b/doc/lightning-offerout.7.md index 968a0ffa39cf..f89dce17daa9 100644 --- a/doc/lightning-offerout.7.md +++ b/doc/lightning-offerout.7.md @@ -7,7 +7,7 @@ SYNOPSIS **(WARNING: experimental-offers only)** -**offerout** *amount* *description* [*issuer*] [*label*] [*absolute_expiry*] [*refund_for*] +**offerout** *amount* *description* [*issuer*] [*label*] [*absolute\_expiry*] [*refund\_for*] DESCRIPTION ----------- @@ -40,13 +40,13 @@ reflects who is issuing this offer (i.e. you) if appropriate. The *label* field is an internal-use name for the offer, which can be any UTF-8 string. -The *absolute_expiry* is optionally the time the offer is valid until, +The *absolute\_expiry* is optionally the time the offer is valid until, in seconds since the first day of 1970 UTC. If not set, the offer remains valid (though it can be deactivated by the issuer of course). This is encoded in the offer. -*refund_for* is a previous (paid) invoice of ours. The -payment_preimage of this is encoded in the offer, and redemption +*refund\_for* is a previous (paid) invoice of ours. The +payment\_preimage of this is encoded in the offer, and redemption requires that the invoice we receive contains a valid signature using that previous `payer_key`. @@ -60,7 +60,6 @@ On success, an object is returned, containing: - **active** (boolean): whether this will pay a matching incoming invoice (always *true*) - **single\_use** (boolean): whether this expires as soon as it's paid out (always *true*) - **bolt12** (string): the bolt12 encoding of the offer -- **bolt12\_unsigned** (string): the bolt12 encoding of the offer, without a signature - **used** (boolean): True if an incoming invoice has been paid (always *false*) - **created** (boolean): false if the offer already existed - **label** (string, optional): the (optional) user-specified label @@ -74,7 +73,7 @@ not. The following error codes may occur: - -1: Catchall nonspecific error. -- 1000: Offer with this offer_id already exists. +- 1000: Offer with this offer\_id already exists. NOTES ----- @@ -100,4 +99,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:7c0f75ca64bdcce2467f42d7671caccf5f7bf6eb97fb3edef1e39f2fdb87b4d8) +[comment]: # ( SHA256STAMP:4f780ca32d486bd715eed86a130b87ff1515fce6f9e225cb13219267b82b33bb) diff --git a/doc/lightning-openchannel_abort.7.md b/doc/lightning-openchannel_abort.7.md index fdfd60c74e63..7a5ddaa50a19 100644 --- a/doc/lightning-openchannel_abort.7.md +++ b/doc/lightning-openchannel_abort.7.md @@ -4,7 +4,7 @@ lightning-openchannel\_abort -- Command to abort a channel to a peer SYNOPSIS -------- -**openchannel_abort** *channel_id* +**openchannel\_abort** *channel\_id* DESCRIPTION ----------- @@ -13,7 +13,7 @@ DESCRIPTION open with a specified peer. It uses the openchannel protocol which allows for interactive transaction construction. -*channel_id* is id of this channel. +*channel\_id* is id of this channel. RETURN VALUE @@ -55,4 +55,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:ed449af5b443c981faaff360cb2276816bbc7cd80f85fdb4403987c29d65baed) + +[comment]: # ( SHA256STAMP:f80423882383e5cb39b86543eb8cfbc0d9b6731ea85af3b3e1fb8973b9355781) diff --git a/doc/lightning-openchannel_bump.7.md b/doc/lightning-openchannel_bump.7.md index 1af6590d3e70..5104618ee1b6 100644 --- a/doc/lightning-openchannel_bump.7.md +++ b/doc/lightning-openchannel_bump.7.md @@ -4,7 +4,7 @@ lightning-openchannel\_bump -- Command to initiate a channel RBF SYNOPSIS -------- -**openchannel_bump** *channel_id* *amount* *initalpsbt* [*funding_feerate*] +**openchannel\_bump** *channel\_id* *amount* *initalpsbt* [*funding\_feerate*] DESCRIPTION ----------- @@ -26,7 +26,7 @@ Must have the Non-Witness UTXO (PSBT\_IN\_NON\_WITNESS\_UTXO) set for every input. An error (code 309) will be returned if this requirement is not met. -*funding_feerate* is an optional field. Sets the feerate for the +*funding\_feerate* is an optional field. Sets the feerate for the funding transaction. Defaults to 1/64th greater than the last feerate used for this channel. @@ -41,7 +41,7 @@ On success, an object is returned, containing: - **channel\_id** (hex): the channel id of the channel (always 64 characters) - **psbt** (string): the (incomplete) PSBT of the RBF transaction - **commitments\_secured** (boolean): whether the *psbt* is complete (always *false*) -- **funding\_serial** (u64): the serial_id of the funding output in the *psbt* +- **funding\_serial** (u64): the serial\_id of the funding output in the *psbt* [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -81,4 +81,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:3cba5d1c16925322754eae979e956132e8b94e40da0dee6925037a8854d9b791) + +[comment]: # ( SHA256STAMP:fe2bf77f2cb693ee91ab1977d05ba8431b0a8bed67aa1bbda6992bf64604081b) diff --git a/doc/lightning-openchannel_init.7.md b/doc/lightning-openchannel_init.7.md index 261afb2719ca..20d8dec73f95 100644 --- a/doc/lightning-openchannel_init.7.md +++ b/doc/lightning-openchannel_init.7.md @@ -4,7 +4,7 @@ lightning-openchannel\_init -- Command to initiate a channel to a peer SYNOPSIS -------- -**openchannel_init** *id* *amount* *initalpsbt* [*commitment_feerate*] [*funding_feerate*] [*announce*] [*close_to*] [*request_amt*] [*compact_lease*] +**openchannel\_init** *id* *amount* *initalpsbt* [*commitment\_feerate*] [*funding\_feerate*] [*announce*] [*close\_to*] [*request\_amt*] [*compact\_lease*] DESCRIPTION ----------- @@ -26,23 +26,23 @@ Must have the Non-Witness UTXO (PSBT\_IN\_NON\_WITNESS\_UTXO) set for every input. An error (code 309) will be returned if this requirement is not met. -*commitment_feerate* is an optional field. Sets the feerate for +*commitment\_feerate* is an optional field. Sets the feerate for commitment transactions: see **fundchannel**. -*funding_feerate* is an optional field. Sets the feerate for the +*funding\_feerate* is an optional field. Sets the feerate for the funding transaction. Defaults to 'opening' feerate. *announce* is an optional field. Whether or not to announce this channel. -*close_to* is a Bitcoin address to which the channel funds should be +*close\_to* is a Bitcoin address to which the channel funds should be sent on close. Only valid if both peers have negotiated `option_upfront_shutdown_script`. -*request_amt* is an amount of liquidity you'd like to lease from the peer. +*request\_amt* is an amount of liquidity you'd like to lease from the peer. If peer supports `option_will_fund`, indicates to them to include this -much liquidity into the channel. Must also pass in *compact_lease*. +much liquidity into the channel. Must also pass in *compact\_lease*. -*compact_lease* is a compact represenation of the peer's expected +*compact\_lease* is a compact represenation of the peer's expected channel lease terms. If the peer's terms don't match this set, we will fail to open the channel. @@ -56,19 +56,19 @@ On success, an object is returned, containing: - **channel\_id** (hex): the channel id of the channel (always 64 characters) - **psbt** (string): the (incomplete) PSBT of the funding transaction - **commitments\_secured** (boolean): whether the *psbt* is complete (always *false*) -- **funding\_serial** (u64): the serial_id of the funding output in the *psbt* +- **funding\_serial** (u64): the serial\_id of the funding output in the *psbt* [comment]: # (GENERATE-FROM-SCHEMA-END) If the peer does not support `option_dual_fund`, this command will return an error. -If you sent a *request_amt* and the peer supports `option_will_fund` and is +If you sent a *request\_amt* and the peer supports `option_will_fund` and is interested in leasing you liquidity in this channel, returns their updated -channel fee max (*channel_fee_proportional_basis*, *channel_fee_base_msat*), -updated rate card for the lease fee (*lease_fee_proportional_basis*, -*lease_fee_base_sat*) and their on-chain weight *weight_charge*, which will -be added to the lease fee at a rate of *funding_feerate* * *weight_charge* +channel fee max (*channel\_fee\_proportional\_basis*, *channel\_fee\_base\_msat*), +updated rate card for the lease fee (*lease\_fee\_proportional\_basis*, +*lease\_fee\_base\_sat*) and their on-chain weight *weight\_charge*, which will +be added to the lease fee at a rate of *funding\_feerate* * *weight\_charge* / 1000. On error the returned object will contain `code` and `message` properties, @@ -103,4 +103,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:18421f03dece31aafe32cb1a9b520dd6b898e018cb187de6d666e391232fab4e) + +[comment]: # ( SHA256STAMP:ca7708f0c64afc898cb336eafb26ee384895f83b2026aecab75596372d33e46e) diff --git a/doc/lightning-openchannel_signed.7.md b/doc/lightning-openchannel_signed.7.md index e62cad7336e3..dba8a14eb2fe 100644 --- a/doc/lightning-openchannel_signed.7.md +++ b/doc/lightning-openchannel_signed.7.md @@ -4,7 +4,7 @@ lightning-openchannel\_signed -- Command to conclude a channel open SYNOPSIS -------- -**openchannel_signed** *channel_id* *signed_psbt* +**openchannel\_signed** *channel\_id* *signed\_psbt* DESCRIPTION ----------- @@ -14,15 +14,15 @@ open with the specified peer. It uses the v2 openchannel protocol, which allows for interactive transaction construction. This command should be called after `openchannel_update` returns -*commitments_secured* `true`. +*commitments\_secured* `true`. This command will broadcast the finalized funding transaction, if we receive valid signatures from the peer. -*channel_id* is the id of the channel. +*channel\_id* is the id of the channel. -*signed_psbt* is the PSBT returned from `openchannel_update` (where -*commitments_secured* was true) with partial signatures or finalized +*signed\_psbt* is the PSBT returned from `openchannel_update` (where +*commitments\_secured* was true) with partial signatures or finalized witness stacks included for every input that we contributed to the PSBT. @@ -67,4 +67,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:41e2a4aed1aaac01675f99e91326197afa370a05e32b2ef20cbbb8247de57289) + +[comment]: # ( SHA256STAMP:2ee447b0f9d13ebe8898addc99f52a9024f0e80f67fa505dcc35a3256c3e4c55) diff --git a/doc/lightning-openchannel_update.7.md b/doc/lightning-openchannel_update.7.md index f48cdd1287c2..14d1eade2206 100644 --- a/doc/lightning-openchannel_update.7.md +++ b/doc/lightning-openchannel_update.7.md @@ -4,23 +4,23 @@ lightning-openchannel\_update -- Command to update a collab channel open SYNOPSIS -------- -**openchannel_update** *channel_id* *psbt* +**openchannel\_update** *channel\_id* *psbt* DESCRIPTION ----------- `openchannel_update` is a low level RPC command which continues an open -channel, as specified by *channel_id*. An updated *psbt* is passed in; any +channel, as specified by *channel\_id*. An updated *psbt* is passed in; any changes from the PSBT last returned (either from `openchannel_init` or a previous call to `openchannel_update`) will be communicated to the peer. Must be called after `openchannel_init` and before `openchannel_signed`. -Must be called until *commitments_secured* is returned as true, at which point +Must be called until *commitments\_secured* is returned as true, at which point `openchannel_signed` should be called with a signed version of the PSBT returned by the last call to `openchannel_update`. -*channel_id* is the id of the channel. +*channel\_id* is the id of the channel. *psbt* is the updated PSBT to be sent to the peer. May be identical to the PSBT last returned by either `openchannel_init` or `openchannel_update`. @@ -39,11 +39,11 @@ On success, an object is returned, containing: [comment]: # (GENERATE-FROM-SCHEMA-END) -If *commitments_secured* is true, will also return: -- The derived *channel_id*. -- A *close_to* script, iff a `close_to` address was provided to +If *commitments\_secured* is true, will also return: +- The derived *channel\_id*. +- A *close\_to* script, iff a `close_to` address was provided to `openchannel_init` and the peer supports `option_upfront_shutdownscript`. -- The *funding_outnum*, the index of the funding output for this channel +- The *funding\_outnum*, the index of the funding output for this channel in the funding transaction. @@ -58,7 +58,7 @@ SEE ALSO -------- lightning-openchannel\_init(7), lightning-openchannel\_signed(7), -lightning-openchannel\_bump(7), lightning-openchannel\_abort(7), +lightning-openchannel\_bump(7), lightning-openchannel\_abort(7), lightning-fundchannel\_start(7), lightning-fundchannel\_complete(7), lightning-fundchannel(7), lightning-fundpsbt(7), lightning-utxopsbt(7), lightning-multifundchannel(7) @@ -72,4 +72,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:14632f65d4c44b34762d3fa7e0f5b823a519d3dc5fc7a2a69f677000efd937fb) + +[comment]: # ( SHA256STAMP:223ec3a444341e4c269eab3c3fbe80f13df9258b5f7a548d9e32698a5d4d6790) diff --git a/doc/lightning-parsefeerate.7.md b/doc/lightning-parsefeerate.7.md index 1d53de74142b..92156eec636c 100644 --- a/doc/lightning-parsefeerate.7.md +++ b/doc/lightning-parsefeerate.7.md @@ -4,13 +4,13 @@ lightning-parsefeerate -- Command for parsing a feerate string to a feerate SYNOPSIS -------- -**parsefeerate** *feerate_str* +**parsefeerate** *feerate\_str* DESCRIPTION ----------- The **parsefeerate** command returns the current feerate for any valid -*feerate_str*. This is useful for finding the current feerate that a +*feerate\_str*. This is useful for finding the current feerate that a **fundpsbt** or **utxopsbt** command might use. RETURN VALUE @@ -19,14 +19,14 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **perkw** (u32, optional): Value of *feerate_str* in kilosipa +- **perkw** (u32, optional): Value of *feerate\_str* in kilosipa [comment]: # (GENERATE-FROM-SCHEMA-END) ERRORS ------ -The **parsefeerate** command will error if the *feerate_str* format is +The **parsefeerate** command will error if the *feerate\_str* format is not recognized. - -32602: If the given parameters are wrong. @@ -44,4 +44,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:62a45d5091e5bdb4581a2986a66681616315999b8497038864ece8e3308c3f50) +[comment]: # ( SHA256STAMP:e61c7a3d05b16533716be2052d7235829c1fb69896d38e6ad31baf12a3f4cb02) diff --git a/doc/lightning-pay.7.md b/doc/lightning-pay.7.md index 04e03cef7dc7..97c214d6dc28 100644 --- a/doc/lightning-pay.7.md +++ b/doc/lightning-pay.7.md @@ -4,17 +4,17 @@ lightning-pay -- Command for sending a payment to a BOLT11 invoice SYNOPSIS -------- -**pay** *bolt11* [*msatoshi*] [*label*] [*riskfactor*] -[*maxfeepercent*] [*retry_for*] [*maxdelay*] [*exemptfee*] -[*localofferid*] [*exclude*] [*maxfee*] [*description*] +**pay** *bolt11* [*amount\_msat*] [*label*] [*riskfactor*] +[*maxfeepercent*] [*retry\_for*] [*maxdelay*] [*exemptfee*] +[*localinvreqid*] [*exclude*] [*maxfee*] [*description*] DESCRIPTION ----------- The **pay** RPC command attempts to find a route to the given destination, and send the funds it asks for. If the *bolt11* does not -contain an amount, *msatoshi* is required, otherwise if it is specified -it must be *null*. *msatoshi* is in millisatoshi precision; it can be a +contain an amount, *amount\_msat* is required, otherwise if it is specified +it must be *null*. *amount\_msat* is in millisatoshi precision; it can be a whole number, or a whole number with suffix *msat* or *sat*, or a three decimal point number with suffix *sat*, or an 1 to 11 decimal point number suffixed by *btc*. @@ -32,8 +32,8 @@ leveraged by forwarding nodes. Setting `exemptfee` allows the `maxfeepercent` check to be skipped on fees that are smaller than `exemptfee` (default: 5000 millisatoshi). -`localofferid` is used by offers to link a payment attempt to a local -`send_invoice` offer created by lightningd-offerout(7). This ensures +`localinvreqid` is used by offers to link a payment attempt to a local +`invoice_request` offer created by lightningd-invoicerequest(7). This ensures that we only make a single payment for an offer, and that the offer is marked `used` once paid. @@ -44,7 +44,8 @@ implement your own heuristics rather than the primitive ones used here. *description* is only required for bolt11 invoices which do not -contain a description themselves, but contain a description hash. +contain a description themselves, but contain a description hash: +in this case *description* is required. *description* is then checked against the hash inside the invoice before it will be paid. @@ -95,8 +96,8 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment\_hash** (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **created\_at** (number): the UNIX timestamp showing when this payment was initiated - **parts** (u32): how many attempts this took - **amount\_msat** (msat): Amount the recipient received @@ -167,4 +168,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:6f7640af4859e4605f4369a4e17fcfbaead1be53928ad8101cc44fde6f441a97) +[comment]: # ( SHA256STAMP:735dd61146b04745f1e884037ead662a386fec2c41e2de1a8698d6bb03f63540) diff --git a/doc/lightning-ping.7.md b/doc/lightning-ping.7.md index 5ed7c9dc6c8b..937706a0f075 100644 --- a/doc/lightning-ping.7.md +++ b/doc/lightning-ping.7.md @@ -9,7 +9,7 @@ SYNOPSIS DESCRIPTION ----------- -The **ping** command checks if the node with *id* is ready to talk. +The **ping** command checks if the node with *id* is ready to talk. It currently only works for peers we have a channel with. It accepts the following parameters: @@ -70,4 +70,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:f33aa4d93ca623ff7cd5e4062e0533f617b00372797f8ee0d2498479d2fe08a9) + +[comment]: # ( SHA256STAMP:fe8760ada0a86222a74dc1c78ff111325a2247d5ca90683347b8e8f5dee8a867) diff --git a/doc/lightning-plugin.7.md b/doc/lightning-plugin.7.md index 9da161408587..a35f959c14fc 100644 --- a/doc/lightning-plugin.7.md +++ b/doc/lightning-plugin.7.md @@ -84,4 +84,4 @@ RESOURCES Main web site: [writing plugins]: PLUGINS.md -[comment]: # ( SHA256STAMP:3d7e6647d7fb3eab2a8c6361bb0cbe60efbd822f30f31e08cce68e2aa41ba532) +[comment]: # ( SHA256STAMP:5e067df44c38f3ee529cc30ac66050830244d0d9b91d7ad386e3c50aa841b0e9) diff --git a/doc/lightning-reserveinputs.7.md b/doc/lightning-reserveinputs.7.md index 0225de9c238a..74cdf6c52e6a 100644 --- a/doc/lightning-reserveinputs.7.md +++ b/doc/lightning-reserveinputs.7.md @@ -40,9 +40,9 @@ which was reserved: - *txid* is the input transaction id. - *vout* is the input index. -- *was_reserved* indicates whether the input was already reserved. +- *was\_reserved* indicates whether the input was already reserved. - *reserved* indicates that the input is now reserved (i.e. true). -- *reserved_to_block* indicates what blockheight the reservation will expire. +- *reserved\_to\_block* indicates what blockheight the reservation will expire. On failure, an error is reported and no UTXOs are reserved. @@ -64,4 +64,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:7fd7e24084f7e7da57bccd98cbcf511be56e44e282813c964bdd69d0785dfd22) +[comment]: # ( SHA256STAMP:c3bb624daff32be6701e54505432ee6d33aab6491e3286791331d0b680fee737) diff --git a/doc/lightning-sendcustommsg.7.md b/doc/lightning-sendcustommsg.7.md index 10e15a604e13..65c28aef9e2d 100644 --- a/doc/lightning-sendcustommsg.7.md +++ b/doc/lightning-sendcustommsg.7.md @@ -4,7 +4,7 @@ lightning-sendcustommsg -- Low-level interface to send protocol messages to peer SYNOPSIS -------- -**sendcustommsg** *node_id* *msg* +**sendcustommsg** *node\_id* *msg* DESCRIPTION ----------- @@ -69,4 +69,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:fded86dbe217eacf0c170db87140fd5f10f23c22760ac08b7aa4d2faa4764b3a) +[comment]: # ( SHA256STAMP:2427c75c952bbbd5a3ccf69a217516a73079a014bb656aff3de7038a26cd301b) diff --git a/doc/lightning-sendinvoice.7.md b/doc/lightning-sendinvoice.7.md index 806594556a08..c3d6cf30587e 100644 --- a/doc/lightning-sendinvoice.7.md +++ b/doc/lightning-sendinvoice.7.md @@ -6,14 +6,14 @@ SYNOPSIS **(WARNING: experimental-offers only)** -**sendinvoice** *offer* *label* [*msatoshi*] [*timeout*] [*quantity*] +**sendinvoice** *offer* *label* [*amount\_msat*] [*timeout*] [*quantity*] DESCRIPTION ----------- The **sendinvoice** RPC command creates and sends an invoice to the issuer of an *offer* for it to pay: the offer must contain -*send_invoice*; see lightning-fetchinvoice(7). +*send\_invoice*; see lightning-fetchinvoice(7). If **fetchinvoice-noconnect** is not specified in the configuation, it will connect to the destination in the (currently common!) case where it @@ -23,7 +23,7 @@ cannot find a route which supports `option_onion_messages`. *label* is the unique label to use for this invoice. -*msatoshi* is optional: it is required if the *offer* does not specify +*amount\_msat* is optional: it is required if the *offer* does not specify an amount at all, or specifies it in a different currency. Otherwise you may set it (e.g. to provide a tip), and if not it defaults to the amount contained in the offer (multiplied by *quantity* if any). @@ -33,7 +33,7 @@ invoice or return an error, default 90 seconds. This will also be the timeout on the invoice that is sent. *quantity* is optional: it is required if the *offer* specifies -*quantity_min* or *quantity_max*, otherwise it is not allowed. +*quantity\_min* or *quantity\_max*, otherwise it is not allowed. RETURN VALUE ------------ @@ -43,7 +43,7 @@ On success, an object is returned, containing: - **label** (string): unique label supplied at invoice creation - **description** (string): description used in the invoice -- **payment\_hash** (hex): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hex): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): Whether it's paid, unpaid or unpayable (one of "unpaid", "paid", "expired") - **expires\_at** (u64): UNIX timestamp of when it will become / became unpayable - **amount\_msat** (msat, optional): the amount required to pay this invoice @@ -52,7 +52,7 @@ On success, an object is returned, containing: If **status** is "paid": - **pay\_index** (u64): Unique incrementing index for this payment - - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount_msat*, since clients may overpay) + - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount\_msat*, since clients may overpay) - **paid\_at** (u64): UNIX timestamp of when it was paid - **payment\_preimage** (hex): proof of payment (always 64 characters) @@ -80,4 +80,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:23d06329b3d5d2d21639ecc152b541788bb204188c24a0294f97582401b2b3dc) +[comment]: # ( SHA256STAMP:32b4918787ebd97b7a64cca0fe7d26f259688cbbad93ce89a4dd3e9201d66b78) diff --git a/doc/lightning-sendonion.7.md b/doc/lightning-sendonion.7.md index 70f0c4a038fa..f9f76d2cc1bc 100644 --- a/doc/lightning-sendonion.7.md +++ b/doc/lightning-sendonion.7.md @@ -4,8 +4,8 @@ lightning-sendonion -- Send a payment with a custom onion packet SYNOPSIS -------- -**sendonion** *onion* *first_hop* *payment_hash* [*label*] [*shared_secrets*] [*partid*] [*bolt11*] -[*msatoshi*] [*destination*] +**sendonion** *onion* *first\_hop* *payment\_hash* [*label*] [*shared\_secrets*] [*partid*] [*bolt11*] +[*amount\_msat*] [*destination*] DESCRIPTION ----------- @@ -18,7 +18,7 @@ of the payment for the final hop. However, it is possible to add arbitrary information for hops in the custom onion, allowing for custom extensions that are not directly supported by Core Lightning. -The onion is specific to the route that is being used and the *payment_hash* +The onion is specific to the route that is being used and the *payment\_hash* used to construct, and therefore cannot be reused for other payments or to attempt a separate route. The custom onion can generally be created using the `devtools/onion` CLI tool, or the **createonion** RPC command. @@ -28,7 +28,7 @@ by either of the tools that can generate onions. It contains the payloads destined for each hop and some metadata. Please refer to [BOLT 04][bolt04] for further details. -The *first_hop* parameter instructs Core Lightning which peer to send the onion +The *first\_hop* parameter instructs Core Lightning which peer to send the onion to. It is a JSON dictionary that corresponds to the first element of the route array returned by *getroute*. The following is a minimal example telling Core Lightning to use any available channel to `022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59` @@ -46,18 +46,18 @@ If the first element of *route* does not have "channel" set, a suitable channel (if any) will be chosen, otherwise that specific short-channel-id is used. -The *payment_hash* parameter specifies the 32 byte hex-encoded hash to use as +The *payment\_hash* parameter specifies the 32 byte hex-encoded hash to use as a challenge to the HTLC that we are sending. It is specific to the onion and has to match the one the onion was created with. The *label* parameter can be used to provide a human readable reference to retrieve the payment at a later time. -The *shared_secrets* parameter is a JSON list of 32 byte hex-encoded secrets +The *shared\_secrets* parameter is a JSON list of 32 byte hex-encoded secrets that were used when creating the onion. Core Lightning can send a payment with a custom onion without the knowledge of these secrets, however it will not be able to parse an eventual error message since that is encrypted with the -shared secrets used in the onion. If *shared_secrets* is provided Core Lightning +shared secrets used in the onion. If *shared\_secrets* is provided Core Lightning will decrypt the error, act accordingly, e.g., add a `channel_update` included in the error to its network view, and set the details in *listsendpays* correctly. If it is not provided Core Lightning will store the encrypted onion, @@ -72,19 +72,19 @@ externally. The following is an example of a 3 hop onion: ] ``` -If *shared_secrets* is not provided the Core Lightning node does not know how +If *shared\_secrets* is not provided the Core Lightning node does not know how long the route is, which channels or nodes are involved, and what an eventual error could have been. It can therefore be used for oblivious payments. The *partid* value, if provided and non-zero, allows for multiple parallel -partial payments with the same *payment_hash*. +partial payments with the same *payment\_hash*. The *bolt11* parameter, if provided, will be returned in *waitsendpay* and *listsendpays* results. The *destination* parameter, if provided, will be returned in **listpays** result. -The *msatoshi* parameter is used to annotate the payment, and is returned by +The *amount\_msat* parameter is used to annotate the payment, and is returned by *waitsendpay* and *listsendpays*. RETURN VALUE @@ -94,7 +94,7 @@ RETURN VALUE On success, an object is returned, containing: - **id** (u64): unique ID for this payment attempt -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): status of the payment (could be complete if already sent previously) (one of "pending", "complete") - **created\_at** (u64): the UNIX timestamp showing when this payment was initiated - **amount\_sent\_msat** (msat): The amount sent @@ -107,7 +107,7 @@ On success, an object is returned, containing: If **status** is "complete": - - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) + - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment\_hash** (always 64 characters) If **status** is "pending": @@ -115,7 +115,7 @@ If **status** is "pending": [comment]: # (GENERATE-FROM-SCHEMA-END) -If *shared_secrets* was provided and an error was returned by one of the +If *shared\_secrets* was provided and an error was returned by one of the intermediate nodes the error details are decrypted and presented here. Otherwise the error code is 202 for an unparseable onion. @@ -135,4 +135,4 @@ RESOURCES Main web site: [bolt04]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md -[comment]: # ( SHA256STAMP:84283d16d289b6f72ffac0fdca6791bb49ac9ec1ef2bbb06028c18453bb15f02) +[comment]: # ( SHA256STAMP:d01679d11406d49930e69a7492550a36118950b0d93acca5c26b299fc91680a4) diff --git a/doc/lightning-sendonionmessage.7.md b/doc/lightning-sendonionmessage.7.md index 9783598eab3f..1e50eceaca77 100644 --- a/doc/lightning-sendonionmessage.7.md +++ b/doc/lightning-sendonionmessage.7.md @@ -6,7 +6,7 @@ SYNOPSIS **(WARNING: experimental-onion-messages only)** -**sendonionmessage** *first_id* *blinding* *hops* +**sendonionmessage** *first\_id* *blinding* *hops* DESCRIPTION ----------- @@ -43,4 +43,4 @@ Main web site: [bolt04]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md -[comment]: # ( SHA256STAMP:200de829c6635242cb2dd8ec0650c2fa8f5fcbf413f4a704884516df80492fcb) +[comment]: # ( SHA256STAMP:4aff9673290966c7b09e65672da5dc8ef4d2601d3d1681009b329a4f8ceb9af6) diff --git a/doc/lightning-sendpay.7.md b/doc/lightning-sendpay.7.md index 08c14bfe0579..aa8ed81ea3da 100644 --- a/doc/lightning-sendpay.7.md +++ b/doc/lightning-sendpay.7.md @@ -4,9 +4,9 @@ lightning-sendpay -- Low-level command for sending a payment via a route SYNOPSIS -------- -**sendpay** *route* *payment\_hash* [*label*] [*msatoshi*] -[*bolt11*] [*payment_secret*] [*partid*] [*localofferid*] [*groupid*] -[*payment_metadata*] [*description*] +**sendpay** *route* *payment\_hash* [*label*] [*amount\_msat*] +[*bolt11*] [*payment\_secret*] [*partid*] [*localinvreqid*] [*groupid*] +[*payment\_metadata*] [*description*] DESCRIPTION ----------- @@ -28,37 +28,36 @@ definite failure. The *label* and *bolt11* parameters, if provided, will be returned in *waitsendpay* and *listsendpays* results. -The *msatoshi* amount must be provided if *partid* is non-zero, otherwise +The *amount\_msat* amount must be provided if *partid* is non-zero, otherwise it must be equal to the final amount to the destination. By default it is in millisatoshi precision; it can be a whole number, or a whole number ending in *msat* or *sat*, or a number with three decimal places ending in *sat*, or a number with 1 to 11 decimal places ending in *btc*. -The *payment_secret* is the value that the final recipient requires to +The *payment\_secret* is the value that the final recipient requires to accept the payment, as defined by the `payment_data` field in BOLT 4 and the `s` field in the BOLT 11 invoice format. It is required if *partid* is non-zero. The *partid* value, if provided and non-zero, allows for multiple parallel -partial payments with the same *payment_hash*. The *msatoshi* amount +partial payments with the same *payment\_hash*. The *amount\_msat* amount (which must be provided) for each **sendpay** with matching -*payment_hash* must be equal, and **sendpay** will fail if there are -already *msatoshi* worth of payments pending. +*payment\_hash* must be equal, and **sendpay** will fail if there are -The *localofferid* value indicates that this payment is being made for a local -send_invoice offer: this ensures that we only send a payment for a single-use -offer once. +The *localinvreqid* value indicates that this payment is being made for a local +invoice\_request: this ensures that we only send a payment for a single-use +invoice\_request once. *groupid* allows you to attach a number which appears in **listsendpays** so payments can be identified as part of a logical group. The *pay* plugin uses this to identify one attempt at a MPP payment, for example. -*payment_metadata* is placed in the final onion hop TLV. +*payment\_metadata* is placed in the final onion hop TLV. Once a payment has succeeded, calls to **sendpay** with the same -*payment\_hash* but a different *msatoshi* or destination will fail; +*payment\_hash* but a different *amount\_msat* or destination will fail; this prevents accidental multiple payments. Calls to **sendpay** with -the same *payment\_hash*, *msatoshi*, and destination as a previous +the same *payment\_hash*, *amount\_msat*, and destination as a previous successful payment (even if a different route or *partid*) will return immediately with success. @@ -69,11 +68,11 @@ RETURN VALUE On success, an object is returned, containing: - **id** (u64): unique ID for this payment attempt -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): status of the payment (could be complete if already sent previously) (one of "pending", "complete") - **created\_at** (u64): the UNIX timestamp showing when this payment was initiated - **amount\_sent\_msat** (msat): The amount sent -- **groupid** (u64, optional): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment_hash +- **groupid** (u64, optional): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment\_hash - **amount\_msat** (msat, optional): The amount delivered to destination (if known) - **destination** (pubkey, optional): the final destination of the payment if known - **completed\_at** (u64, optional): the UNIX timestamp showing when this payment was completed @@ -84,7 +83,7 @@ On success, an object is returned, containing: If **status** is "complete": - - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) + - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment\_hash** (always 64 characters) If **status** is "pending": @@ -109,7 +108,7 @@ The following error codes may occur: will be routing failure object. - 204: Failure along route; retry a different route. The *data* field of the error will be routing failure object. -- 212: *localofferid* refers to an invalid, or used, local offer. +- 212: *localinvreqid* refers to an invalid, or used, local invoice\_request. A routing failure object has the fields below: - *erring\_index*. The index of the node along the route that reported @@ -143,4 +142,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:c129f537b1af8a5dc767a25a72be419634cb21ebc26a9e6b9bb091db8db7e6ca) +[comment]: # ( SHA256STAMP:b7f1a5efd80156722e5f9cca6f291306fcd22ab7b9b2754beac880f2ae5a7418) diff --git a/doc/lightning-sendpsbt.7.md b/doc/lightning-sendpsbt.7.md index 2829e58f9a65..8039426159a2 100644 --- a/doc/lightning-sendpsbt.7.md +++ b/doc/lightning-sendpsbt.7.md @@ -66,4 +66,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:6c0054088c17481dedbedb6a5ed4be7f09ce8783780707432907508ebf4bbd7a) + +[comment]: # ( SHA256STAMP:632868a585b9150a80ccc4ba173d90a8beebab8e604c06f1ccdc4493604152e3) diff --git a/doc/lightning-setchannel.7.md b/doc/lightning-setchannel.7.md index 602cf3f72b15..87b01106ef16 100644 --- a/doc/lightning-setchannel.7.md +++ b/doc/lightning-setchannel.7.md @@ -22,7 +22,7 @@ will accept: we allow 2 a day, with a few extra occasionally). *id* is required and should contain a scid (short channel ID), channel id or peerid (pubkey) of the channel to be modified. If *id* is set to "all", the updates are applied to all channels in states -CHANNELD\_NORMAL CHANNELD\_AWAITING\_LOCKIN or DUALOPEND_AWAITING_LOCKIN. +CHANNELD\_NORMAL CHANNELD\_AWAITING\_LOCKIN or DUALOPEND\_AWAITING\_LOCKIN. If *id* is a peerid, all channels with the +peer in those states are changed. @@ -68,13 +68,13 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object containing **channels** is returned. It is an array of objects, where each object contains: -- **peer\_id** (pubkey): The node_id of the peer -- **channel\_id** (hex): The channel_id of the channel (always 64 characters) +- **peer\_id** (pubkey): The node\_id of the peer +- **channel\_id** (hex): The channel\_id of the channel (always 64 characters) - **fee\_base\_msat** (msat): The resulting feebase (this is the BOLT #7 name) - **fee\_proportional\_millionths** (u32): The resulting feeppm (this is the BOLT #7 name) -- **minimum\_htlc\_out\_msat** (msat): The resulting htlcmin we will advertize (the BOLT #7 name is htlc_minimum_msat) -- **maximum\_htlc\_out\_msat** (msat): The resulting htlcmax we will advertize (the BOLT #7 name is htlc_maximum_msat) -- **short\_channel\_id** (short\_channel\_id, optional): the short_channel_id (if locked in) +- **minimum\_htlc\_out\_msat** (msat): The resulting htlcmin we will advertize (the BOLT #7 name is htlc\_minimum\_msat) +- **maximum\_htlc\_out\_msat** (msat): The resulting htlcmax we will advertize (the BOLT #7 name is htlc\_maximum\_msat) +- **short\_channel\_id** (short\_channel\_id, optional): the short\_channel\_id (if locked in) - the following warnings are possible: - **warning\_htlcmin\_too\_low**: The requested htlcmin was too low for this peer, so we set it to the minimum they will allow - **warning\_htlcmax\_too\_high**: The requested htlcmax was greater than the channel capacity, so we set it to the channel capacity @@ -107,4 +107,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:0f7cd751f329360a8cd957dfc8ea0b7d579aa05f4de4f8577039e50266a04f30) +[comment]: # ( SHA256STAMP:31300838ff4b12d9bf43879c91a5d51af76f70ebe8527a35ba476801424c3e65) diff --git a/doc/lightning-setchannelfee.7.md b/doc/lightning-setchannelfee.7.md deleted file mode 100644 index f31f0030b8db..000000000000 --- a/doc/lightning-setchannelfee.7.md +++ /dev/null @@ -1,86 +0,0 @@ -lightning-setchannelfee -- Command for setting specific routing fees on a lightning channel -=========================================================================================== - -SYNOPSIS --------- - -(DEPRECATED) **setchannelfee** *id* [*base*] [*ppm*] [*enforcedelay*] - -DESCRIPTION ------------ - -The **setchannelfee** RPC command sets channel specific routing fees as -defined in BOLT \#7. The channel has to be in normal or awaiting state. -This can be checked by **listpeers** reporting a *state* of -CHANNELD\_NORMAL, CHANNELD\_AWAITING\_LOCKIN or DUALOPEND_AWAITING_LOCKIN for the channel. - -*id* is required and should contain a scid (short channel ID), channel -id or peerid (pubkey) of the channel to be modified. If *id* is set to -"all", the fees for all channels are updated that are in state -CHANNELD\_NORMAL, CHANNELD\_AWAITING\_LOCKIN or -DUALOPEND_AWAITING_LOCKIN. If *id* is a peerid, all channels with the -peer in those states are changed. - -*base* is an optional value in millisatoshi that is added as base fee to -any routed payment. If the parameter is left out, the global config -value fee-base will be used again. It can be a whole number, or a whole -number ending in *msat* or *sat*, or a number with three decimal places -ending in *sat*, or a number with 1 to 11 decimal places ending in -*btc*. - -*ppm* is an optional value that is added proportionally per-millionths -to any routed payment volume in satoshi. For example, if ppm is 1,000 -and 1,000,000 satoshi is being routed through the channel, an -proportional fee of 1,000 satoshi is added, resulting in a 0.1% fee. If -the parameter is left out, the global config value will be used again. - -*enforcedelay* is the number of seconds to delay before enforcing the -new fees (default 600, which is ten minutes). This gives the network -a chance to catch up with the new rates and avoids rejecting HTLCs -before they do. This only has an effect if rates are increased (we -always allow users to overpay fees), only applies to a single rate -increase per channel (we don't remember an arbitrary number of prior -feerates) and if the node is restarted the updated fees are enforced -immediately. - -RETURN VALUE ------------- - -[comment]: # (GENERATE-FROM-SCHEMA-START) -On success, an object is returned, containing: - -- **base** (u32): The fee_base_msat value -- **ppm** (u32): The fee_proportional_millionths value -- **channels** (array of objects): channel(s) whose rate is now set: - - **peer\_id** (pubkey): The node_id of the peer - - **channel\_id** (hex): The channel_id of the channel (always 64 characters) - - **short\_channel\_id** (short\_channel\_id, optional): the short_channel_id (if locked in) - -[comment]: # (GENERATE-FROM-SCHEMA-END) - -ERRORS ------- - -The following error codes may occur: -- -1: Channel is in incorrect state, i.e. Catchall nonspecific error. -- -32602: JSONRPC2\_INVALID\_PARAMS, i.e. Given id is not a channel ID -or short channel ID. - -AUTHOR ------- - -Michael Schmoock <> is the author of this -feature. Rusty Russell <> is mainly -responsible for the Core Lightning project. - -SEE ALSO --------- - -lightningd-setchannel(7) - -RESOURCES ---------- - -Main web site: - -[comment]: # ( SHA256STAMP:a7f079e9a25ee5f4c3d8bf3ed2c61d2f807eae99e6bfe02b0737a9692aca503b) diff --git a/doc/lightning-signmessage.7.md b/doc/lightning-signmessage.7.md index 71f82901602d..37333de7a8bd 100644 --- a/doc/lightning-signmessage.7.md +++ b/doc/lightning-signmessage.7.md @@ -23,7 +23,7 @@ On success, an object is returned, containing: - **signature** (hex): The signature (always 128 characters) - **recid** (hex): The recovery id (0, 1, 2 or 3) (always 2 characters) -- **zbase** (string): *signature* and *recid* encoded in a style compatible with **lnd**'s [SignMessageRequest](https://api.lightning.community/#grpc-request-signmessagerequest) +- **zbase** (string): *signature* and *recid* encoded in a style compatible with **lnd**'s [SignMessageRequest](https://api.lightning.community/#signmessage-2) [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -42,4 +42,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:6ff35aee05f86de2c44be50a156afc1e325183d8c9333bff68114c8d846a75e6) +[comment]: # ( SHA256STAMP:ac618ebda6ab3acac85729f7b3e5607ccdcc78c75e40129ced84ae02e321f5c3) diff --git a/doc/lightning-signpsbt.7.md b/doc/lightning-signpsbt.7.md index 2effa067c9f5..4772042045a0 100644 --- a/doc/lightning-signpsbt.7.md +++ b/doc/lightning-signpsbt.7.md @@ -72,4 +72,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:0daef100b12490126849fcb93a9e861554807d1a5acf68bc17de92a30505e18a) + +[comment]: # ( SHA256STAMP:655ef649bd68e29da6026cacf3d6f7399c5d9b2ac153c53e66cda9ece3fd761f) diff --git a/doc/lightning-stop.7.md b/doc/lightning-stop.7.md index fb6c3c911cbe..600cd375c992 100644 --- a/doc/lightning-stop.7.md +++ b/doc/lightning-stop.7.md @@ -26,6 +26,7 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, returns a single element (string) (always "Shutdown complete") + [comment]: # (GENERATE-FROM-SCHEMA-END) Once it has returned, the daemon has cleaned up completely, and if @@ -42,4 +43,5 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:2103952683449a5aa313eefa9c850dc0ae1cf4aa65edeb7897a8748a010a9349) + +[comment]: # ( SHA256STAMP:3ad64970d67b1084b6f33bb690ba1dd3744292a60b3efe8a845f88a0ebc61450) diff --git a/doc/lightning-txdiscard.7.md b/doc/lightning-txdiscard.7.md index 22d5292a31bd..722532bd4065 100644 --- a/doc/lightning-txdiscard.7.md +++ b/doc/lightning-txdiscard.7.md @@ -19,7 +19,7 @@ RETURN VALUE On success, an object is returned, containing: - **unsigned\_tx** (hex): the unsigned transaction -- **txid** (txid): the transaction id of *unsigned_tx* +- **txid** (txid): the transaction id of *unsigned\_tx* [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -45,4 +45,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:f52ad03cccaea672deefada22f0a11acff9d0c4f98ccfedca12759eaa6bac057) +[comment]: # ( SHA256STAMP:3b3b5c8e6bc2f30080053f93cc7db3dfc39bef118354ebfc2ed62e621329afc4) diff --git a/doc/lightning-txprepare.7.md b/doc/lightning-txprepare.7.md index e885f8dafff6..427f42aa3b1a 100644 --- a/doc/lightning-txprepare.7.md +++ b/doc/lightning-txprepare.7.md @@ -57,7 +57,7 @@ On success, an object is returned, containing: - **psbt** (string): the PSBT representing the unsigned transaction - **unsigned\_tx** (hex): the unsigned transaction -- **txid** (txid): the transaction id of *unsigned_tx*; you hand this to lightning-txsend(7) or lightning-txdiscard(7), as the inputs of this transaction are reserved. +- **txid** (txid): the transaction id of *unsigned\_tx*; you hand this to lightning-txsend(7) or lightning-txdiscard(7), as the inputs of this transaction are reserved. [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -85,4 +85,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:c13561bf71189143811cd4bd49db69c163b8443f1660931671eb1e95e0a7e3ff) +[comment]: # ( SHA256STAMP:200dbb8ac175dbb5321e699cfa78dd319a96ceef0d61a7569415544503562d52) diff --git a/doc/lightning-txsend.7.md b/doc/lightning-txsend.7.md index abba2b31d199..f67f9aede163 100644 --- a/doc/lightning-txsend.7.md +++ b/doc/lightning-txsend.7.md @@ -45,4 +45,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:dcb4d5f03b44bf3bc6852f97f56c0ac7d34505df71f042ed86a0daf4927dcaff) +[comment]: # ( SHA256STAMP:db5c1f15e439f7784dcb759d444cf4f0844aa8093c6af2252e5989e1b75f0523) diff --git a/doc/lightning-unreserveinputs.7.md b/doc/lightning-unreserveinputs.7.md index 93f4fe078054..c6256e75794f 100644 --- a/doc/lightning-unreserveinputs.7.md +++ b/doc/lightning-unreserveinputs.7.md @@ -55,4 +55,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:41e0fba4aea57e12d91366a55663e7331e952b223eeb8fc9f83deb5a948f63b4) +[comment]: # ( SHA256STAMP:4560823ed1adae2127b71b599cdaae1539bd5c87c03ecf593beed5813bb68511) diff --git a/doc/lightning-utxopsbt.7.md b/doc/lightning-utxopsbt.7.md index d2ea02688443..b6abc12199df 100644 --- a/doc/lightning-utxopsbt.7.md +++ b/doc/lightning-utxopsbt.7.md @@ -4,7 +4,7 @@ lightning-utxopsbt -- Command to populate PSBT inputs from given UTXOs SYNOPSIS -------- -**utxopsbt** *satoshi* *feerate* *startweight* *utxos* [*reserve*] [*reservedok*] [*locktime*] [*min_witness_weight*] [*excess_as_change*] +**utxopsbt** *satoshi* *feerate* *startweight* *utxos* [*reserve*] [*reservedok*] [*locktime*] [*min\_witness\_weight*] [*excess\_as\_change*] DESCRIPTION ----------- @@ -33,11 +33,11 @@ if any of the *utxos* are already reserved. *locktime* is an optional locktime: if not set, it is set to a recent block height. -*min_witness_weight* is an optional minimum weight to use for a UTXO's +*min\_witness\_weight* is an optional minimum weight to use for a UTXO's witness. If the actual witness weight is greater than the provided minimum, the actual witness weight will be used. -*excess_as_change* is an optional boolean to flag to add a change output +*excess\_as\_change* is an optional boolean to flag to add a change output for the excess sats. RETURN VALUE @@ -49,8 +49,8 @@ On success, an object is returned, containing: - **psbt** (string): Unsigned PSBT which fulfills the parameters given - **feerate\_per\_kw** (u32): The feerate used to create the PSBT, in satoshis-per-kiloweight - **estimated\_final\_weight** (u32): The estimated weight of the transaction once fully signed -- **excess\_msat** (msat): The amount above *satoshi* which is available. This could be zero, or dust; it will be zero if *change_outnum* is also returned -- **change\_outnum** (u32, optional): The 0-based output number where change was placed (only if parameter *excess_as_change* was true and there was sufficient funds) +- **excess\_msat** (msat): The amount above *satoshi* which is available. This could be zero, or dust; it will be zero if *change\_outnum* is also returned +- **change\_outnum** (u32, optional): The 0-based output number where change was placed (only if parameter *excess\_as\_change* was true and there was sufficient funds) - **reservations** (array of objects, optional): If *reserve* was true or a non-zero number, just as per lightning-reserveinputs(7): - **txid** (txid): The txid of the transaction - **vout** (u32): The 0-based output number @@ -62,20 +62,20 @@ On success, an object is returned, containing: On success, returns the *psbt* it created, containing the inputs, -*feerate_per_kw* showing the exact numeric feerate it used, -*estimated_final_weight* for the estimated weight of the transaction -once fully signed, and *excess_msat* containing the amount above *satoshi* +*feerate\_per\_kw* showing the exact numeric feerate it used, +*estimated\_final\_weight* for the estimated weight of the transaction +once fully signed, and *excess\_msat* containing the amount above *satoshi* which is available. This could be zero, or dust. If *satoshi* was "all", -then *excess_msat* is the entire amount once fees are subtracted +then *excess\_msat* is the entire amount once fees are subtracted for the weights of the inputs and *startweight*. If *reserve* was *true* or a non-zero number, then a *reservations* array is returned, exactly like *reserveinputs*. -If *excess_as_change* is true and the excess is enough to cover +If *excess\_as\_change* is true and the excess is enough to cover an additional output above the `dust_limit`, then an output is -added to the PSBT for the excess amount. The *excess_msat* will -be zero. A *change_outnum* will be returned with the index of +added to the PSBT for the excess amount. The *excess\_msat* will +be zero. A *change\_outnum* will be returned with the index of the change output. On error the returned object will contain `code` and `message` properties, @@ -100,4 +100,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:c2c513b40099c9cd2ef7bda1c430fdff055499b67ef2ff9edf7772ea4d87fb2d) +[comment]: # ( SHA256STAMP:237c832f7a7c1ea2567192b7432f4ea7fe79e553c9c531acf5be733b92095464) diff --git a/doc/lightning-waitanyinvoice.7.md b/doc/lightning-waitanyinvoice.7.md index a2f3c8f9d90d..670b72960925 100644 --- a/doc/lightning-waitanyinvoice.7.md +++ b/doc/lightning-waitanyinvoice.7.md @@ -38,7 +38,7 @@ On success, an object is returned, containing: - **label** (string): unique label supplied at invoice creation - **description** (string): description used in the invoice -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): Whether it's paid or expired (one of "paid", "expired") - **expires\_at** (u64): UNIX timestamp of when it will become / became unpayable - **amount\_msat** (msat, optional): the amount required to pay this invoice @@ -48,7 +48,7 @@ On success, an object is returned, containing: If **status** is "paid": - **pay\_index** (u64): Unique incrementing index for this payment - - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount_msat*, since clients may overpay) + - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount\_msat*, since clients may overpay) - **paid\_at** (u64): UNIX timestamp of when it was paid - **payment\_preimage** (secret): proof of payment (always 64 characters) @@ -75,4 +75,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:2b0c9e70bb03f5cf9999731fdf5b8bcd761ea70ef6fc04575a1c2451174ea769) +[comment]: # ( SHA256STAMP:bd853f0a27258e0e3780c0dd6cdd8fca7ba8d95a00d247704ed3f3f55c2f086e) diff --git a/doc/lightning-waitblockheight.7.md b/doc/lightning-waitblockheight.7.md index 801bfb8d3d8c..7b84b715b058 100644 --- a/doc/lightning-waitblockheight.7.md +++ b/doc/lightning-waitblockheight.7.md @@ -39,4 +39,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:e84e2ddf33c5abafe434ad0dcd76a3c1e6e2a2bdbba5dcf786f2a2ed80e61061) +[comment]: # ( SHA256STAMP:b7986f9829dd7616ac4236c175b9d7011c27de19dd4fb50138b5957c59678177) diff --git a/doc/lightning-waitinvoice.7.md b/doc/lightning-waitinvoice.7.md index 6000f5063dce..7980f4be0789 100644 --- a/doc/lightning-waitinvoice.7.md +++ b/doc/lightning-waitinvoice.7.md @@ -20,7 +20,7 @@ On success, an object is returned, containing: - **label** (string): unique label supplied at invoice creation - **description** (string): description used in the invoice -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): Whether it's paid or expired (one of "paid", "expired") - **expires\_at** (u64): UNIX timestamp of when it will become / became unpayable - **amount\_msat** (msat, optional): the amount required to pay this invoice @@ -30,7 +30,7 @@ On success, an object is returned, containing: If **status** is "paid": - **pay\_index** (u64): Unique incrementing index for this payment - - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount_msat*, since clients may overpay) + - **amount\_received\_msat** (msat): the amount actually received (could be slightly greater than *amount\_msat*, since clients may overpay) - **paid\_at** (u64): UNIX timestamp of when it was paid - **payment\_preimage** (secret): proof of payment (always 64 characters) @@ -60,4 +60,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:2b0c9e70bb03f5cf9999731fdf5b8bcd761ea70ef6fc04575a1c2451174ea769) +[comment]: # ( SHA256STAMP:bd853f0a27258e0e3780c0dd6cdd8fca7ba8d95a00d247704ed3f3f55c2f086e) diff --git a/doc/lightning-waitsendpay.7.md b/doc/lightning-waitsendpay.7.md index 8144833a640d..a7035d671af5 100644 --- a/doc/lightning-waitsendpay.7.md +++ b/doc/lightning-waitsendpay.7.md @@ -36,11 +36,11 @@ RETURN VALUE On success, an object is returned, containing: - **id** (u64): unique ID for this payment attempt -- **payment\_hash** (hash): the hash of the *payment_preimage* which will prove payment (always 64 characters) +- **payment\_hash** (hash): the hash of the *payment\_preimage* which will prove payment (always 64 characters) - **status** (string): status of the payment (always "complete") - **created\_at** (u64): the UNIX timestamp showing when this payment was initiated - **amount\_sent\_msat** (msat): The amount sent -- **groupid** (u64, optional): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment_hash +- **groupid** (u64, optional): Grouping key to disambiguate multiple attempts to pay an invoice or the same payment\_hash - **amount\_msat** (msat, optional): The amount delivered to destination (if known) - **destination** (pubkey, optional): the final destination of the payment if known - **completed\_at** (number, optional): the UNIX timestamp showing when this payment was completed @@ -51,7 +51,7 @@ On success, an object is returned, containing: If **status** is "complete": - - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment_hash** (always 64 characters) + - **payment\_preimage** (secret): the proof of payment: SHA256 of this **payment\_hash** (always 64 characters) [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -104,4 +104,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:f4dbe3ecc88a294f7bb983a2f2b8e9613e440e4564580e51dd30fc83ba218a91) +[comment]: # ( SHA256STAMP:5c783babcd7a98ef4f1bd676f7aa36c3441d52414dcd1038183d9c4445ddcf7d) diff --git a/doc/lightning-withdraw.7.md b/doc/lightning-withdraw.7.md index 6a52452e78fa..533c6934abee 100644 --- a/doc/lightning-withdraw.7.md +++ b/doc/lightning-withdraw.7.md @@ -74,4 +74,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:fcfd3c91a3cee9bbd36e86edccb5d6407b19c2beda7de1f51ebba5fbd1c2340a) +[comment]: # ( SHA256STAMP:7ec01e1903d75e2a8694c50d051c40fcbdb8a8001943c79748ca8fd41d5d59b1) diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index d0ce63b2b532..116b06313d39 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -141,8 +141,8 @@ Current subdaemons are *channeld*, *closingd*, If the supplied path is relative the subdaemon binary is found in the working directory. This option may be specified multiple times. - So, **subdaemon=hsmd:remote_signer** would use a -hypothetical remote signing proxy instead of the standard *lightning_hsmd* + So, **subdaemon=hsmd:remote\_signer** would use a +hypothetical remote signing proxy instead of the standard *lightning\_hsmd* binary. * **pid-file**=*PATH* @@ -174,7 +174,7 @@ Subsystems include: * *jsonrpc#FD*: Each JSONRPC connection, FD = file descriptor number - + The following subsystems exist for each channel, where N is an incrementing internal integer id assigned for the lifetime of the channel: * *openingd-chan#N*: Each opening / idling daemon @@ -185,10 +185,10 @@ Subsystems include: * *onchaind-chan#N*: Each onchain close handling daemon - + So, **log-level=debug:plugin** would set debug level logging on all plugins and the plugin manager. **log-level=io:chan#55** would set -IO logging on channel number 55 (or 550, for that matter). +IO logging on channel number 55 (or 550, for that matter). **log-level=debug:024b9a1fa8** would set debug logging for that channel (or any node id containing that string). @@ -343,8 +343,8 @@ This allows override of one or more of our standard feerates (see lightning-feerates(7)). Up to 5 values, separated by '/' can be provided: if fewer are provided, then the final value is used for the remainder. The values are in per-kw (roughly 1/4 of bitcoind's per-kb -values), and the order is "opening", "mutual_close", "unilateral_close", -"delayed_to_us", "htlc_resolution", and "penalty". +values), and the order is "opening", "mutual\_close", "unilateral\_close", +"delayed\_to\_us", "htlc\_resolution", and "penalty". You would usually put this option in the per-chain config file, to avoid setting it on Bitcoin mainnet! e.g. `~rusty/.lightning/regtest/config`. @@ -493,9 +493,8 @@ precisely control where to bind and what to announce with the *bind-addr* and *announce-addr* options. These will **disable** the *autolisten* logic, so you must specifiy exactly what you want! -* **addr**=*\[IPADDRESS\[:PORT\]\]|autotor:TORIPADDRESS\[:SERVICEPORT\]\[/torport=TORPORT\]|statictor:TORIPADDRESS\[:SERVICEPORT\]\[/torport=TORPORT\]\[/torblob=\[blob\]\]* +* **addr**=*\[IPADDRESS\[:PORT\]\]|autotor:TORIPADDRESS\[:SERVICEPORT\]\[/torport=TORPORT\]|statictor:TORIPADDRESS\[:SERVICEPORT\]\[/torport=TORPORT\]\[/torblob=\[blob\]\]|DNS\[:PORT\]* - Set an IP address (v4 or v6) or automatic Tor address to listen on and (maybe) announce as our node address. @@ -531,10 +530,12 @@ defined by you and possibly different from your local node port assignment. This option can be used multiple times to add more addresses, and its use disables autolisten. If necessary, and 'always-use-proxy' -is not specified, a DNS lookup may be done to resolve 'IPADDRESS' -or 'TORIPADDRESS'. +is not specified, a DNS lookup may be done to resolve 'DNS' or 'TORIPADDRESS'. + + If a 'DNS' hostname was given that resolves to a local interface, the daemon +will bind to that interface: if **announce-addr-dns** is true then it will also announce that as type 'DNS' (rather than announcing the IP address). -* **bind-addr**=*\[IPADDRESS\[:PORT\]\]|SOCKETPATH* +* **bind-addr**=*\[IPADDRESS\[:PORT\]\]|SOCKETPATH|DNS\[:PORT\]|DNS\[:PORT\]* Set an IP address or UNIX domain socket to listen to, but do not announce. A UNIX domain socket is distinguished from an IP address by @@ -549,7 +550,10 @@ not specified, 9735 is used. its use disables autolisten. If necessary, and 'always-use-proxy' is not specified, a DNS lookup may be done to resolve 'IPADDRESS'. -* **announce-addr**=*IPADDRESS\[:PORT\]|TORADDRESS.onion\[:PORT\]* + If a 'DNS' hostname was given and 'always-use-proxy' is not specified, +a lookup may be done to resolve it and bind to a local interface (if found). + +* **announce-addr**=*IPADDRESS\[:PORT\]|TORADDRESS.onion\[:PORT\]|DNS\[:PORT\]* Set an IP (v4 or v6) address or Tor address to announce; a Tor address is distinguished by ending in *.onion*. *PORT* defaults to 9735. @@ -561,8 +565,12 @@ announced addresses are public (e.g. not localhost). This option can be used multiple times to add more addresses, and its use disables autolisten. - If necessary, and 'always-use-proxy' is not specified, a DNS -lookup may be done to resolve 'IPADDRESS'. + Since v22.11 'DNS' hostnames can be used for announcement: see **announce-addr-dns**. + +* **announce-addr-dns**=*BOOL* + + Set to *true* (default is *false), this so that names given as arguments to **addr** and **announce-addr** are published in node announcement messages as names, rather than IP addresses. Please note that most mainnet nodes do not yet use, read or propagate this information correctly. + * **offline** @@ -654,21 +662,26 @@ Experimental options are subject to breakage between releases: they are made available for advanced users who want to test proposed features. When the build is configured _without_ `--enable-experimental-features`, below options are available but disabled by default. -A build _with_ `--enable-experimental-features` enables some of below options -by default and also adds support for even more features. Supported features can -be listed with `lightningd --list-features-only`. +Supported features can be listed with `lightningd --list-features-only` + +A build _with_ `--enable-experimental-features` flag hard-codes some of below +options as enabled, ignoring their command line flag. It may also add support for +even more features. The safest way to determine the active configuration is by +checking `listconfigs` or by looking at `our_features` (bits) in `getinfo`. * **experimental-onion-messages** Specifying this enables sending, forwarding and receiving onion messages, -which are in draft status in the BOLT specifications. +which are in draft status in the [bolt][bolt] specifications (PR #759). +A build with `--enable-experimental-features` usually enables this via +experimental-offers, see below. * **experimental-offers** Specifying this enables the `offers` and `fetchinvoice` plugins and -corresponding functionality, which are in draft status as BOLT12. -This usually requires **experimental-onion-messages** as well. See -lightning-offer(7) and lightning-fetchinvoice(7). +corresponding functionality, which are in draft status ([bolt][bolt] #798) as [bolt12][bolt12]. +A build with `--enable-experimental-features` enables this permanently and usually +enables experimental-onion-messages as well. * **fetchinvoice-noconnect** @@ -677,14 +690,14 @@ trying to connect directly to the offering node as a last resort. * **experimental-shutdown-wrong-funding** - Specifying this allows the `wrong_funding` field in shutdown: if a + Specifying this allows the `wrong_funding` field in _shutdown: if a remote node has opened a channel but claims it used the incorrect txid (and the channel hasn't been used yet at all) this allows them to -negotiate a clean shutdown with the txid they offer. +negotiate a clean shutdown with the txid they offer ([#4421][pr4421]). * **experimental-dual-fund** - Specifying this enables support for the dual funding protocol, + Specifying this enables support for the dual funding protocol ([bolt][bolt] #851), allowing both parties to contribute funds to a channel. The decision about whether to add funds or not to a proposed channel is handled automatically by a plugin that implements the appropriate logic for @@ -694,7 +707,7 @@ your needs. The default behavior is to not contribute funds. Specifying this enables support for accepting incoming WebSocket connections on that port, on any IPv4 and IPv6 addresses you listen -to. The normal protocol is expected to be sent over WebSocket binary +to ([bolt][bolt] #891). The normal protocol is expected to be sent over WebSocket binary frames once the connection is upgraded. BUGS @@ -726,3 +739,7 @@ COPYING Note: the modules in the ccan/ directory have their own licenses, but the rest of the code is covered by the BSD-style MIT license. + +[bolt]: https://github.com/lightning/bolts +[bolt12]: https://github.com/rustyrussell/lightning-rfc/blob/guilt/offers/12-offer-encoding.md +[pr4421]: https://github.com/ElementsProject/lightning/pull/4421 diff --git a/doc/lightningd-rpc.7.md b/doc/lightningd-rpc.7.md new file mode 100644 index 000000000000..0590581e83ab --- /dev/null +++ b/doc/lightningd-rpc.7.md @@ -0,0 +1,304 @@ +lightningd-rpc -- Lightning Daemon RPC Protocols +================================================ + +SYNOPSIS +-------- + +**~/.lightning/bitcoin/lightning-rpc** + +DESCRIPTION +----------- + +lightningd(8) communicates via RPC, especially JSONRPC over the UNIX +domain socket (by default **$HOME/.lightning/bitcoin/lightning-rpc**, +but configuable with lightningd-config(5)). + + +JSON WIRE FORMAT +---------------- + +JSON RPC is defined at and +generally involves writing a JSON request with a unique ID, and +receiving a response containing that ID. + +Every response given by lightningd(8) is followed by two '\n' +characters, which should not appear in normal JSON (though plugins may +produce them). This means efficient code can simply read until it +sees two '\n' characters, and then attempt to parse the JSON (if the +JSON is incomplete, it should continue reading and file a bug). + +JSON COMMANDS +------------- + +We support "params" as an array (ordered parameters) or a dictionary +(named parameters). In the array case, JSON "null" is treated as if +the parameter was not specified (if that is allowed). + +You should probably prefer named parameters if possible, as they have +generally been shown to be less confusing for complex commands and +more robust when fields are deprecated. + +The lightning-cli(1) tool uses ordered parameters by default, but +named parameters if explicitly specified or the first parameter +contains an '='. + +JSON IDS +-------- + +JSON `id` fields in requests are used to match requests and responses. +These used to be simple numbers, but with modern plugins that is deprecated: +we use a specific format, which makes them very useful for debugging +and tracking the cause of commands: + +```EBNF +JSONID := IDPART ['/' IDPART]* +IDPART := PREFIX ':' METHOD '#' NUMBER +``` + +`PREFIX` is cln for the main daemon, cli for lightning-cli, and should +be the plugin name for plugins. `METHOD` is an internal identifier, +indicating what caused the request: for `cli` it's simply the method +it's invoking, but for plugins it may be the routine which created the +request. And `NUMBER` ensures uniqueness (it's usually a simple +increment). + +Importantly for plugins, incoming requests often trigger outgoing +requests, and for these, the outgoing request id is created by +appending a `/` and another id part into the incoming. This makes the +chain of responsibility much clearer. e.g, this shows the JSON `id` +of a `sendrawtransaction` RPC call, and we can tell that lightning-cli +has invoked the `withdraw` command, which lightningd passes through +to the `txprepare` plugin, which called `sendrawtransaction`. + +``` +cli:withdraw#123/cln:withdraw#7/txprepare:sendpsbt#1/cln:sendrawtransaction#9 +``` + +JSON REPLIES +------------ + +All JSON replies are wrapped in an object; this allows fields to +be added in future. You should safely ignore any unknown fields. + +Any field name which starts with "warning" is a specific warning, and +should be documented in the commands' manual page. Each warning field +has an associated human-readable string, but it's redudant, as each +separate warning should have a distinct field name +(e.g. **warning\_offer\_unknown\_currency** and +**warning\_offer\_missing\_description**). + +JSON TYPES +---------- + +The exact specification for (most!) commands is specified in +`doc/schemas/` in the source directory. This is also used to generate +part of the documentation for each command; the following types are +referred to in addition to simple JSON types: + +* `hex`: an even-length string of hexidecimal digits. +* `hash`: a 64-character `hex` which is a sha256 hash. +* `secret`: a 64-character `hex` which is a secret of some kind. +* `u64`: a JSON number without decimal point in the range 0 to 18446744073709551615 inclusive. +* `u32`: a JSON number without decimal point in the range 0 to 4294967295 inclusive. +* `u16`: a JSON number without decimal point in the range 0 to 65535 inclusive. +* `u16`: a JSON number without decimal point in the range 0 to 255 inclusive. +* `pubkey`: a 66-character `hex` which is an SEC-1 encoded secp256k1 point (usually used as a public key). +* `msat`: a `u64` which indicates an amount of millisatoshis. Deprecated: may also be a string of the number, with "msat" appended. As an input parameter, lightningd(8) will accept strings with suffixes (see below). +* `txid`: a 64-character `hex` Bitcoin transaction identifier. +* `signature`: a `hex` (144 bytes or less), which is a DER-encoded Bitcoin signature (without any sighash flags appended), +* `bip340sig`: a 128-character `hex` which is a BIP-340 (Schnorr) signature. +* `point32`: a 64-character `hex` which represents an x-only pubkey. +* `short_channel_id`: a string of form BLOCK "x" TXNUM "x" OUTNUM. +* `short_channel_id_dir`: a `short_channel_id` with "/0" or "/1" appended, indicating the direction between peers. +* `outpoint`: a string containing a `txid` followed by a ":" and an output number (bitcoind uses this form). +* `feerate`: an integer, or a string consisting of a number followed by "perkw" or "perkb". +* `outputdesc`: an object containing onchain addresses as keys, and "all" or a valid `msat` field as values. + +The following forms of `msat` are supported as parameters: + +- An integer (representing that many millisatoshis), e.g. `10000` +- A string of an integer N and the suffix *msat* (representing N millisatoshis) e.g. `"10000msat"` +- A string of an integer N and the suffix *sat* (representing N times 1000 millisatoshis ) e.g. `"10sat"` +- A string of a number N.M (where M is exactly three digits) and the suffix *sat* (representing N times 1000 plus M millisatoshis) e.g. `"10.000sat"` +- A string of an integer N and the suffix *btc* (representing N times 100000000000 millisatoshis) e.g. `"1btc"` +- A string of a number N.M (where M is exactly eight digits) and the suffix *btc* (representing N times 100000000000 plus M times 1000 millisatoshis) e.g `"0.00000010btc"` +- A string of a number N.M (where M is exactly elevent digits) and the suffix *btc* (representing N times 100000000000 plus M millisatoshis) e.g `"0.00000010000btc"` + +JSON NOTIFICATIONS +------------------ + +Notifications are (per JSONRPC spec) JSON commands without an "id" +field. They give information about ongoing commands, but you +need to enable them. See lightning-notifications(7). + +FIELD FILTERING +--------------- + +You can restrict what fields are in the output of any command, by +including a `"filter"` member in your request, alongside the standard +`"method"` and `"params"` fields. + +`filter` is a template, with keys indicating what fields are to be +output (values must be `true`). Only fields which appear in the +template will be output. For example, here is a normal `result` of +`listtransactions`: + +``` +"result": { + "transactions": [ + { + "hash": "3b15dbc81d6a70abe1e75c1796c3eeba71c3954b7a90dfa67d55c1e989e20dbb", + "rawtx": "020000000001019db609b099735fada240b82cec9da880b35d7a944065c280b8534cb4e2f5a7e90000000000feffffff0240420f000000000017a914d8b7ebd0ccc80266a97d9a828baf1877032ac6648731aff6290100000017a9142cb0814338091a73b388579b025c34f328dfb7898702473044022060a7ede98390111bc33bb12b09b38ad8e31b2a6fd62e9ce39a165b4c15ed39f8022040537219d42af28be18fd223af7cb2367f2300c9f0eb20dcaf677a96cd23efc7012102b2e79c36f2173bc24754214b6eeecd8dc753afda44f606d6f8c55c60c4d614ac65000000", + "blockheight": 102, + "txindex": 1, + "locktime": 101, + "version": 2, + "inputs": [ + { + "txid": "e9a7f5e2b44c53b880c26540947a5db380a89dec2cb840a2ad5f7399b009b69d", + "index": 0, + "sequence": 4294967294 + } + ], + "outputs": [ + { + "index": 0, + "amount_msat": "1000000000msat", + "type": "deposit", + "scriptPubKey": "a914d8b7ebd0ccc80266a97d9a828baf1877032ac66487" + }, + { + "index": 1, + "amount_msat": "4998999857000msat", + "scriptPubKey": "a9142cb0814338091a73b388579b025c34f328dfb78987" + } + ] + }, + { + "hash": "3a5ebaae466a9cb69c59553a3100ed545523e7450c32684cbc6bf0b305a6c448", + "rawtx": "02000000000101bb0de289e9c1557da6df907a4b95c371baeec396175ce7e1ab706a1dc8db153b000000001716001401fad90abcd66697e2592164722de4a95ebee165fdffffff0217a70d0000000000160014c2ccab171c2a5be9dab52ec41b825863024c5466a0860100000000002200205b8cd3b914cf67cdd8fa6273c930353dd36476734fbd962102c2df53b90880cd0247304402201ce0fef95f6aa0e04a87bdc3083259a8aa7212568f672962d1c3da968daf4f72022041ff4e4e255757c12335e67acde8cf4528c60d4afee45d3f891c81b3a0218c75012103d745445c9362665f22e0d96e9e766f273f3260dea39c8a76bfa05dd2684ddccf66000000", + "blockheight": 103, + "txindex": 1, + "locktime": 102, + "version": 2, + "inputs": [ + { + "txid": "3b15dbc81d6a70abe1e75c1796c3eeba71c3954b7a90dfa67d55c1e989e20dbb", + "index": 0, + "sequence": 4294967293 + } + ], + "outputs": [ + { + "index": 0, + "amount_msat": "894743000msat", + "type": "deposit", + "scriptPubKey": "0014c2ccab171c2a5be9dab52ec41b825863024c5466" + }, + { + "index": 1, + "amount_msat": "100000000msat", + "type": "channel_funding", + "channel": "103x1x1", + "scriptPubKey": "00205b8cd3b914cf67cdd8fa6273c930353dd36476734fbd962102c2df53b90880cd" + } + ] + } + ] +} +``` + +If we only wanted the output amounts and types, we would create a filter like so: + +``` +"filter": {"transactions": [{"outputs": [{"amount_msat": true, "type": true}]}]} +``` + +The result would be: + +``` +"result": { + "transactions": [ + { + "outputs": [ + { + "amount_msat": "1000000000msat", + "type": "deposit", + }, + { + "amount_msat": "4998999857000msat", + } + ] + }, + { + "outputs": [ + { + "amount_msat": "894743000msat", + "type": "deposit", + }, + { + "amount_msat": "100000000msat", + "type": "channel_funding", + } + ] + } + ] +} +``` + +Note: `"filter"` doesn't change the order, just which fields are +printed. Any fields not explictly mentioned are omitted from the +output, but plugins which don't support filter (and some routines +doing simple JSON transfers) may ignore `"filter"`, so you should treat +it as an optimazation only). + +Note: if you specify an array where an object is specified or vice +versa, the response may include a `warning_parameter_filter` field +which describes the problem. + + +DEALING WITH FORMAT CHANGES +--------------------------- + +Fields can be added to the JSON output at any time, but to remove (or, +very rarely) change a field requires a minimum deprecation period of 6 +months and two releases. Usually a new field will be added if one is +deprecated, so both will be present in transition. + +To test that you're not using deprecated fields, you can use the +lightningd-config(5) option `allow-deprecated-apis=false`. You should +only use this in internal tests: it is not recommended that users use +this directly. + +The documentation tends to only refer to non-deprecated items, so if +you seen an output field which is not documented, its either a bug +(like that ever happens!) or a deprecated field you should ignore. + +DEBUGGING +--------- + +You can use `log-level=io` to see much of the JSON conversation (in +hex) that occurs. It's extremely noisy though! + +AUTHOR +------ + +Rusty Russell <> wrote this man page, and +much of the configuration language, but many others did the hard work of +actually implementing these options. + +SEE ALSO +-------- + +lightningd-config(5), lightning-notifications(7), lightningd(8) + +RESOURCES +--------- + +Main web site: + +COPYING +------- + +Note: the modules in the ccan/ directory have their own licenses, but +the rest of the code is covered by the BSD-style MIT license. diff --git a/doc/lightningd.8.md b/doc/lightningd.8.md index c16429689b39..a978941da6ca 100644 --- a/doc/lightningd.8.md +++ b/doc/lightningd.8.md @@ -142,11 +142,11 @@ and look at the *state* of the channel: $ lightning-cli listpeers $PUBLICKEY The channel will initially start with a *state* of -*CHANNELD\_AWAITING_LOCKIN*. You need to wait for the channel *state* -to become *CHANNELD_NORMAL*, meaning the funding transaction has been +*CHANNELD\_AWAITING\_LOCKIN*. You need to wait for the channel *state* +to become *CHANNELD\_NORMAL*, meaning the funding transaction has been confirmed deeply. -Once the channel *state* is *CHANNELD_NORMAL*, you can start paying +Once the channel *state* is *CHANNELD\_NORMAL*, you can start paying merchants over Lightning. Acquire a Lightning invoice from your favorite merchant, and use lightning-pay(7) to pay it: @@ -182,6 +182,7 @@ implementation. SEE ALSO -------- +lightningd-rpc(7), lightning-listconfigs(7), lightningd-config(5), lightning-cli(1), lightning-newaddr(7), lightning-listfunds(7), lightning-connect(7), lightning-fundchannel(7), lightning-listpeers(7), lightning-pay(7), diff --git a/doc/reckless.7.md b/doc/reckless.7.md new file mode 100644 index 000000000000..4413df23bb42 --- /dev/null +++ b/doc/reckless.7.md @@ -0,0 +1,142 @@ +reckless - install and activate a CLN plugin by name +==================================================== + +SYNOPSIS +-------- + +**reckless** [*options*] **install/uninstall/enable/disable/source** *target* + +DESCRIPTION +----------- + +Reckless is a plugin manager for Core-Lightning. Typical plugin +installation involves: finding the source plugin, copying, +installing dependencies, testing, activating, and updating the +lightningd config file. Reckless does all of these by invoking: + +**reckless** **install** *plugin\_name* + +reckless will exit early in the event that: +- the plugin is not found in any available source repositories +- dependencies are not sucessfully installed +- the plugin fails to execute + +Reckless-installed plugins reside in the 'reckless' subdirectory +of the user's `.lightning` folder. By default, plugins are activated +on the `bitcoin` network (and use lightningd's bitcoin network +config), but regtest may also be used. + +Other commands include: + +**reckless** **uninstall** *plugin\_name* + disables the plugin, removes the directory. + +**reckless** **search** *plugin\_name* + looks through all available sources for a plugin matching + this name. + +**reckless** **enable** *plugin\_name* + dynamically enables the reckless-installed plugin and updates + the config to match. + +**reckless** **disable** *plugin\_name* + dynamically disables the reckless-installed plugin and updates + the config to match. + +**reckless** **source** **list** + list available plugin repositories. + +**reckless** **source** **add** *repo\_url* + add another plugin repo for reckless to search. + +**reckless** **source** **rm** *repo\_url* + remove a plugin repo for reckless to search. + +OPTIONS +------- + +Available option flags: + +**-d**, **--reckless-dir** *reckless\_dir* + specify an alternative data directory for reckless to use. + Useful if your .lightning is protected from execution. + +**-l**, **--lightning** *lightning\_data\_dir* + lightning data directory (defaults to $USER/.lightning) + +**-c**, **--conf** *lightning\_config* + pass the config used by lightningd + +**-r**, **--regtest** + use the regtest network and config instead of bitcoin mainnet + +**-v**, **--verbose** + request additional debug output + +NOTES +----- + +Reckless currently supports python plugins only. Additional language +support will be provided in future releases. + +Running the first time will prompt the user that their lightningd's +bitcoin config will be appended (or created) to inherit the reckless +config file (this config is specific to bitcoin by default.) +Management of plugins will subsequently modify this file. + + +Troubleshooting tips: + +Plugins must be executable. For python plugins, the shebang is +invoked, so **python3** should be available in your environment. This +can be verified with **which Python3**. The default reckless directory +is $USER/.lightning/reckless and it should be possible for the +lightningd user to execute files located here. If this is a problem, +the option flag **reckless -d=** may be used to +relocate the reckless directory from its default. Consider creating a +permanent alias in this case. + +For Plugin Developers: + +To make your plugin compatible with reckless install: +- Choose a unique plugin name. +- The plugin entrypoint is inferred. Naming your plugin executable + the same as your plugin name will allow reckless to identify it + correctly (file extensions are okay.) +- For python plugins, a requirements.txt is the preferred medium for + python dependencies. A pyproject.toml will be used as a fallback, + but test installation via `pip install -e .` - Poetry looks for + additional files in the working directory, whereas with pip, any + references to these will require something like + `packages = [{ include = "*.py" }]` under the `[tool.poetry]` + section. +- Additional repository sources may be added with + `reckless source add https://my.repo.url/here` however, + https://github.com/lightningd/plugins is included by default. + Consider adding your plugin lightningd/plugins to make + installation simpler. +- If your plugin is located in a subdirectory of your repo with a + different name than your plugin, it will likely be overlooked. + +AUTHOR +------ + +Antoine Poinsot wrote the original reckless plugin on which this is +based. + +Rusty Russell wrote the outline for the reckless utility's function + +Alex Myers <> is mostly responsible for the +reckless code and this man page, with thanks to Christian Decker for +extensive review. + +SEE ALSO +-------- + +Core-Lightning plugins repo: + +RESOURCES +--------- + +Main web site: + diff --git a/doc/schemas/WRITING_SCHEMAS.md b/doc/schemas/WRITING_SCHEMAS.md index bf42a5fff666..545edfccc0e9 100644 --- a/doc/schemas/WRITING_SCHEMAS.md +++ b/doc/schemas/WRITING_SCHEMAS.md @@ -37,7 +37,7 @@ You should always list all fields which are *always* present in `"required"`. We extend the basic types; see -[contrib/pyln-testing/pyln/testing/fixtures.py](fixtures.py). +[fixtures.py][contrib/pyln-testing/pyln/testing/fixtures.py]. In addition, before committing a new schema or a new version of it, make sure that it is well formatted. If you don't want do it by hand, use `make fmt-schema` that uses @@ -78,3 +78,5 @@ To add conditional fields: Good luck! Rusty. + +[contrib/pyln-testing/pyln/testing/fixtures.py]: https://github.com/ElementsProject/lightning/tree/master/contrib/pyln-testing/pyln/testing/fixtures.py diff --git a/doc/schemas/autoclean-once.request.json b/doc/schemas/autoclean-once.request.json new file mode 100644 index 000000000000..9cc41516273e --- /dev/null +++ b/doc/schemas/autoclean-once.request.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "required": [ + "subsystem", + "age" + ], + "properties": { + "subsystem": { + "type": "string", + "enum": [ + "succeededforwards", + "failedforwards", + "succeededpays", + "failedpays", + "paidinvoices", + "expiredinvoices" + ], + "description": "What subsystem to clean" + }, + "age": { + "type": "u64", + "description": "How many seconds old an entry must be to delete it" + } + } +} diff --git a/doc/schemas/autoclean-once.schema.json b/doc/schemas/autoclean-once.schema.json new file mode 100644 index 000000000000..36e4969bff7c --- /dev/null +++ b/doc/schemas/autoclean-once.schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": true, + "required": [ + "autoclean" + ], + "properties": { + "autoclean": { + "type": "object", + "additionalProperties": false, + "properties": { + "succeededforwards": { + "type": "object", + "additionalProperties": false, + "required": [ + "cleaned", + "uncleaned" + ], + "properties": { + "cleaned": { + "type": "u64", + "description": "total number of deletions done this run" + }, + "uncleaned": { + "type": "u64", + "description": "the total number of entries *not* deleted this run" + } + } + }, + "failedforwards": { + "type": "object", + "additionalProperties": false, + "required": [ + "cleaned", + "uncleaned" + ], + "properties": { + "cleaned": { + "type": "u64", + "description": "total number of deletions done this run" + }, + "uncleaned": { + "type": "u64", + "description": "the total number of entries *not* deleted this run" + } + } + }, + "succeededpays": { + "type": "object", + "additionalProperties": false, + "required": [ + "cleaned", + "uncleaned" + ], + "properties": { + "cleaned": { + "type": "u64", + "description": "total number of deletions done this run" + }, + "uncleaned": { + "type": "u64", + "description": "the total number of entries *not* deleted this run" + } + } + }, + "failedpays": { + "type": "object", + "additionalProperties": false, + "required": [ + "cleaned", + "uncleaned" + ], + "properties": { + "cleaned": { + "type": "u64", + "description": "total number of deletions done this run" + }, + "uncleaned": { + "type": "u64", + "description": "the total number of entries *not* deleted this run" + } + } + }, + "paidinvoices": { + "type": "object", + "additionalProperties": false, + "required": [ + "cleaned", + "uncleaned" + ], + "properties": { + "cleaned": { + "type": "u64", + "description": "total number of deletions done this run" + }, + "uncleaned": { + "type": "u64", + "description": "the total number of entries *not* deleted this run" + } + } + }, + "expiredinvoices": { + "type": "object", + "additionalProperties": false, + "required": [ + "cleaned", + "uncleaned" + ], + "properties": { + "cleaned": { + "type": "u64", + "description": "total number of deletions done this run" + }, + "uncleaned": { + "type": "u64", + "description": "the total number of entries *not* deleted this run" + } + } + } + } + } + } +} diff --git a/doc/schemas/bkpr-channelsapy.schema.json b/doc/schemas/bkpr-channelsapy.schema.json index 0264a1f14b18..d66161037d1e 100644 --- a/doc/schemas/bkpr-channelsapy.schema.json +++ b/doc/schemas/bkpr-channelsapy.schema.json @@ -59,7 +59,7 @@ }, "our_start_balance_msat": { "type": "msat", - "description": "Starting balance in channel at funding. Note that if our start ballance is zero, any _initial field will be omitted (can't divide by zero)" + "description": "Starting balance in channel at funding. Note that if our start balance is zero, any _initial field will be omitted (can't divide by zero)" }, "channel_start_balance_msat": { "type": "msat", diff --git a/doc/schemas/createinvoice.schema.json b/doc/schemas/createinvoice.schema.json index ed97308a4386..e25ce1232f5c 100644 --- a/doc/schemas/createinvoice.schema.json +++ b/doc/schemas/createinvoice.schema.json @@ -73,9 +73,9 @@ "maxLength": 64, "minLength": 64 }, - "payer_note": { + "invreq_payer_note": { "type": "string", - "description": "the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only)." + "description": "the optional *invreq_payer_note* from invoice_request which created this invoice (**experimental-offers** only)." } } } diff --git a/doc/schemas/decode.schema.json b/doc/schemas/decode.schema.json index 66b86a4f2a9a..ca5d94358e7a 100644 --- a/doc/schemas/decode.schema.json +++ b/doc/schemas/decode.schema.json @@ -43,8 +43,8 @@ "then": { "required": [ "offer_id", - "node_id", - "description" + "offer_node_id", + "offer_description" ], "additionalProperties": false, "properties": { @@ -52,19 +52,11 @@ "valid": {}, "offer_id": { "type": "hex", - "description": "the id of this offer (merkle hash of non-signature fields)", + "description": "the id we use to identify this offer", "maxLength": 64, "minLength": 64 }, - "node_id": { - "type": "point32", - "description": "x-only public key of the offering node" - }, - "signature": { - "type": "bip340sig", - "description": "BIP-340 signature of the *node_id* on this offer" - }, - "chains": { + "offer_chains": { "type": "array", "description": "which blockchains this offer is for (missing implies bitcoin mainnet only)", "items": { @@ -74,68 +66,68 @@ "minLength": 64 } }, - "currency": { + "offer_metadata": { + "type": "hex", + "description": "any metadata the creater of the offer includes" + }, + "offer_currency": { "type": "string", "description": "ISO 4217 code of the currency (missing implies Bitcoin)", "maxLength": 3, "minLength": 3 }, - "minor_unit": { + "warning_unknown_offer_currency": { + "type": "string", + "description": "The currency code is unknown (so no `currency_minor_unit`)" + }, + "currency_minor_unit": { "type": "u32", "description": "the number of decimal places to apply to amount (if currency known)" }, - "warning_offer_unknown_currency": { - "type": "string", - "description": "The currency code is unknown (so no **minor_unit**)" - }, - "amount": { + "offer_amount": { "type": "u64", - "description": "the amount in the *currency* adjusted by *minor_unit*, if any" + "description": "the amount in the `offer_currency` adjusted by `currency_minor_unit`, if any" }, - "amount_msat": { + "offer_amount_msat": { "type": "msat", - "description": "the amount in bitcoin (if specified, and no *currency*)" - }, - "send_invoice": { - "type": "boolean", - "description": "present if this is a send_invoice offer", - "enum": [ - true - ] - }, - "refund_for": { - "type": "hex", - "description": "the *payment_preimage* of invoice this is a refund for", - "maxLength": 64, - "minLength": 64 + "description": "the amount in bitcoin (if specified, and no `offer_currency`)" }, - "description": { + "offer_description": { "type": "string", "description": "the description of the purpose of the offer" }, - "vendor": { + "offer_issuer": { "type": "string", - "description": "the name of the vendor for this offer" + "description": "the description of the creator of the offer" }, - "features": { + "offer_features": { "type": "hex", - "description": "the array of feature bits for this offer" + "description": "the feature bits of the offer" }, - "absolute_expiry": { + "offer_absolute_expiry": { "type": "u64", "description": "UNIX timestamp of when this offer expires" }, - "paths": { + "offer_quantity_max": { + "type": "u64", + "description": "the maximum quantity (or, if 0, means any quantity)" + }, + "offer_paths": { "type": "array", "description": "Paths to the destination", "items": { "type": "object", "required": [ + "first_node_id", "blinding", "path" ], "additionalProperties": false, "properties": { + "first_node_id": { + "type": "pubkey", + "description": "the (presumably well-known) public key of the start of the path" + }, "blinding": { "type": "pubkey", "description": "blinding factor for this path" @@ -146,12 +138,12 @@ "items": { "type": "object", "required": [ - "node_id", + "blinded_node_id", "encrypted_recipient_data" ], "additionalProperties": false, "properties": { - "node_id": { + "blinded_node_id": { "type": "pubkey", "description": "node_id of the hop" }, @@ -165,15 +157,11 @@ } } }, - "quantity_min": { - "type": "u64", - "description": "the minimum quantity" - }, - "quantity_max": { - "type": "u64", - "description": "the maximum quantity" + "offer_node_id": { + "type": "pubkey", + "description": "public key of the offering node" }, - "recurrence": { + "offer_recurrence": { "type": "object", "description": "how often to this offer should be used", "required": [ @@ -188,11 +176,11 @@ }, "time_unit_name": { "type": "string", - "description": "the name of *time_unit* (if valid)" + "description": "the name of `time_unit` (if valid)" }, "period": { "type": "u32", - "description": "how many *time_unit* per payment period" + "description": "how many `time_unit` per payment period" }, "basetime": { "type": "u64", @@ -200,7 +188,7 @@ }, "start_any_period": { "type": "u64", - "description": "you can start at any period (only if **basetime** present)" + "description": "you can start at any period (only if `basetime` present)" }, "limit": { "type": "u32", @@ -233,6 +221,33 @@ } } } + }, + "unknown_offer_tlvs": { + "type": "array", + "description": "Any extra fields we didn't know how to parse", + "items": { + "type": "object", + "required": [ + "type", + "length", + "value" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "u64", + "description": "The type" + }, + "length": { + "type": "u64", + "description": "The length" + }, + "value": { + "type": "hex", + "description": "The value" + } + } + } } } } @@ -266,22 +281,37 @@ "chains": {}, "currency": {}, "minor_unit": {}, - "warning_offer_unknown_currency": {}, + "warning_unknown_offer_currency": {}, "amount": {}, "amount_msat": {}, "send_invoice": {}, - "refund_for": {}, "description": {}, "vendor": {}, "features": {}, "absolute_expiry": {}, "paths": {}, - "quantity_min": {}, "quantity_max": {}, + "unknown_offer_tlvs": {}, "recurrence": {}, - "warning_offer_missing_description": { + "warning_missing_offer_node_id": { "type": "string", - "description": "No **description**" + "description": "`offer_node_id` is not present" + }, + "warning_invalid_offer_description": { + "type": "string", + "description": "`offer_description` is not valid UTF8" + }, + "warning_missing_offer_description": { + "type": "string", + "description": "`offer_description` is not present" + }, + "warning_invalid_offer_currency": { + "type": "string", + "description": "`offer_currency_code` is not valid UTF8" + }, + "warning_invalid_offer_issuer": { + "type": "string", + "description": "`offer_issuer` is not valid UTF8" } } } @@ -292,7 +322,7 @@ "type": { "type": "string", "enum": [ - "bolt12 invoice" + "bolt12 invoice_request" ] }, "valid": { @@ -305,14 +335,11 @@ }, "then": { "required": [ - "node_id", - "signature", - "amount_msat", - "description", - "created_at", - "payment_hash", - "relative_expiry", - "min_final_cltv_expiry" + "offer_node_id", + "offer_description", + "invreq_metadata", + "invreq_payer_id", + "signature" ], "additionalProperties": false, "properties": { @@ -320,64 +347,82 @@ "valid": {}, "offer_id": { "type": "hex", - "description": "the id of this offer (merkle hash of non-signature fields)", + "description": "the id we use to identify this offer", "maxLength": 64, "minLength": 64 }, - "node_id": { - "type": "point32", - "description": "x-only public key of the offering node" - }, - "signature": { - "type": "bip340sig", - "description": "BIP-340 signature of the *node_id* on this offer" + "offer_chains": { + "type": "array", + "description": "which blockchains this offer is for (missing implies bitcoin mainnet only)", + "items": { + "type": "hex", + "description": "the genesis blockhash", + "maxLength": 64, + "minLength": 64 + } }, - "chain": { + "offer_metadata": { "type": "hex", - "description": "which blockchain this invoice is for (missing implies bitcoin mainnet only)", - "maxLength": 64, - "minLength": 64 + "description": "any metadata the creator of the offer includes" }, - "amount_msat": { - "type": "msat", - "description": "the amount in bitcoin" + "offer_currency": { + "type": "string", + "description": "ISO 4217 code of the currency (missing implies Bitcoin)", + "maxLength": 3, + "minLength": 3 }, - "send_invoice": { - "type": "boolean", - "description": "present if this offer was a send_invoice offer", - "enum": [ - true - ] + "warning_unknown_offer_currency": { + "type": "string", + "description": "The currency code is unknown (so no `currency_minor_unit`)" }, - "refund_for": { - "type": "hex", - "description": "the *payment_preimage* of invoice this is a refund for", - "maxLength": 64, - "minLength": 64 + "currency_minor_unit": { + "type": "u32", + "description": "the number of decimal places to apply to amount (if currency known)" }, - "description": { + "offer_amount": { + "type": "u64", + "description": "the amount in the `offer_currency` adjusted by `currency_minor_unit`, if any" + }, + "offer_amount_msat": { + "type": "msat", + "description": "the amount in bitcoin (if specified, and no `offer_currency`)" + }, + "offer_description": { "type": "string", "description": "the description of the purpose of the offer" }, - "vendor": { + "offer_issuer": { "type": "string", - "description": "the name of the vendor for this offer" + "description": "the description of the creator of the offer" }, - "features": { + "offer_features": { "type": "hex", - "description": "the array of feature bits for this offer" + "description": "the feature bits of the offer" }, - "paths": { + "offer_absolute_expiry": { + "type": "u64", + "description": "UNIX timestamp of when this offer expires" + }, + "offer_quantity_max": { + "type": "u64", + "description": "the maximum quantity (or, if 0, means any quantity)" + }, + "offer_paths": { "type": "array", "description": "Paths to the destination", "items": { "type": "object", "required": [ + "first_node_id", "blinding", "path" ], "additionalProperties": false, "properties": { + "first_node_id": { + "type": "pubkey", + "description": "the (presumably well-known) public key of the start of the path" + }, "blinding": { "type": "pubkey", "description": "blinding factor for this path" @@ -388,12 +433,12 @@ "items": { "type": "object", "required": [ - "node_id", + "blinded_node_id", "encrypted_recipient_data" ], "additionalProperties": false, "properties": { - "node_id": { + "blinded_node_id": { "type": "pubkey", "description": "node_id of the hop" }, @@ -407,80 +452,139 @@ } } }, - "quantity": { - "type": "u64", - "description": "the quantity ordered" - }, - "recurrence_counter": { - "type": "u32", - "description": "the 0-based counter for a recurring payment" + "offer_node_id": { + "type": "pubkey", + "description": "public key of the offering node" }, - "recurrence_start": { - "type": "u32", - "description": "the optional start period for a recurring payment" + "offer_recurrence": { + "type": "object", + "description": "how often to this offer should be used", + "required": [ + "period", + "time_unit" + ], + "additionalProperties": false, + "properties": { + "time_unit": { + "type": "u32", + "description": "the BOLT12 time unit" + }, + "time_unit_name": { + "type": "string", + "description": "the name of `time_unit` (if valid)" + }, + "period": { + "type": "u32", + "description": "how many `time_unit` per payment period" + }, + "basetime": { + "type": "u64", + "description": "period starts at this UNIX timestamp" + }, + "start_any_period": { + "type": "u64", + "description": "you can start at any period (only if `basetime` present)" + }, + "limit": { + "type": "u32", + "description": "maximum period number for recurrence" + }, + "paywindow": { + "type": "object", + "description": "when within a period will payment be accepted (default is prior and during the period)", + "required": [ + "seconds_before", + "seconds_after" + ], + "additionalProperties": false, + "properties": { + "seconds_before": { + "type": "u32", + "description": "seconds prior to period start" + }, + "seconds_after": { + "type": "u32", + "description": "seconds after to period start" + }, + "proportional_amount": { + "type": "boolean", + "enum": [ + true + ], + "description": "amount should be scaled if payed after period start" + } + } + } + } }, - "recurrence_basetime": { - "type": "u32", - "description": "the UNIX timestamp of the first recurrence period start" + "invreq_metadata": { + "type": "hex", + "description": "the payer-provided blob to derive invreq_payer_id" }, - "payer_key": { - "type": "point32", - "description": "the transient key which identifies the payer" + "invreq_payer_id": { + "type": "hex", + "description": "the payer-provided key" }, - "payer_info": { + "invreq_chain": { "type": "hex", - "description": "the payer-provided blob to derive payer_key" + "description": "which blockchain this offer is for (missing implies bitcoin mainnet only)", + "maxLength": 64, + "minLength": 64 }, - "timestamp": { - "deprecated": true + "invreq_amount_msat": { + "type": "msat", + "description": "the amount the invoice should be for" }, - "created_at": { + "invreq_features": { + "type": "hex", + "description": "the feature bits of the invoice_request" + }, + "invreq_quantity": { "type": "u64", - "description": "the UNIX timestamp of invoice creation" + "description": "the number of items to invoice for" }, - "payment_hash": { - "type": "hex", - "description": "the hash of the *payment_preimage*", - "maxLength": 64, - "minLength": 64 + "invreq_payer_note": { + "type": "string", + "description": "a note attached by the payer" }, - "relative_expiry": { + "invreq_recurrence_counter": { "type": "u32", - "description": "the number of seconds after *created_at* when this expires" + "description": "which number request this is for the same invoice" }, - "min_final_cltv_expiry": { + "invreq_recurrence_start": { "type": "u32", - "description": "the number of blocks required by destination" + "description": "when we're requesting to start an invoice at a non-zero period" }, - "fallbacks": { + "signature": { + "type": "bip340sig", + "description": "BIP-340 signature of the `invreq_payer_id` on this invoice_request" + }, + "unknown_invoice_request_tlvs": { "type": "array", - "description": "onchain addresses", + "description": "Any extra fields we didn't know how to parse", "items": { "type": "object", "required": [ - "version", - "hex" + "type", + "length", + "value" ], "additionalProperties": false, "properties": { - "version": { - "type": "u8", - "description": "Segwit address version" + "type": { + "type": "u64", + "description": "The type" }, - "hex": { - "type": "hex", - "description": "Raw encoded segwit address" + "length": { + "type": "u64", + "description": "The length" }, - "address": { - "type": "string", - "description": "bech32 segwit address" + "value": { + "type": "hex", + "description": "The value" } } } - }, - "refund_signature": { - "type": "bip340sig", - "description": "the payer key signature to get a refund" } } } @@ -491,7 +595,7 @@ "type": { "type": "string", "enum": [ - "bolt12 invoice" + "bolt12 invoice_request" ] }, "valid": { @@ -509,86 +613,65 @@ "type": {}, "valid": {}, "offer_id": {}, - "node_id": {}, - "signature": {}, - "chain": {}, - "amount_msat": {}, - "send_invoice": {}, - "refund_for": {}, - "description": {}, - "vendor": {}, - "features": {}, - "paths": {}, - "quantity": {}, - "recurrence_counter": {}, - "recurrence_start": {}, - "recurrence_basetime": {}, - "payer_key": {}, - "payer_info": {}, - "timestamp": {}, - "created_at": {}, - "payment_hash": {}, - "relative_expiry": {}, - "min_final_cltv_expiry": {}, - "fallbacks": { - "type": "array", - "items": { - "type": "object", - "required": [ - "version", - "hex" - ], - "properties": { - "version": {}, - "hex": {}, - "address": {}, - "warning_invoice_fallbacks_version_invalid": { - "type": "string", - "description": "**version** is > 16" - } - } - } - }, - "refund_signature": {}, - "warning_invoice_missing_amount": { + "offer_chains": {}, + "offer_metadata": {}, + "offer_currency": {}, + "warning_unknown_offer_currency": {}, + "currency_minor_unit": {}, + "offer_amount": {}, + "offer_amount_msat": {}, + "offer_description": {}, + "offer_issuer": {}, + "offer_features": {}, + "offer_absolute_expiry": {}, + "offer_quantity_max": {}, + "offer_paths": {}, + "offer_node_id": {}, + "offer_recurrence": {}, + "invreq_metadata": {}, + "invreq_payer_id": {}, + "invreq_chain": {}, + "invreq_amount_msat": {}, + "invreq_features": {}, + "invreq_quantity": {}, + "invreq_payer_note": {}, + "invreq_recurrence_counter": {}, + "invreq_recurrence_start": {}, + "warning_invalid_offer_description": { "type": "string", - "description": "**amount_msat* missing" + "description": "`offer_description` is not valid UTF8" }, - "warning_invoice_missing_description": { + "warning_missing_offer_description": { "type": "string", - "description": "No **description**" + "description": "`offer_description` is not present" }, - "warning_invoice_missing_blinded_payinfo": { + "warning_invalid_offer_currency": { "type": "string", - "description": "Has **paths** without payinfo" + "description": "`offer_currency_code` is not valid UTF8" }, - "warning_invoice_invalid_blinded_payinfo": { + "warning_invalid_offer_issuer": { "type": "string", - "description": "Does not have exactly one payinfo for each of **paths**" + "description": "`offer_issuer` is not valid UTF8" }, - "warning_invoice_missing_recurrence_basetime": { + "warning_missing_invreq_metadata": { "type": "string", - "description": "Has **recurrence_counter** without **recurrence_basetime**" + "description": "`invreq_metadata` is not present" }, - "warning_invoice_missing_created_at": { + "warning_missing_invreq_payer_id": { "type": "string", - "description": "Missing **created_at**" + "description": "`invreq_payer_id` is not present" }, - "warning_invoice_missing_payment_hash": { + "warning_invalid_invreq_payer_note": { "type": "string", - "description": "Missing **payment_hash**" + "description": "`invreq_payer_note` is not valid UTF8" }, - "warning_invoice_refund_signature_missing_payer_key": { + "warning_missing_invoice_request_signature": { "type": "string", - "description": "Missing **payer_key** for refund_signature" + "description": "`signature` is not present" }, - "warning_invoice_refund_signature_invalid": { + "warning_invalid_invoice_request_signature": { "type": "string", - "description": "**refund_signature** incorrect" - }, - "warning_invoice_refund_missing_signature": { - "type": "string", - "description": "No **refund_signature**" + "description": "Incorrect `signature`" } } } @@ -599,7 +682,7 @@ "type": { "type": "string", "enum": [ - "bolt12 invoice_request" + "bolt12 invoice" ] }, "valid": { @@ -612,8 +695,15 @@ }, "then": { "required": [ - "offer_id", - "payer_key" + "offer_node_id", + "offer_description", + "invreq_metadata", + "invreq_payer_id", + "invoice_paths", + "invoice_created_at", + "invoice_payment_hash", + "invoice_amount_msat", + "signature" ], "additionalProperties": false, "properties": { @@ -621,47 +711,361 @@ "valid": {}, "offer_id": { "type": "hex", - "description": "the id of the offer this is requesting (merkle hash of non-signature fields)", - "maxLength": 64, - "minLength": 64 - }, - "chain": { - "type": "hex", - "description": "which blockchain this invoice_request is for (missing implies bitcoin mainnet only)", + "description": "the id we use to identify this offer", "maxLength": 64, "minLength": 64 }, - "amount_msat": { - "type": "msat", - "description": "the amount in bitcoin" + "offer_chains": { + "type": "array", + "description": "which blockchains this offer is for (missing implies bitcoin mainnet only)", + "items": { + "type": "hex", + "description": "the genesis blockhash", + "maxLength": 64, + "minLength": 64 + } }, - "features": { + "offer_metadata": { "type": "hex", - "description": "the array of feature bits for this offer" - }, - "quantity": { - "type": "u64", - "description": "the quantity ordered" + "description": "any metadata the creator of the offer includes" }, - "recurrence_counter": { - "type": "u32", - "description": "the 0-based counter for a recurring payment" + "offer_currency": { + "type": "string", + "description": "ISO 4217 code of the currency (missing implies Bitcoin)", + "maxLength": 3, + "minLength": 3 + }, + "warning_unknown_offer_currency": { + "type": "string", + "description": "The currency code is unknown (so no `currency_minor_unit`)" + }, + "currency_minor_unit": { + "type": "u32", + "description": "the number of decimal places to apply to amount (if currency known)" + }, + "offer_amount": { + "type": "u64", + "description": "the amount in the `offer_currency` adjusted by `currency_minor_unit`, if any" + }, + "offer_amount_msat": { + "type": "msat", + "description": "the amount in bitcoin (if specified, and no `offer_currency`)" + }, + "offer_description": { + "type": "string", + "description": "the description of the purpose of the offer" + }, + "offer_issuer": { + "type": "string", + "description": "the description of the creator of the offer" + }, + "offer_features": { + "type": "hex", + "description": "the feature bits of the offer" + }, + "offer_absolute_expiry": { + "type": "u64", + "description": "UNIX timestamp of when this offer expires" + }, + "offer_quantity_max": { + "type": "u64", + "description": "the maximum quantity (or, if 0, means any quantity)" + }, + "offer_paths": { + "type": "array", + "description": "Paths to the destination", + "items": { + "type": "object", + "required": [ + "first_node_id", + "blinding", + "path" + ], + "additionalProperties": false, + "properties": { + "first_node_id": { + "type": "pubkey", + "description": "the (presumably well-known) public key of the start of the path" + }, + "blinding": { + "type": "pubkey", + "description": "blinding factor for this path" + }, + "path": { + "type": "array", + "description": "an individual path", + "items": { + "type": "object", + "required": [ + "blinded_node_id", + "encrypted_recipient_data" + ], + "additionalProperties": false, + "properties": { + "blinded_node_id": { + "type": "pubkey", + "description": "node_id of the hop" + }, + "encrypted_recipient_data": { + "type": "hex", + "description": "encrypted TLV entry for this hop" + } + } + } + } + } + } + }, + "offer_node_id": { + "type": "pubkey", + "description": "public key of the offering node" + }, + "offer_recurrence": { + "type": "object", + "description": "how often to this offer should be used", + "required": [ + "period", + "time_unit" + ], + "additionalProperties": false, + "properties": { + "time_unit": { + "type": "u32", + "description": "the BOLT12 time unit" + }, + "time_unit_name": { + "type": "string", + "description": "the name of `time_unit` (if valid)" + }, + "period": { + "type": "u32", + "description": "how many `time_unit` per payment period" + }, + "basetime": { + "type": "u64", + "description": "period starts at this UNIX timestamp" + }, + "start_any_period": { + "type": "u64", + "description": "you can start at any period (only if `basetime` present)" + }, + "limit": { + "type": "u32", + "description": "maximum period number for recurrence" + }, + "paywindow": { + "type": "object", + "description": "when within a period will payment be accepted (default is prior and during the period)", + "required": [ + "seconds_before", + "seconds_after" + ], + "additionalProperties": false, + "properties": { + "seconds_before": { + "type": "u32", + "description": "seconds prior to period start" + }, + "seconds_after": { + "type": "u32", + "description": "seconds after to period start" + }, + "proportional_amount": { + "type": "boolean", + "enum": [ + true + ], + "description": "amount should be scaled if payed after period start" + } + } + } + } + }, + "invreq_metadata": { + "type": "hex", + "description": "the payer-provided blob to derive invreq_payer_id" + }, + "invreq_payer_id": { + "type": "hex", + "description": "the payer-provided key" + }, + "invreq_chain": { + "type": "hex", + "description": "which blockchain this offer is for (missing implies bitcoin mainnet only)", + "maxLength": 64, + "minLength": 64 + }, + "invreq_amount_msat": { + "type": "msat", + "description": "the amount the invoice should be for" + }, + "invreq_features": { + "type": "hex", + "description": "the feature bits of the invoice_request" + }, + "invreq_quantity": { + "type": "u64", + "description": "the number of items to invoice for" + }, + "invreq_payer_note": { + "type": "string", + "description": "a note attached by the payer" + }, + "invreq_recurrence_counter": { + "type": "u32", + "description": "which number request this is for the same invoice" + }, + "invreq_recurrence_start": { + "type": "u32", + "description": "when we're requesting to start an invoice at a non-zero period" }, - "recurrence_start": { + "invoice_paths": { + "type": "array", + "description": "Paths to pay the destination", + "items": { + "type": "object", + "required": [ + "first_node_id", + "blinding", + "path" + ], + "additionalProperties": false, + "properties": { + "first_node_id": { + "type": "pubkey", + "description": "the (presumably well-known) public key of the start of the path" + }, + "blinding": { + "type": "pubkey", + "description": "blinding factor for this path" + }, + "path": { + "type": "array", + "description": "an individual path", + "items": { + "type": "object", + "required": [ + "blinded_node_id", + "encrypted_recipient_data" + ], + "additionalProperties": false, + "properties": { + "blinded_node_id": { + "type": "pubkey", + "description": "node_id of the hop" + }, + "encrypted_recipient_data": { + "type": "hex", + "description": "encrypted TLV entry for this hop" + }, + "fee_base_msat": { + "type": "msat", + "description": "basefee for path" + }, + "fee_proportional_millionths": { + "type": "u32", + "description": "proportional fee for path" + }, + "cltv_expiry_delta": { + "type": "u32", + "description": "CLTV delta for path" + }, + "features": { + "type": "hex", + "description": "features allowed for path" + } + } + } + } + } + } + }, + "invoice_created_at": { + "type": "u64", + "description": "the UNIX timestamp of invoice creation" + }, + "invoice_relative_expiry": { "type": "u32", - "description": "the optional start period for a recurring payment" + "description": "the number of seconds after *invoice_created_at* when this expires" }, - "payer_key": { - "type": "point32", - "description": "the transient key which identifies the payer" + "invoice_payment_hash": { + "type": "hex", + "description": "the hash of the *payment_preimage*", + "maxLength": 64, + "minLength": 64 }, - "payer_info": { + "invoice_amount_msat": { + "type": "msat", + "description": "the amount required to fulfill invoice" + }, + "invoice_fallbacks": { + "type": "array", + "description": "onchain addresses", + "items": { + "type": "object", + "required": [ + "version", + "hex" + ], + "additionalProperties": false, + "properties": { + "version": { + "type": "u8", + "description": "Segwit address version" + }, + "hex": { + "type": "hex", + "description": "Raw encoded segwit address" + }, + "address": { + "type": "string", + "description": "bech32 segwit address" + } + } + } + }, + "invoice_features": { "type": "hex", - "description": "the payer-provided blob to derive payer_key" + "description": "the feature bits of the invoice" + }, + "invoice_node_id": { + "type": "pubkey", + "description": "the id to pay (usually the same as offer_node_id)" + }, + "invoice_recurrence_basetime": { + "type": "u64", + "description": "the UNIX timestamp to base the invoice periods on" }, - "recurrence_signature": { + "signature": { "type": "bip340sig", - "description": "the payer key signature" + "description": "BIP-340 signature of the `offer_node_id` on this invoice" + }, + "unknown_invoice_tlvs": { + "type": "array", + "description": "Any extra fields we didn't know how to parse", + "items": { + "type": "object", + "required": [ + "type", + "length", + "value" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "u64", + "description": "The type" + }, + "length": { + "type": "u64", + "description": "The length" + }, + "value": { + "type": "hex", + "description": "The value" + } + } + } } } } @@ -672,7 +1076,7 @@ "type": { "type": "string", "enum": [ - "bolt12 invoice_request" + "bolt12 invoice" ] }, "valid": { @@ -690,30 +1094,109 @@ "type": {}, "valid": {}, "offer_id": {}, - "chain": {}, - "amount_msat": {}, - "features": {}, - "quantity": {}, - "recurrence_counter": {}, - "recurrence_start": {}, - "payer_key": {}, - "payer_info": {}, - "recurrence_signature": {}, - "warning_invoice_request_missing_offer_id": { + "offer_chains": {}, + "offer_metadata": {}, + "offer_currency": {}, + "warning_unknown_offer_currency": {}, + "currency_minor_unit": {}, + "offer_amount": {}, + "offer_amount_msat": {}, + "offer_description": {}, + "offer_issuer": {}, + "offer_features": {}, + "offer_absolute_expiry": {}, + "offer_quantity_max": {}, + "offer_paths": {}, + "offer_node_id": {}, + "offer_recurrence": {}, + "invreq_metadata": {}, + "invreq_payer_id": {}, + "invreq_chain": {}, + "invreq_amount_msat": {}, + "invreq_features": {}, + "invreq_quantity": {}, + "invreq_payer_note": {}, + "invreq_node_id": {}, + "invreq_recurrence_counter": {}, + "invreq_recurrence_start": {}, + "warning_invalid_offer_description": { "type": "string", - "description": "No **offer_id**" + "description": "`offer_description` is not valid UTF8" }, - "warning_invoice_request_missing_payer_key": { + "warning_missing_offer_description": { "type": "string", - "description": "No **payer_key**" + "description": "`offer_description` is not present" }, - "warning_invoice_request_missing_recurrence_signature": { + "warning_invalid_offer_currency": { "type": "string", - "description": "No **recurrence_signature**" + "description": "`offer_currency_code` is not valid UTF8" }, - "warning_invoice_request_invalid_recurrence_signature": { + "warning_invalid_offer_issuer": { "type": "string", - "description": "**recurrence_signature** incorrect" + "description": "`offer_issuer` is not valid UTF8" + }, + "warning_missing_invreq_metadata": { + "type": "string", + "description": "`invreq_metadata` is not present" + }, + "warning_invalid_invreq_payer_note": { + "type": "string", + "description": "`invreq_payer_note` is not valid UTF8" + }, + "warning_missing_invoice_paths": { + "type": "string", + "description": "`invoice_paths` is not present" + }, + "warning_missing_invoice_blindedpay": { + "type": "string", + "description": "`invoice_blindedpay` is not present" + }, + "warning_missing_invoice_created_at": { + "type": "string", + "description": "`invoice_created_at` is not present" + }, + "warning_missing_invoice_payment_hash": { + "type": "string", + "description": "`invoice_payment_hash` is not present" + }, + "warning_missing_invoice_amount": { + "type": "string", + "description": "`invoice_amount` is not present" + }, + "warning_missing_invoice_recurrence_basetime": { + "type": "string", + "description": "`invoice_recurrence_basetime` is not present" + }, + "warning_missing_invoice_node_id": { + "type": "string", + "description": "`invoice_node_id` is not present" + }, + "warning_missing_invoice_signature": { + "type": "string", + "description": "`signature` is not present" + }, + "warning_invalid_invoice_signature": { + "type": "string", + "description": "Incorrect `signature`" + }, + "fallbacks": { + "type": "array", + "items": { + "type": "object", + "required": [ + "version", + "hex" + ], + "properties": { + "version": {}, + "hex": {}, + "address": {}, + "warning_invoice_fallbacks_version_invalid": { + "type": "string", + "description": "`version` is > 16" + } + } + } } } } @@ -759,7 +1242,7 @@ }, "expiry": { "type": "u64", - "description": "the number of seconds this is valid after *timestamp*" + "description": "the number of seconds this is valid after `created_at`" }, "payee": { "type": "pubkey", diff --git a/doc/schemas/delinvoice.schema.json b/doc/schemas/delinvoice.schema.json index bbc724807644..0736b331a6c8 100644 --- a/doc/schemas/delinvoice.schema.json +++ b/doc/schemas/delinvoice.schema.json @@ -79,9 +79,9 @@ "type": "hex", "description": "offer for which this invoice was created" }, - "payer_note": { + "invreq_payer_note": { "type": "string", - "description": "the optional *payer_note* from invoice_request which created this invoice" + "description": "the optional *invreq_payer_note* from invoice_request which created this invoice" } } }, @@ -136,7 +136,7 @@ "amount_msat": {}, "description": {}, "payment_hash": {}, - "payer_note": {}, + "invreq_payer_note": {}, "local_offer_id": {}, "pay_index": { "type": "u64", @@ -174,7 +174,7 @@ "payment_hash": {}, "expires_at": {}, "pay_index": {}, - "payer_note": {}, + "invreq_payer_note": {}, "local_offer_id": {} } } diff --git a/doc/schemas/disableoffer.schema.json b/doc/schemas/disableoffer.schema.json index 8b7331bf5de4..ccb64d27388d 100644 --- a/doc/schemas/disableoffer.schema.json +++ b/doc/schemas/disableoffer.schema.json @@ -6,7 +6,6 @@ "active", "single_use", "bolt12", - "bolt12_unsigned", "used" ], "additionalProperties": false, @@ -32,10 +31,6 @@ "type": "string", "description": "The bolt12 string representing this offer" }, - "bolt12_unsigned": { - "type": "string", - "description": "The bolt12 string representing this offer, without signature" - }, "used": { "type": "boolean", "description": "Whether the offer has had an invoice paid / payment made" diff --git a/doc/schemas/listconfigs.schema.json b/doc/schemas/listconfigs.schema.json index 4377c4cea2d9..5548293491f3 100644 --- a/doc/schemas/listconfigs.schema.json +++ b/doc/schemas/listconfigs.schema.json @@ -285,7 +285,7 @@ }, "accept-htlc-tlv-types": { "type": "string", - "description": "`accept-extra-tlvs-type` fields from config or cmdline, or not present" + "description": "`accept-htlc-tlv-types` fields from config or cmdline, or not present" }, "tor-service-password": { "type": "string", @@ -294,6 +294,10 @@ "dev-allowdustreserve": { "type": "boolean", "description": "Whether we allow setting dust reserves" + }, + "announce-addr-dns": { + "type": "boolean", + "description": "Whether we put DNS entries into node_announcement" } } } diff --git a/doc/schemas/listfunds.schema.json b/doc/schemas/listfunds.schema.json index 5ebd497af772..4c02c72e2e1c 100644 --- a/doc/schemas/listfunds.schema.json +++ b/doc/schemas/listfunds.schema.json @@ -50,7 +50,8 @@ "enum": [ "unconfirmed", "confirmed", - "spent" + "spent", + "immature" ] }, "reserved": { diff --git a/doc/schemas/listinvoices.schema.json b/doc/schemas/listinvoices.schema.json index 44054e3bbc1b..b624c91fa1ae 100644 --- a/doc/schemas/listinvoices.schema.json +++ b/doc/schemas/listinvoices.schema.json @@ -66,9 +66,9 @@ "maxLength": 64, "minLength": 64 }, - "payer_note": { + "invreq_payer_note": { "type": "string", - "description": "the optional *payer_note* from invoice_request which created this invoice (**experimental-offers** only)." + "description": "the optional *invreq_payer_note* from invoice_request which created this invoice (**experimental-offers** only)." } }, "allOf": [ @@ -101,7 +101,7 @@ "bolt11": {}, "bolt12": {}, "local_offer_id": {}, - "payer_note": {}, + "invreq_payer_note": {}, "expires_at": {}, "pay_index": { "type": "u64", @@ -138,7 +138,7 @@ "bolt11": {}, "bolt12": {}, "local_offer_id": {}, - "payer_note": {}, + "invreq_payer_note": {}, "expires_at": {} } } diff --git a/doc/schemas/listoffers.schema.json b/doc/schemas/listoffers.schema.json index 9f5be747da54..a97287c38ca3 100644 --- a/doc/schemas/listoffers.schema.json +++ b/doc/schemas/listoffers.schema.json @@ -16,7 +16,6 @@ "active", "single_use", "bolt12", - "bolt12_unsigned", "used" ], "properties": { @@ -38,10 +37,6 @@ "type": "string", "description": "the bolt12 encoding of the offer" }, - "bolt12_unsigned": { - "type": "string", - "description": "the bolt12 encoding of the offer, without signature" - }, "used": { "type": "boolean", "description": "True if an associated invoice has been paid" diff --git a/doc/schemas/offer.schema.json b/doc/schemas/offer.schema.json index b57c306ed8ee..fc21a80d3c97 100644 --- a/doc/schemas/offer.schema.json +++ b/doc/schemas/offer.schema.json @@ -7,7 +7,6 @@ "active", "single_use", "bolt12", - "bolt12_unsigned", "used", "created" ], @@ -33,10 +32,6 @@ "type": "string", "description": "the bolt12 encoding of the offer" }, - "bolt12_unsigned": { - "type": "string", - "description": "the bolt12 encoding of the offer, without a signature" - }, "used": { "type": "boolean", "description": "True if an associated invoice has been paid" diff --git a/doc/schemas/offerout.schema.json b/doc/schemas/offerout.schema.json index 12f7972cc20c..e0874e094357 100644 --- a/doc/schemas/offerout.schema.json +++ b/doc/schemas/offerout.schema.json @@ -7,7 +7,6 @@ "active", "single_use", "bolt12", - "bolt12_unsigned", "used", "created" ], @@ -36,10 +35,6 @@ "type": "string", "description": "the bolt12 encoding of the offer" }, - "bolt12_unsigned": { - "type": "string", - "description": "the bolt12 encoding of the offer, without a signature" - }, "used": { "type": "boolean", "enum": [ diff --git a/doc/schemas/pay.request.json b/doc/schemas/pay.request.json index 464600b9a22d..784035cfac3c 100644 --- a/doc/schemas/pay.request.json +++ b/doc/schemas/pay.request.json @@ -30,7 +30,7 @@ "exemptfee": { "type": "msat" }, - "localofferid": { + "localinvreqid": { "type": "hex" }, "exclude": { diff --git a/doc/schemas/ping.request.json b/doc/schemas/ping.request.json index 5ca1e50d4cdc..31f2aefdbd7a 100644 --- a/doc/schemas/ping.request.json +++ b/doc/schemas/ping.request.json @@ -10,10 +10,10 @@ "type": "pubkey" }, "len": { - "type": "number" + "type": "u16" }, "pongbytes": { - "type": "number" + "type": "u16" } } } diff --git a/doc/schemas/sendonion.request.json b/doc/schemas/sendonion.request.json index 318932e2e57f..26cd99aa6ee7 100644 --- a/doc/schemas/sendonion.request.json +++ b/doc/schemas/sendonion.request.json @@ -54,7 +54,7 @@ "destination": { "type": "pubkey" }, - "localofferid": { + "localinvreqid": { "type": "hash" }, "groupid": { diff --git a/doc/schemas/sendpay.request.json b/doc/schemas/sendpay.request.json index 6550b509b4eb..b3d5ef424911 100644 --- a/doc/schemas/sendpay.request.json +++ b/doc/schemas/sendpay.request.json @@ -54,7 +54,7 @@ "partid": { "type": "u16" }, - "localofferid": { + "localinvreqid": { "type": "hex" }, "groupid": { diff --git a/external/.gitignore b/external/.gitignore index ee164eb9059d..fd9b925c84cc 100644 --- a/external/.gitignore +++ b/external/.gitignore @@ -3,6 +3,7 @@ aarch64-linux-gnu arm-linux-gnueabihf x86_64-pc-linux-gnu arm64-apple-darwin* +x86_64-apple-darwin* libbacktrace-build/ libbacktrace.a diff --git a/external/Makefile b/external/Makefile index 60b8068fe264..a41aa1c039ef 100644 --- a/external/Makefile +++ b/external/Makefile @@ -128,10 +128,10 @@ external-clean: if [ -f ${TARGET_DIR}/libwally-core-build/Makefile ]; then make -C ${TARGET_DIR}/libwally-core-build clean; fi if [ -f ${TARGET_DIR}/libwally-core-build/src/Makefile ]; then make -C ${TARGET_DIR}/libwally-core-build/src clean; fi if [ -f ${TARGET_DIR}/libbacktrace-build/Makefile ]; then make -C ${TARGET_DIR}/libbacktrace-build clean; fi - [ -f external/lowdown/Makefile.configure ] && $(MAKE) -C external/lowdown clean + if [ -f external/lowdown/Makefile.configure ]; then $(MAKE) -C external/lowdown clean; fi external-distclean: make -C external/libsodium distclean || true - [ -f external/lowdown/Makefile.configure ] && $(MAKE) -C external/lowdown distclean + if [ -f external/lowdown/Makefile.configure ]; then $(MAKE) -C external/lowdown distclean; fi $(RM) -rf ${TARGET_DIR}/libbacktrace-build ${TARGET_DIR}/libsodium-build ${TARGET_DIR}/libwally-core-build ${TARGET_DIR}/jsmn-build $(RM) -r `git status --ignored --porcelain external/libwally-core | grep '^!! ' | cut -c3-` diff --git a/gossipd/routing.c b/gossipd/routing.c index 80f7c74dba4a..11109c765228 100644 --- a/gossipd/routing.c +++ b/gossipd/routing.c @@ -1282,6 +1282,21 @@ static void update_pending(struct pending_cannouncement *pending, } } +static void delete_spam_update(struct routing_state *rstate, + struct half_chan *hc, + bool update_is_public) +{ + /* Spam updates will have a unique rgraph index */ + if (hc->rgraph.index == hc->bcast.index) + return; + gossip_store_delete(rstate->gs, &hc->rgraph, + update_is_public + ? WIRE_CHANNEL_UPDATE + : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); + hc->rgraph.index = hc->bcast.index; + hc->rgraph.timestamp = hc->bcast.timestamp; +} + bool routing_add_channel_update(struct routing_state *rstate, const u8 *update TAKES, u32 index, @@ -1394,9 +1409,11 @@ bool routing_add_channel_update(struct routing_state *rstate, return true; } - /* Make sure it's not spamming us. */ - if (!ratelimit(rstate, - &hc->tokens, hc->bcast.timestamp, timestamp)) { + /* Make sure it's not spamming us (private channel + * updates are never considered spam) */ + if (is_chan_public(chan) + && !ratelimit(rstate, + &hc->tokens, hc->bcast.timestamp, timestamp)) { status_peer_debug(peer ? &peer->id : NULL, "Spammy update for %s/%u flagged" " (last %u, now %u)", @@ -1414,34 +1431,19 @@ bool routing_add_channel_update(struct routing_state *rstate, } if (force_spam_flag) spam = true; - /* Routing graph always uses the latest message. */ - hc->rgraph.timestamp = timestamp; - if (spam) { - /* Remove the prior spam update if it exists. */ - if (hc->rgraph.index != hc->bcast.index) { - gossip_store_delete(rstate->gs, &hc->rgraph, - is_chan_public(chan) - ? WIRE_CHANNEL_UPDATE - : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); - } - } else { - /* Safe to broadcast */ - hc->bcast.timestamp = timestamp; - /* Remove prior spam update if one exists. */ - if (hc->rgraph.index != hc->bcast.index) { - /* If it's a private channel it would be a - * WIRE_GOSSIP_STORE_PRIVATE_UPDATE. */ - gossip_store_delete(rstate->gs, &hc->rgraph, - is_chan_public(chan) - ? WIRE_CHANNEL_UPDATE - : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); - } - /* Harmless if it was never added. */ + + /* Delete any prior entries (noop if they don't exist) */ + delete_spam_update(rstate, hc, is_chan_public(chan)); + if (!spam) gossip_store_delete(rstate->gs, &hc->bcast, is_chan_public(chan) ? WIRE_CHANNEL_UPDATE : WIRE_GOSSIP_STORE_PRIVATE_UPDATE); - } + + /* Update timestamp(s) */ + hc->rgraph.timestamp = timestamp; + if (!spam) + hc->bcast.timestamp = timestamp; /* BOLT #7: * - MUST consider the `timestamp` of the `channel_announcement` to be diff --git a/gossipd/test/Makefile b/gossipd/test/Makefile index 21ef60425ac4..2abf4a91a002 100644 --- a/gossipd/test/Makefile +++ b/gossipd/test/Makefile @@ -18,10 +18,8 @@ GOSSIPD_TEST_COMMON_OBJS := \ common/hmac.o \ common/node_id.o \ common/lease_rates.o \ - common/onion.o \ common/pseudorand.o \ common/setup.o \ - common/sphinx.o \ common/type_to_string.o \ common/utils.o \ common/wireaddr.o \ @@ -41,7 +39,6 @@ gossipd/test/run-onion_message: \ common/blindedpath.o \ common/blinding.o \ common/hmac.o \ - common/onion.o \ common/sphinx.o \ # JSON needed for this test diff --git a/gossipd/test/run-check_channel_announcement.c b/gossipd/test/run-check_channel_announcement.c index fc8f88884d0f..0e983618e024 100644 --- a/gossipd/test/run-check_channel_announcement.c +++ b/gossipd/test/run-check_channel_announcement.c @@ -59,9 +59,6 @@ bool cupdate_different(struct gossip_store *gs UNNEEDED, const struct half_chan *hc UNNEEDED, const u8 *cupdate UNNEEDED) { fprintf(stderr, "cupdate_different called!\n"); abort(); } -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } /* Generated stub for gossip_store_add */ u64 gossip_store_add(struct gossip_store *gs UNNEEDED, const u8 *gossip_msg UNNEEDED, u32 timestamp UNNEEDED, bool push UNNEEDED, bool spam UNNEEDED, const u8 *addendum UNNEEDED) @@ -107,9 +104,6 @@ bool nannounce_different(struct gossip_store *gs UNNEEDED, const u8 *nannounce UNNEEDED, bool *only_missing_tlv UNNEEDED) { fprintf(stderr, "nannounce_different called!\n"); abort(); } -/* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) -{ fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for new_reltimer_ */ struct oneshot *new_reltimer_(struct timers *timers UNNEEDED, const tal_t *ctx UNNEEDED, diff --git a/gossipd/test/run-check_node_announcement.c b/gossipd/test/run-check_node_announcement.c index a869d588d68e..1230077e3102 100644 --- a/gossipd/test/run-check_node_announcement.c +++ b/gossipd/test/run-check_node_announcement.c @@ -27,9 +27,6 @@ bool blinding_next_pubkey(const struct pubkey *pk UNNEEDED, /* Generated stub for daemon_conn_send */ void daemon_conn_send(struct daemon_conn *dc UNNEEDED, const u8 *msg UNNEEDED) { fprintf(stderr, "daemon_conn_send called!\n"); abort(); } -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } /* Generated stub for find_peer */ struct peer *find_peer(struct daemon *daemon UNNEEDED, const struct node_id *id UNNEEDED) { fprintf(stderr, "find_peer called!\n"); abort(); } @@ -65,9 +62,6 @@ u8 *handle_node_announcement(struct routing_state *rstate UNNEEDED, const u8 *no /* Generated stub for master_badmsg */ void master_badmsg(u32 type_expected UNNEEDED, const u8 *msg) { fprintf(stderr, "master_badmsg called!\n"); abort(); } -/* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) -{ fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for new_reltimer_ */ struct oneshot *new_reltimer_(struct timers *timers UNNEEDED, const tal_t *ctx UNNEEDED, diff --git a/gossipd/test/run-crc32_of_update.c b/gossipd/test/run-crc32_of_update.c index e6d966af2400..c47be94f2028 100644 --- a/gossipd/test/run-crc32_of_update.c +++ b/gossipd/test/run-crc32_of_update.c @@ -45,9 +45,6 @@ bigsize_t *decode_scid_query_flags(const tal_t *ctx UNNEEDED, /* Generated stub for decode_short_ids */ struct short_channel_id *decode_short_ids(const tal_t *ctx UNNEEDED, const u8 *encoded UNNEEDED) { fprintf(stderr, "decode_short_ids called!\n"); abort(); } -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } /* Generated stub for find_peer */ struct peer *find_peer(struct daemon *daemon UNNEEDED, const struct node_id *id UNNEEDED) { fprintf(stderr, "find_peer called!\n"); abort(); } @@ -91,9 +88,6 @@ u8 *handle_node_announcement(struct routing_state *rstate UNNEEDED, const u8 *no /* Generated stub for master_badmsg */ void master_badmsg(u32 type_expected UNNEEDED, const u8 *msg) { fprintf(stderr, "master_badmsg called!\n"); abort(); } -/* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) -{ fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for new_reltimer_ */ struct oneshot *new_reltimer_(struct timers *timers UNNEEDED, const tal_t *ctx UNNEEDED, diff --git a/gossipd/test/run-extended-info.c b/gossipd/test/run-extended-info.c index d69b852f7476..835d8bfae193 100644 --- a/gossipd/test/run-extended-info.c +++ b/gossipd/test/run-extended-info.c @@ -45,9 +45,6 @@ bigsize_t *decode_scid_query_flags(const tal_t *ctx UNNEEDED, /* Generated stub for decode_short_ids */ struct short_channel_id *decode_short_ids(const tal_t *ctx UNNEEDED, const u8 *encoded UNNEEDED) { fprintf(stderr, "decode_short_ids called!\n"); abort(); } -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } /* Generated stub for fromwire_gossipd_dev_set_max_scids_encode_size */ bool fromwire_gossipd_dev_set_max_scids_encode_size(const void *p UNNEEDED, u32 *max UNNEEDED) { fprintf(stderr, "fromwire_gossipd_dev_set_max_scids_encode_size called!\n"); abort(); } @@ -68,9 +65,6 @@ const u8 *gossip_store_get(const tal_t *ctx UNNEEDED, /* Generated stub for master_badmsg */ void master_badmsg(u32 type_expected UNNEEDED, const u8 *msg) { fprintf(stderr, "master_badmsg called!\n"); abort(); } -/* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) -{ fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for peer_supplied_good_gossip */ void peer_supplied_good_gossip(struct peer *peer UNNEEDED, size_t amount UNNEEDED) { fprintf(stderr, "peer_supplied_good_gossip called!\n"); abort(); } diff --git a/gossipd/test/run-next_block_range.c b/gossipd/test/run-next_block_range.c index b7f39e6fe2cc..b83fa2bf9f67 100644 --- a/gossipd/test/run-next_block_range.c +++ b/gossipd/test/run-next_block_range.c @@ -26,12 +26,6 @@ bool blinding_next_pubkey(const struct pubkey *pk UNNEEDED, const struct sha256 *h UNNEEDED, struct pubkey *next UNNEEDED) { fprintf(stderr, "blinding_next_pubkey called!\n"); abort(); } -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } -/* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) -{ fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for new_reltimer_ */ struct oneshot *new_reltimer_(struct timers *timers UNNEEDED, const tal_t *ctx UNNEEDED, diff --git a/gossipd/test/run-txout_failure.c b/gossipd/test/run-txout_failure.c index 2307c1d7deed..598f11ff9d27 100644 --- a/gossipd/test/run-txout_failure.c +++ b/gossipd/test/run-txout_failure.c @@ -30,9 +30,6 @@ bool cupdate_different(struct gossip_store *gs UNNEEDED, const struct half_chan *hc UNNEEDED, const u8 *cupdate UNNEEDED) { fprintf(stderr, "cupdate_different called!\n"); abort(); } -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } /* Generated stub for gossip_store_add */ u64 gossip_store_add(struct gossip_store *gs UNNEEDED, const u8 *gossip_msg UNNEEDED, u32 timestamp UNNEEDED, bool push UNNEEDED, bool spam UNNEEDED, const u8 *addendum UNNEEDED) @@ -74,9 +71,6 @@ bool nannounce_different(struct gossip_store *gs UNNEEDED, const u8 *nannounce UNNEEDED, bool *only_missing_tlv UNNEEDED) { fprintf(stderr, "nannounce_different called!\n"); abort(); } -/* Generated stub for new_onionreply */ -struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) -{ fprintf(stderr, "new_onionreply called!\n"); abort(); } /* Generated stub for notleak_ */ void *notleak_(void *ptr UNNEEDED, bool plus_children UNNEEDED) { fprintf(stderr, "notleak_ called!\n"); abort(); } diff --git a/hsmd/Makefile b/hsmd/Makefile index 6fd4dac59704..40f74edb5389 100644 --- a/hsmd/Makefile +++ b/hsmd/Makefile @@ -56,4 +56,10 @@ HSMD_COMMON_OBJS := \ lightningd/lightning_hsmd: $(HSMD_OBJS) $(HSMD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) +check-source: check-hsm-versions + +# common/hsm_version.h should at least *mention* this! +check-hsm-versions: hsmd/hsmd_wire.csv common/hsm_version.h + @SUM=`grep -vE '^(#| *$$)' hsmd/hsmd_wire.csv | sha256sum | cut -c1-64`; if ! grep -q "$$SUM" common/hsm_version.h; then echo "*** hsmd_wire.csv changed to $$SUM without update to common/hsm_version.h">&2; exit 1; fi + -include hsmd/test/Makefile diff --git a/hsmd/hsmd.c b/hsmd/hsmd.c index f48be782d50f..4e1acbd7dfc5 100644 --- a/hsmd/hsmd.c +++ b/hsmd/hsmd.c @@ -443,6 +443,8 @@ static struct io_plan *init_hsm(struct io_conn *conn, struct sha256 *shaseed; struct secret *hsm_encryption_key; struct bip32_key_version bip32_key_version; + u32 minversion, maxversion; + const u32 our_minversion = 2, our_maxversion = 2; /* This must be lightningd. */ assert(is_lightningd(c)); @@ -452,9 +454,19 @@ static struct io_plan *init_hsm(struct io_conn *conn, * an extension of the simple comma-separated format output by the * BOLT tools/extract-formats.py tool. */ if (!fromwire_hsmd_init(NULL, msg_in, &bip32_key_version, &chainparams, - &hsm_encryption_key, &privkey, &seed, &secrets, &shaseed)) + &hsm_encryption_key, &privkey, &seed, &secrets, &shaseed, + &minversion, &maxversion)) return bad_req(conn, c, msg_in); + /*~ Usually we don't worry about API breakage between internal daemons, + * but there are other implementations of the HSM daemon now, so we + * do at least the simplest, clearest thing. */ + if (our_minversion > maxversion || our_maxversion < minversion) + return bad_req_fmt(conn, c, msg_in, + "Version %u-%u not valid: we need %u-%u", + minversion, maxversion, + our_minversion, our_maxversion); + /*~ The memory is actually copied in towire(), so lock the `hsm_secret` * encryption key (new) memory again here. */ if (hsm_encryption_key && sodium_mlock(hsm_encryption_key, @@ -681,7 +693,8 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c) case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: case WIRE_HSMD_SIGN_INVOICE_REPLY: - case WIRE_HSMD_INIT_REPLY: + case WIRE_HSMD_INIT_REPLY_V1: + case WIRE_HSMD_INIT_REPLY_V2: case WIRE_HSMD_DERIVE_SECRET_REPLY: case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: diff --git a/hsmd/hsmd_wire.csv b/hsmd/hsmd_wire.csv index 043a8861323d..81a447d4a276 100644 --- a/hsmd/hsmd_wire.csv +++ b/hsmd/hsmd_wire.csv @@ -15,13 +15,21 @@ msgdata,hsmd_init,dev_force_privkey,?privkey, msgdata,hsmd_init,dev_force_bip32_seed,?secret, msgdata,hsmd_init,dev_force_channel_secrets,?secrets, msgdata,hsmd_init,dev_force_channel_secrets_shaseed,?sha256, +msgdata,hsmd_init,hsm_wire_min_version,u32, +msgdata,hsmd_init,hsm_wire_max_version,u32, #include -msgtype,hsmd_init_reply,111 -msgdata,hsmd_init_reply,node_id,node_id, -msgdata,hsmd_init_reply,bip32,ext_key, -msgdata,hsmd_init_reply,bolt12,point32, -msgdata,hsmd_init_reply,onion_reply_secret,secret, +# DEPRECATED after v0.12, remove in two versions! +msgtype,hsmd_init_reply_v1,111 +msgdata,hsmd_init_reply_v1,node_id,node_id, +msgdata,hsmd_init_reply_v1,bip32,ext_key, +msgdata,hsmd_init_reply_v1,bolt12,u8,32 +msgdata,hsmd_init_reply_v1,onion_reply_secret,secret, + +msgtype,hsmd_init_reply_v2,113 +msgdata,hsmd_init_reply_v2,node_id,node_id, +msgdata,hsmd_init_reply_v2,bip32,ext_key, +msgdata,hsmd_init_reply_v2,bolt12,pubkey, # Declare a new channel. msgtype,hsmd_new_channel,30 diff --git a/hsmd/libhsmd.c b/hsmd/libhsmd.c index ebad8b931dd9..dcb0ad24b3cd 100644 --- a/hsmd/libhsmd.c +++ b/hsmd/libhsmd.c @@ -28,7 +28,7 @@ struct secret *dev_force_bip32_seed; struct { struct secret hsm_secret; struct ext_key bip32; - secp256k1_keypair bolt12; + struct secret bolt12; struct secret derived_secret; } secretstuff; @@ -137,7 +137,8 @@ bool hsmd_check_client_capabilities(struct hsmd_client *client, case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: case WIRE_HSMD_SIGN_INVOICE_REPLY: - case WIRE_HSMD_INIT_REPLY: + case WIRE_HSMD_INIT_REPLY_V1: + case WIRE_HSMD_INIT_REPLY_V2: case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: case WIRE_HSMD_VALIDATE_COMMITMENT_TX_REPLY: @@ -224,29 +225,16 @@ static void node_key(struct privkey *node_privkey, struct pubkey *node_id) #endif } -/*~ This returns the secret and/or public x-only key for this node. */ -static void node_schnorrkey(secp256k1_keypair *node_keypair, - struct point32 *node_id32) +/*~ This returns the secret key for this node. */ +static void node_schnorrkey(secp256k1_keypair *node_keypair) { - secp256k1_keypair unused_kp; struct privkey node_privkey; - if (!node_keypair) - node_keypair = &unused_kp; - node_key(&node_privkey, NULL); if (secp256k1_keypair_create(secp256k1_ctx, node_keypair, node_privkey.secret.data) != 1) hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, "Failed to derive keypair"); - - if (node_id32) { - if (secp256k1_keypair_xonly_pub(secp256k1_ctx, - &node_id32->pubkey, - NULL, node_keypair) != 1) - hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Failed to derive xonly pub"); - } } /*~ This secret is the basis for all per-channel secrets: the per-channel seeds @@ -635,29 +623,32 @@ static u8 *handle_sign_bolt12(struct hsmd_client *c, const u8 *msg_in) sighash_from_merkle(messagename, fieldname, &merkle, &sha); if (!publictweak) { - node_schnorrkey(&kp, NULL); + node_schnorrkey(&kp); } else { /* If we're tweaking key, we use bolt12 key */ - struct point32 bolt12; + struct privkey tweakedkey; + struct pubkey bolt12; struct sha256 tweak; - if (secp256k1_keypair_xonly_pub(secp256k1_ctx, - &bolt12.pubkey, NULL, - &secretstuff.bolt12) != 1) - hsmd_status_failed( - STATUS_FAIL_INTERNAL_ERROR, - "Could not derive bolt12 public key."); + if (secp256k1_ec_pubkey_create(secp256k1_ctx, &bolt12.pubkey, + secretstuff.bolt12.data) != 1) + hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could derive bolt12 public key."); + payer_key_tweak(&bolt12, publictweak, tal_bytelen(publictweak), &tweak); - kp = secretstuff.bolt12; + tweakedkey.secret = secretstuff.bolt12; + if (secp256k1_ec_seckey_tweak_add(secp256k1_ctx, + tweakedkey.secret.data, + tweak.u.u8) != 1) + hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Could tweak bolt12 key."); - if (secp256k1_keypair_xonly_tweak_add(secp256k1_ctx, - &kp, - tweak.u.u8) != 1) { - return hsmd_status_bad_request_fmt( - c, msg_in, "Failed to get tweak key"); - } + if (secp256k1_keypair_create(secp256k1_ctx, &kp, + tweakedkey.secret.data) != 1) + hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Failed to derive bolt12 keypair"); } if (!secp256k1_schnorrsig_sign32(secp256k1_ctx, sig.u8, @@ -1668,7 +1659,8 @@ u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client, case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: case WIRE_HSMD_SIGN_INVOICE_REPLY: - case WIRE_HSMD_INIT_REPLY: + case WIRE_HSMD_INIT_REPLY_V1: + case WIRE_HSMD_INIT_REPLY_V2: case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: case WIRE_HSMD_VALIDATE_COMMITMENT_TX_REPLY: @@ -1691,12 +1683,10 @@ u8 *hsmd_init(struct secret hsm_secret, struct bip32_key_version bip32_key_version) { u8 bip32_seed[BIP32_ENTROPY_LEN_256]; - struct pubkey key; - struct point32 bolt12; + struct pubkey key, bolt12; u32 salt = 0; struct ext_key master_extkey, child_extkey; struct node_id node_id; - struct secret onion_reply_secret; /*~ Don't swap this. */ sodium_mlock(secretstuff.hsm_secret.data, @@ -1797,10 +1787,8 @@ u8 *hsmd_init(struct secret hsm_secret, /* libwally says: The private key with prefix byte 0; remove it * for libsecp256k1. */ - if (secp256k1_keypair_create(secp256k1_ctx, &secretstuff.bolt12, - child_extkey.priv_key+1) != 1) - hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, - "Can't derive bolt12 keypair"); + memcpy(&secretstuff.bolt12, child_extkey.priv_key+1, + sizeof(secretstuff.bolt12)); /* Now we can consider ourselves initialized, and we won't get * upset if we get a non-init message. */ @@ -1811,19 +1799,11 @@ u8 *hsmd_init(struct secret hsm_secret, node_id_from_pubkey(&node_id, &key); /* We also give it the base key for bolt12 payerids */ - if (secp256k1_keypair_xonly_pub(secp256k1_ctx, &bolt12.pubkey, NULL, - &secretstuff.bolt12) != 1) + if (secp256k1_ec_pubkey_create(secp256k1_ctx, &bolt12.pubkey, + secretstuff.bolt12.data) != 1) hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, "Could derive bolt12 public key."); - /*~ We derive a secret for onion_message's self_id so we can tell - * if it used a path we created (i.e. do not leak our public id!) */ - hkdf_sha256(&onion_reply_secret, sizeof(onion_reply_secret), - NULL, 0, - &secretstuff.hsm_secret, - sizeof(secretstuff.hsm_secret), - "onion reply secret", strlen("onion reply secret")); - /* We derive the derived_secret key for generating pseudorandom keys * by taking input string from the makesecret RPC */ hkdf_sha256(&secretstuff.derived_secret, sizeof(struct secret), NULL, 0, @@ -1833,7 +1813,7 @@ u8 *hsmd_init(struct secret hsm_secret, /*~ Note: marshalling a bip32 tree only marshals the public side, * not the secrets! So we're not actually handing them out here! */ - return take(towire_hsmd_init_reply( + return take(towire_hsmd_init_reply_v2( NULL, &node_id, &secretstuff.bip32, - &bolt12, &onion_reply_secret)); + &bolt12)); } diff --git a/lightningd/Makefile b/lightningd/Makefile index afea16752fda..0e2418be1822 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -99,8 +99,10 @@ LIGHTNINGD_COMMON_OBJS := \ common/htlc_state.o \ common/htlc_trim.o \ common/htlc_wire.o \ + common/invoice_path_id.o \ common/key_derive.o \ common/keyset.o \ + common/json_filter.o \ common/json_param.o \ common/json_parse.o \ common/json_parse_simple.o \ @@ -109,7 +111,8 @@ LIGHTNINGD_COMMON_OBJS := \ common/memleak.o \ common/msg_queue.o \ common/node_id.o \ - common/onion.o \ + common/onion_decode.o \ + common/onion_encode.o \ common/onionreply.o \ common/penalty_base.o \ common/per_peer_state.o \ @@ -142,6 +145,6 @@ lightningd/plugin.o: plugins/list_of_builtin_plugins_gen.h lightningd/channel_state_names_gen.h: lightningd/channel_state.h ccan/ccan/cdump/tools/cdump-enumstr ccan/ccan/cdump/tools/cdump-enumstr lightningd/channel_state.h > $@ -lightningd/lightningd: $(LIGHTNINGD_OBJS) $(WALLET_OBJS) $(LIGHTNINGD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(WIRE_ONION_OBJS) $(LIGHTNINGD_CONTROL_OBJS) $(HSMD_CLIENT_OBJS) $(DB_OBJS) +lightningd/lightningd: $(LIGHTNINGD_OBJS) $(WALLET_OBJS) $(LIGHTNINGD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) $(WIRE_ONION_OBJS) $(LIGHTNINGD_CONTROL_OBJS) $(HSMD_CLIENT_OBJS) $(DB_OBJS) include lightningd/test/Makefile diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index 367abf05f692..1baec3c587a5 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -49,7 +49,8 @@ static void config_plugin(struct plugin *plugin) struct jsonrpc_request *req; void *ret; - req = jsonrpc_request_start(plugin, "init", NULL, plugin->log, + req = jsonrpc_request_start(plugin, "init", NULL, + plugin->non_numeric_ids, plugin->log, NULL, plugin_config_cb, plugin); plugin_populate_init_request(plugin, req); jsonrpc_request_end(req); @@ -237,7 +238,7 @@ void bitcoind_estimate_fees_(struct bitcoind *bitcoind, call->cb = cb; call->arg = arg; - req = jsonrpc_request_start(bitcoind, "estimatefees", NULL, + req = jsonrpc_request_start(bitcoind, "estimatefees", NULL, true, bitcoind->log, NULL, estimatefees_callback, call); jsonrpc_request_end(req); @@ -314,7 +315,8 @@ void bitcoind_sendrawtx_(struct bitcoind *bitcoind, call->cb_arg = cb_arg; log_debug(bitcoind->log, "sendrawtransaction: %s", hextx); - req = jsonrpc_request_start(bitcoind, "sendrawtransaction", id_prefix, + req = jsonrpc_request_start(bitcoind, "sendrawtransaction", + id_prefix, true, bitcoind->log, NULL, sendrawtx_callback, call); @@ -401,7 +403,7 @@ void bitcoind_getrawblockbyheight_(struct bitcoind *bitcoind, call->cb = cb; call->cb_arg = cb_arg; - req = jsonrpc_request_start(bitcoind, "getrawblockbyheight", NULL, + req = jsonrpc_request_start(bitcoind, "getrawblockbyheight", NULL, true, bitcoind->log, NULL, getrawblockbyheight_callback, call); @@ -482,7 +484,7 @@ void bitcoind_getchaininfo_(struct bitcoind *bitcoind, call->cb_arg = cb_arg; call->first_call = first_call; - req = jsonrpc_request_start(bitcoind, "getchaininfo", NULL, + req = jsonrpc_request_start(bitcoind, "getchaininfo", NULL, true, bitcoind->log, NULL, getchaininfo_callback, call); jsonrpc_request_end(req); @@ -555,7 +557,8 @@ void bitcoind_getutxout_(struct bitcoind *bitcoind, call->cb = cb; call->cb_arg = cb_arg; - req = jsonrpc_request_start(bitcoind, "getutxout", NULL, bitcoind->log, + req = jsonrpc_request_start(bitcoind, "getutxout", NULL, true, + bitcoind->log, NULL, getutxout_callback, call); json_add_txid(req->stream, "txid", &outpoint->txid); json_add_num(req->stream, "vout", outpoint->n); diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 103752c46c8b..b3f0fc75c1a4 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -53,13 +53,7 @@ static void next_topology_timer(struct chain_topology *topo) static bool we_broadcast(const struct chain_topology *topo, const struct bitcoin_txid *txid) { - const struct outgoing_tx *otx; - - list_for_each(&topo->outgoing_txs, otx, list) { - if (bitcoin_txid_eq(&otx->txid, txid)) - return true; - } - return false; + return outgoing_tx_map_get(&topo->outgoing_txs, txid) != NULL; } static void filter_block_txs(struct chain_topology *topo, struct block *b) @@ -72,6 +66,7 @@ static void filter_block_txs(struct chain_topology *topo, struct block *b) const struct bitcoin_tx *tx = b->full_txs[i]; struct bitcoin_txid txid; size_t j; + bool is_coinbase = i == 0; /* Tell them if it spends a txo we care about. */ for (j = 0; j < tx->wtx->num_inputs; j++) { @@ -92,7 +87,7 @@ static void filter_block_txs(struct chain_topology *topo, struct block *b) txid = b->txids[i]; if (txfilter_match(topo->bitcoind->ld->owned_txfilter, tx)) { wallet_extract_owned_outputs(topo->bitcoind->ld->wallet, - tx->wtx, &b->height, &owned); + tx->wtx, is_coinbase, &b->height, &owned); wallet_transaction_add(topo->ld->wallet, tx->wtx, b->height, i); } @@ -159,13 +154,16 @@ static void rebroadcast_txs(struct chain_topology *topo) /* Copy txs now (peers may go away, and they own txs). */ struct txs_to_broadcast *txs; struct outgoing_tx *otx; + struct outgoing_tx_map_iter it; txs = tal(topo, struct txs_to_broadcast); txs->cmd_id = tal_arr(txs, const char *, 0); /* Put any txs we want to broadcast in ->txs. */ txs->txs = tal_arr(txs, const char *, 0); - list_for_each(&topo->outgoing_txs, otx, list) { + + for (otx = outgoing_tx_map_first(&topo->outgoing_txs, &it); otx; + otx = outgoing_tx_map_next(&topo->outgoing_txs, &it)) { if (wallet_transaction_height(topo->ld->wallet, &otx->txid)) continue; @@ -179,9 +177,9 @@ static void rebroadcast_txs(struct chain_topology *topo) broadcast_remainder(topo->bitcoind, true, "", txs); } -static void destroy_outgoing_tx(struct outgoing_tx *otx) +static void destroy_outgoing_tx(struct outgoing_tx *otx, struct chain_topology *topo) { - list_del(&otx->list); + outgoing_tx_map_del(&topo->outgoing_txs, otx); } static void clear_otx_channel(struct channel *channel, struct outgoing_tx *otx) @@ -207,11 +205,18 @@ static void broadcast_done(struct bitcoind *bitcoind, if (otx->failed_or_success) { otx->failed_or_success(otx->channel, success, msg); tal_free(otx); + } else if (we_broadcast(bitcoind->ld->topology, &otx->txid)) { + log_debug( + bitcoind->ld->topology->log, + "Not adding %s to list of outgoing transactions, already " + "present", + type_to_string(tmpctx, struct bitcoin_txid, &otx->txid)); + tal_free(otx); } else { /* For continual rebroadcasting, until channel freed. */ tal_steal(otx->channel, otx); - list_add_tail(&bitcoind->ld->topology->outgoing_txs, &otx->list); - tal_add_destructor(otx, destroy_outgoing_tx); + outgoing_tx_map_add(&bitcoind->ld->topology->outgoing_txs, notleak(otx)); + tal_add_destructor2(otx, destroy_outgoing_tx, bitcoind->ld->topology); } } @@ -933,13 +938,17 @@ u32 feerate_max(struct lightningd *ld, bool *unknown) static void destroy_chain_topology(struct chain_topology *topo) { struct outgoing_tx *otx; - - while ((otx = list_pop(&topo->outgoing_txs, struct outgoing_tx, list))) + struct outgoing_tx_map_iter it; + for (otx = outgoing_tx_map_first(&topo->outgoing_txs, &it); otx; + otx = outgoing_tx_map_next(&topo->outgoing_txs, &it)) { + tal_del_destructor2(otx, destroy_outgoing_tx, topo); tal_free(otx); + } /* htable uses malloc, so it would leak here */ txwatch_hash_clear(&topo->txwatches); txowatch_hash_clear(&topo->txowatches); + outgoing_tx_map_clear(&topo->outgoing_txs); block_map_clear(&topo->block_map); } @@ -949,7 +958,7 @@ struct chain_topology *new_topology(struct lightningd *ld, struct log *log) topo->ld = ld; block_map_init(&topo->block_map); - list_head_init(&topo->outgoing_txs); + outgoing_tx_map_init(&topo->outgoing_txs); txwatch_hash_init(&topo->txwatches); txowatch_hash_init(&topo->txowatches); topo->log = log; @@ -959,6 +968,7 @@ struct chain_topology *new_topology(struct lightningd *ld, struct log *log) topo->feerate_uninitialized = true; topo->root = NULL; topo->sync_waiters = tal(topo, struct list_head); + topo->extend_timer = NULL; topo->stopping = false; list_head_init(topo->sync_waiters); diff --git a/lightningd/chaintopology.h b/lightningd/chaintopology.h index 30566d6ffcfb..08076e8f4779 100644 --- a/lightningd/chaintopology.h +++ b/lightningd/chaintopology.h @@ -18,7 +18,6 @@ struct txwatch; /* Off topology->outgoing_txs */ struct outgoing_tx { - struct list_node list; struct channel *channel; const char *hextx; struct bitcoin_txid txid; @@ -66,6 +65,26 @@ static inline bool block_eq(const struct block *b, const struct bitcoin_blkid *k } HTABLE_DEFINE_TYPE(struct block, keyof_block_map, hash_sha, block_eq, block_map); +/* Hash blocks by sha */ +static inline const struct bitcoin_txid *keyof_outgoing_tx_map(const struct outgoing_tx *t) +{ + return &t->txid; +} + +static inline size_t outgoing_tx_hash_sha(const struct bitcoin_txid *key) +{ + size_t ret; + memcpy(&ret, key, sizeof(ret)); + return ret; +} + +static inline bool outgoing_tx_eq(const struct outgoing_tx *b, const struct bitcoin_txid *key) +{ + return bitcoin_txid_eq(&b->txid, key); +} +HTABLE_DEFINE_TYPE(struct outgoing_tx, keyof_outgoing_tx_map, + outgoing_tx_hash_sha, outgoing_tx_eq, outgoing_tx_map); + struct chain_topology { struct lightningd *ld; struct block *root; @@ -97,7 +116,7 @@ struct chain_topology { struct oneshot *extend_timer, *updatefee_timer; /* Bitcoin transactions we're broadcasting */ - struct list_head outgoing_txs; + struct outgoing_tx_map outgoing_txs; /* Transactions/txos we are watching. */ struct txwatch_hash txwatches; diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index ffaa37aada5f..88adc5c5d981 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -816,7 +816,7 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg) * - if the `scriptpubkey` is not in one of the above forms: * - SHOULD send a `warning`. */ - if (!valid_shutdown_scriptpubkey(scriptpubkey, anysegwit, anchors)) { + if (!valid_shutdown_scriptpubkey(scriptpubkey, anysegwit, !anchors)) { u8 *warning = towire_warningfmt(NULL, &channel->cid, "Bad shutdown scriptpubkey %s", diff --git a/lightningd/closing_control.c b/lightningd/closing_control.c index 8093842d9a92..b7ce9779baf8 100644 --- a/lightningd/closing_control.c +++ b/lightningd/closing_control.c @@ -724,11 +724,11 @@ static struct command_result *json_close(struct command *cmd, channel->peer->their_features, OPT_SHUTDOWN_ANYSEGWIT); if (!valid_shutdown_scriptpubkey(channel->shutdown_scriptpubkey[LOCAL], - anysegwit, !deprecated_apis)) { + anysegwit, false)) { /* Explicit check for future segwits. */ if (!anysegwit && valid_shutdown_scriptpubkey(channel->shutdown_scriptpubkey - [LOCAL], true, !deprecated_apis)) { + [LOCAL], true, false)) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Peer does not allow v1+ shutdown addresses"); } diff --git a/lightningd/coin_mvts.c b/lightningd/coin_mvts.c index b1cf1418beca..cdba2af1de2f 100644 --- a/lightningd/coin_mvts.c +++ b/lightningd/coin_mvts.c @@ -1,6 +1,5 @@ #include "config.h" #include -#include #include #include #include diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index 61a6bcca856f..b85e3bd95856 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -76,7 +76,8 @@ static void try_connect(const tal_t *ctx, struct lightningd *ld, const struct node_id *id, u32 seconds_delay, - const struct wireaddr_internal *addrhint); + const struct wireaddr_internal *addrhint, + bool dns_fallback); struct id_and_addr { struct node_id id; @@ -226,7 +227,7 @@ static struct command_result *json_connect(struct command *cmd, &peer->addr); } - try_connect(cmd, cmd->ld, &id_addr.id, 0, addr); + try_connect(cmd, cmd->ld, &id_addr.id, 0, addr, true); /* Leave this here for peer_connected, connect_failed or peer_disconnect_done. */ new_connect(cmd->ld, &id_addr.id, cmd); @@ -248,6 +249,7 @@ struct delayed_reconnect { struct lightningd *ld; struct node_id id; struct wireaddr_internal *addrhint; + bool dns_fallback; }; static void gossipd_got_addrs(struct subd *subd, @@ -265,7 +267,8 @@ static void gossipd_got_addrs(struct subd *subd, connectmsg = towire_connectd_connect_to_peer(NULL, &d->id, addrs, - d->addrhint); + d->addrhint, + d->dns_fallback); subd_send_msg(d->ld->connectd, take(connectmsg)); tal_free(d); } @@ -282,7 +285,8 @@ static void try_connect(const tal_t *ctx, struct lightningd *ld, const struct node_id *id, u32 seconds_delay, - const struct wireaddr_internal *addrhint) + const struct wireaddr_internal *addrhint, + bool dns_fallback) { struct delayed_reconnect *d; struct peer *peer; @@ -291,6 +295,7 @@ static void try_connect(const tal_t *ctx, d->ld = ld; d->id = *id; d->addrhint = tal_dup_or_null(d, struct wireaddr_internal, addrhint); + d->dns_fallback = dns_fallback; if (!seconds_delay) { do_connect(d); @@ -347,11 +352,14 @@ void try_reconnect(const tal_t *ctx, } else peer->reconnect_delay = INITIAL_WAIT_SECONDS; + /* We only do DNS fallback lookups for manual connections, to + * avoid stressing DNS servers for private nodes (sorry!) */ try_connect(ctx, peer->ld, &peer->id, peer->reconnect_delay, - addrhint); + addrhint, + false); } /* We were trying to connect, but they disconnected. */ diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 68b30a49b92e..c65b2bd29de8 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1364,7 +1364,7 @@ static void handle_peer_wants_to_close(struct subd *dualopend, * - if the `scriptpubkey` is not in one of the above forms: * - SHOULD send a `warning` */ - if (!valid_shutdown_scriptpubkey(scriptpubkey, anysegwit, anchors)) { + if (!valid_shutdown_scriptpubkey(scriptpubkey, anysegwit, !anchors)) { u8 *warning = towire_warningfmt(NULL, &channel->cid, "Bad shutdown scriptpubkey %s", @@ -1461,7 +1461,8 @@ static void handle_tx_broadcast(struct channel_send *cs) /* This might have spent UTXOs from our wallet */ num_utxos = wallet_extract_owned_outputs(ld->wallet, - wtx, NULL, + /* FIXME: what txindex? */ + wtx, false, NULL, &unused); if (num_utxos) wallet_transaction_add(ld->wallet, wtx, 0, 0); diff --git a/lightningd/hsm_control.c b/lightningd/hsm_control.c index f360d813a649..0cd4be1cc70d 100644 --- a/lightningd/hsm_control.c +++ b/lightningd/hsm_control.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -109,20 +111,47 @@ struct ext_key *hsm_init(struct lightningd *ld) IFDEV(ld->dev_force_privkey, NULL), IFDEV(ld->dev_force_bip32_seed, NULL), IFDEV(ld->dev_force_channel_secrets, NULL), - IFDEV(ld->dev_force_channel_secrets_shaseed, NULL)))) + IFDEV(ld->dev_force_channel_secrets_shaseed, NULL), + HSM_MIN_VERSION, + HSM_MAX_VERSION))) err(EXITCODE_HSM_GENERIC_ERROR, "Writing init msg to hsm"); bip32_base = tal(ld, struct ext_key); msg = wire_sync_read(tmpctx, ld->hsm_fd); - if (!fromwire_hsmd_init_reply(msg, - &ld->id, bip32_base, - &ld->bolt12_base, - &ld->onion_reply_secret)) { - if (ld->config.keypass) - errx(EXITCODE_HSM_BAD_PASSWORD, "Wrong password for encrypted hsm_secret."); - errx(EXITCODE_HSM_GENERIC_ERROR, "HSM did not give init reply"); + if (!fromwire_hsmd_init_reply_v2(msg, + &ld->id, bip32_base, + &ld->bolt12_base)) { + /* v1 had x-only pubkey */ + u8 pubkey32[33]; + /* And gave us a secret to use for onion_reply paths */ + struct secret onion_reply_secret; + + pubkey32[0] = SECP256K1_TAG_PUBKEY_EVEN; + if (!fromwire_hsmd_init_reply_v1(msg, + &ld->id, bip32_base, + pubkey32 + 1, + &onion_reply_secret)) { + if (ld->config.keypass) + errx(EXITCODE_HSM_BAD_PASSWORD, "Wrong password for encrypted hsm_secret."); + errx(EXITCODE_HSM_GENERIC_ERROR, "HSM did not give init reply"); + } + if (!pubkey_from_der(pubkey32, sizeof(pubkey32), + &ld->bolt12_base)) + errx(EXITCODE_HSM_GENERIC_ERROR, + "HSM gave invalid v1 bolt12_base"); } + /* This is equivalent to makesecret("bolt12-invoice-base") */ + msg = towire_hsmd_derive_secret(NULL, tal_dup_arr(tmpctx, u8, + (const u8 *)INVOICE_PATH_BASE_STRING, + strlen(INVOICE_PATH_BASE_STRING), 0)); + if (!wire_sync_write(ld->hsm_fd, take(msg))) + err(EXITCODE_HSM_GENERIC_ERROR, "Writing derive_secret msg to hsm"); + + msg = wire_sync_read(tmpctx, ld->hsm_fd); + if (!fromwire_hsmd_derive_secret_reply(msg, &ld->invoicesecret_base)) + err(EXITCODE_HSM_GENERIC_ERROR, "Bad derive_secret_reply"); + return bip32_base; } diff --git a/lightningd/htlc_end.c b/lightningd/htlc_end.c index d9a80ac1b50b..607da655c4ee 100644 --- a/lightningd/htlc_end.c +++ b/lightningd/htlc_end.c @@ -130,7 +130,6 @@ struct htlc_in *new_htlc_in(const tal_t *ctx, const struct sha256 *payment_hash, const struct secret *shared_secret TAKES, const struct pubkey *blinding TAKES, - const struct secret *blinding_ss, const u8 *onion_routing_packet, bool fail_immediate) { @@ -145,10 +144,9 @@ struct htlc_in *new_htlc_in(const tal_t *ctx, hin->status = NULL; hin->fail_immediate = fail_immediate; hin->shared_secret = tal_dup_or_null(hin, struct secret, shared_secret); - if (blinding) { + if (blinding) hin->blinding = tal_dup(hin, struct pubkey, blinding); - hin->blinding_ss = *blinding_ss; - } else + else hin->blinding = NULL; memcpy(hin->onion_routing_packet, onion_routing_packet, sizeof(hin->onion_routing_packet)); diff --git a/lightningd/htlc_end.h b/lightningd/htlc_end.h index da5f2ce18f74..b98a96f97dd4 100644 --- a/lightningd/htlc_end.h +++ b/lightningd/htlc_end.h @@ -48,8 +48,6 @@ struct htlc_in { /* If it was blinded. */ struct pubkey *blinding; - /* Only set if blinding != NULL */ - struct secret blinding_ss; /* true if we supplied the preimage */ bool *we_filled; /* true if we immediately fail the htlc (too much dust) */ @@ -159,7 +157,6 @@ struct htlc_in *new_htlc_in(const tal_t *ctx, const struct sha256 *payment_hash, const struct secret *shared_secret TAKES, const struct pubkey *blinding TAKES, - const struct secret *blinding_ss, const u8 *onion_routing_packet, bool fail_immediate); diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 459a807044cd..8162ae4d4b0f 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -5,12 +5,13 @@ #include #include #include +#include #include #include #include +#include #include #include -#include #include #include #include @@ -69,10 +70,10 @@ static void json_add_invoice_fields(struct json_stream *response, tinv = invoice_decode(tmpctx, inv->invstring, strlen(inv->invstring), NULL, NULL, &fail); - if (tinv && tinv->payer_note) - json_add_stringn(response, "payer_note", - tinv->payer_note, - tal_bytelen(tinv->payer_note)); + if (tinv && tinv->invreq_payer_note) + json_add_stringn(response, "invreq_payer_note", + tinv->invreq_payer_note, + tal_bytelen(tinv->invreq_payer_note)); } } @@ -141,27 +142,18 @@ static void invoice_secret(const struct preimage *payment_preimage, memcpy(payment_secret->data, secret.u.u8, sizeof(secret.u.u8)); } -/* FIXME: This is a hack. The real secret should be a signature of some - * onion key, using the payer_id */ +/* FIXME: The spec should require a *real* secret: a signature of the + * payment_hash using the payer_id key. This just means they've + * *seen* the invoice! */ static void invoice_secret_bolt12(struct lightningd *ld, - const char *invstring, + const struct sha256 *payment_hash, struct secret *payment_secret) { - char *fail; - struct tlv_invoice *inv; - struct sha256 merkle; - - inv = invoice_decode(tmpctx, invstring, strlen(invstring), - NULL, NULL, &fail); - if (!inv) { - log_broken(ld->log, "Unable to decode our invoice %s", - invstring); - return; - } - - merkle_tlv(inv->fields, &merkle); - BUILD_ASSERT(sizeof(*payment_secret) == sizeof(merkle)); - memcpy(payment_secret, &merkle, sizeof(merkle)); + const void *path_id = invoice_path_id(tmpctx, + &ld->invoicesecret_base, + payment_hash); + assert(tal_bytelen(path_id) == sizeof(*payment_secret)); + memcpy(payment_secret, path_id, sizeof(*payment_secret)); } struct invoice_payment_hook_payload { @@ -415,7 +407,7 @@ invoice_check_payment(const tal_t *ctx, struct secret expected; if (details->invstring && strstarts(details->invstring, "lni1")) - invoice_secret_bolt12(ld, details->invstring, &expected); + invoice_secret_bolt12(ld, payment_hash, &expected); else invoice_secret(&details->r, &expected); if (!secret_eq_consttime(payment_secret, &expected)) { @@ -1026,56 +1018,6 @@ static struct command_result *param_positive_msat_or_any(struct command *cmd, "should be positive msat or 'any'"); } -/* Parse time with optional suffix, return seconds */ -static struct command_result *param_time(struct command *cmd, const char *name, - const char *buffer, - const jsmntok_t *tok, - uint64_t **secs) -{ - /* We need to manipulate this, so make copy */ - jsmntok_t timetok = *tok; - u64 mul; - char s; - struct { - char suffix; - u64 mul; - } suffixes[] = { - { 's', 1 }, - { 'm', 60 }, - { 'h', 60*60 }, - { 'd', 24*60*60 }, - { 'w', 7*24*60*60 } }; - - if (!deprecated_apis) - return param_u64(cmd, name, buffer, tok, secs); - - mul = 1; - if (timetok.end == timetok.start) - s = '\0'; - else - s = buffer[timetok.end - 1]; - for (size_t i = 0; i < ARRAY_SIZE(suffixes); i++) { - if (s == suffixes[i].suffix) { - mul = suffixes[i].mul; - timetok.end--; - break; - } - } - - *secs = tal(cmd, uint64_t); - if (json_to_u64(buffer, &timetok, *secs)) { - if (mul_overflows_u64(**secs, mul)) { - return command_fail_badparam(cmd, name, buffer, tok, - "value too large"); - } - **secs *= mul; - return NULL; - } - - return command_fail_badparam(cmd, name, buffer, tok, - "should be a number"); -} - static struct command_result *param_chanhints(struct command *cmd, const char *name, const char *buffer, @@ -1161,7 +1103,7 @@ static struct command_result *json_invoice(struct command *cmd, p_req("amount_msat|msatoshi", param_positive_msat_or_any, &msatoshi_val), p_req("label", param_label, &info->label), p_req("description", param_escaped_string, &desc_val), - p_opt_def("expiry", param_time, &expiry, 3600*24*7), + p_opt_def("expiry", param_u64, &expiry, 3600*24*7), p_opt("fallbacks", param_array, &fallbacks), p_opt("preimage", param_preimage, &preimage), p_opt("exposeprivatechannels", param_chanhints, @@ -1249,22 +1191,21 @@ static struct command_result *json_invoice(struct command *cmd, if (fallback_scripts) info->b11->fallbacks = tal_steal(info->b11, fallback_scripts); + /* We can't generate routehints without listincoming. */ + plugin = find_plugin_for_command(cmd->ld, "listincoming"); + if (!plugin) { + return invoice_complete(info, true, + false, false, false, false, false); + } + req = jsonrpc_request_start(info, "listincoming", - cmd->id, + cmd->id, plugin->non_numeric_ids, command_log(cmd), NULL, listincoming_done, info); jsonrpc_request_end(req); - - plugin = find_plugin_for_command(cmd->ld, "listincoming"); - if (plugin) { - plugin_request_send(plugin, req); - return command_still_pending(cmd); - } - - /* We can't generate routehints without listincoming. */ - return invoice_complete(info, true, - false, false, false, false, false); + plugin_request_send(plugin, req); + return command_still_pending(cmd); } static const struct json_command invoice_command = { @@ -1360,11 +1301,11 @@ static struct command_result *json_listinvoices(struct command *cmd, strlen(invstring), cmd->ld->our_features, NULL, &fail); - if (!b12 || !b12->payment_hash) { + if (!b12 || !b12->invoice_payment_hash) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid invstring"); } - payment_hash = b12->payment_hash; + payment_hash = b12->invoice_payment_hash; } } @@ -1640,6 +1581,63 @@ static struct command_result *fail_exists(struct command *cmd, return command_failed(cmd, data); } +/* This is only if we're a public node; otherwise, the offers plugin + * will have populated a real blinded path */ +static void add_stub_blindedpath(const tal_t *ctx, + struct lightningd *ld, + struct tlv_invoice *inv) +{ + struct blinded_path *path; + struct privkey blinding; + struct tlv_encrypted_data_tlv *tlv; + + path = tal(NULL, struct blinded_path); + if (!pubkey_from_node_id(&path->first_node_id, &ld->id)) + abort(); + randombytes_buf(&blinding, sizeof(blinding)); + if (!pubkey_from_privkey(&blinding, &path->blinding)) + abort(); + path->path = tal_arr(path, struct onionmsg_hop *, 1); + path->path[0] = tal(path->path, struct onionmsg_hop); + + /* A message in a bottle to ourselves: match it with + * the invoice: we assume the payment_hash is unique! */ + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->path_id = invoice_path_id(inv, + &ld->invoicesecret_base, + inv->invoice_payment_hash); + + path->path[0]->encrypted_recipient_data + = encrypt_tlv_encrypted_data(path->path[0], + &blinding, + &path->first_node_id, + tlv, + NULL, + &path->path[0]->blinded_node_id); + + inv->invoice_paths = tal_arr(inv, struct blinded_path *, 1); + inv->invoice_paths[0] = tal_steal(inv->invoice_paths, path); + + /* BOLT-offers #12: + * - MUST include `invoice_paths` containing one or more paths to the node. + * - MUST specify `invoice_paths` in order of most-preferred to least-preferred if it has a preference. + * - MUST include `invoice_blindedpay` with exactly one `blinded_payinfo` for each `blinded_path` in `paths`, in order. + */ + inv->invoice_blindedpay = tal_arr(inv, struct blinded_payinfo *, 1); + inv->invoice_blindedpay[0] = tal(inv->invoice_blindedpay, + struct blinded_payinfo); + inv->invoice_blindedpay[0]->fee_base_msat = 0; + inv->invoice_blindedpay[0]->fee_proportional_millionths = 0; + inv->invoice_blindedpay[0]->cltv_expiry_delta = ld->config.cltv_final; + inv->invoice_blindedpay[0]->htlc_minimum_msat = AMOUNT_MSAT(0); + inv->invoice_blindedpay[0]->htlc_maximum_msat = AMOUNT_MSAT(21000000 * MSAT_PER_BTC); + inv->invoice_blindedpay[0]->features = NULL; + + /* Recalc ->fields */ + tal_free(inv->fields); + inv->fields = tlv_make_fields(inv, tlv_invoice); +} + static struct command_result *json_createinvoice(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -1701,76 +1699,98 @@ static struct command_result *json_createinvoice(struct command *cmd, notify_invoice_creation(cmd->ld, b11->msat, *preimage, label); } else { struct tlv_invoice *inv; - struct sha256 *local_offer_id; + struct sha256 offer_id, *local_offer_id; + char *b12enc; + struct amount_msat msat; + const char *desc; + u32 expiry; + enum offer_status status; inv = invoice_decode_nosig(cmd, invstring, strlen(invstring), cmd->ld->our_features, chainparams, &fail); - if (inv) { - char *b12enc; - struct amount_msat msat; - const char *desc; - u32 expiry; - enum offer_status status; - - if (inv->signature) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "invoice already signed"); - hsm_sign_b12_invoice(cmd->ld, inv); - b12enc = invoice_encode(cmd, inv); + if (!inv) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Unparsable invoice '%s': %s", + invstring, fail); - if (inv->offer_id - && wallet_offer_find(tmpctx, cmd->ld->wallet, - inv->offer_id, NULL, &status)) { + /* BOLT-offers #12: + * A writer of an invoice: + *... + * - MUST include `invoice_paths` containing one or more paths + * to the node. + */ + /* If they don't create a blinded path, add a simple one so we + * can recognize payments (bolt12 doesn't use + * payment_secret) */ + if (!inv->invoice_paths) + add_stub_blindedpath(cmd, cmd->ld, inv); + + if (inv->signature) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "invoice already signed"); + hsm_sign_b12_invoice(cmd->ld, inv); + b12enc = invoice_encode(cmd, inv); + + if (inv->offer_node_id) { + invoice_offer_id(inv, &offer_id); + if (wallet_offer_find(tmpctx, cmd->ld->wallet, + &offer_id, NULL, &status)) { if (!offer_status_active(status)) - return command_fail(cmd, INVOICE_OFFER_INACTIVE, + return command_fail(cmd, + INVOICE_OFFER_INACTIVE, "offer not active"); - local_offer_id = inv->offer_id; + local_offer_id = &offer_id; } else local_offer_id = NULL; + } else + local_offer_id = NULL; + + /* BOLT-offers #12: + * A writer of an invoice: + *... + * - MUST set `invoice_amount` to the minimum amount it will + * accept, in units of the minimal lightning-payable unit + * (e.g. milli-satoshis for bitcoin) for `invreq_chain`. + */ + if (!inv->invoice_amount) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Missing invoice_amount in invoice"); + msat = amount_msat(*inv->invoice_amount); - if (inv->amount) - msat = amount_msat(*inv->amount); - - if (inv->relative_expiry) - expiry = *inv->relative_expiry; - else - expiry = BOLT12_DEFAULT_REL_EXPIRY; + if (inv->invoice_relative_expiry) + expiry = *inv->invoice_relative_expiry; + else + expiry = BOLT12_DEFAULT_REL_EXPIRY; - if (!inv->payment_hash) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Missing payment_hash in invoice"); - if (!sha256_eq(&payment_hash, inv->payment_hash)) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + if (!inv->invoice_payment_hash) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Missing payment_hash in invoice"); + if (!sha256_eq(&payment_hash, inv->invoice_payment_hash)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Incorrect preimage"); - if (!inv->description) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Missing description in invoice"); - desc = tal_strndup(cmd, - cast_signed(char *, inv->description), - tal_bytelen(inv->description)); - - if (!wallet_invoice_create(cmd->ld->wallet, - &invoice, - inv->amount ? &msat : NULL, - label, - expiry, - b12enc, - desc, - inv->features, - preimage, - &payment_hash, - local_offer_id)) - return fail_exists(cmd, label); - - notify_invoice_creation(cmd->ld, - inv->amount ? &msat : NULL, - *preimage, label); - } else + if (!inv->offer_description) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Unparsable invoice '%s': %s", - invstring, fail); + "Missing description in invoice"); + desc = tal_strndup(cmd, + inv->offer_description, + tal_bytelen(inv->offer_description)); + + if (!wallet_invoice_create(cmd->ld->wallet, + &invoice, + &msat, + label, + expiry, + b12enc, + desc, + inv->invoice_features, + preimage, + &payment_hash, + local_offer_id)) + return fail_exists(cmd, label); + + notify_invoice_creation(cmd->ld, &msat, *preimage, label); } response = json_stream_success(cmd); diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c index 5c799e4eae5b..9aec1111452c 100644 --- a/lightningd/jsonrpc.c +++ b/lightningd/jsonrpc.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -461,6 +462,16 @@ struct command_result *command_success(struct command *cmd, { assert(cmd); assert(cmd->json_stream == result); + + /* Filter will get upset if we close "result" object it didn't + * see! */ + if (cmd->filter) { + const char *err = json_stream_detach_filter(tmpctx, result); + if (err) + json_add_string(result, "warning_parameter_filter", + err); + } + json_object_end(result); json_object_end(result); @@ -493,6 +504,11 @@ struct command_result *command_fail(struct command *cmd, enum jsonrpc_errcode co return command_failed(cmd, r); } +struct json_filter **command_filter_ptr(struct command *cmd) +{ + return &cmd->filter; +} + struct command_result *command_still_pending(struct command *cmd) { notleak_with_children(cmd); @@ -600,6 +616,10 @@ struct json_stream *json_stream_success(struct command *cmd) { struct json_stream *r = json_start(cmd); json_object_start(r, "result"); + + /* We have results? OK, start filtering */ + if (cmd->filter) + json_stream_attach_filter(r, cmd->filter); return r; } @@ -691,7 +711,7 @@ static void replace_command(struct rpc_command_hook_payload *p, const char *buffer, const jsmntok_t *replacetok) { - const jsmntok_t *method = NULL, *params = NULL, *jsonrpc; + const jsmntok_t *method = NULL, *params = NULL; const char *bad; /* Must contain "method", "params" and "id" */ @@ -723,10 +743,14 @@ static void replace_command(struct rpc_command_hook_payload *p, goto fail; } - jsonrpc = json_get_member(buffer, replacetok, "jsonrpc"); - if (!jsonrpc || jsonrpc->type != JSMN_STRING || !json_tok_streq(buffer, jsonrpc, "2.0")) { - bad = "jsonrpc: \"2.0\" must be specified in the request"; - goto fail; + // deprecated phase to give the possibility to all to migrate and stay safe + // from this more restrictive change. + if (!deprecated_apis) { + const jsmntok_t *jsonrpc = json_get_member(buffer, replacetok, "jsonrpc"); + if (!jsonrpc || jsonrpc->type != JSMN_STRING || !json_tok_streq(buffer, jsonrpc, "2.0")) { + bad = "jsonrpc: \"2.0\" must be specified in the request"; + goto fail; + } } was_pending(command_exec(p->cmd->jcon, p->cmd, buffer, replacetok, @@ -863,7 +887,7 @@ REGISTER_PLUGIN_HOOK(rpc_command, static struct command_result * parse_request(struct json_connection *jcon, const jsmntok_t tok[]) { - const jsmntok_t *method, *id, *params, *jsonrpc; + const jsmntok_t *method, *id, *params, *filter; struct command *c; struct rpc_command_hook_payload *rpc_hook; bool completed; @@ -876,6 +900,7 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[]) method = json_get_member(jcon->buffer, tok, "method"); params = json_get_member(jcon->buffer, tok, "params"); + filter = json_get_member(jcon->buffer, tok, "filter"); id = json_get_member(jcon->buffer, tok, "id"); if (!id) { @@ -891,11 +916,13 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[]) // Adding a deprecated phase to make sure that all the Core Lightning wrapper // can migrate all the frameworks - jsonrpc = json_get_member(jcon->buffer, tok, "jsonrpc"); + if (!deprecated_apis) { + const jsmntok_t *jsonrpc = json_get_member(jcon->buffer, tok, "jsonrpc"); - if (!jsonrpc || jsonrpc->type != JSMN_STRING || !json_tok_streq(jcon->buffer, jsonrpc, "2.0")) { - json_command_malformed(jcon, "null", "jsonrpc: \"2.0\" must be specified in the request"); - return NULL; + if (!jsonrpc || jsonrpc->type != JSMN_STRING || !json_tok_streq(jcon->buffer, jsonrpc, "2.0")) { + json_command_malformed(jcon, "null", "jsonrpc: \"2.0\" must be specified in the request"); + return NULL; + } } /* Allocate the command off of the `jsonrpc` object and not @@ -909,6 +936,7 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[]) c->id_is_string = (id->type == JSMN_STRING); c->id = json_strdup(c, jcon->buffer, id); c->mode = CMD_NORMAL; + c->filter = NULL; list_add_tail(&jcon->commands, &c->list); tal_add_destructor(c, destroy_command); @@ -922,6 +950,13 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[]) "Expected string for method"); } + if (filter) { + struct command_result *ret; + ret = parse_filter(c, "filter", jcon->buffer, filter); + if (ret) + return ret; + } + /* Debug was too chatty, so we use IO here, even though we're * actually just logging the id */ log_io(jcon->log, LOG_IO_IN, NULL, c->id, NULL, 0); @@ -1080,6 +1115,8 @@ static struct io_plan *read_json(struct io_conn *conn, start_time), time_from_msec(250))) { db_commit_transaction(jcon->ld->wallet->db); + /* Call us back, as if we read nothing new */ + jcon->len_read = 0; return io_always(conn, read_json, jcon); } } @@ -1347,7 +1384,7 @@ void jsonrpc_notification_end(struct jsonrpc_notification *n) struct jsonrpc_request *jsonrpc_request_start_( const tal_t *ctx, const char *method, - const char *id_prefix, struct log *log, + const char *id_prefix, bool id_as_string, struct log *log, bool add_header, void (*notify_cb)(const char *buffer, const jsmntok_t *methodtok, @@ -1360,11 +1397,17 @@ struct jsonrpc_request *jsonrpc_request_start_( { struct jsonrpc_request *r = tal(ctx, struct jsonrpc_request); static u64 next_request_id = 0; - if (id_prefix) - r->id = tal_fmt(r, "%s/cln:%s#%"PRIu64, - id_prefix, method, next_request_id); - else - r->id = tal_fmt(r, "cln:%s#%"PRIu64, method, next_request_id); + + r->id_is_string = id_as_string; + if (r->id_is_string) { + if (id_prefix) + r->id = tal_fmt(r, "%s/cln:%s#%"PRIu64, + id_prefix, method, next_request_id); + else + r->id = tal_fmt(r, "cln:%s#%"PRIu64, method, next_request_id); + } else { + r->id = tal_fmt(r, "%"PRIu64, next_request_id); + } if (taken(id_prefix)) tal_free(id_prefix); next_request_id++; @@ -1380,7 +1423,10 @@ struct jsonrpc_request *jsonrpc_request_start_( if (add_header) { json_object_start(r->stream, NULL); json_add_string(r->stream, "jsonrpc", "2.0"); - json_add_string(r->stream, "id", r->id); + if (r->id_is_string) + json_add_string(r->stream, "id", r->id); + else + json_add_primitive(r->stream, "id", r->id); json_add_string(r->stream, "method", method); json_object_start(r->stream, "params"); } diff --git a/lightningd/jsonrpc.h b/lightningd/jsonrpc.h index 38bff3caedcf..a3f6cd5d6777 100644 --- a/lightningd/jsonrpc.h +++ b/lightningd/jsonrpc.h @@ -41,6 +41,8 @@ struct command { enum command_mode mode; /* Have we started a json stream already? For debugging. */ struct json_stream *json_stream; + /* Optional output field filter. */ + struct json_filter *filter; }; /** @@ -71,6 +73,7 @@ struct jsonrpc_notification { struct jsonrpc_request { const char *id; + bool id_is_string; const char *method; struct json_stream *stream; void (*notify_cb)(const char *buffer, @@ -224,9 +227,9 @@ void jsonrpc_notification_end(struct jsonrpc_notification *n); * start a JSONRPC request; id_prefix is non-NULL if this was triggered by * another JSONRPC request. */ -#define jsonrpc_request_start(ctx, method, id_prefix, log, notify_cb, response_cb, response_cb_arg) \ +#define jsonrpc_request_start(ctx, method, id_prefix, id_as_string, log, notify_cb, response_cb, response_cb_arg) \ jsonrpc_request_start_( \ - (ctx), (method), (id_prefix), (log), true, \ + (ctx), (method), (id_prefix), (id_as_string), (log), true, \ typesafe_cb_preargs(void, void *, (notify_cb), (response_cb_arg), \ const char *buffer, \ const jsmntok_t *idtok, \ @@ -238,9 +241,9 @@ void jsonrpc_notification_end(struct jsonrpc_notification *n); const jsmntok_t *idtok), \ (response_cb_arg)) -#define jsonrpc_request_start_raw(ctx, method, id_prefix, log, notify_cb, response_cb, response_cb_arg) \ +#define jsonrpc_request_start_raw(ctx, method, id_prefix, id_as_string,log, notify_cb, response_cb, response_cb_arg) \ jsonrpc_request_start_( \ - (ctx), (method), (id_prefix), (log), false, \ + (ctx), (method), (id_prefix), (id_as_string), (log), false, \ typesafe_cb_preargs(void, void *, (notify_cb), (response_cb_arg), \ const char *buffer, \ const jsmntok_t *idtok, \ @@ -254,7 +257,9 @@ void jsonrpc_notification_end(struct jsonrpc_notification *n); struct jsonrpc_request *jsonrpc_request_start_( const tal_t *ctx, const char *method, - const char *id_prefix TAKES, struct log *log, bool add_header, + const char *id_prefix TAKES, + bool id_as_string, + struct log *log, bool add_header, void (*notify_cb)(const char *buffer, const jsmntok_t *idtok, const jsmntok_t *methodtok, diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 6045e31fd16d..afdcaf36bbf5 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -207,6 +207,10 @@ static struct lightningd *new_lightningd(const tal_t *ctx) ld->proposed_wireaddr = tal_arr(ld, struct wireaddr_internal, 0); ld->proposed_listen_announce = tal_arr(ld, enum addr_listen_announce, 0); + /*~ The network is not yet ready for DNS names inside node_announcements, + * so we disable this by default for now. */ + ld->announce_dns = false; + ld->remote_addr_v4 = NULL; ld->remote_addr_v6 = NULL; ld->discovered_ip_v4 = NULL; @@ -877,7 +881,7 @@ int main(int argc, char *argv[]) struct htlc_in_map *unconnected_htlcs_in; struct ext_key *bip32_base; int sigchld_rfd; - struct io_conn *sigchld_conn; + struct io_conn *sigchld_conn = NULL; int exit_code = 0; char **orig_argv; bool try_reexec; @@ -1105,7 +1109,8 @@ int main(int argc, char *argv[]) /*~ Now that the rpc path exists, we can start the plugins and they * can start talking to us. */ - plugins_config(ld->plugins); + if (!plugins_config(ld->plugins)) + goto stop; /*~ Process any HTLCs we were in the middle of when we exited, now * that plugins (who might want to know via htlc_accepted hook) are @@ -1202,6 +1207,7 @@ int main(int argc, char *argv[]) assert(io_loop_ret == ld); log_debug(ld->log, "io_loop_with_timers: %s", __func__); +stop: /* Stop *new* JSON RPC requests. */ jsonrpc_stop_listening(ld->jsonrpc); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 8043e36f853c..1cd1930a489d 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -122,10 +122,10 @@ struct lightningd { struct node_id id; /* The public base for our payer_id keys */ - struct point32 bolt12_base; + struct pubkey bolt12_base; - /* The secret we put in onion message paths to know it's ours. */ - struct secret onion_reply_secret; + /* Secret base for our invoices */ + struct secret invoicesecret_base; /* Feature set we offer. */ struct feature_set *our_features; @@ -237,6 +237,9 @@ struct lightningd { /* If they force db upgrade on or off this is set. */ bool *db_upgrade_ok; + /* Announce names in config as DNS records (recently BOLT 7 addition) */ + bool announce_dns; + #if DEVELOPER /* If we want to debug a subdaemon/plugin. */ const char *dev_debug_subprocess; diff --git a/lightningd/offer.c b/lightningd/offer.c index bd54cc465ebe..db3ee5b3b88e 100644 --- a/lightningd/offer.c +++ b/lightningd/offer.c @@ -19,7 +19,6 @@ static void json_populate_offer(struct json_stream *response, const struct sha256 *offer_id, const char *b12, - const char *b12_nosig, const struct json_escape *label, enum offer_status status) { @@ -27,8 +26,6 @@ static void json_populate_offer(struct json_stream *response, json_add_bool(response, "active", offer_status_active(status)); json_add_bool(response, "single_use", offer_status_single(status)); json_add_string(response, "bolt12", b12); - if (b12_nosig) - json_add_string(response, "bolt12_unsigned", b12_nosig); json_add_bool(response, "used", offer_status_used(status)); if (label) json_add_escaped_string(response, "label", label); @@ -46,9 +43,6 @@ static struct command_result *param_b12_offer(struct command *cmd, cmd->ld->our_features, chainparams, &fail); if (!*offer) return command_fail_badparam(cmd, name, buffer, tok, fail); - if ((*offer)->signature) - return command_fail_badparam(cmd, name, buffer, tok, - "must be unsigned offer"); return NULL; } @@ -57,7 +51,7 @@ static void hsm_sign_b12(struct lightningd *ld, const char *fieldname, const struct sha256 *merkle, const u8 *publictweak, - const struct point32 *key, + const struct pubkey *key, struct bip340sig *sig) { u8 *msg; @@ -75,11 +69,10 @@ static void hsm_sign_b12(struct lightningd *ld, /* Now we sanity-check! */ sighash_from_merkle(messagename, fieldname, merkle, &sighash); - if (secp256k1_schnorrsig_verify(secp256k1_ctx, sig->u8, - sighash.u.u8, sizeof(sighash.u.u8), &key->pubkey) != 1) + if (!check_schnorr_sig(&sighash, &key->pubkey, sig)) fatal("HSM gave bad signature %s for pubkey %s", type_to_string(tmpctx, struct bip340sig, sig), - type_to_string(tmpctx, struct point32, key)); + type_to_string(tmpctx, struct pubkey, (struct pubkey *)key)); } static struct command_result *json_createoffer(struct command *cmd, @@ -90,11 +83,10 @@ static struct command_result *json_createoffer(struct command *cmd, struct json_stream *response; struct json_escape *label; struct tlv_offer *offer; - struct sha256 merkle; - const char *b12str, *b12str_nosig; + struct sha256 offer_id; + const char *b12str; bool *single_use; enum offer_status status; - struct point32 key; bool created; if (!param(cmd, buffer, params, @@ -108,19 +100,14 @@ static struct command_result *json_createoffer(struct command *cmd, status = OFFER_SINGLE_USE_UNUSED; else status = OFFER_MULTIPLE_USE_UNUSED; - merkle_tlv(offer->fields, &merkle); - offer->signature = tal(offer, struct bip340sig); - if (!point32_from_node_id(&key, &cmd->ld->id)) - fatal("invalid own node_id?"); - hsm_sign_b12(cmd->ld, "offer", "signature", &merkle, NULL, &key, - offer->signature); b12str = offer_encode(cmd, offer); + offer_offer_id(offer, &offer_id); /* If it already exists, we use that one instead (and then * the offer plugin will complain if it's inactive or expired) */ - if (!wallet_offer_create(cmd->ld->wallet, &merkle, + if (!wallet_offer_create(cmd->ld->wallet, &offer_id, b12str, label, status)) { - if (!wallet_offer_find(cmd, cmd->ld->wallet, &merkle, + if (!wallet_offer_find(cmd, cmd->ld->wallet, &offer_id, cast_const2(const struct json_escape **, &label), &status)) { @@ -131,11 +118,8 @@ static struct command_result *json_createoffer(struct command *cmd, } else created = true; - offer->signature = tal_free(offer->signature); - b12str_nosig = offer_encode(cmd, offer); - response = json_stream_success(cmd); - json_populate_offer(response, &merkle, b12str, b12str_nosig, label, status); + json_populate_offer(response, &offer_id, b12str, label, status); json_add_bool(response, "created", created); return command_success(cmd, response); } @@ -148,25 +132,6 @@ static const struct json_command createoffer_command = { }; AUTODATA(json_command, &createoffer_command); -/* We store strings in the db, so removing signatures is easiest by conversion */ -static const char *offer_str_nosig(const tal_t *ctx, - struct lightningd *ld, - const char *b12str) -{ - char *fail; - struct tlv_offer *offer = offer_decode(tmpctx, b12str, strlen(b12str), - ld->our_features, chainparams, - &fail); - - if (!offer) { - log_broken(ld->log, "Cannot reparse offerstr from db %s: %s", - b12str, fail); - return NULL; - } - offer->signature = tal_free(offer->signature); - return offer_encode(ctx, offer); -} - static struct command_result *json_listoffers(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -195,7 +160,6 @@ static struct command_result *json_listoffers(struct command *cmd, json_object_start(response, NULL); json_populate_offer(response, offer_id, b12, - offer_str_nosig(tmpctx, cmd->ld, b12), label, status); json_object_end(response); } @@ -212,8 +176,6 @@ static struct command_result *json_listoffers(struct command *cmd, json_object_start(response, NULL); json_populate_offer(response, &id, b12, - offer_str_nosig(tmpctx, - cmd->ld, b12), label, status); json_object_end(response); } @@ -259,10 +221,7 @@ static struct command_result *json_disableoffer(struct command *cmd, status = wallet_offer_disable(wallet, offer_id, status); response = json_stream_success(cmd); - json_populate_offer(response, offer_id, b12, - offer_str_nosig(tmpctx, - cmd->ld, b12), - label, status); + json_populate_offer(response, offer_id, b12, label, status); return command_success(cmd, response); } @@ -275,24 +234,28 @@ static const struct json_command disableoffer_command = { AUTODATA(json_command, &disableoffer_command); /* We do some sanity checks now, since we're looking up prev payment anyway, - * but our main purpose is to fill in invreq->payer_info tweak. */ + * but our main purpose is to fill in invreq->invreq_metadata tweak. */ static struct command_result *prev_payment(struct command *cmd, - const char *label, + struct json_escape *label, struct tlv_invoice_request *invreq, u64 **prev_basetime) { const struct wallet_payment **payments; bool prev_paid = false; + struct sha256 invreq_oid; - assert(!invreq->payer_info); + invreq_offer_id(invreq, &invreq_oid); + assert(!invreq->invreq_metadata); payments = wallet_payment_list(cmd, cmd->ld->wallet, NULL); for (size_t i = 0; i < tal_count(payments); i++) { const struct tlv_invoice *inv; char *fail; + struct sha256 inv_oid; /* FIXME: Restrict db queries instead */ - if (!payments[i]->label || !streq(label, payments[i]->label)) + if (!payments[i]->label + || !streq(label->s, payments[i]->label)) continue; if (!payments[i]->invstring) @@ -305,12 +268,13 @@ static struct command_result *prev_payment(struct command *cmd, continue; /* They can reuse labels across different offers. */ - if (!sha256_eq(inv->offer_id, invreq->offer_id)) + invoice_offer_id(inv, &inv_oid); + if (!sha256_eq(&inv_oid, &invreq_oid)) continue; /* Be paranoid, in case someone inserts their own * clashing label! */ - if (!inv->recurrence_counter) + if (!inv->invreq_recurrence_counter) continue; /* BOLT-offers-recurrence #12: @@ -322,40 +286,40 @@ static struct command_result *prev_payment(struct command *cmd, * - MUST set `period_offset` to the same value on all * following requests. */ - if (invreq->recurrence_start) { - if (!inv->recurrence_start) + if (invreq->invreq_recurrence_start) { + if (!inv->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unexpected" " recurrence_start"); - if (*inv->recurrence_start != *invreq->recurrence_start) + if (*inv->invreq_recurrence_start != *invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "recurrence_start was" " previously %u", - *inv->recurrence_start); + *inv->invreq_recurrence_start); } else { - if (inv->recurrence_start) + if (inv->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "missing" " recurrence_start"); } - if (*inv->recurrence_counter == *invreq->recurrence_counter-1) { + if (*inv->invreq_recurrence_counter == *invreq->invreq_recurrence_counter-1) { if (payments[i]->status == PAYMENT_COMPLETE) prev_paid = true; } - if (inv->payer_info) { - invreq->payer_info - = tal_dup_talarr(invreq, u8, inv->payer_info); + if (inv->invreq_metadata) { + invreq->invreq_metadata + = tal_dup_talarr(invreq, u8, inv->invreq_metadata); *prev_basetime = tal_dup(cmd, u64, - inv->recurrence_basetime); + inv->invoice_recurrence_basetime); } - if (prev_paid && inv->payer_info) + if (prev_paid && inv->invreq_metadata) break; } - if (!invreq->payer_info) + if (!invreq->invreq_metadata) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "No previous payment attempted for this" " label and offer"); @@ -382,36 +346,44 @@ static struct command_result *param_b12_invreq(struct command *cmd, return command_fail_badparam(cmd, name, buffer, tok, fail); #if !DEVELOPER /* We use this for testing with known payer_info */ - if ((*invreq)->payer_info) + if ((*invreq)->invreq_metadata) return command_fail_badparam(cmd, name, buffer, tok, - "must not have payer_info"); + "must not have invreq_metadata"); #endif - if ((*invreq)->payer_key) + if ((*invreq)->invreq_payer_id) return command_fail_badparam(cmd, name, buffer, tok, - "must not have payer_key"); + "must not have invreq_payer_id"); return NULL; } static bool payer_key(struct lightningd *ld, const u8 *public_tweak, size_t public_tweak_len, - struct point32 *key) + struct pubkey *key) { struct sha256 tweakhash; - secp256k1_pubkey tweaked; payer_key_tweak(&ld->bolt12_base, public_tweak, public_tweak_len, &tweakhash); - /* Tweaking gives a not-x-only pubkey, must then convert. */ - if (secp256k1_xonly_pubkey_tweak_add(secp256k1_ctx, - &tweaked, - &ld->bolt12_base.pubkey, - tweakhash.u.u8) != 1) - return false; + *key = ld->bolt12_base; + return secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, + &key->pubkey, + tweakhash.u.u8) == 1; +} - return secp256k1_xonly_pubkey_from_pubkey(secp256k1_ctx, - &key->pubkey, - NULL, &tweaked) == 1; +static void json_populate_invreq(struct json_stream *response, + const struct sha256 *invreq_id, + const char *b12, + const struct json_escape *label, + enum offer_status status) +{ + json_add_sha256(response, "invreq_id", invreq_id); + json_add_bool(response, "active", offer_status_active(status)); + json_add_bool(response, "single_use", offer_status_single(status)); + json_add_string(response, "bolt12", b12); + json_add_bool(response, "used", offer_status_used(status)); + if (label) + json_add_escaped_string(response, "label", label); } static struct command_result *json_createinvoicerequest(struct command *cmd, @@ -420,23 +392,37 @@ static struct command_result *json_createinvoicerequest(struct command *cmd, const jsmntok_t *params) { struct tlv_invoice_request *invreq; - const char *label; + struct json_escape *label; struct json_stream *response; u64 *prev_basetime = NULL; struct sha256 merkle; + bool *save, *single_use, *exposeid; + enum offer_status status; + struct sha256 invreq_id; + const char *b12str; if (!param(cmd, buffer, params, p_req("bolt12", param_b12_invreq, &invreq), - p_opt("recurrence_label", param_escaped_string, &label), + p_req("savetodb", param_bool, &save), + p_opt_def("exposeid", param_bool, &exposeid, false), + p_opt("recurrence_label", param_label, &label), + p_opt_def("single_use", param_bool, &single_use, true), NULL)) return command_param_failed(); - if (invreq->recurrence_counter) { + if (*single_use) + status = OFFER_SINGLE_USE_UNUSED; + else + status = OFFER_MULTIPLE_USE_UNUSED; + + /* If it's a recurring payment, we look for previous to copy + * invreq_metadata, basetime */ + if (invreq->invreq_recurrence_counter) { if (!label) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Need payment label for recurring payments"); - if (*invreq->recurrence_counter != 0) { + if (*invreq->invreq_recurrence_counter != 0) { struct command_result *err = prev_payment(cmd, label, invreq, &prev_basetime); @@ -445,46 +431,63 @@ static struct command_result *json_createinvoicerequest(struct command *cmd, } } - if (!invreq->payer_info) { + if (!invreq->invreq_metadata) { /* BOLT-offers #12: - * `payer_info` might typically contain information about the - * derivation of the `payer_key`. This should not leak any - * information (such as using a simple BIP-32 derivation - * path); a valid system might be for a node to maintain a - * base payer key, and encode a 128-bit tweak here. The - * payer_key would be derived by tweaking the base key with - * SHA256(payer_base_pubkey || tweak). + * + * `invreq_metadata` might typically contain information about + * the derivation of the `invreq_payer_id`. This should not + * leak any information (such as using a simple BIP-32 + * derivation path); a valid system might be for a node to + * maintain a base payer key and encode a 128-bit tweak here. + * The payer_id would be derived by tweaking the base key with + * SHA256(payer_base_pubkey || tweak). It's also the first + * entry (if present), ensuring an unpredictable nonce for + * hashing. */ - invreq->payer_info = tal_arr(invreq, u8, 16); - randombytes_buf(invreq->payer_info, - tal_bytelen(invreq->payer_info)); + invreq->invreq_metadata = tal_arr(invreq, u8, 16); + randombytes_buf(invreq->invreq_metadata, + tal_bytelen(invreq->invreq_metadata)); } - invreq->payer_key = tal(invreq, struct point32); - if (!payer_key(cmd->ld, - invreq->payer_info, tal_bytelen(invreq->payer_info), - invreq->payer_key)) { + invreq->invreq_payer_id = tal(invreq, struct pubkey); + if (*exposeid) { + if (!pubkey_from_node_id(invreq->invreq_payer_id, + &cmd->ld->id)) + fatal("Our ID is invalid?"); + } else if (!payer_key(cmd->ld, + invreq->invreq_metadata, + tal_bytelen(invreq->invreq_metadata), + invreq->invreq_payer_id)) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid tweak"); } /* BOLT-offers #12: - * - MUST set `signature` `sig` as detailed in - * [Signature Calculation](#signature-calculation) using the `payer_key`. + * - MUST set `signature`.`sig` as detailed in + * [Signature Calculation](#signature-calculation) using the `invreq_payer_id`. */ /* This populates the ->fields from our entries */ invreq->fields = tlv_make_fields(invreq, tlv_invoice_request); merkle_tlv(invreq->fields, &merkle); invreq->signature = tal(invreq, struct bip340sig); hsm_sign_b12(cmd->ld, "invoice_request", "signature", - &merkle, invreq->payer_info, invreq->payer_key, - invreq->signature); + &merkle, *exposeid ? NULL : invreq->invreq_metadata, + invreq->invreq_payer_id, invreq->signature); + + b12str = invrequest_encode(cmd, invreq); + + invreq_invreq_id(invreq, &invreq_id); + if (*save && !wallet_invoice_request_create(cmd->ld->wallet, &invreq_id, + b12str, label, status)) { + return command_fail(cmd, LIGHTNINGD, + "Could not create invoice_request!"); + } response = json_stream_success(cmd); - json_add_string(response, "bolt12", invrequest_encode(tmpctx, invreq)); - if (label) - json_add_escaped_string(response, "recurrence_label", - take(json_escape(NULL, label))); + json_populate_invreq(response, &invreq_id, + b12str, + label, + status); if (prev_basetime) json_add_u64(response, "previous_basetime", *prev_basetime); return command_success(cmd, response); @@ -508,7 +511,7 @@ static struct command_result *json_payersign(struct command *cmd, u8 *tweak; struct bip340sig sig; const char *messagename, *fieldname; - struct point32 key; + struct pubkey key; if (!param(cmd, buffer, params, p_req("messagename", param_string, &messagename), @@ -534,3 +537,107 @@ static const struct json_command payersign_command = { "Sign {messagename} {fieldname} {merkle} (a 32-byte hex string) using public {tweak}", }; AUTODATA(json_command, &payersign_command); + +static struct command_result *json_listinvoicerequests(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct sha256 *invreq_id; + struct json_stream *response; + struct wallet *wallet = cmd->ld->wallet; + const char *b12; + const struct json_escape *label; + bool *active_only; + enum offer_status status; + + if (!param(cmd, buffer, params, + p_opt("invreq_id", param_sha256, &invreq_id), + p_opt_def("active_only", param_bool, &active_only, false), + NULL)) + return command_param_failed(); + + response = json_stream_success(cmd); + json_array_start(response, "invoicerequests"); + if (invreq_id) { + b12 = wallet_invoice_request_find(tmpctx, wallet, + invreq_id, &label, + &status); + if (b12 && offer_status_active(status) >= *active_only) { + json_object_start(response, NULL); + json_populate_invreq(response, + invreq_id, b12, + label, status); + json_object_end(response); + } + } else { + struct db_stmt *stmt; + struct sha256 id; + + for (stmt = wallet_invreq_id_first(cmd->ld->wallet, &id); + stmt; + stmt = wallet_invreq_id_next(cmd->ld->wallet, stmt, &id)) { + b12 = wallet_invoice_request_find(tmpctx, wallet, &id, + &label, &status); + if (offer_status_active(status) >= *active_only) { + json_object_start(response, NULL); + json_populate_invreq(response, + &id, b12, + label, status); + json_object_end(response); + } + } + } + json_array_end(response); + return command_success(cmd, response); +} + +static const struct json_command listinvoicerequests_command = { + "listinvoicerequests", + "payment", + json_listinvoicerequests, + "If {invreq_id} is set, show that." + " Otherwise, if {showdisabled} is true, list all, otherwise just non-disabled ones." +}; +AUTODATA(json_command, &listinvoicerequests_command); + +static struct command_result *json_disableinvoicerequest(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct json_stream *response; + struct sha256 *invreq_id; + struct wallet *wallet = cmd->ld->wallet; + const char *b12; + const struct json_escape *label; + enum offer_status status; + + if (!param(cmd, buffer, params, + p_req("invreq_id", param_sha256, &invreq_id), + NULL)) + return command_param_failed(); + + b12 = wallet_invoice_request_find(tmpctx, wallet, invreq_id, + &label, &status); + if (!b12) + return command_fail(cmd, LIGHTNINGD, "Unknown invoice_request"); + + if (!offer_status_active(status)) + return command_fail(cmd, OFFER_ALREADY_DISABLED, + "invoice_request is not active"); + status = wallet_invoice_request_disable(wallet, invreq_id, status); + + response = json_stream_success(cmd); + json_populate_invreq(response, invreq_id, b12, label, status); + return command_success(cmd, response); +} + +static const struct json_command disableinvoicerequest_command = { + "disableinvoicerequest", + "payment", + json_disableinvoicerequest, + "Disable invoice_request {invreq_id}", +}; +AUTODATA(json_command, &disableinvoicerequest_command); + diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index ac54c65033df..5f3f03fb2e47 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -614,7 +614,7 @@ enum watch_result onchaind_funding_spent(struct channel *channel, struct lightningd *ld = channel->peer->ld; struct pubkey final_key; int hsmfd; - u32 feerates[3]; + u32 feerates[4]; enum state_change reason; /* use REASON_ONCHAIN or closer's reason, if known */ @@ -730,6 +730,9 @@ enum watch_result onchaind_funding_spent(struct channel *channel, feerates[i] = feerate_floor(); } } + /* This is 10x highest bitcoind estimate (depending on dev-max-fee-multiplier), + * so cap at 2x */ + feerates[3] = feerate_max(ld, NULL) / 5; log_debug(channel->log, "channel->static_remotekey_start[LOCAL] %"PRIu64, channel->static_remotekey_start[LOCAL]); @@ -749,8 +752,8 @@ enum watch_result onchaind_funding_spent(struct channel *channel, * we specify theirs. */ channel->channel_info.their_config.to_self_delay, channel->our_config.to_self_delay, - /* delayed_to_us, htlc, and penalty. */ - feerates[0], feerates[1], feerates[2], + /* delayed_to_us, htlc, penalty, and penalty_max. */ + feerates[0], feerates[1], feerates[2], feerates[3], channel->our_config.dust_limit, &our_last_txid, channel->shutdown_scriptpubkey[LOCAL], diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c index 26f951995df3..7316b4346f4b 100644 --- a/lightningd/onion_message.c +++ b/lightningd/onion_message.c @@ -15,28 +15,25 @@ struct onion_message_hook_payload { /* Optional */ - struct pubkey *reply_blinding; - struct onionmsg_path **reply_path; - struct pubkey *reply_first_node; - struct pubkey *our_alias; - struct tlv_onionmsg_payload *om; + struct blinded_path *reply_path; + struct secret *pathsecret; + struct tlv_onionmsg_tlv *om; }; static void json_add_blindedpath(struct json_stream *stream, const char *fieldname, - const struct pubkey *blinding, - const struct pubkey *first_node_id, - struct onionmsg_path **path) + const struct blinded_path *path) { json_object_start(stream, fieldname); - json_add_pubkey(stream, "blinding", blinding); - json_add_pubkey(stream, "first_node_id", first_node_id); + json_add_pubkey(stream, "first_node_id", &path->first_node_id); + json_add_pubkey(stream, "blinding", &path->blinding); json_array_start(stream, "hops"); - for (size_t i = 0; i < tal_count(path); i++) { + for (size_t i = 0; i < tal_count(path->path); i++) { json_object_start(stream, NULL); - json_add_pubkey(stream, "id", &path[i]->node_id); + json_add_pubkey(stream, "blinded_node_id", + &path->path[i]->blinded_node_id); json_add_hex_talarr(stream, "encrypted_recipient_data", - path[i]->encrypted_recipient_data); + path->path[i]->encrypted_recipient_data); json_object_end(stream); }; json_array_end(stream); @@ -48,15 +45,12 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload, struct plugin *plugin) { json_object_start(stream, "onion_message"); - if (payload->our_alias) - json_add_pubkey(stream, "our_alias", payload->our_alias); + if (payload->pathsecret) + json_add_secret(stream, "pathsecret", payload->pathsecret); - if (payload->reply_first_node) { + if (payload->reply_path) json_add_blindedpath(stream, "reply_blindedpath", - payload->reply_blinding, - payload->reply_first_node, payload->reply_path); - } if (payload->om->invoice_request) json_add_hex_talarr(stream, "invoice_request", @@ -92,38 +86,34 @@ onion_message_hook_cb(struct onion_message_hook_payload *payload STEALS) tal_free(payload); } -/* Two hooks, because it's critical we only accept blinding if we expect that - * exact blinding key. Otherwise, we can be probed using old blinded paths. */ -REGISTER_PLUGIN_HOOK(onion_message_blinded, +/* This is for unsolicted messages */ +REGISTER_PLUGIN_HOOK(onion_message_recv, plugin_hook_continue, onion_message_hook_cb, onion_message_serialize, struct onion_message_hook_payload *); -REGISTER_PLUGIN_HOOK(onion_message_ourpath, +/* This is for messages claiming to be using our paths: caller must + * check pathsecret! */ + REGISTER_PLUGIN_HOOK(onion_message_recv_secret, plugin_hook_continue, onion_message_hook_cb, onion_message_serialize, struct onion_message_hook_payload *); + void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) { struct onion_message_hook_payload *payload; u8 *submsg; - struct secret *self_id; size_t submsglen; const u8 *subptr; payload = tal(tmpctx, struct onion_message_hook_payload); - payload->our_alias = tal(payload, struct pubkey); - if (!fromwire_connectd_got_onionmsg_to_us(payload, msg, - payload->our_alias, - &self_id, - &payload->reply_blinding, - &payload->reply_first_node, - &payload->reply_path, - &submsg)) { + &payload->pathsecret, + &payload->reply_path, + &submsg)) { log_broken(ld->log, "bad got_onionmsg_tous: %s", tal_hex(tmpctx, msg)); return; @@ -134,15 +124,9 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) return; #endif - /* If there's no self_id, or it's not correct, ignore alias: alias - * means we created the path it's using. */ - if (!self_id || !secret_eq_consttime(self_id, &ld->onion_reply_secret)) - payload->our_alias = tal_free(payload->our_alias); - tal_free(self_id); - submsglen = tal_bytelen(submsg); subptr = submsg; - payload->om = fromwire_tlv_onionmsg_payload(payload, &subptr, &submsglen); + payload->om = fromwire_tlv_onionmsg_tlv(payload, &subptr, &submsglen); if (!payload->om) { log_broken(ld->log, "bad got_onionmsg_tous om: %s", tal_hex(tmpctx, msg)); @@ -151,23 +135,16 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) tal_free(submsg); /* Make sure connectd gets this right. */ - if (payload->reply_path - && (!payload->reply_blinding || !payload->reply_first_node)) { - log_broken(ld->log, - "No reply blinding/first_node, ignoring reply path"); - payload->reply_path = tal_free(payload->reply_path); - } - log_debug(ld->log, "Got onionmsg%s%s", - payload->our_alias ? " via-ourpath": "", + payload->pathsecret ? " with pathsecret": "", payload->reply_path ? " reply_path": ""); /* We'll free this on return */ tal_steal(ld, payload); - if (payload->our_alias) - plugin_hook_call_onion_message_ourpath(ld, NULL, payload); + if (payload->pathsecret) + plugin_hook_call_onion_message_recv_secret(ld, NULL, payload); else - plugin_hook_call_onion_message_blinded(ld, NULL, payload); + plugin_hook_call_onion_message_recv(ld, NULL, payload); } struct onion_hop { @@ -295,17 +272,21 @@ static struct command_result *json_blindedpath(struct command *cmd, const jsmntok_t *params) { struct pubkey *ids; - struct onionmsg_path **path; struct privkey first_blinding, blinding_iter; - struct pubkey first_blinding_pubkey, first_node, me; + struct pubkey me; + struct blinded_path *path; size_t nhops; struct json_stream *response; + struct tlv_encrypted_data_tlv *tlv; + struct secret *pathsecret; if (!param(cmd, buffer, params, p_req("ids", param_pubkeys, &ids), + p_req("pathsecret", param_secret, &pathsecret), NULL)) return command_param_failed(); + path = tal(cmd, struct blinded_path); nhops = tal_count(ids); /* Final id should be us! */ @@ -313,7 +294,7 @@ static struct command_result *json_blindedpath(struct command *cmd, fatal("My id %s is invalid?", type_to_string(tmpctx, struct node_id, &cmd->ld->id)); - first_node = ids[0]; + path->first_node_id = ids[0]; if (!pubkey_eq(&ids[nhops-1], &me)) return command_fail(cmd, LIGHTNINGD, "Final of ids must be this node (%s), not %s", @@ -322,41 +303,47 @@ static struct command_result *json_blindedpath(struct command *cmd, &ids[nhops-1])); randombytes_buf(&first_blinding, sizeof(first_blinding)); - if (!pubkey_from_privkey(&first_blinding, &first_blinding_pubkey)) + if (!pubkey_from_privkey(&first_blinding, &path->blinding)) /* Should not happen! */ return command_fail(cmd, LIGHTNINGD, "Could not convert blinding to pubkey!"); /* We convert ids into aliases as we go. */ - path = tal_arr(cmd, struct onionmsg_path *, nhops); + path->path = tal_arr(cmd, struct onionmsg_hop *, nhops); blinding_iter = first_blinding; for (size_t i = 0; i < nhops - 1; i++) { - path[i] = tal(path, struct onionmsg_path); - path[i]->encrypted_recipient_data = create_enctlv(path[i], + path->path[i] = tal(path->path, struct onionmsg_hop); + + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->next_node_id = &ids[i+1]; + /* FIXME: Pad? */ + + path->path[i]->encrypted_recipient_data + = encrypt_tlv_encrypted_data(path->path[i], &blinding_iter, &ids[i], - &ids[i+1], - /* FIXME: Pad? */ - 0, - NULL, + tlv, &blinding_iter, - &path[i]->node_id); + &path->path[i]->blinded_node_id); } /* FIXME: Add padding! */ - path[nhops-1] = tal(path, struct onionmsg_path); - path[nhops-1]->encrypted_recipient_data = create_final_enctlv(path[nhops-1], - &blinding_iter, - &ids[nhops-1], - /* FIXME: Pad? */ - 0, - &cmd->ld->onion_reply_secret, - &path[nhops-1]->node_id); + path->path[nhops-1] = tal(path->path, struct onionmsg_hop); + + tlv = tlv_encrypted_data_tlv_new(tmpctx); + + tlv->path_id = (u8 *)tal_dup(tlv, struct secret, pathsecret); + path->path[nhops-1]->encrypted_recipient_data + = encrypt_tlv_encrypted_data(path->path[nhops-1], + &blinding_iter, + &ids[nhops-1], + tlv, + NULL, + &path->path[nhops-1]->blinded_node_id); response = json_stream_success(cmd); - json_add_blindedpath(response, "blindedpath", - &first_blinding_pubkey, &first_node, path); + json_add_blindedpath(response, "blindedpath", path); return command_success(cmd, response); } diff --git a/lightningd/options.c b/lightningd/options.c index 80f75f0b5850..968f0ae8220c 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -222,7 +222,7 @@ static char *opt_add_addr_withtype(const char *arg, assert(arg != NULL); dns_ok = !ld->always_use_proxy && ld->config.use_dns; - /* Will be overridden in next call iff has port */ + /* Will be overridden in next call, if it has a port */ port = 0; if (!separate_address_and_port(tmpctx, arg, &address, &port)) return tal_fmt(NULL, "Unable to parse address:port '%s'", arg); @@ -230,6 +230,7 @@ static char *opt_add_addr_withtype(const char *arg, if (is_ipaddr(address) || is_toraddr(address) || is_wildcardaddr(address) + || (is_dnsaddr(address) && !ld->announce_dns) || ala != ADDR_ANNOUNCE) { if (!parse_wireaddr_internal(arg, &wi, ld->portnum, wildcard_ok, dns_ok, false, @@ -254,7 +255,7 @@ static char *opt_add_addr_withtype(const char *arg, } /* Add ADDR_TYPE_DNS to announce DNS hostnames */ - if (is_dnsaddr(address) && ala & ADDR_ANNOUNCE) { + if (is_dnsaddr(address) && ld->announce_dns && (ala & ADDR_ANNOUNCE)) { /* BOLT-hostnames #7: * The origin node: * ... @@ -516,9 +517,15 @@ static char *opt_set_hsm_password(struct lightningd *ld) int is_encrypted; is_encrypted = is_hsm_secret_encrypted("hsm_secret"); + /* While lightningd is performing the first initialization + * this check is always true because the file does not exist. + * + * Maybe the is_hsm_secret_encrypted is performing a not useful + * check at this stage, but the hsm is a delicate part, + * so it is a good information to have inside the log. */ if (is_encrypted == -1) - return tal_fmt(NULL, "Could not access 'hsm_secret': %s", - strerror(errno)); + log_info(ld->log, "'hsm_secret' does not exist (%s)", + strerror(errno)); prompt(ld, "The hsm_secret is encrypted with a password. In order to " "decrypt it and start the node you must provide the password."); @@ -635,8 +642,8 @@ static char *opt_force_featureset(const char *optarg, char **parts = tal_strsplit(tmpctx, optarg, "/", STR_EMPTY_OK); if (tal_count(parts) != NUM_FEATURE_PLACE + 1) { if (!strstarts(optarg, "-") && !strstarts(optarg, "+")) - return "Expected 5 feature sets (init/globalinit/" - " node_announce/channel/bolt11) each terminated by /" + return "Expected 8 feature sets (init/globalinit/" + " node_announce/channel/bolt11/b12offer/b12invreq/b12inv) each terminated by /" " OR +/-"; char *endp; @@ -752,6 +759,11 @@ static void dev_register_opts(struct lightningd *ld) opt_register_noarg("--dev-no-ping-timer", opt_set_bool, &ld->dev_no_ping_timer, "Don't hang up if we don't get a ping response"); + opt_register_arg("--dev-onion-reply-length", + opt_set_uintval, + opt_show_uintval, + &dev_onion_reply_length, + "Send onion errors of custom length"); } #endif /* DEVELOPER */ @@ -1007,6 +1019,9 @@ static char *opt_set_onion_messages(struct lightningd *ld) feature_set_or(ld->our_features, take(feature_set_for_feature(NULL, OPTIONAL_FEATURE(OPT_ONION_MESSAGES)))); + feature_set_or(ld->our_features, + take(feature_set_for_feature(NULL, + OPTIONAL_FEATURE(OPT_ROUTE_BLINDING)))); return NULL; } @@ -1081,7 +1096,7 @@ static void register_opts(struct lightningd *ld) opt_register_early_noarg("--experimental-onion-messages", opt_set_onion_messages, ld, "EXPERIMENTAL: enable send, receive and relay" - " of onion messages"); + " of onion messages and blinded payments"); opt_register_early_noarg("--experimental-offers", opt_set_offers, ld, "EXPERIMENTAL: enable send and receive of offers" @@ -1089,6 +1104,10 @@ static void register_opts(struct lightningd *ld) opt_register_early_noarg("--experimental-shutdown-wrong-funding", opt_set_shutdown_wrong_funding, ld, "EXPERIMENTAL: allow shutdown with alternate txids"); + opt_register_early_arg("--announce-addr-dns", + opt_set_bool_arg, opt_show_bool, + &ld->announce_dns, + "Use DNS entries in --announce-addr and --addr (not widely supported!)"); opt_register_noarg("--help|-h", opt_lightningd_usage, ld, "Print this message."); diff --git a/lightningd/pay.c b/lightningd/pay.c index e8fe8abee64e..75cdbcc427e1 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -342,8 +341,9 @@ void payment_succeeded(struct lightningd *ld, struct htlc_out *hout, hout->partid, hout->groupid); assert(payment); - if (payment->local_offer_id) - wallet_offer_mark_used(ld->wallet->db, payment->local_offer_id); + if (payment->local_invreq_id) + wallet_invoice_request_mark_used(ld->wallet->db, + payment->local_invreq_id); tell_waiters_success(ld, &hout->payment_hash, payment); } @@ -778,46 +778,48 @@ static const u8 *send_onion(const tal_t *ctx, struct lightningd *ld, blinding, partid, groupid, onion, NULL, hout); } -static struct command_result *check_offer_usage(struct command *cmd, - const struct sha256 *local_offer_id) +static struct command_result *check_invoice_request_usage(struct command *cmd, + const struct sha256 *local_invreq_id) { enum offer_status status; const struct wallet_payment **payments; - if (!local_offer_id) + if (!local_invreq_id) return NULL; - if (!wallet_offer_find(tmpctx, cmd->ld->wallet, local_offer_id, - NULL, &status)) - return command_fail(cmd, PAY_OFFER_INVALID, - "Unknown offer %s", + if (!wallet_invoice_request_find(tmpctx, cmd->ld->wallet, + local_invreq_id, + NULL, &status)) + return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID, + "Unknown invoice_request %s", type_to_string(tmpctx, struct sha256, - local_offer_id)); + local_invreq_id)); if (!offer_status_active(status)) - return command_fail(cmd, PAY_OFFER_INVALID, - "Inactive offer %s", + return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID, + "Inactive invoice_request %s", type_to_string(tmpctx, struct sha256, - local_offer_id)); + local_invreq_id)); if (!offer_status_single(status)) return NULL; /* OK, we must not attempt more than one payment at once for - * single_use offer */ - payments = wallet_payments_by_offer(tmpctx, cmd->ld->wallet, local_offer_id); + * single_use invoice_request we publish! */ + payments = wallet_payments_by_invoice_request(tmpctx, cmd->ld->wallet, + local_invreq_id); for (size_t i = 0; i < tal_count(payments); i++) { switch (payments[i]->status) { case PAYMENT_COMPLETE: - return command_fail(cmd, PAY_OFFER_INVALID, - "Single-use offer already paid" + return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID, + "Single-use invoice_request already paid" " with %s", type_to_string(tmpctx, struct sha256, &payments[i] ->payment_hash)); case PAYMENT_PENDING: - return command_fail(cmd, PAY_OFFER_INVALID, - "Single-use offer already" + return command_fail(cmd, PAY_INVOICE_REQUEST_INVALID, + "Single-use invoice_request already" " in progress with %s", type_to_string(tmpctx, struct sha256, &payments[i] @@ -832,19 +834,23 @@ static struct command_result *check_offer_usage(struct command *cmd, static struct channel *find_channel_for_htlc_add(struct lightningd *ld, const struct node_id *node, - const struct short_channel_id *scid) + const struct short_channel_id *scid_or_alias) { struct channel *channel; struct peer *peer = peer_by_id(ld, node); if (!peer) return NULL; - channel = find_channel_by_scid(peer, scid); + channel = find_channel_by_scid(peer, scid_or_alias); + if (channel && channel_can_add_htlc(channel)) + return channel; + + channel = find_channel_by_alias(peer, scid_or_alias, LOCAL); if (channel && channel_can_add_htlc(channel)) return channel; /* We used to ignore scid: now all-zero means "any" */ - if (!channel && (deprecated_apis || memeqzero(scid, sizeof(*scid)))) { + if (!channel && (deprecated_apis || memeqzero(scid_or_alias, sizeof(*scid_or_alias)))) { list_for_each(&peer->channels, channel, list) { if (channel_can_add_htlc(channel)) { return channel; @@ -873,7 +879,7 @@ send_payment_core(struct lightningd *ld, struct node_id *route_nodes TAKES, struct short_channel_id *route_channels TAKES, struct secret *path_secrets, - const struct sha256 *local_offer_id) + const struct sha256 *local_invreq_id) { const struct wallet_payment **payments, *old_payment = NULL; struct channel *channel; @@ -882,6 +888,7 @@ send_payment_core(struct lightningd *ld, struct routing_failure *fail; struct amount_msat msat_already_pending = AMOUNT_MSAT(0); bool have_complete = false; + struct command_result *invreq_err; /* Now, do we already have one or more payments? */ payments = wallet_payment_list(tmpctx, ld->wallet, rhash); @@ -1038,10 +1045,9 @@ send_payment_core(struct lightningd *ld, &total_msat)); } - struct command_result *offer_err; - offer_err = check_offer_usage(cmd, local_offer_id); - if (offer_err) - return offer_err; + invreq_err = check_invoice_request_usage(cmd, local_invreq_id); + if (invreq_err) + return invreq_err; channel = find_channel_for_htlc_add(ld, &first_hop->node_id, &first_hop->scid); @@ -1118,8 +1124,8 @@ send_payment_core(struct lightningd *ld, payment->description = tal_strdup(payment, description); else payment->description = NULL; - payment->local_offer_id = tal_dup_or_null(payment, struct sha256, - local_offer_id); + payment->local_invreq_id = tal_dup_or_null(payment, struct sha256, + local_invreq_id); /* We write this into db when HTLC is actually sent. */ wallet_payment_setup(ld->wallet, payment); @@ -1140,7 +1146,7 @@ send_payment(struct lightningd *ld, const char *label TAKES, const char *invstring TAKES, const char *description TAKES, - const struct sha256 *local_offer_id, + const struct sha256 *local_invreq_id, const struct secret *payment_secret, const u8 *payment_metadata) { @@ -1172,9 +1178,7 @@ send_payment(struct lightningd *ld, take(onion_nonfinal_hop(NULL, &route[i + 1].scid, route[i + 1].amount, - base_expiry + route[i + 1].delay, - route[i].blinding, - route[i].enctlv))); + base_expiry + route[i + 1].delay))); } /* And finally set the final hop to the special values in @@ -1193,7 +1197,7 @@ send_payment(struct lightningd *ld, onion = onion_final_hop(cmd, route[i].amount, base_expiry + route[i].delay, - total_msat, route[i].blinding, route[i].enctlv, + total_msat, payment_secret, payment_metadata); if (!onion) { return command_fail(cmd, PAY_DESTINATION_PERM_FAIL, @@ -1215,7 +1219,7 @@ send_payment(struct lightningd *ld, msat, total_msat, label, invstring, description, packet, &ids[n_hops - 1], ids, - channels, path_secrets, local_offer_id); + channels, path_secrets, local_invreq_id); } static struct command_result * @@ -1289,7 +1293,7 @@ static struct command_result *json_sendonion(struct command *cmd, struct secret *path_secrets; struct amount_msat *msat; u64 *partid, *group; - struct sha256 *local_offer_id = NULL; + struct sha256 *local_invreq_id = NULL; if (!param(cmd, buffer, params, p_req("onion", param_bin_from_hex, &onion), @@ -1302,7 +1306,7 @@ static struct command_result *json_sendonion(struct command *cmd, p_opt("bolt11", param_string, &invstring), p_opt_def("amount_msat|msatoshi", param_msat, &msat, AMOUNT_MSAT(0)), p_opt("destination", param_node_id, &destination), - p_opt("localofferid", param_sha256, &local_offer_id), + p_opt("localinvreqid", param_sha256, &local_invreq_id), p_opt("groupid", param_u64, &group), p_opt("description", param_string, &description), NULL)) @@ -1328,7 +1332,7 @@ static struct command_result *json_sendonion(struct command *cmd, first_hop, *msat, AMOUNT_MSAT(0), label, invstring, description, packet, destination, NULL, NULL, - path_secrets, local_offer_id); + path_secrets, local_invreq_id); } static const struct json_command sendonion_command = { @@ -1354,10 +1358,6 @@ static struct command_result *param_route_hop_style(struct command *cmd, return NULL; } - /* We still let you *specify* this, but we ignore it! */ - if (deprecated_apis && json_tok_streq(buffer, tok, "legacy")) - return NULL; - return command_fail_badparam(cmd, name, buffer, tok, "should be 'tlv' ('legacy' not supported)"); } @@ -1381,8 +1381,6 @@ static struct command_result *param_route_hops(struct command *cmd, struct node_id *id; struct short_channel_id *channel; unsigned *delay, *direction; - struct pubkey *blinding; - u8 *enctlv; int *ignored; if (!param(cmd, buffer, t, @@ -1394,8 +1392,6 @@ static struct command_result *param_route_hops(struct command *cmd, /* Allowed (getroute supplies it) but ignored */ p_opt("direction", param_number, &direction), p_opt("style", param_route_hop_style, &ignored), - p_opt("blinding", param_pubkey, &blinding), - p_opt("encrypted_recipient_data", param_bin_from_hex, &enctlv), NULL)) return command_param_failed(); @@ -1403,8 +1399,6 @@ static struct command_result *param_route_hops(struct command *cmd, (*hops)[i].node_id = *id; (*hops)[i].delay = *delay; (*hops)[i].scid = *channel; - (*hops)[i].blinding = blinding; - (*hops)[i].enctlv = enctlv; } return NULL; @@ -1421,7 +1415,7 @@ static struct command_result *json_sendpay(struct command *cmd, const char *invstring, *label, *description; u64 *partid, *group; struct secret *payment_secret; - struct sha256 *local_offer_id; + struct sha256 *local_invreq_id; u8 *payment_metadata; /* For generating help, give new-style. */ @@ -1434,7 +1428,7 @@ static struct command_result *json_sendpay(struct command *cmd, p_opt("bolt11", param_string, &invstring), p_opt("payment_secret", param_secret, &payment_secret), p_opt_def("partid", param_u64, &partid, 0), - p_opt("localofferid", param_sha256, &local_offer_id), + p_opt("localinvreqid", param_sha256, &local_invreq_id), p_opt("groupid", param_u64, &group), p_opt("payment_metadata", param_bin_from_hex, &payment_metadata), p_opt("description", param_string, &description), @@ -1487,7 +1481,7 @@ static struct command_result *json_sendpay(struct command *cmd, route, final_amount, msat ? *msat : final_amount, - label, invstring, description, local_offer_id, + label, invstring, description, local_invreq_id, payment_secret, payment_metadata); } @@ -1601,8 +1595,8 @@ static struct command_result *json_listsendpays(struct command *cmd, b12 = invoice_decode(cmd, invstring, strlen(invstring), cmd->ld->our_features, chainparams, &fail); - if (b12 && b12->payment_hash) - rhash = b12->payment_hash; + if (b12 && b12->invoice_payment_hash) + rhash = b12->invoice_payment_hash; else return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Invalid invstring: %s", fail); diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 39c6e5a05aa8..fa3b9f014b0f 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -613,6 +613,7 @@ static void subtract_received_htlcs(const struct channel *channel, struct amount_msat channel_amount_spendable(const struct channel *channel) { struct amount_msat spendable; + bool wumbo; /* Compute how much we can send via this channel in one payment. */ if (!amount_msat_sub_sat(&spendable, @@ -636,9 +637,15 @@ struct amount_msat channel_amount_spendable(const struct channel *channel) channel->channel_info.their_config.htlc_minimum)) return AMOUNT_MSAT(0); + wumbo = feature_negotiated(channel->peer->ld->our_features, + channel->peer->their_features, + OPT_LARGE_CHANNELS); + /* We can't offer an HTLC over the max payment threshold either. */ - if (amount_msat_greater(spendable, chainparams->max_payment)) + if (amount_msat_greater(spendable, chainparams->max_payment) + && !wumbo) { spendable = chainparams->max_payment; + } return spendable; } @@ -646,6 +653,7 @@ struct amount_msat channel_amount_spendable(const struct channel *channel) struct amount_msat channel_amount_receivable(const struct channel *channel) { struct amount_msat their_msat, receivable; + bool wumbo; /* Compute how much we can receive via this channel in one payment */ if (!amount_sat_sub_msat(&their_msat, @@ -672,9 +680,15 @@ struct amount_msat channel_amount_receivable(const struct channel *channel) if (amount_msat_less(receivable, channel->our_config.htlc_minimum)) return AMOUNT_MSAT(0); + wumbo = feature_negotiated(channel->peer->ld->our_features, + channel->peer->their_features, + OPT_LARGE_CHANNELS); + /* They can't offer an HTLC over the max payment threshold either. */ - if (amount_msat_greater(receivable, chainparams->max_payment)) + if (amount_msat_greater(receivable, chainparams->max_payment) + && !wumbo) { receivable = chainparams->max_payment; + } return receivable; } @@ -1901,8 +1915,8 @@ void channel_watch_funding(struct lightningd *ld, struct channel *channel) } void channel_watch_inflight(struct lightningd *ld, - struct channel *channel, - struct channel_inflight *inflight) + struct channel *channel, + struct channel_inflight *inflight) { /* FIXME: Remove arg from cb? */ watch_txid(channel, ld->topology, channel, @@ -2629,79 +2643,6 @@ static void set_channel_config(struct command *cmd, struct channel *channel, json_object_end(response); } -static struct command_result *json_setchannelfee(struct command *cmd, - const char *buffer, - const jsmntok_t *obj UNNEEDED, - const jsmntok_t *params) -{ - struct json_stream *response; - struct peer *peer; - struct channel **channels; - u32 *base, *ppm, *delaysecs; - - /* Parse the JSON command */ - if (!param(cmd, buffer, params, - p_req("id", param_channel_or_all, &channels), - p_opt_def("base", param_msat_u32, - &base, cmd->ld->config.fee_base), - p_opt_def("ppm", param_number, &ppm, - cmd->ld->config.fee_per_satoshi), - /* BOLT #7: - * If it creates a new `channel_update` with updated channel parameters: - * - SHOULD keep accepting the previous channel parameters for 10 minutes - */ - p_opt_def("enforcedelay", param_number, &delaysecs, 600), - NULL)) - return command_param_failed(); - - /* Open JSON response object for later iteration */ - response = json_stream_success(cmd); - json_add_num(response, "base", *base); - json_add_num(response, "ppm", *ppm); - json_array_start(response, "channels"); - - /* If the users requested 'all' channels we need to iterate */ - if (channels == NULL) { - list_for_each(&cmd->ld->peers, peer, list) { - struct channel *channel; - list_for_each(&peer->channels, channel, list) { - if (channel->state != CHANNELD_NORMAL && - channel->state != CHANNELD_AWAITING_SPLICE && - channel->state != CHANNELD_AWAITING_LOCKIN && - channel->state != DUALOPEND_AWAITING_LOCKIN) - continue; - set_channel_config(cmd, channel, base, ppm, NULL, NULL, - *delaysecs, response, false); - } - } - /* single peer should be updated */ - } else { - for (size_t i = 0; i < tal_count(channels); i++) { - set_channel_config(cmd, channels[i], base, ppm, NULL, NULL, - *delaysecs, response, false); - } - } - - /* Close and return response */ - json_array_end(response); - return command_success(cmd, response); -} - -static const struct json_command setchannelfee_command = { - "setchannelfee", - "channels", - json_setchannelfee, - "Sets specific routing fees for channel with {id} " - "(either peer ID, channel ID, short channel ID or 'all'). " - "Routing fees are defined by a fixed {base} (msat) " - "and a {ppm} (proportional per millionth) value. " - "If values for {base} or {ppm} are left out, defaults will be used. " - "{base} can also be defined in other units, for example '1sat'. " - "If {id} is 'all', the fees will be applied for all channels. ", - true /* deprecated */ -}; -AUTODATA(json_command, &setchannelfee_command); - static struct command_result *json_setchannel(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index efc25574f5ad..b1f158855df6 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -98,6 +98,16 @@ static struct failed_htlc *mk_failed_htlc_badonion(const tal_t *ctx, { struct failed_htlc *f = tal(ctx, struct failed_htlc); + /* BOLT-route-blinding #4: + * - If `blinding_point` is set in the incoming `update_add_htlc`: + * - MUST return `invalid_onion_blinding` for any local error or + * other downstream errors. + */ + /* FIXME: That's not enough! Don't leak information about forward + * failures either! */ + if (hin->blinding || (hin->payload && hin->payload->blinding)) + badonion = WIRE_INVALID_ONION_BLINDING; + f->id = hin->key.id; f->onion = NULL; f->badonion = badonion; @@ -113,6 +123,26 @@ static struct failed_htlc *mk_failed_htlc(const tal_t *ctx, { struct failed_htlc *f = tal(ctx, struct failed_htlc); + /* BOLT-route-blinding #4: + * - If `blinding_point` is set in the incoming `update_add_htlc`: + * - MUST return `invalid_onion_blinding` for any local error or + * other downstream errors. + */ + if (hin->blinding) { + return mk_failed_htlc_badonion(ctx, hin, + WIRE_INVALID_ONION_BLINDING); + } + + /* Also, at head of the blinded path, return "normal" invalid + * onion blinding. */ + if (hin->payload && hin->payload->blinding) { + struct sha256 sha; + sha256(&sha, hin->onion_routing_packet, + sizeof(hin->onion_routing_packet)); + failonion = create_onionreply(tmpctx, hin->shared_secret, + towire_invalid_onion_blinding(tmpctx, &sha)); + } + f->id = hin->key.id; f->sha256_of_onion = NULL; f->badonion = 0; @@ -149,14 +179,7 @@ static void fail_in_htlc(struct htlc_in *hin, htlc_in_update_state(hin->key.channel, hin, SENT_REMOVE_HTLC); htlc_in_check(hin, __func__); -#if EXPERIMENTAL_FEATURES - /* In a blinded path, all failures become invalid_onion_blinding */ - if (hin->blinding) { - failed_htlc = mk_failed_htlc_badonion(tmpctx, hin, - WIRE_INVALID_ONION_BLINDING); - } else -#endif - failed_htlc = mk_failed_htlc(tmpctx, hin, hin->failonion); + failed_htlc = mk_failed_htlc(tmpctx, hin, hin->failonion); bool we_filled = false; wallet_htlc_update(hin->key.channel->peer->ld->wallet, @@ -692,7 +715,7 @@ static void forward_htlc(struct htlc_in *hin, /* Update this to where we're actually trying to send. */ if (next) forward_scid = channel_scid_or_local_alias(next); - }else + } else next = NULL; /* Unknown peer, or peer not ready. */ @@ -905,7 +928,7 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re struct lightningd *ld = request->ld; struct preimage payment_preimage; const jsmntok_t *resulttok, *paykeytok, *payloadtok, *fwdtok; - u8 *payload, *failonion; + u8 *failonion; if (!toks || !buffer) return true; @@ -921,7 +944,7 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re payloadtok = json_get_member(buffer, toks, "payload"); if (payloadtok) { - payload = json_tok_bin_from_hex(rs, buffer, payloadtok); + u8 *payload = json_tok_bin_from_hex(rs, buffer, payloadtok); if (!payload) fatal("Bad payload for htlc_accepted" " hook: %.*s", @@ -931,13 +954,17 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re tal_free(rs->raw_payload); rs->raw_payload = prepend_length(rs, take(payload)); - request->payload = onion_decode(request, rs, - hin->blinding, &hin->blinding_ss, + request->payload = onion_decode(request, + feature_offered(ld->our_features->bits[INIT_FEATURE], + OPT_ROUTE_BLINDING), + rs, + hin->blinding, ld->accept_extra_tlv_types, + hin->msat, + hin->cltv_expiry, &request->failtlvtype, &request->failtlvpos); - } else - payload = NULL; + } fwdtok = json_get_member(buffer, toks, "forward_to"); if (fwdtok) { @@ -1051,6 +1078,9 @@ static void htlc_accepted_hook_serialize(struct htlc_accepted_hook_payload *p, if (p->payload->forward_channel) json_add_short_channel_id(s, "short_channel_id", p->payload->forward_channel); + if (p->payload->forward_node_id) + json_add_pubkey(s, "next_node_id", + p->payload->forward_node_id); if (deprecated_apis) json_add_string(s, "forward_amount", fmt_amount_msat(tmpctx, @@ -1138,17 +1168,17 @@ htlc_accepted_hook_final(struct htlc_accepted_hook_payload *request STEALS) /* Apply tweak to ephemeral key if blinding is non-NULL, then do ECDH */ static bool ecdh_maybe_blinding(const struct pubkey *ephemeral_key, const struct pubkey *blinding, - const struct secret *blinding_ss, struct secret *ss) { struct pubkey point = *ephemeral_key; -#if EXPERIMENTAL_FEATURES if (blinding) { struct secret hmac; + struct secret blinding_ss; + ecdh(blinding, &blinding_ss); /* b(i) = HMAC256("blinded_node_id", ss(i)) * k(i) */ - subkey_from_hmac("blinded_node_id", blinding_ss, &hmac); + subkey_from_hmac("blinded_node_id", &blinding_ss, &hmac); /* We instead tweak the *ephemeral* key from the onion and use * our normal privkey: since hsmd knows only how to ECDH with @@ -1159,7 +1189,6 @@ static bool ecdh_maybe_blinding(const struct pubkey *ephemeral_key, return false; } } -#endif /* EXPERIMENTAL_FEATURES */ ecdh(&point, ss); return true; } @@ -1177,20 +1206,42 @@ static struct channel_id *calc_forwarding_channel(struct lightningd *ld, const struct route_step *rs) { const struct onion_payload *p = hp->payload; + struct peer *peer; struct channel *c, *best; if (rs->nextcase != ONION_FORWARD) return NULL; - if (!p || !p->forward_channel) + if (!p) return NULL; - c = any_channel_by_scid(ld, p->forward_channel, false); - if (!c) - return NULL; - - best = best_channel(ld, c->peer, p->amt_to_forward, c); - if (best != c) { + if (p->forward_channel) { + c = any_channel_by_scid(ld, p->forward_channel, false); + if (!c) + return NULL; + peer = c->peer; + } else { + struct node_id id; + if (!p->forward_node_id) + return NULL; + node_id_from_pubkey(&id, p->forward_node_id); + peer = peer_by_id(ld, &id); + if (!peer) + return NULL; + c = NULL; + } + + best = best_channel(ld, peer, p->amt_to_forward, c); + if (!c) { + if (!best) + return NULL; + log_debug(hp->channel->log, + "Chose channel %s for peer %s", + type_to_string(tmpctx, struct short_channel_id, + channel_scid_or_local_alias(best)), + type_to_string(tmpctx, struct node_id, + &peer->id)); + } else if (best != c) { log_debug(hp->channel->log, "Chose a better channel than %s: %s", type_to_string(tmpctx, struct short_channel_id, @@ -1226,6 +1277,10 @@ static bool peer_accepted_htlc(const tal_t *ctx, struct onionpacket *op; struct lightningd *ld = channel->peer->ld; struct htlc_accepted_hook_payload *hook_payload; + const bool opt_blinding + = feature_offered(ld->our_features->bits[INIT_FEATURE], + OPT_ROUTE_BLINDING); + *failmsg = NULL; *badonion = 0; @@ -1312,9 +1367,14 @@ static bool peer_accepted_htlc(const tal_t *ctx, hook_payload = tal(NULL, struct htlc_accepted_hook_payload); hook_payload->route_step = tal_steal(hook_payload, rs); - hook_payload->payload = onion_decode(hook_payload, rs, - hin->blinding, &hin->blinding_ss, + hook_payload->payload = onion_decode(hook_payload, + feature_offered(ld->our_features->bits[INIT_FEATURE], + OPT_ROUTE_BLINDING), + rs, + hin->blinding, ld->accept_extra_tlv_types, + hin->msat, + hin->cltv_expiry, &hook_payload->failtlvtype, &hook_payload->failtlvpos); hook_payload->ld = ld; @@ -1322,9 +1382,9 @@ static bool peer_accepted_htlc(const tal_t *ctx, hook_payload->channel = channel; hook_payload->next_onion = serialize_onionpacket(hook_payload, rs->next); -#if EXPERIMENTAL_FEATURES /* We could have blinding from hin or from inside onion. */ - if (hook_payload->payload && hook_payload->payload->blinding) { + if (opt_blinding + && hook_payload->payload && hook_payload->payload->blinding) { struct sha256 sha; blinding_hash_e_and_ss(hook_payload->payload->blinding, &hook_payload->payload->blinding_ss, @@ -1333,7 +1393,6 @@ static bool peer_accepted_htlc(const tal_t *ctx, blinding_next_pubkey(hook_payload->payload->blinding, &sha, hook_payload->next_blinding); } else -#endif hook_payload->next_blinding = NULL; /* The scid is merely used to indicate the next peer, it is not @@ -1352,13 +1411,11 @@ static bool peer_accepted_htlc(const tal_t *ctx, return true; fail: -#if EXPERIMENTAL_FEATURES /* In a blinded path, *all* failures are "invalid_onion_blinding" */ if (hin->blinding) { *failmsg = tal_free(*failmsg); *badonion = WIRE_INVALID_ONION_BLINDING; } -#endif return false; } @@ -2073,7 +2130,7 @@ static bool channel_added_their_htlc(struct channel *channel, &failcode); if (op) { if (!ecdh_maybe_blinding(&op->ephemeralkey, - added->blinding, &added->blinding_ss, + added->blinding, &shared_secret)) { log_debug(channel->log, "htlc %"PRIu64 ": can't tweak pubkey", added->id); @@ -2086,7 +2143,7 @@ static bool channel_added_their_htlc(struct channel *channel, hin = new_htlc_in(channel, channel, added->id, added->amount, added->cltv_expiry, &added->payment_hash, op ? &shared_secret : NULL, - added->blinding, &added->blinding_ss, + added->blinding, added->onion_routing_packet, added->fail_immediate); diff --git a/lightningd/plugin.c b/lightningd/plugin.c index b569d28f360e..8a883e220bd8 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -180,6 +180,9 @@ static void check_plugins_initted(struct plugins *plugins) for (size_t i = 0; i < tal_count(plugin_cmds); i++) plugin_cmd_all_complete(plugins, plugin_cmds[i]); tal_free(plugin_cmds); + + if (plugins->startup) + io_break(plugins); } struct command_result *plugin_register_all_complete(struct lightningd *ld, @@ -291,6 +294,7 @@ struct plugin *plugin_register(struct plugins *plugins, const char* path TAKES, p->notification_topics = tal_arr(p, const char *, 0); p->subscriptions = NULL; p->dynamic = false; + p->non_numeric_ids = false; p->index = plugins->plugin_idx++; p->log = new_log(p, plugins->log_book, NULL, "plugin-%s", p->shortname); @@ -1142,7 +1146,8 @@ static struct command_result *plugin_rpcmethod_dispatch(struct command *cmd, call = tal(plugin, struct plugin_rpccall); call->cmd = cmd; - req = jsonrpc_request_start_raw(plugin, cmd->json_cmd->name, cmd->id, + req = jsonrpc_request_start_raw(plugin, cmd->json_cmd->name, + cmd->id, plugin->non_numeric_ids, plugin->log, plugin_notify_cb, plugin_rpcmethod_cb, call); @@ -1151,7 +1156,7 @@ static struct command_result *plugin_rpcmethod_dispatch(struct command *cmd, list_add_tail(&plugin->pending_rpccalls, &call->list); json_stream_forward_change_id(req->stream, buffer, toks, idtok, req->id, - true); + req->id_is_string); json_stream_double_cr(req->stream); plugin_request_send(plugin, req); req->stream = NULL; @@ -1528,6 +1533,20 @@ static const char *plugin_parse_getmanifest_response(const char *buffer, } } + tok = json_get_member(buffer, resulttok, "nonnumericids"); + if (tok) { + if (!json_to_bool(buffer, tok, &plugin->non_numeric_ids)) + return tal_fmt(plugin, + "Invalid nonnumericids: %.*s", + json_tok_full_len(tok), + json_tok_full(buffer, tok)); + if (!deprecated_apis && !plugin->non_numeric_ids) + return tal_fmt(plugin, + "Plugin does not allow nonnumericids"); + } else + /* Default is false in deprecated mode */ + plugin->non_numeric_ids = !deprecated_apis; + err = plugin_notifications_add(buffer, resulttok, plugin); if (!err) err = plugin_opts_add(plugin, buffer, resulttok); @@ -1730,8 +1749,8 @@ const char *plugin_send_getmanifest(struct plugin *p, const char *cmd_id) * write-only on p->stdin */ p->stdout_conn = io_new_conn(p, stdoutfd, plugin_stdout_conn_init, p); p->stdin_conn = io_new_conn(p, stdinfd, plugin_stdin_conn_init, p); - req = jsonrpc_request_start(p, "getmanifest", cmd_id, p->log, - NULL, plugin_manifest_cb, p); + req = jsonrpc_request_start(p, "getmanifest", cmd_id, p->non_numeric_ids, + p->log, NULL, plugin_manifest_cb, p); json_add_bool(req->stream, "allow-deprecated-apis", deprecated_apis); jsonrpc_request_end(req); plugin_request_send(p, req); @@ -1911,15 +1930,15 @@ plugin_config(struct plugin *plugin) struct jsonrpc_request *req; plugin_set_timeout(plugin); - req = jsonrpc_request_start(plugin, "init", NULL, plugin->log, - NULL, plugin_config_cb, plugin); + req = jsonrpc_request_start(plugin, "init", NULL, plugin->non_numeric_ids, + plugin->log, NULL, plugin_config_cb, plugin); plugin_populate_init_request(plugin, req); jsonrpc_request_end(req); plugin_request_send(plugin, req); plugin->plugin_state = AWAITING_INIT_RESPONSE; } -void plugins_config(struct plugins *plugins) +bool plugins_config(struct plugins *plugins) { struct plugin *p; list_for_each(&plugins->plugins, p, list) { @@ -1927,7 +1946,17 @@ void plugins_config(struct plugins *plugins) plugin_config(p); } + /* Wait for them to configure, before continuing: large + * nodes can take a while to startup! */ + if (plugins->startup) { + /* This happens if an important plugin fails init, + * or if they call shutdown now. */ + if (io_loop_with_timers(plugins->ld) == plugins->ld) + return false; + } + plugins->startup = false; + return true; } /** json_add_opt_plugins_array diff --git a/lightningd/plugin.h b/lightningd/plugin.h index fbcfbf486645..cc6c5d5bd850 100644 --- a/lightningd/plugin.h +++ b/lightningd/plugin.h @@ -81,6 +81,9 @@ struct plugin { * C-lightning should terminate as well. */ bool important; + /* Can this handle non-numeric JSON ids? */ + bool non_numeric_ids; + /* Parameters for dynamically-started plugins. */ const char *parambuf; const jsmntok_t *params; @@ -262,8 +265,11 @@ struct command_result *plugin_register_all_complete(struct lightningd *ld, * and send them over to the plugin. This finalizes the initialization * of the plugins and signals that lightningd is now ready to process * incoming JSON-RPC calls and messages. + * + * It waits for plugins to be initialized, but returns false if we + * should exit (an important plugin failed, or we got a shutdown command). */ -void plugins_config(struct plugins *plugins); +bool plugins_config(struct plugins *plugins); /** * This populates the jsonrpc request with the plugin/lightningd specifications diff --git a/lightningd/plugin_hook.c b/lightningd/plugin_hook.c index d167d3a1825f..4b90a57d7af7 100644 --- a/lightningd/plugin_hook.c +++ b/lightningd/plugin_hook.c @@ -235,6 +235,7 @@ static void plugin_hook_call_next(struct plugin_hook_request *ph_req) log_debug(ph_req->ld->log, "Calling %s hook of plugin %s", ph_req->hook->name, ph_req->plugin->shortname); req = jsonrpc_request_start(NULL, hook->name, ph_req->cmd_id, + ph_req->plugin->non_numeric_ids, plugin_get_log(ph_req->plugin), NULL, plugin_hook_callback, ph_req); @@ -380,7 +381,9 @@ void plugin_hook_db_sync(struct db *db) /* FIXME: id_prefix from caller? */ /* FIXME: do IO logging for this! */ - req = jsonrpc_request_start(NULL, hook->name, NULL, NULL, NULL, + req = jsonrpc_request_start(NULL, hook->name, NULL, + dwh_req->plugin->non_numeric_ids, + NULL, NULL, db_hook_response, dwh_req); diff --git a/lightningd/signmessage.c b/lightningd/signmessage.c index 59862caad24d..5a008a425a46 100644 --- a/lightningd/signmessage.c +++ b/lightningd/signmessage.c @@ -211,17 +211,18 @@ static struct command_result *json_checkmessage(struct command *cmd, node_id_from_pubkey(&can->id, &reckey); can->cmd = cmd; - req = jsonrpc_request_start(cmd, "listnodes", - cmd->id, - command_log(cmd), - NULL, listnodes_done, - can); - json_add_node_id(req->stream, "id", &can->id); - jsonrpc_request_end(req); /* Only works if we have listnodes! */ plugin = find_plugin_for_command(cmd->ld, "listnodes"); if (plugin) { + req = jsonrpc_request_start(cmd, "listnodes", + cmd->id, + plugin->non_numeric_ids, + command_log(cmd), + NULL, listnodes_done, + can); + json_add_node_id(req->stream, "id", &can->id); + jsonrpc_request_end(req); plugin_request_send(plugin, req); return command_still_pending(cmd); } diff --git a/lightningd/test/Makefile b/lightningd/test/Makefile index f9b10b4662c7..b2fc992305ac 100644 --- a/lightningd/test/Makefile +++ b/lightningd/test/Makefile @@ -29,7 +29,7 @@ LIGHTNINGD_TEST_COMMON_OBJS := \ common/permute_tx.o \ common/wireaddr.o \ -$(LIGHTNINGD_TEST_PROGRAMS): $(CCAN_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(LIGHTNINGD_TEST_COMMON_OBJS) +$(LIGHTNINGD_TEST_PROGRAMS): $(CCAN_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(LIGHTNINGD_TEST_COMMON_OBJS) $(WIRE_BOLT12_OBJS) $(LIGHTNINGD_TEST_OBJS): $(LIGHTNINGD_HDRS) $(LIGHTNINGD_SRC) $(LIGHTNINGD_SRC_NOHDR) diff --git a/lightningd/test/run-find_my_abspath.c b/lightningd/test/run-find_my_abspath.c index 85fd5fcf9029..37547757fdc9 100644 --- a/lightningd/test/run-find_my_abspath.c +++ b/lightningd/test/run-find_my_abspath.c @@ -168,7 +168,7 @@ struct chain_topology *new_topology(struct lightningd *ld UNNEEDED, struct log * void onchaind_replay_channels(struct lightningd *ld UNNEEDED) { fprintf(stderr, "onchaind_replay_channels called!\n"); abort(); } /* Generated stub for plugins_config */ -void plugins_config(struct plugins *plugins UNNEEDED) +bool plugins_config(struct plugins *plugins UNNEEDED) { fprintf(stderr, "plugins_config called!\n"); abort(); } /* Generated stub for plugins_init */ void plugins_init(struct plugins *plugins UNNEEDED) diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 95b27e1c32ce..2497a8647208 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -182,6 +182,15 @@ char *encode_scriptpubkey_to_addr(const tal_t *ctx UNNEEDED, const struct chainparams *chainparams UNNEEDED, const u8 *scriptPubkey UNNEEDED) { fprintf(stderr, "encode_scriptpubkey_to_addr called!\n"); abort(); } +/* Generated stub for encrypt_tlv_encrypted_data */ +u8 *encrypt_tlv_encrypted_data(const tal_t *ctx UNNEEDED, + const struct privkey *blinding UNNEEDED, + const struct pubkey *node UNNEEDED, + const struct tlv_encrypted_data_tlv *tlv UNNEEDED, + struct privkey *next_blinding UNNEEDED, + struct pubkey *node_alias) + +{ fprintf(stderr, "encrypt_tlv_encrypted_data called!\n"); abort(); } /* Generated stub for failmsg_incorrect_or_unknown_ */ const u8 *failmsg_incorrect_or_unknown_(const tal_t *ctx UNNEEDED, struct lightningd *ld UNNEEDED, @@ -306,6 +315,14 @@ struct tlv_invoice *invoice_decode_nosig(const tal_t *ctx UNNEEDED, /* Generated stub for invoice_encode */ char *invoice_encode(const tal_t *ctx UNNEEDED, const struct tlv_invoice *bolt12_tlv UNNEEDED) { fprintf(stderr, "invoice_encode called!\n"); abort(); } +/* Generated stub for invoice_offer_id */ +void invoice_offer_id(const struct tlv_invoice *invoice UNNEEDED, struct sha256 *id UNNEEDED) +{ fprintf(stderr, "invoice_offer_id called!\n"); abort(); } +/* Generated stub for invoice_path_id */ +u8 *invoice_path_id(const tal_t *ctx UNNEEDED, + const struct secret *base_secret UNNEEDED, + const struct sha256 *payment_hash UNNEEDED) +{ fprintf(stderr, "invoice_path_id called!\n"); abort(); } /* Generated stub for json_add_address */ void json_add_address(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, const struct wireaddr *addr UNNEEDED) @@ -500,7 +517,9 @@ void jsonrpc_request_end(struct jsonrpc_request *request UNNEEDED) /* Generated stub for jsonrpc_request_start_ */ struct jsonrpc_request *jsonrpc_request_start_( const tal_t *ctx UNNEEDED, const char *method UNNEEDED, - const char *id_prefix TAKES UNNEEDED, struct log *log UNNEEDED, bool add_header UNNEEDED, + const char *id_prefix TAKES UNNEEDED, + bool id_as_string UNNEEDED, + struct log *log UNNEEDED, bool add_header UNNEEDED, void (*notify_cb)(const char *buffer UNNEEDED, const jsmntok_t *idtok UNNEEDED, const jsmntok_t *methodtok UNNEEDED, @@ -687,6 +706,9 @@ bool plugin_hook_call_(struct lightningd *ld UNNEEDED, void plugin_request_send(struct plugin *plugin UNNEEDED, struct jsonrpc_request *req TAKES UNNEEDED) { fprintf(stderr, "plugin_request_send called!\n"); abort(); } +/* Generated stub for pubkey_from_node_id */ +bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); } /* Generated stub for report_subd_memleak */ void report_subd_memleak(struct leak_detect *leak_detect UNNEEDED, struct subd *leaker UNNEEDED) { fprintf(stderr, "report_subd_memleak called!\n"); abort(); } diff --git a/lightningd/test/run-jsonrpc.c b/lightningd/test/run-jsonrpc.c index 8a0a3f9b6028..0919a3a93599 100644 --- a/lightningd/test/run-jsonrpc.c +++ b/lightningd/test/run-jsonrpc.c @@ -1,4 +1,5 @@ #include "config.h" +#include "../../common/json_filter.c" #include "../../common/json_stream.c" #include "../jsonrpc.c" #include "../feerate.c" diff --git a/onchaind/onchaind.c b/onchaind/onchaind.c index 937235cd74e5..d72724a21e88 100644 --- a/onchaind/onchaind.c +++ b/onchaind/onchaind.c @@ -43,7 +43,7 @@ static u32 delayed_to_us_feerate; static u32 htlc_feerate; /* The feerate for transactions spending from revoked transactions. */ -static u32 penalty_feerate; +static u32 penalty_feerate, max_penalty_feerate; /* Min and max feerates we ever used */ static u32 min_possible_feerate, max_possible_feerate; @@ -878,10 +878,19 @@ compute_penalty_output_amount(struct amount_sat initial_amount, struct amount_sat max_output_amount; struct amount_sat output_amount; struct amount_sat deducted_amount; + struct amount_sat min_output_amount, max_fee; assert(depth <= max_depth); assert(depth > 0); + /* We never pay more than max_penalty_feerate; at some point, + * it's clearly not working. */ + max_fee = amount_tx_fee(max_penalty_feerate, weight); + if (!amount_sat_sub(&min_output_amount, initial_amount, max_fee)) + /* We may just donate the whole output as fee, meaning + * we get zero amount. */ + min_output_amount = AMOUNT_SAT(0); + /* The difference between initial_amount, and the fee suggested * by min_rbf_bump, is the largest allowed output amount. * @@ -892,11 +901,7 @@ compute_penalty_output_amount(struct amount_sat initial_amount, */ if (!amount_sat_sub(&max_output_amount, initial_amount, min_rbf_bump(weight, depth - 1))) - /* If min_rbf_bump is larger than the initial_amount, - * we should just donate the whole output as fee, - * meaning we get 0 output amount. - */ - return AMOUNT_SAT(0); + return min_output_amount; /* Map the depth / max_depth into a number between 0->1. */ double x = (double) depth / (double) max_depth; @@ -910,9 +915,14 @@ compute_penalty_output_amount(struct amount_sat initial_amount, /* output_amount = initial_amount - deducted_amount. */ if (!amount_sat_sub(&output_amount, - initial_amount, deducted_amount)) - /* If underflow, force to 0. */ - output_amount = AMOUNT_SAT(0); + initial_amount, deducted_amount)) { + /* If underflow, force to min. */ + output_amount = min_output_amount; + } + + /* If output below min, return min. */ + if (amount_sat_less(output_amount, min_output_amount)) + return min_output_amount; /* If output exceeds max, return max. */ if (amount_sat_less(max_output_amount, output_amount)) @@ -3908,6 +3918,7 @@ int main(int argc, char *argv[]) &delayed_to_us_feerate, &htlc_feerate, &penalty_feerate, + &max_penalty_feerate, &dust_limit, &our_broadcast_txid, &scriptpubkey[LOCAL], diff --git a/onchaind/onchaind_wire.csv b/onchaind/onchaind_wire.csv index e2dfd031620a..f6f3776a37c8 100644 --- a/onchaind/onchaind_wire.csv +++ b/onchaind/onchaind_wire.csv @@ -23,6 +23,7 @@ msgdata,onchaind_init,remote_to_self_delay,u32, msgdata,onchaind_init,delayed_to_us_feerate,u32, msgdata,onchaind_init,htlc_feerate,u32, msgdata,onchaind_init,penalty_feerate,u32, +msgdata,onchaind_init,max_penalty_feerate,u32, msgdata,onchaind_init,local_dust_limit_satoshi,amount_sat, # Gives an easy way to tell if it's our unilateral close or theirs... msgdata,onchaind_init,our_broadcast_txid,bitcoin_txid, diff --git a/onchaind/test/run-grind_feerate-bug.c b/onchaind/test/run-grind_feerate-bug.c index 34917cb3a753..0c069752d0c9 100644 --- a/onchaind/test/run-grind_feerate-bug.c +++ b/onchaind/test/run-grind_feerate-bug.c @@ -49,7 +49,7 @@ bool fromwire_onchaind_dev_memleak(const void *p UNNEEDED) bool fromwire_onchaind_htlcs(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct htlc_stub **htlc UNNEEDED, bool **tell_if_missing UNNEEDED, bool **tell_immediately UNNEEDED) { fprintf(stderr, "fromwire_onchaind_htlcs called!\n"); abort(); } /* Generated stub for fromwire_onchaind_init */ -bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct shachain *shachain UNNEEDED, const struct chainparams **chainparams UNNEEDED, struct amount_sat *funding_amount_satoshi UNNEEDED, struct amount_msat *our_msat UNNEEDED, struct pubkey *old_remote_per_commitment_point UNNEEDED, struct pubkey *remote_per_commitment_point UNNEEDED, u32 *local_to_self_delay UNNEEDED, u32 *remote_to_self_delay UNNEEDED, u32 *delayed_to_us_feerate UNNEEDED, u32 *htlc_feerate UNNEEDED, u32 *penalty_feerate UNNEEDED, struct amount_sat *local_dust_limit_satoshi UNNEEDED, struct bitcoin_txid *our_broadcast_txid UNNEEDED, u8 **local_scriptpubkey UNNEEDED, u8 **remote_scriptpubkey UNNEEDED, u32 *ourwallet_index UNNEEDED, struct ext_key *ourwallet_ext_key UNNEEDED, struct pubkey *ourwallet_pubkey UNNEEDED, enum side *opener UNNEEDED, struct basepoints *local_basepoints UNNEEDED, struct basepoints *remote_basepoints UNNEEDED, struct tx_parts **tx_parts UNNEEDED, u32 *locktime UNNEEDED, u32 *tx_blockheight UNNEEDED, u32 *reasonable_depth UNNEEDED, struct bitcoin_signature **htlc_signature UNNEEDED, u32 *min_possible_feerate UNNEEDED, u32 *max_possible_feerate UNNEEDED, struct pubkey **possible_remote_per_commit_point UNNEEDED, struct pubkey *local_funding_pubkey UNNEEDED, struct pubkey *remote_funding_pubkey UNNEEDED, u64 *local_static_remotekey_start UNNEEDED, u64 *remote_static_remotekey_start UNNEEDED, bool *option_anchor_outputs UNNEEDED, u32 *min_relay_feerate UNNEEDED) +bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct shachain *shachain UNNEEDED, const struct chainparams **chainparams UNNEEDED, struct amount_sat *funding_amount_satoshi UNNEEDED, struct amount_msat *our_msat UNNEEDED, struct pubkey *old_remote_per_commitment_point UNNEEDED, struct pubkey *remote_per_commitment_point UNNEEDED, u32 *local_to_self_delay UNNEEDED, u32 *remote_to_self_delay UNNEEDED, u32 *delayed_to_us_feerate UNNEEDED, u32 *htlc_feerate UNNEEDED, u32 *penalty_feerate UNNEEDED, u32 *max_penalty_feerate UNNEEDED, struct amount_sat *local_dust_limit_satoshi UNNEEDED, struct bitcoin_txid *our_broadcast_txid UNNEEDED, u8 **local_scriptpubkey UNNEEDED, u8 **remote_scriptpubkey UNNEEDED, u32 *ourwallet_index UNNEEDED, struct ext_key *ourwallet_ext_key UNNEEDED, struct pubkey *ourwallet_pubkey UNNEEDED, enum side *opener UNNEEDED, struct basepoints *local_basepoints UNNEEDED, struct basepoints *remote_basepoints UNNEEDED, struct tx_parts **tx_parts UNNEEDED, u32 *locktime UNNEEDED, u32 *tx_blockheight UNNEEDED, u32 *reasonable_depth UNNEEDED, struct bitcoin_signature **htlc_signature UNNEEDED, u32 *min_possible_feerate UNNEEDED, u32 *max_possible_feerate UNNEEDED, struct pubkey **possible_remote_per_commit_point UNNEEDED, struct pubkey *local_funding_pubkey UNNEEDED, struct pubkey *remote_funding_pubkey UNNEEDED, u64 *local_static_remotekey_start UNNEEDED, u64 *remote_static_remotekey_start UNNEEDED, bool *option_anchor_outputs UNNEEDED, u32 *min_relay_feerate UNNEEDED) { fprintf(stderr, "fromwire_onchaind_init called!\n"); abort(); } /* Generated stub for fromwire_onchaind_known_preimage */ bool fromwire_onchaind_known_preimage(const void *p UNNEEDED, struct preimage *preimage UNNEEDED) diff --git a/onchaind/test/run-grind_feerate.c b/onchaind/test/run-grind_feerate.c index 057ef558c99e..7eb8d65166a6 100644 --- a/onchaind/test/run-grind_feerate.c +++ b/onchaind/test/run-grind_feerate.c @@ -54,7 +54,7 @@ bool fromwire_onchaind_dev_memleak(const void *p UNNEEDED) bool fromwire_onchaind_htlcs(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct htlc_stub **htlc UNNEEDED, bool **tell_if_missing UNNEEDED, bool **tell_immediately UNNEEDED) { fprintf(stderr, "fromwire_onchaind_htlcs called!\n"); abort(); } /* Generated stub for fromwire_onchaind_init */ -bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct shachain *shachain UNNEEDED, const struct chainparams **chainparams UNNEEDED, struct amount_sat *funding_amount_satoshi UNNEEDED, struct amount_msat *our_msat UNNEEDED, struct pubkey *old_remote_per_commitment_point UNNEEDED, struct pubkey *remote_per_commitment_point UNNEEDED, u32 *local_to_self_delay UNNEEDED, u32 *remote_to_self_delay UNNEEDED, u32 *delayed_to_us_feerate UNNEEDED, u32 *htlc_feerate UNNEEDED, u32 *penalty_feerate UNNEEDED, struct amount_sat *local_dust_limit_satoshi UNNEEDED, struct bitcoin_txid *our_broadcast_txid UNNEEDED, u8 **local_scriptpubkey UNNEEDED, u8 **remote_scriptpubkey UNNEEDED, u32 *ourwallet_index UNNEEDED, struct ext_key *ourwallet_ext_key UNNEEDED, struct pubkey *ourwallet_pubkey UNNEEDED, enum side *opener UNNEEDED, struct basepoints *local_basepoints UNNEEDED, struct basepoints *remote_basepoints UNNEEDED, struct tx_parts **tx_parts UNNEEDED, u32 *locktime UNNEEDED, u32 *tx_blockheight UNNEEDED, u32 *reasonable_depth UNNEEDED, struct bitcoin_signature **htlc_signature UNNEEDED, u32 *min_possible_feerate UNNEEDED, u32 *max_possible_feerate UNNEEDED, struct pubkey **possible_remote_per_commit_point UNNEEDED, struct pubkey *local_funding_pubkey UNNEEDED, struct pubkey *remote_funding_pubkey UNNEEDED, u64 *local_static_remotekey_start UNNEEDED, u64 *remote_static_remotekey_start UNNEEDED, bool *option_anchor_outputs UNNEEDED, u32 *min_relay_feerate UNNEEDED) +bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct shachain *shachain UNNEEDED, const struct chainparams **chainparams UNNEEDED, struct amount_sat *funding_amount_satoshi UNNEEDED, struct amount_msat *our_msat UNNEEDED, struct pubkey *old_remote_per_commitment_point UNNEEDED, struct pubkey *remote_per_commitment_point UNNEEDED, u32 *local_to_self_delay UNNEEDED, u32 *remote_to_self_delay UNNEEDED, u32 *delayed_to_us_feerate UNNEEDED, u32 *htlc_feerate UNNEEDED, u32 *penalty_feerate UNNEEDED, u32 *max_penalty_feerate UNNEEDED, struct amount_sat *local_dust_limit_satoshi UNNEEDED, struct bitcoin_txid *our_broadcast_txid UNNEEDED, u8 **local_scriptpubkey UNNEEDED, u8 **remote_scriptpubkey UNNEEDED, u32 *ourwallet_index UNNEEDED, struct ext_key *ourwallet_ext_key UNNEEDED, struct pubkey *ourwallet_pubkey UNNEEDED, enum side *opener UNNEEDED, struct basepoints *local_basepoints UNNEEDED, struct basepoints *remote_basepoints UNNEEDED, struct tx_parts **tx_parts UNNEEDED, u32 *locktime UNNEEDED, u32 *tx_blockheight UNNEEDED, u32 *reasonable_depth UNNEEDED, struct bitcoin_signature **htlc_signature UNNEEDED, u32 *min_possible_feerate UNNEEDED, u32 *max_possible_feerate UNNEEDED, struct pubkey **possible_remote_per_commit_point UNNEEDED, struct pubkey *local_funding_pubkey UNNEEDED, struct pubkey *remote_funding_pubkey UNNEEDED, u64 *local_static_remotekey_start UNNEEDED, u64 *remote_static_remotekey_start UNNEEDED, bool *option_anchor_outputs UNNEEDED, u32 *min_relay_feerate UNNEEDED) { fprintf(stderr, "fromwire_onchaind_init called!\n"); abort(); } /* Generated stub for fromwire_onchaind_known_preimage */ bool fromwire_onchaind_known_preimage(const void *p UNNEEDED, struct preimage *preimage UNNEEDED) diff --git a/openingd/openingd.c b/openingd/openingd.c index e9de31a3dc74..2f0b7a08fac2 100644 --- a/openingd/openingd.c +++ b/openingd/openingd.c @@ -311,7 +311,7 @@ static void set_remote_upfront_shutdown(struct state *state, = tal_steal(state, shutdown_scriptpubkey); if (shutdown_scriptpubkey - && !valid_shutdown_scriptpubkey(shutdown_scriptpubkey, anysegwit, anchors)) + && !valid_shutdown_scriptpubkey(shutdown_scriptpubkey, anysegwit, !anchors)) peer_failed_err(state->pps, &state->channel_id, "Unacceptable upfront_shutdown_script %s", diff --git a/plugins/Cargo.toml b/plugins/Cargo.toml index 88e96b795660..cc7656e5fd0c 100644 --- a/plugins/Cargo.toml +++ b/plugins/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "cln-plugin" -version = "0.1.0" +version = "0.1.2" edition = "2021" license = "MIT" -repository = "https://github.com/ElementsProject/lightning/tree/master/plugins" description = "A CLN plugin library. Write your plugin in Rust." +homepage = "https://github.com/ElementsProject/lightning/tree/master/plugins" +repository = "https://github.com/ElementsProject/lightning" +documentation = "https://docs.rs/cln-plugin" [[example]] name = "cln-plugin-startup" @@ -16,12 +18,11 @@ bytes = "1.1.0" log = { version = "0.4.14", features = ['std'] } serde = { version = "1.0.131", features = ["derive"] } serde_json = "1.0.72" -tokio-util = { version = "0.6.9", features = ["codec"] } +tokio-util = { version = "0.7", features = ["codec"] } tokio = { version="1", features = ['io-std', 'rt', 'sync', 'macros', 'io-util'] } tokio-stream = "0.1" futures = "0.3" -cln-rpc = { path = "../cln-rpc", version = "0.1.0" } -env_logger = "0.9" +env_logger = "0.10" [dev-dependencies] tokio = { version = "1", features = ["macros", "rt-multi-thread", ] } diff --git a/plugins/Makefile b/plugins/Makefile index d9d49c23cded..87791c906519 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -1,4 +1,5 @@ PLUGIN_PAY_SRC := plugins/pay.c +PLUGIN_PAY_HEADER := PLUGIN_PAY_OBJS := $(PLUGIN_PAY_SRC:.c=.o) PLUGIN_AUTOCLEAN_SRC := plugins/autoclean.c @@ -36,7 +37,7 @@ PLUGIN_OFFERS_HEADER := $(PLUGIN_OFFERS_SRC:.c=.h) PLUGIN_FETCHINVOICE_SRC := plugins/fetchinvoice.c PLUGIN_FETCHINVOICE_OBJS := $(PLUGIN_FETCHINVOICE_SRC:.c=.o) -PLUGIN_FETCHINVOICE_HEADER := +PLUGIN_FETCHINVOICE_HEADER := PLUGIN_SPENDER_SRC := \ plugins/spender/fundchannel.c \ @@ -76,6 +77,7 @@ PLUGIN_ALL_SRC := \ $(PLUGIN_SPENDER_SRC) PLUGIN_ALL_HEADER := \ + $(PLUGIN_PAY_HEADER) \ $(PLUGIN_LIB_HEADER) \ $(PLUGIN_FUNDER_HEADER) \ $(PLUGIN_PAY_LIB_HEADER) \ @@ -138,6 +140,7 @@ PLUGIN_COMMON_OBJS := \ common/json_param.o \ common/json_parse.o \ common/json_parse_simple.o \ + common/json_filter.o \ common/json_stream.o \ common/lease_rates.o \ common/memleak.o \ @@ -169,7 +172,7 @@ ALL_PROGRAMS += $(C_PLUGINS) # Make all plugins depend on all plugin headers, for simplicity. $(PLUGIN_ALL_OBJS): $(PLUGIN_ALL_HEADER) -plugins/pay: $(PLUGIN_PAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o wire/bolt12$(EXP)_wiregen.o bitcoin/block.o +plugins/pay: $(PLUGIN_PAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o wire/bolt12$(EXP)_wiregen.o bitcoin/block.o common/blindedpay.o common/blindedpath.o common/hmac.o common/blinding.o common/onion_encode.o plugins/autoclean: $(PLUGIN_AUTOCLEAN_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) @@ -185,14 +188,14 @@ plugins/txprepare: $(PLUGIN_TXPREPARE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_O plugins/bcli: $(PLUGIN_BCLI_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) -plugins/keysend: wire/tlvstream.o wire/onion$(EXP)_wiregen.o $(PLUGIN_KEYSEND_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o +plugins/keysend: wire/tlvstream.o wire/onion$(EXP)_wiregen.o $(PLUGIN_KEYSEND_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) common/gossmap.o common/fp16.o common/route.o common/dijkstra.o common/blindedpay.o common/blindedpath.o common/hmac.o common/blinding.o common/onion_encode.o $(PLUGIN_KEYSEND_OBJS): $(PLUGIN_PAY_LIB_HEADER) plugins/spenderp: bitcoin/block.o bitcoin/preimage.o bitcoin/psbt.o common/psbt_open.o wire/peer${EXP}_wiregen.o $(PLUGIN_SPENDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) -plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o $(WIRE_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) +plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o common/blindedpath.o common/invoice_path_id.o common/blinding.o common/hmac.o $(JSMN_OBJS) -plugins/fetchinvoice: $(PLUGIN_FETCHINVOICE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/iso4217.o $(WIRE_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) common/gossmap.o common/fp16.o common/dijkstra.o common/route.o common/blindedpath.o common/hmac.o common/blinding.o +plugins/fetchinvoice: $(PLUGIN_FETCHINVOICE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) common/gossmap.o common/fp16.o common/dijkstra.o common/route.o common/blindedpath.o common/hmac.o common/blinding.o plugins/funder: bitcoin/psbt.o common/psbt_open.o $(PLUGIN_FUNDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) diff --git a/plugins/autoclean.c b/plugins/autoclean.c index 81e50352afd0..201dea4cb8da 100644 --- a/plugins/autoclean.c +++ b/plugins/autoclean.c @@ -223,7 +223,8 @@ static struct command_result *listinvoices_done(struct command *cmd, json_add_tok(req->js, "label", label, buf); json_add_tok(req->js, "status", status, buf); send_outreq(plugin, req); - } + } else + cinfo->num_uncleaned++; } if (cinfo->cleanup_reqs_remaining) @@ -324,6 +325,14 @@ static struct command_result *listforwards_done(struct command *cmd, continue; } + /* Check if we have a resolved_time, before making a + * decision on it. This is possible in older nodes + * that predate our annotations for forwards.*/ + if (json_get_member(buf, t, "resolved_time") == NULL) { + cinfo->num_uncleaned++; + continue; + } + time = *json_get_member(buf, t, "resolved_time"); /* This is a float, so truncate at '.' */ for (int off = time.start; off < time.end; off++) { diff --git a/plugins/bkpr/Makefile b/plugins/bkpr/Makefile index 47d622c84831..b55a22bb651a 100644 --- a/plugins/bkpr/Makefile +++ b/plugins/bkpr/Makefile @@ -37,7 +37,7 @@ PLUGIN_ALL_HEADER += $(BOOKKEEPER_HEADER) C_PLUGINS += plugins/bookkeeper PLUGINS += plugins/bookkeeper -plugins/bookkeeper: common/bolt12.o common/bolt12_merkle.o $(BOOKKEEPER_OBJS) $(PLUGIN_LIB_OBJS) $(JSMN_OBJTS) $(PLUGIN_COMMON_OBJS) $(WIRE_OBJS) $(DB_OBJS) +plugins/bookkeeper: common/bolt12.o common/bolt12_merkle.o $(BOOKKEEPER_OBJS) $(PLUGIN_LIB_OBJS) $(JSMN_OBJTS) $(PLUGIN_COMMON_OBJS) $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) $(DB_OBJS) # The following files contain SQL-annotated statements that we need to extact BOOKKEEPER_SQL_FILES := \ diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c index be0b256ef78c..e541d00674f4 100644 --- a/plugins/bkpr/bookkeeper.c +++ b/plugins/bkpr/bookkeeper.c @@ -1192,10 +1192,10 @@ static char *fetch_out_desc_invstr(const tal_t *ctx, const char *buf, return NULL; } - if (bolt12->description) + if (bolt12->offer_description) desc = tal_strndup(ctx, - cast_signed(char *, bolt12->description), - tal_bytelen(bolt12->description)); + cast_signed(char *, bolt12->offer_description), + tal_bytelen(bolt12->offer_description)); else desc = NULL; } else diff --git a/plugins/bkpr/test/run-bkpr_db.c b/plugins/bkpr/test/run-bkpr_db.c index a27cb93ec442..e71d7c1d4503 100644 --- a/plugins/bkpr/test/run-bkpr_db.c +++ b/plugins/bkpr/test/run-bkpr_db.c @@ -19,6 +19,7 @@ void db_fatal(const char *fmt, ...) } #endif /* DB_FATAL */ +#include "common/json_filter.c" #include "plugins/bkpr/db.c" #include "plugins/libplugin.c" diff --git a/plugins/bkpr/test/run-recorder.c b/plugins/bkpr/test/run-recorder.c index c3e4494264bc..7872800bee16 100644 --- a/plugins/bkpr/test/run-recorder.c +++ b/plugins/bkpr/test/run-recorder.c @@ -1,4 +1,5 @@ #include "config.h" +#include "common/json_filter.c" #include "test_utils.h" #include "plugins/libplugin.c" diff --git a/plugins/examples/cln-plugin-startup.rs b/plugins/examples/cln-plugin-startup.rs index 3fdd6bdcf358..b283bbc9a03b 100644 --- a/plugins/examples/cln-plugin-startup.rs +++ b/plugins/examples/cln-plugin-startup.rs @@ -14,7 +14,17 @@ async fn main() -> Result<(), anyhow::Error> { options::Value::Integer(42), "a test-option with default 42", )) + .option(options::ConfigOption::new( + "opt-option", + options::Value::OptInteger, + "An optional option", + )) .rpcmethod("testmethod", "This is a test", testmethod) + .rpcmethod( + "testoptions", + "Retrieve options from this plugin", + testoptions, + ) .subscribe("connect", connect_handler) .hook("peer_connected", peer_connected_handler) .start(state) @@ -26,6 +36,12 @@ async fn main() -> Result<(), anyhow::Error> { } } +async fn testoptions(p: Plugin<()>, _v: serde_json::Value) -> Result { + Ok(json!({ + "opt-option": format!("{:?}", p.option("opt-option").unwrap()) + })) +} + async fn testmethod(_p: Plugin<()>, _v: serde_json::Value) -> Result { Ok(json!("Hello")) } diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 8c587d60d05c..a70577065ce8 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -29,8 +29,8 @@ static LIST_HEAD(sent_list); struct sent { /* We're in sent_invreqs, awaiting reply. */ struct list_node list; - /* The alias used by reply */ - struct pubkey *reply_alias; + /* The secret used by reply */ + struct secret *reply_secret; /* The command which sent us. */ struct command *cmd; /* The offer we are trying to get an invoice/payment for. */ @@ -48,48 +48,17 @@ struct sent { u32 wait_timeout; }; -static struct sent *find_sent_by_alias(const struct pubkey *alias) +static struct sent *find_sent_by_secret(const struct secret *pathsecret) { struct sent *i; list_for_each(&sent_list, i, list) { - if (i->reply_alias && pubkey_eq(i->reply_alias, alias)) + if (i->reply_secret && secret_eq_consttime(i->reply_secret, pathsecret)) return i; } return NULL; } -static const char *field_diff_(struct plugin *plugin, - const tal_t *a, const tal_t *b, - const char *fieldname) -{ - /* One is set and the other isn't? */ - if ((a == NULL) != (b == NULL)) { - plugin_log(plugin, LOG_DBG, "field_diff %s: a is %s, b is %s", - fieldname, a ? "set": "unset", b ? "set": "unset"); - return fieldname; - } - if (!memeq(a, tal_bytelen(a), b, tal_bytelen(b))) { - plugin_log(plugin, LOG_DBG, "field_diff %s: a=%s, b=%s", - fieldname, tal_hex(tmpctx, a), tal_hex(tmpctx, b)); - return fieldname; - } - return NULL; -} - -#define field_diff(a, b, fieldname) \ - field_diff_((cmd)->plugin, a->fieldname, b->fieldname, #fieldname) - -/* Returns true if b is a with something appended. */ -static bool description_is_appended(const char *a, const char *b) -{ - if (!a || !b) - return false; - if (tal_bytelen(b) < tal_bytelen(a)) - return false; - return memeq(a, tal_bytelen(a), b, tal_bytelen(a)); -} - /* Hack to suppress warnings when we finish a different command */ static void discard_result(struct command_result *ret) { @@ -155,12 +124,33 @@ static struct command_result *handle_error(struct command *cmd, return command_hook_success(cmd); } +/* BOLT-offers #12: + * - if the invoice is a response to an `invoice_request`: + * - MUST reject the invoice if all fields less than type 160 do not + * exactly match the `invoice_request`. + */ +static bool invoice_matches_request(struct command *cmd, + const u8 *invbin, + const struct tlv_invoice_request *invreq) +{ + size_t len1, len2; + u8 *wire; + + /* We linearize then strip signature. This is dumb! */ + wire = tal_arr(tmpctx, u8, 0); + towire_tlv_invoice_request(&wire, invreq); + len1 = tlv_span(wire, 0, 159, NULL); + + len2 = tlv_span(invbin, 0, 159, NULL); + return memeq(wire, len1, invbin, len2); +} + static struct command_result *handle_invreq_response(struct command *cmd, struct sent *sent, const char *buf, const jsmntok_t *om) { - const u8 *invbin; + const u8 *invbin, *cursor; const jsmntok_t *invtok; size_t len; struct tlv_invoice *inv; @@ -184,8 +174,9 @@ static struct command_result *handle_invreq_response(struct command *cmd, } invbin = json_tok_bin_from_hex(cmd, buf, invtok); + cursor = invbin; len = tal_bytelen(invbin); - inv = fromwire_tlv_invoice(cmd, &invbin, &len); + inv = fromwire_tlv_invoice(cmd, &cursor, &len); if (!inv) { badfield = "invoice"; goto badinv; @@ -202,89 +193,62 @@ static struct command_result *handle_invreq_response(struct command *cmd, #endif /* DEVELOPER */ /* BOLT-offers #12: - * - MUST reject the invoice unless `node_id` is equal to the offer. + * - if the invoice is a response to an `invoice_request`: + * - MUST reject the invoice if all fields less than type 160 do not + * exactly match the `invoice_request`. + */ + if (!invoice_matches_request(cmd, invbin, sent->invreq)) { + badfield = "invoice_request match"; + goto badinv; + } + + /* BOLT-offers #12: + * - if `offer_node_id` is present (invoice_request for an offer): + * - MUST reject the invoice if `invoice_node_id` is not equal to `offer_node_id`. */ - if (!point32_eq(sent->offer->node_id, inv->node_id)) { - badfield = "node_id"; + if (!inv->invoice_node_id || !pubkey_eq(inv->offer_node_id, inv->invoice_node_id)) { + badfield = "invoice_node_id"; goto badinv; } /* BOLT-offers #12: * - MUST reject the invoice if `signature` is not a valid signature - * using `node_id` as described in [Signature Calculation] + * using `invoice_node_id` as described in [Signature Calculation] */ merkle_tlv(inv->fields, &merkle); sighash_from_merkle("invoice", "signature", &merkle, &sighash); if (!inv->signature - || secp256k1_schnorrsig_verify(secp256k1_ctx, inv->signature->u8, - sighash.u.u8, sizeof(sighash.u.u8), &inv->node_id->pubkey) != 1) { + || !check_schnorr_sig(&sighash, &inv->invoice_node_id->pubkey, inv->signature)) { badfield = "signature"; goto badinv; } /* BOLT-offers #12: - * - MUST reject the invoice if `msat` is not present. + * A reader of an invoice: + * - MUST reject the invoice if `invoice_amount` is not present. */ - if (!inv->amount) { - badfield = "amount"; + if (!inv->invoice_amount) { + badfield = "invoice_amount"; goto badinv; } - /* BOLT-offers #12: - * - MUST reject the invoice unless `offer_id` is equal to the id of the - * offer. - */ - if ((badfield = field_diff(sent->invreq, inv, offer_id))) - goto badinv; - - /* BOLT-offers #12: - * - if the invoice is a reply to an `invoice_request`: - *... - * - MUST reject the invoice unless the following fields are equal or - * unset exactly as they are in the `invoice_request:` - * - `quantity` - * - `payer_key` - * - `payer_info` - */ - /* BOLT-offers-recurrence #12: - * - if the invoice is a reply to an `invoice_request`: - *... - * - MUST reject the invoice unless the following fields are equal or - * unset exactly as they are in the `invoice_request:` - * - `quantity` - * - `recurrence_counter` - * - `recurrence_start` - * - `payer_key` - * - `payer_info` - */ - if ((badfield = field_diff(sent->invreq, inv, quantity))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, recurrence_counter))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, recurrence_start))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, payer_key))) - goto badinv; - if ((badfield = field_diff(sent->invreq, inv, payer_info))) - goto badinv; - /* Get the amount we expected: firstly, if that's what we sent, * secondly, if specified in the invoice. */ - if (sent->invreq->amount) { - expected_amount = tal_dup(tmpctx, u64, sent->invreq->amount); - } else if (sent->offer->amount && !sent->offer->currency) { + if (inv->invreq_amount) { + expected_amount = tal_dup(tmpctx, u64, inv->invreq_amount); + } else if (inv->offer_amount && !inv->offer_currency) { expected_amount = tal(tmpctx, u64); - *expected_amount = *sent->offer->amount; - if (sent->invreq->quantity) { + *expected_amount = *inv->offer_amount; + if (inv->invreq_quantity) { /* We should never have sent this! */ if (mul_overflows_u64(*expected_amount, - *sent->invreq->quantity)) { + *inv->invreq_quantity)) { badfield = "quantity overflow"; goto badinv; } - *expected_amount *= *sent->invreq->quantity; + *expected_amount *= *inv->invreq_quantity; } } else expected_amount = NULL; @@ -293,97 +257,56 @@ static struct command_result *handle_invreq_response(struct command *cmd, * - if the offer contained `recurrence`: * - MUST reject the invoice if `recurrence_basetime` is not set. */ - if (sent->invreq->recurrence_counter && !inv->recurrence_basetime) { + if (inv->invreq_recurrence_counter && !inv->invoice_recurrence_basetime) { badfield = "recurrence_basetime"; goto badinv; } - /* BOLT-offers #12: - * - SHOULD confirm authorization if the `description` does not exactly - * match the `offer` - * - MAY highlight if `description` has simply had a change appended. - */ - /* We highlight these changes to the caller, for them to handle */ out = jsonrpc_stream_success(sent->cmd); json_add_string(out, "invoice", invoice_encode(tmpctx, inv)); json_object_start(out, "changes"); - if (field_diff(sent->offer, inv, description)) { - /* Did they simply append? */ - if (description_is_appended(sent->offer->description, - inv->description)) { - size_t off = tal_bytelen(sent->offer->description); - json_add_stringn(out, "description_appended", - inv->description + off, - tal_bytelen(inv->description) - off); - } else if (!inv->description) - json_add_stringn(out, "description_removed", - sent->offer->description, - tal_bytelen(sent->offer->description)); - else - json_add_stringn(out, "description", - inv->description, - tal_bytelen(inv->description)); - } - - /* BOLT-offers #12: - * - SHOULD confirm authorization if `issuer` does not exactly - * match the `offer` - */ - if (field_diff(sent->offer, inv, issuer)) { - if (!inv->issuer) - json_add_stringn(out, "issuer_removed", - sent->offer->issuer, - tal_bytelen(sent->offer->issuer)); - else - json_add_stringn(out, "issuer", - inv->issuer, - tal_bytelen(inv->issuer)); - } /* BOLT-offers #12: - * - SHOULD confirm authorization if `msat` is not within the amount - * range authorized. + * - SHOULD confirm authorization if `invoice_amount`.`msat` is not within + * the amount range authorized. */ /* We always tell them this unless it's trivial to calc and * exactly as expected. */ - if (!expected_amount || *inv->amount != *expected_amount) { - if (deprecated_apis) - json_add_amount_msat_only(out, "msat", - amount_msat(*inv->amount)); + if (!expected_amount || *inv->invoice_amount != *expected_amount) { json_add_amount_msat_only(out, "amount_msat", - amount_msat(*inv->amount)); + amount_msat(*inv->invoice_amount)); } json_object_end(out); /* We tell them about next period at this point, if any. */ - if (sent->offer->recurrence) { + if (inv->offer_recurrence) { u64 next_counter, next_period_idx; u64 paywindow_start, paywindow_end; - next_counter = *sent->invreq->recurrence_counter + 1; - if (sent->invreq->recurrence_start) - next_period_idx = *sent->invreq->recurrence_start + next_counter = *inv->invreq_recurrence_counter + 1; + if (inv->invreq_recurrence_start) + next_period_idx = *inv->invreq_recurrence_start + next_counter; else next_period_idx = next_counter; /* If this was the last, don't tell them about a next! */ - if (!sent->offer->recurrence_limit - || next_period_idx <= *sent->offer->recurrence_limit) { + if (!inv->offer_recurrence_limit + || next_period_idx <= *inv->offer_recurrence_limit) { json_object_start(out, "next_period"); json_add_u64(out, "counter", next_counter); json_add_u64(out, "starttime", - offer_period_start(*inv->recurrence_basetime, + offer_period_start(*inv->invoice_recurrence_basetime, next_period_idx, - sent->offer->recurrence)); + inv->offer_recurrence)); json_add_u64(out, "endtime", - offer_period_start(*inv->recurrence_basetime, + offer_period_start(*inv->invoice_recurrence_basetime, next_period_idx + 1, - sent->offer->recurrence) - 1); + inv->offer_recurrence) - 1); - offer_period_paywindow(sent->offer->recurrence, - sent->offer->recurrence_paywindow, - sent->offer->recurrence_base, - *inv->recurrence_basetime, + offer_period_paywindow(inv->offer_recurrence, + inv->offer_recurrence_paywindow, + inv->offer_recurrence_base, + *inv->invoice_recurrence_basetime, next_period_idx, &paywindow_start, &paywindow_end); json_add_u64(out, "paywindow_start", paywindow_start); @@ -410,18 +333,16 @@ static struct command_result *recv_modern_onion_message(struct command *cmd, const char *buf, const jsmntok_t *params) { - const jsmntok_t *om, *aliastok; + const jsmntok_t *om, *secrettok; struct sent *sent; - struct pubkey alias; + struct secret pathsecret; struct command_result *err; om = json_get_member(buf, params, "onion_message"); - aliastok = json_get_member(buf, om, "our_alias"); - if (!aliastok || !json_to_pubkey(buf, aliastok, &alias)) - return command_hook_success(cmd); - - sent = find_sent_by_alias(&alias); + secrettok = json_get_member(buf, om, "pathsecret"); + json_to_secret(buf, secrettok, &pathsecret); + sent = find_sent_by_secret(&pathsecret); if (!sent) { plugin_log(cmd->plugin, LOG_DBG, "No match for modern onion %.*s", @@ -503,18 +424,8 @@ static struct command_result *param_offer(struct command *cmd, struct tlv_offer **offer) { char *fail; + int badf; - /* BOLT-offers #12: - * - if `features` contains unknown _odd_ bits that are non-zero: - * - MUST ignore the bit. - * - if `features` contains unknown _even_ bits that are non-zero: - * - MUST NOT respond to the offer. - * - SHOULD indicate the unknown bit to the user. - */ - /* BOLT-offers #12: - * - MUST NOT set or imply any `chain_hash` not set or implied by - * the offer. - */ *offer = offer_decode(cmd, buffer + tok->start, tok->end - tok->start, plugin_feature_set(cmd->plugin), chainparams, &fail); @@ -523,19 +434,64 @@ static struct command_result *param_offer(struct command *cmd, tal_fmt(cmd, "Unparsable offer: %s", fail)); + /* BOLT-offers #12: + * A reader of an offer: + * - if the offer contains any TLV fields greater or equal to 80: + * - MUST NOT respond to the offer. + * - if `offer_features` contains unknown _odd_ bits that are non-zero: + * - MUST ignore the bit. + * - if `offer_features` contains unknown _even_ bits that are non-zero: + * - MUST NOT respond to the offer. + * - SHOULD indicate the unknown bit to the user. + */ + for (size_t i = 0; i < tal_count((*offer)->fields); i++) { + if ((*offer)->fields[i].numtype > 80) { + return command_fail_badparam(cmd, name, buffer, tok, + tal_fmt(tmpctx, + "Offer %"PRIu64 + " field >= 80", + (*offer)->fields[i].numtype)); + } + } + + badf = features_unsupported(plugin_feature_set(cmd->plugin), + (*offer)->offer_features, + BOLT12_OFFER_FEATURE); + if (badf != -1) { + return command_fail_badparam(cmd, name, buffer, tok, + tal_fmt(tmpctx, + "unknown feature %i", + badf)); + } /* BOLT-offers #12: - * - * - if `node_id` or `description` is not set: - * - MUST NOT respond to the offer. + * - if `offer_description` is not set: + * - MUST NOT respond to the offer. + * - if `offer_node_id` is not set: + * - MUST NOT respond to the offer. */ - if (!(*offer)->node_id) + if (!(*offer)->offer_description) + return command_fail_badparam(cmd, name, buffer, tok, + "Offer does not contain a description"); + if (!(*offer)->offer_node_id) return command_fail_badparam(cmd, name, buffer, tok, "Offer does not contain a node_id"); - if (!(*offer)->description) + /* BOLT-offers #12: + * - if `offer_chains` is not set: + * - if the node does not accept bitcoin invoices: + * - MUST NOT respond to the offer + * - otherwise: (`offer_chains` is set): + * - if the node does not accept invoices for any of the `chains`: + * - MUST NOT respond to the offer + */ + if (!bolt12_chains_match((*offer)->offer_chains, + tal_count((*offer)->offer_chains), + chainparams)) { return command_fail_badparam(cmd, name, buffer, tok, - "Offer does not contain a description"); + "Offer for wrong chains"); + } + return NULL; } @@ -557,39 +513,9 @@ static bool can_carry_onionmsg(const struct gossmap *map, || gossmap_node_get_feature(map, n, 102) != -1; } -enum nodeid_parity { - nodeid_parity_even = SECP256K1_TAG_PUBKEY_EVEN, - nodeid_parity_odd = SECP256K1_TAG_PUBKEY_ODD, - nodeid_parity_unknown = 1, -}; - -static enum nodeid_parity node_parity(const struct gossmap *gossmap, - const struct gossmap_node *node) - -{ - struct node_id id; - gossmap_node_get_id(gossmap, node, &id); - return id.k[0]; -} - -static void node_id_from_point32(struct node_id *nid, - const struct point32 *node32_id, - enum nodeid_parity parity) -{ - assert(parity == SECP256K1_TAG_PUBKEY_EVEN - || parity == SECP256K1_TAG_PUBKEY_ODD); - nid->k[0] = parity; - secp256k1_xonly_pubkey_serialize(secp256k1_ctx, nid->k+1, - &node32_id->pubkey); -} - -/* Create path to node which can carry onion messages (including - * self); if it can't find one, returns NULL. Fills in nodeid_parity - * for 33rd nodeid byte. */ static struct pubkey *path_to_node(const tal_t *ctx, struct plugin *plugin, - const struct point32 *node32_id, - enum nodeid_parity *parity) + const struct pubkey *node_id) { struct route_hop *r; const struct dijkstra *dij; @@ -599,21 +525,10 @@ static struct pubkey *path_to_node(const tal_t *ctx, struct pubkey *nodes; struct gossmap *gossmap = get_gossmap(plugin); - /* We try both parities. */ - *parity = nodeid_parity_even; - node_id_from_point32(&dstid, node32_id, *parity); + node_id_from_pubkey(&dstid, node_id); dst = gossmap_find_node(gossmap, &dstid); - if (!dst) { - *parity = nodeid_parity_odd; - node_id_from_point32(&dstid, node32_id, *parity); - dst = gossmap_find_node(gossmap, &dstid); - if (!dst) { - *parity = nodeid_parity_unknown; - return NULL; - } - } - - *parity = node_parity(gossmap, dst); + if (!dst) + return NULL; /* If we don't exist in gossip, routing can't happen. */ node_id_from_pubkey(&local_nodeid, &local_id); @@ -643,7 +558,7 @@ static struct pubkey *path_to_node(const tal_t *ctx, /* Marshal arguments for sending onion messages */ struct sending { struct sent *sent; - struct tlv_onionmsg_payload *payload; + struct tlv_onionmsg_tlv *payload; struct command_result *(*done)(struct command *cmd, const char *buf UNUSED, const jsmntok_t *result UNUSED, @@ -652,15 +567,16 @@ struct sending { static struct command_result * send_modern_message(struct command *cmd, - struct tlv_onionmsg_payload_reply_path *reply_path, + struct blinded_path *reply_path, struct sending *sending) { struct sent *sent = sending->sent; struct privkey blinding_iter; struct pubkey fwd_blinding, *node_alias; size_t nhops = tal_count(sent->path); - struct tlv_onionmsg_payload **payloads; + struct tlv_onionmsg_tlv **payloads; struct out_req *req; + struct tlv_encrypted_data_tlv *tlv; /* Now create enctlvs for *forward* path. */ randombytes_buf(&blinding_iter, sizeof(blinding_iter)); @@ -671,29 +587,35 @@ send_modern_message(struct command *cmd, &blinding_iter)); /* We overallocate: this node (0) doesn't have payload or alias */ - payloads = tal_arr(cmd, struct tlv_onionmsg_payload *, nhops); + payloads = tal_arr(cmd, struct tlv_onionmsg_tlv *, nhops); node_alias = tal_arr(cmd, struct pubkey, nhops); for (size_t i = 1; i < nhops - 1; i++) { - payloads[i] = tlv_onionmsg_payload_new(payloads); - payloads[i]->encrypted_data_tlv = create_enctlv(payloads[i], - &blinding_iter, - &sent->path[i], - &sent->path[i+1], - /* FIXME: Pad? */ - 0, - NULL, - &blinding_iter, - &node_alias[i]); + payloads[i] = tlv_onionmsg_tlv_new(payloads); + + tlv = tlv_encrypted_data_tlv_new(tmpctx); + tlv->next_node_id = &sent->path[i+1]; + /* FIXME: Pad? */ + + payloads[i]->encrypted_recipient_data + = encrypt_tlv_encrypted_data(payloads[i], + &blinding_iter, + &sent->path[i], + tlv, + &blinding_iter, + &node_alias[i]); } /* Final payload contains the actual data. */ payloads[nhops-1] = sending->payload; /* We don't include enctlv in final, but it gives us final alias */ - if (!create_final_enctlv(tmpctx, &blinding_iter, &sent->path[nhops-1], - /* FIXME: Pad? */ 0, - NULL, - &node_alias[nhops-1])) { + tlv = tlv_encrypted_data_tlv_new(tmpctx); + if (!encrypt_tlv_encrypted_data(tmpctx, + &blinding_iter, + &sent->path[nhops-1], + tlv, + NULL, + &node_alias[nhops-1])) { /* Should not happen! */ return command_fail(cmd, LIGHTNINGD, "Could create final enctlv"); @@ -709,12 +631,12 @@ send_modern_message(struct command *cmd, json_add_pubkey(req->js, "blinding", &fwd_blinding); json_array_start(req->js, "hops"); for (size_t i = 1; i < nhops; i++) { - u8 *tlv; + u8 *tlvbin; json_object_start(req->js, NULL); json_add_pubkey(req->js, "id", &node_alias[i]); - tlv = tal_arr(tmpctx, u8, 0); - towire_tlv_onionmsg_payload(&tlv, payloads[i]); - json_add_hex_talarr(req->js, "tlv", tlv); + tlvbin = tal_arr(tmpctx, u8, 0); + towire_tlv_onionmsg_tlv(&tlvbin, payloads[i]); + json_add_hex_talarr(req->js, "tlv", tlvbin); json_object_end(req->js); } json_array_end(req->js); @@ -728,21 +650,16 @@ static struct command_result *use_reply_path(struct command *cmd, const jsmntok_t *result, struct sending *sending) { - struct tlv_onionmsg_payload_reply_path *rpath; + struct blinded_path *rpath; - rpath = json_to_reply_path(cmd, buf, - json_get_member(buf, result, "blindedpath")); + rpath = json_to_blinded_path(cmd, buf, + json_get_member(buf, result, "blindedpath")); if (!rpath) plugin_err(cmd->plugin, "could not parse reply path %.*s?", json_tok_full_len(result), json_tok_full(buf, result)); - /* Remember our alias we used so we can recognize reply */ - sending->sent->reply_alias - = tal_dup(sending->sent, struct pubkey, - &rpath->path[tal_count(rpath->path)-1]->node_id); - return send_modern_message(cmd, rpath, sending); } @@ -757,6 +674,10 @@ static struct command_result *make_reply_path(struct command *cmd, return command_fail(cmd, PAY_ROUTE_NOT_FOUND, "Refusing to talk to ourselves"); + /* Create transient secret so we can validate reply! */ + sending->sent->reply_secret = tal(sending->sent, struct secret); + randombytes_buf(sending->sent->reply_secret, sizeof(struct secret)); + req = jsonrpc_request_start(cmd->plugin, cmd, "blindedpath", use_reply_path, forward_error, @@ -768,12 +689,13 @@ static struct command_result *make_reply_path(struct command *cmd, for (int i = nhops - 2; i >= 0; i--) json_add_pubkey(req->js, NULL, &sending->sent->path[i]); json_array_end(req->js); + json_add_secret(req->js, "pathsecret", sending->sent->reply_secret); return send_outreq(cmd->plugin, req); } static struct command_result *send_message(struct command *cmd, struct sent *sent, - struct tlv_onionmsg_payload *payload STEALS, + struct tlv_onionmsg_tlv *payload STEALS, struct command_result *(*done) (struct command *cmd, const char *buf UNUSED, @@ -821,7 +743,7 @@ sendinvreq_after_connect(struct command *cmd, const jsmntok_t *result UNUSED, struct sent *sent) { - struct tlv_onionmsg_payload *payload = tlv_onionmsg_payload_new(sent); + struct tlv_onionmsg_tlv *payload = tlv_onionmsg_tlv_new(sent); payload->invoice_request = tal_arr(payload, u8, 0); towire_tlv_invoice_request(&payload->invoice_request, sent->invreq); @@ -856,41 +778,11 @@ static struct command_result *connect_failed(struct command *command, NULL); } -/* Offers contain only a 32-byte id. If we can't find the address, we - * don't know if it's 02 or 03, so we try both. If we're here, we - * failed 02. */ -static struct command_result *try_other_parity(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct connect_attempt *ca) -{ - struct out_req *req; - - /* Flip parity */ - ca->node_id.k[0] = SECP256K1_TAG_PUBKEY_ODD; - /* Path is us -> them, so they're second entry */ - if (!pubkey_from_node_id(&ca->sent->path[1], &ca->node_id)) { - /* Should not happen! - * Pieter Wuille points out: - * y^2 = x^3 + 7 mod p - * negating y doesn’t change the left hand side - */ - return command_done_err(cmd, LIGHTNINGD, - "Failed: could not convert inverted pubkey?", - NULL); - } - req = jsonrpc_request_start(cmd->plugin, cmd, "connect", connected, - connect_failed, ca); - json_add_node_id(req->js, "id", &ca->node_id); - return send_outreq(cmd->plugin, req); -} - /* We can't find a route, so we're going to try to connect, then just blast it * to them. */ static struct command_result * connect_direct(struct command *cmd, - const struct point32 *dst, - enum nodeid_parity parity, + const struct pubkey *dst, struct command_result *(*cb)(struct command *command, const char *buf, const jsmntok_t *result, @@ -902,20 +794,7 @@ connect_direct(struct command *cmd, ca->cb = cb; ca->sent = sent; - - if (parity == nodeid_parity_unknown) { - plugin_notify_message(cmd, LOG_INFORM, - "Cannot find route, trying connect to 02/03%s directly", - type_to_string(tmpctx, struct point32, dst)); - /* Try even first. */ - node_id_from_point32(&ca->node_id, dst, SECP256K1_TAG_PUBKEY_EVEN); - } else { - plugin_notify_message(cmd, LOG_INFORM, - "Cannot find route, trying connect to %02x%s directly", - parity, - type_to_string(tmpctx, struct point32, dst)); - node_id_from_point32(&ca->node_id, dst, parity); - } + node_id_from_pubkey(&ca->node_id, dst); /* Make a direct path -> dst. */ sent->path = tal_arr(sent, struct pubkey, 2); @@ -933,14 +812,13 @@ connect_direct(struct command *cmd, "Cannot find route, but" " fetchplugin-noconnect set:" " trying direct anyway to %s", - type_to_string(tmpctx, struct point32, + type_to_string(tmpctx, struct pubkey, dst)); return cb(cmd, NULL, NULL, sent); } req = jsonrpc_request_start(cmd->plugin, cmd, "connect", connected, - parity == nodeid_parity_unknown ? - try_other_parity : connect_failed, ca); + connect_failed, ca); json_add_node_id(req->js, "id", &ca->node_id); return send_outreq(cmd->plugin, req); } @@ -952,7 +830,6 @@ static struct command_result *invreq_done(struct command *cmd, { const jsmntok_t *t; char *fail; - enum nodeid_parity parity; /* Get invoice request */ t = json_get_member(buf, result, "bolt12"); @@ -983,26 +860,26 @@ static struct command_result *invreq_done(struct command *cmd, /* Now that's given us the previous base, check this is an OK time * to request an invoice. */ - if (sent->invreq->recurrence_counter) { + if (sent->invreq->invreq_recurrence_counter) { u64 *base; const jsmntok_t *pbtok; - u64 period_idx = *sent->invreq->recurrence_counter; + u64 period_idx = *sent->invreq->invreq_recurrence_counter; - if (sent->invreq->recurrence_start) - period_idx += *sent->invreq->recurrence_start; + if (sent->invreq->invreq_recurrence_start) + period_idx += *sent->invreq->invreq_recurrence_start; /* BOLT-offers-recurrence #12: * - if the offer contained `recurrence_limit`: * - MUST NOT send an `invoice_request` for a period greater * than `max_period` */ - if (sent->offer->recurrence_limit - && period_idx > *sent->offer->recurrence_limit) + if (sent->invreq->offer_recurrence_limit + && period_idx > *sent->invreq->offer_recurrence_limit) return command_fail(cmd, LIGHTNINGD, "Can't send invreq for period %" PRIu64" (limit %u)", period_idx, - *sent->offer->recurrence_limit); + *sent->invreq->offer_recurrence_limit); /* BOLT-offers-recurrence #12: * - SHOULD NOT send an `invoice_request` for a period which has @@ -1015,19 +892,19 @@ static struct command_result *invreq_done(struct command *cmd, if (pbtok) { base = tal(tmpctx, u64); json_to_u64(buf, pbtok, base); - } else if (sent->offer->recurrence_base) - base = &sent->offer->recurrence_base->basetime; + } else if (sent->invreq->offer_recurrence_base) + base = &sent->invreq->offer_recurrence_base->basetime; else { /* happens with *recurrence_base == 0 */ - assert(*sent->invreq->recurrence_counter == 0); + assert(*sent->invreq->invreq_recurrence_counter == 0); base = NULL; } if (base) { u64 period_start, period_end, now = time_now().ts.tv_sec; - offer_period_paywindow(sent->offer->recurrence, - sent->offer->recurrence_paywindow, - sent->offer->recurrence_base, + offer_period_paywindow(sent->invreq->offer_recurrence, + sent->invreq->offer_recurrence_paywindow, + sent->invreq->offer_recurrence_base, *base, period_idx, &period_start, &period_end); if (now < period_start) @@ -1046,10 +923,9 @@ static struct command_result *invreq_done(struct command *cmd, } sent->path = path_to_node(sent, cmd->plugin, - sent->offer->node_id, - &parity); + sent->invreq->offer_node_id); if (!sent->path) - return connect_direct(cmd, sent->offer->node_id, parity, + return connect_direct(cmd, sent->invreq->offer_node_id, sendinvreq_after_connect, sent); return sendinvreq_after_connect(cmd, NULL, NULL, sent); @@ -1060,38 +936,29 @@ static struct command_result *invreq_done(struct command *cmd, static struct command_result * force_payer_secret(struct command *cmd, struct sent *sent, - struct tlv_invoice_request *invreq, + struct tlv_invoice_request *invreq STEALS, const struct secret *payer_secret) { struct sha256 merkle, sha; - enum nodeid_parity parity; secp256k1_keypair kp; - u8 *msg; - const u8 *p; - size_t len; if (secp256k1_keypair_create(secp256k1_ctx, &kp, payer_secret->data) != 1) return command_fail(cmd, LIGHTNINGD, "Bad payer_secret"); - invreq->payer_key = tal(invreq, struct point32); + invreq->invreq_payer_id = tal(invreq, struct pubkey); /* Docs say this only happens if arguments are invalid! */ - if (secp256k1_keypair_xonly_pub(secp256k1_ctx, - &invreq->payer_key->pubkey, NULL, - &kp) != 1) + if (secp256k1_keypair_pub(secp256k1_ctx, + &invreq->invreq_payer_id->pubkey, + &kp) != 1) plugin_err(cmd->plugin, "secp256k1_keypair_pub failed on %s?", type_to_string(tmpctx, struct secret, payer_secret)); - /* Linearize populates ->fields */ - msg = tal_arr(tmpctx, u8, 0); - towire_tlv_invoice_request(&msg, invreq); - p = msg; - len = tal_bytelen(msg); - sent->invreq = fromwire_tlv_invoice_request(cmd, &p, &len); - if (!sent->invreq) - plugin_err(cmd->plugin, - "Could not remarshall invreq %s", tal_hex(tmpctx, msg)); + /* Re-calculate ->fields */ + tal_free(invreq->fields); + invreq->fields = tlv_make_fields(invreq, tlv_invoice_request); + sent->invreq = tal_steal(sent, invreq); merkle_tlv(sent->invreq->fields, &merkle); sighash_from_merkle("invoice_request", "signature", &merkle, &sha); @@ -1106,10 +973,9 @@ force_payer_secret(struct command *cmd, } sent->path = path_to_node(sent, cmd->plugin, - sent->offer->node_id, - &parity); + sent->invreq->invreq_payer_id); if (!sent->path) - return connect_direct(cmd, sent->offer->node_id, parity, + return connect_direct(cmd, sent->invreq->offer_node_id, sendinvreq_after_connect, sent); return sendinvreq_after_connect(cmd, NULL, NULL, sent); @@ -1127,16 +993,15 @@ static struct command_result *json_fetchinvoice(struct command *cmd, struct sent *sent = tal(cmd, struct sent); struct secret *payer_secret = NULL; u32 *timeout; + u64 *quantity; + u32 *recurrence_counter, *recurrence_start; - invreq = tlv_invoice_request_new(sent); if (!param(cmd, buffer, params, p_req("offer", param_offer, &sent->offer), p_opt("amount_msat|msatoshi", param_msat, &msat), - p_opt("quantity", param_u64, &invreq->quantity), - p_opt("recurrence_counter", param_number, - &invreq->recurrence_counter), - p_opt("recurrence_start", param_number, - &invreq->recurrence_start), + p_opt("quantity", param_u64, &quantity), + p_opt("recurrence_counter", param_number, &recurrence_counter), + p_opt("recurrence_start", param_number, &recurrence_start), p_opt("recurrence_label", param_string, &rec_label), p_opt_def("timeout", param_number, &timeout, 60), p_opt("payer_note", param_string, &payer_note), @@ -1149,73 +1014,67 @@ static struct command_result *json_fetchinvoice(struct command *cmd, sent->wait_timeout = *timeout; /* BOLT-offers #12: - * - MUST set `offer_id` to the Merkle root of the offer as described - * in [Signature Calculation](#signature-calculation). + * - SHOULD not respond to an offer if the current time is after + * `offer_absolute_expiry`. */ - invreq->offer_id = tal(invreq, struct sha256); - merkle_tlv(sent->offer->fields, invreq->offer_id); - - /* Check if they are trying to send us money. */ - if (sent->offer->send_invoice) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Offer wants an invoice, not invoice_request"); + if (sent->offer->offer_absolute_expiry + && time_now().ts.tv_sec > *sent->offer->offer_absolute_expiry) + return command_fail(cmd, OFFER_EXPIRED, "Offer expired"); /* BOLT-offers #12: - * - SHOULD not respond to an offer if the current time is after - * `absolute_expiry`. + * The writer: + * - if it is responding to an offer: + * - MUST copy all fields from the offer (including unknown fields). */ - if (sent->offer->absolute_expiry - && time_now().ts.tv_sec > *sent->offer->absolute_expiry) - return command_fail(cmd, OFFER_EXPIRED, "Offer expired"); + invreq = invoice_request_for_offer(sent, sent->offer); + invreq->invreq_recurrence_counter = tal_steal(invreq, recurrence_counter); + invreq->invreq_recurrence_start = tal_steal(invreq, recurrence_start); /* BOLT-offers-recurrence #12: - * - if the offer did not specify `amount`: - * - MUST specify `amount`.`msat` in multiples of the minimum - * lightning-payable unit (e.g. milli-satoshis for bitcoin) for - * `chain` (or for bitcoin, if there is no `chain`). - * - otherwise: - * - MAY omit `amount`. - * - if it sets `amount`: - * - MUST specify `amount`.`msat` as greater or equal to amount - * expected by the offer (before any proportional period amount). + * - if `offer_amount` is not present: + * - MUST specify `invreq_amount`. + * - otherwise: + * - MAY omit `invreq_amount`. + * - if it sets `invreq_amount`: + * - MUST specify `invreq_amount`.`msat` as greater or equal to + * amount expected by `offer_amount` (and, if present, + * `offer_currency` and `invreq_quantity`). */ - if (sent->offer->amount) { + if (invreq->offer_amount) { /* FIXME: Check after quantity? */ if (msat) { - invreq->amount = tal_dup(invreq, u64, - &msat->millisatoshis); /* Raw: tu64 */ + invreq->invreq_amount = tal_dup(invreq, u64, + &msat->millisatoshis); /* Raw: tu64 */ } } else { if (!msat) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "msatoshi parameter required"); - invreq->amount = tal_dup(invreq, u64, - &msat->millisatoshis); /* Raw: tu64 */ + invreq->invreq_amount = tal_dup(invreq, u64, + &msat->millisatoshis); /* Raw: tu64 */ } /* BOLT-offers #12: - * - if the offer had a `quantity_min` or `quantity_max` field: - * - MUST set `quantity` - * - MUST set it within that (inclusive) range. - * - otherwise: - * - MUST NOT set `quantity` + * - if `offer_quantity_max` is present: + * - MUST set `invreq_quantity` to greater than zero. + * - if `offer_quantity_max` is non-zero: + * - MUST set `invreq_quantity` less than or equal to + * `offer_quantity_max`. */ - if (sent->offer->quantity_min || sent->offer->quantity_max) { - if (!invreq->quantity) + if (invreq->offer_quantity_max) { + if (!invreq->invreq_quantity) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "quantity parameter required"); - if (sent->offer->quantity_min - && *invreq->quantity < *sent->offer->quantity_min) + if (*invreq->invreq_quantity == 0) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "quantity must be >= %"PRIu64, - *sent->offer->quantity_min); - if (sent->offer->quantity_max - && *invreq->quantity > *sent->offer->quantity_max) + "quantity parameter must be non-zero"); + if (*invreq->offer_quantity_max + && *invreq->invreq_quantity > *invreq->offer_quantity_max) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "quantity must be <= %"PRIu64, - *sent->offer->quantity_max); + *invreq->offer_quantity_max); } else { - if (invreq->quantity) + if (invreq->invreq_quantity) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "quantity parameter unnecessary"); } @@ -1223,7 +1082,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, /* BOLT-offers-recurrence #12: * - if the offer contained `recurrence`: */ - if (sent->offer->recurrence) { + if (invreq->offer_recurrence) { /* BOLT-offers-recurrence #12: * - for the initial request: *... @@ -1235,7 +1094,7 @@ static struct command_result *json_fetchinvoice(struct command *cmd, * - MUST set `recurrence_counter` `counter` to one greater * than the highest-paid invoice. */ - if (!invreq->recurrence_counter) + if (!invreq->invreq_recurrence_counter) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "needs recurrence_counter"); @@ -1247,13 +1106,13 @@ static struct command_result *json_fetchinvoice(struct command *cmd, * - otherwise: * - MUST NOT include `recurrence_start` */ - if (sent->offer->recurrence_base - && sent->offer->recurrence_base->start_any_period) { - if (!invreq->recurrence_start) + if (invreq->offer_recurrence_base + && invreq->offer_recurrence_base->start_any_period) { + if (!invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "needs recurrence_start"); } else { - if (invreq->recurrence_start) + if (invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unnecessary recurrence_start"); } @@ -1269,34 +1128,41 @@ static struct command_result *json_fetchinvoice(struct command *cmd, * - MUST NOT set `recurrence_counter`. * - MUST NOT set `recurrence_start` */ - if (invreq->recurrence_counter) + if (invreq->invreq_recurrence_counter) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unnecessary recurrence_counter"); - if (invreq->recurrence_start) + if (invreq->invreq_recurrence_start) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "unnecessary recurrence_start"); } /* BOLT-offers #12: * - * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. + * - if `offer_chains` is set: + * - MUST set `invreq_chain` to one of `offer_chains` unless that + * chain is bitcoin, in which case it MAY omit `invreq_chain`. * - otherwise: - * - the bitcoin chain is implied as the first and only entry. + * - if it sets `invreq_chain` it MUST set it to bitcoin. */ + /* We already checked that we're compatible chain, in param_offer */ if (!streq(chainparams->network_name, "bitcoin")) { - invreq->chain = tal_dup(invreq, struct bitcoin_blkid, - &chainparams->genesis_blockhash); + invreq->invreq_chain = tal_dup(invreq, struct bitcoin_blkid, + &chainparams->genesis_blockhash); } - invreq->features - = plugin_feature_set(cmd->plugin)->bits[BOLT11_FEATURE]; + /* BOLT-offers #12: + * - if it supports bolt12 invoice request features: + * - MUST set `invreq_features`.`features` to the bitmap of features. + */ + invreq->invreq_features + = plugin_feature_set(cmd->plugin)->bits[BOLT12_OFFER_FEATURE]; - /* invreq->payer_note is not a nul-terminated string! */ + /* invreq->invreq_payer_note is not a nul-terminated string! */ if (payer_note) - invreq->payer_note = tal_dup_arr(invreq, utf8, - payer_note, strlen(payer_note), - 0); + invreq->invreq_payer_note = tal_dup_arr(invreq, utf8, + payer_note, + strlen(payer_note), + 0); /* They can provide a secret, and we don't assume it's our job * to pay. */ @@ -1308,7 +1174,10 @@ static struct command_result *json_fetchinvoice(struct command *cmd, &invreq_done, &forward_error, sent); + + /* We don't want this is the database: that's only for ones we publish */ json_add_string(req->js, "bolt12", invrequest_encode(tmpctx, invreq)); + json_add_bool(req->js, "savetodb", false); if (rec_label) json_add_string(req->js, "recurrence_label", rec_label); return send_outreq(cmd->plugin, req); @@ -1368,7 +1237,7 @@ sendinvoice_after_connect(struct command *cmd, const jsmntok_t *result UNUSED, struct sent *sent) { - struct tlv_onionmsg_payload *payload = tlv_onionmsg_payload_new(sent); + struct tlv_onionmsg_tlv *payload = tlv_onionmsg_tlv_new(sent); payload->invoice = tal_arr(payload, u8, 0); towire_tlv_invoice(&payload->invoice, sent->inv); @@ -1383,7 +1252,6 @@ static struct command_result *createinvoice_done(struct command *cmd, { const jsmntok_t *invtok = json_get_member(buf, result, "bolt12"); char *fail; - enum nodeid_parity parity; /* Replace invoice with signed one */ tal_free(sent->inv); @@ -1403,11 +1271,21 @@ static struct command_result *createinvoice_done(struct command *cmd, "Bad createinvoice response %s", fail); } + /* BOLT-offers #12: + * - if it sends an invoice in response: + * - MUST use `offer_paths` if present, otherwise MUST use + * `invreq_payer_id` as the node id to send to. + */ + /* FIXME! */ + if (sent->invreq->offer_paths) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "FIXME: support blinded paths!"); + } + sent->path = path_to_node(sent, cmd->plugin, - sent->offer->node_id, - &parity); + sent->invreq->invreq_payer_id); if (!sent->path) - return connect_direct(cmd, sent->offer->node_id, parity, + return connect_direct(cmd, sent->invreq->invreq_payer_id, sendinvoice_after_connect, sent); return sendinvoice_after_connect(cmd, NULL, NULL, sent); @@ -1429,106 +1307,130 @@ static struct command_result *sign_invoice(struct command *cmd, return send_outreq(cmd->plugin, req); } -static bool json_to_bip340sig(const char *buffer, const jsmntok_t *tok, - struct bip340sig *sig) +static struct command_result *param_invreq(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct tlv_invoice_request **invreq) { - return hex_decode(buffer + tok->start, tok->end - tok->start, - sig->u8, sizeof(sig->u8)); -} + char *fail; + int badf; + u8 *wire; + struct sha256 merkle, sighash; -static struct command_result *payersign_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct sent *sent) -{ - const jsmntok_t *sig; + /* BOLT-offers #12: + * - if `invreq_chain` is not present: + * - MUST fail the request if bitcoin is not a supported chain. + * - otherwise: + * - MUST fail the request if `invreq_chain`.`chain` is not a + * supported chain. + */ + *invreq = invrequest_decode(cmd, + buffer + tok->start, tok->end - tok->start, + plugin_feature_set(cmd->plugin), + chainparams, + &fail); + if (!*invreq) + return command_fail_badparam(cmd, name, buffer, tok, + tal_fmt(cmd, + "Unparsable invoice_request: %s", + fail)); + /* BOLT-offers #12: + * The reader: + * - MUST fail the request if `invreq_payer_id` or `invreq_metadata` + * are not present. + * - MUST fail the request if any non-signature TLV fields greater or + * equal to 160. + * - if `invreq_features` contains unknown _odd_ bits that are + * non-zero: + * - MUST ignore the bit. + * - if `invreq_features` contains unknown _even_ bits that are + * non-zero: + * - MUST fail the request. + */ + if (!(*invreq)->invreq_payer_id) + return command_fail_badparam(cmd, name, buffer, tok, + "Missing invreq_payer_id"); - sent->inv->refund_signature = tal(sent->inv, struct bip340sig); - sig = json_get_member(buf, result, "signature"); - json_to_bip340sig(buf, sig, sent->inv->refund_signature); + if (!(*invreq)->invreq_metadata) + return command_fail_badparam(cmd, name, buffer, tok, + "Missing invreq_metadata"); - return sign_invoice(cmd, sent); -} + wire = tal_arr(tmpctx, u8, 0); + towire_tlv_invoice_request(&wire, *invreq); + if (tlv_span(wire, 160, 239, NULL) != 0 + || tlv_span(wire, 1001, UINT64_MAX, NULL) != 0) { + return command_fail_badparam(cmd, name, buffer, tok, + "Invalid high-numbered fields"); + } -/* They're offering a refund, so we need to sign with same key as used - * in initial payment. */ -static struct command_result *listsendpays_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct sent *sent) -{ - const jsmntok_t *t, *arr = json_get_member(buf, result, "payments"); - size_t i; - const u8 *public_tweak = NULL, *p; - u8 *msg; - size_t len; - struct sha256 merkle; - struct out_req *req; + badf = features_unsupported(plugin_feature_set(cmd->plugin), + (*invreq)->invreq_features, + BOLT12_INVREQ_FEATURE); + if (badf != -1) { + return command_fail_badparam(cmd, name, buffer, tok, + tal_fmt(tmpctx, + "unknown feature %i", + badf)); + } - /* Linearize populates ->fields */ - msg = tal_arr(tmpctx, u8, 0); - towire_tlv_invoice(&msg, sent->inv); - p = msg; - len = tal_bytelen(msg); - sent->inv = fromwire_tlv_invoice(cmd, &p, &len); - if (!sent->inv) - plugin_err(cmd->plugin, - "Could not remarshall %s", tal_hex(tmpctx, msg)); - - merkle_tlv(sent->inv->fields, &merkle); - - json_for_each_arr(i, t, arr) { - const jsmntok_t *b12tok; - struct tlv_invoice *inv; - char *fail; - - b12tok = json_get_member(buf, t, "bolt12"); - if (!b12tok) { - /* This could happen if they try to refund a bolt11 */ - plugin_log(cmd->plugin, LOG_UNUSUAL, - "Not bolt12 string in %.*s?", - json_tok_full_len(t), - json_tok_full(buf, t)); - continue; - } + /* BOLT-offers #12: + * - MUST fail the request if `signature` is not correct as detailed in [Signature + * Calculation](#signature-calculation) using the `invreq_payer_id`. + */ + merkle_tlv((*invreq)->fields, &merkle); + sighash_from_merkle("invoice_request", "signature", &merkle, &sighash); - inv = invoice_decode(tmpctx, buf + b12tok->start, - b12tok->end - b12tok->start, - plugin_feature_set(cmd->plugin), - chainparams, - &fail); - if (!inv) { - plugin_log(cmd->plugin, LOG_BROKEN, - "Bad bolt12 string in %.*s?", - json_tok_full_len(t), - json_tok_full(buf, t)); - continue; - } + if (!(*invreq)->signature) + return command_fail_badparam(cmd, name, buffer, tok, + "Missing signature"); + if (!check_schnorr_sig(&sighash, + &(*invreq)->invreq_payer_id->pubkey, + (*invreq)->signature)) + return command_fail_badparam(cmd, name, buffer, tok, + "Invalid signature"); - public_tweak = inv->payer_info; - break; + /* Plugin handles these automatically, you shouldn't send one + * manually. */ + if ((*invreq)->offer_node_id) { + return command_fail_badparam(cmd, name, buffer, tok, + "This is based on an offer?"); } - if (!public_tweak) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Cannot find invoice %s for refund", - type_to_string(tmpctx, struct sha256, - sent->offer->refund_for)); + /* BOLT-offers #12: + * - otherwise (no `offer_node_id`, not a response to our offer): + * - MUST fail the request if any of the following are present: + * - `offer_chains`, `offer_features` or `offer_quantity_max`. + * - MUST fail the request if `invreq_amount` is not present. + */ + if ((*invreq)->offer_chains) + return command_fail_badparam(cmd, name, buffer, tok, + "Unexpected offer_chains"); + if ((*invreq)->offer_features) + return command_fail_badparam(cmd, name, buffer, tok, + "Unexpected offer_features"); + if ((*invreq)->offer_quantity_max) + return command_fail_badparam(cmd, name, buffer, tok, + "Unexpected offer_quantity_max"); + if (!(*invreq)->invreq_amount) + return command_fail_badparam(cmd, name, buffer, tok, + "Missing invreq_amount"); /* BOLT-offers #12: - * - MUST set `refund_signature` to the signature of the - * `refunded_payment_hash` using prefix `refund_signature` and the - * `payer_key` from the to-be-refunded invoice. + * - otherwise (no `offer_node_id`, not a response to our offer): + *... + * - MAY use `offer_amount` (or `offer_currency`) for informational display to user. */ - req = jsonrpc_request_start(cmd->plugin, cmd, "payersign", - &payersign_done, - &forward_error, - sent); - json_add_string(req->js, "messagename", "invoice"); - json_add_string(req->js, "fieldname", "refund_signature"); - json_add_sha256(req->js, "merkle", &merkle); - json_add_hex_talarr(req->js, "tweak", public_tweak); - return send_outreq(cmd->plugin, req); + if ((*invreq)->offer_amount && (*invreq)->offer_currency) { + plugin_notify_message(cmd, LOG_INFORM, + "invoice_request offers %.*s%"PRIu64" as %s", + (int)tal_bytelen((*invreq)->offer_currency), + (*invreq)->offer_currency, + *(*invreq)->offer_amount, + fmt_amount_msat(tmpctx, + amount_msat(*(*invreq)->invreq_amount))); + } + return NULL; } static struct command_result *json_sendinvoice(struct command *cmd, @@ -1536,193 +1438,105 @@ static struct command_result *json_sendinvoice(struct command *cmd, const jsmntok_t *params) { struct amount_msat *msat; - struct out_req *req; u32 *timeout; struct sent *sent = tal(cmd, struct sent); - sent->inv = tlv_invoice_new(cmd); - sent->invreq = NULL; + sent->offer = NULL; sent->cmd = cmd; - /* FIXME: Support recurring send_invoice offers? */ + /* FIXME: Support recurring invoice_requests? */ if (!param(cmd, buffer, params, - p_req("offer", param_offer, &sent->offer), + p_req("invreq", param_invreq, &sent->invreq), p_req("label", param_label, &sent->inv_label), - p_opt("amount_msat|msatoshi", param_msat, &msat), + p_opt("amount_msat", param_msat, &msat), p_opt_def("timeout", param_number, &timeout, 90), - p_opt("quantity", param_u64, &sent->inv->quantity), NULL)) return command_param_failed(); - /* This is how long we'll wait for a reply for. */ - sent->wait_timeout = *timeout; - - /* Check they are really trying to send us money. */ - if (!sent->offer->send_invoice) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Offer wants an invoice_request, not invoice"); - - /* If they don't tell us how much, base it on offer. */ - if (!msat) { - if (sent->offer->currency) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Offer in different currency: need amount"); - if (!sent->offer->amount) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Offer did not specify: need amount"); - sent->inv->amount = tal_dup(sent->inv, u64, sent->offer->amount); - if (sent->inv->quantity) - *sent->inv->amount *= *sent->inv->quantity; - } else - sent->inv->amount = tal_dup(sent->inv, u64, - &msat->millisatoshis); /* Raw: tlv */ - - /* FIXME: Support blinded paths, in which case use fake nodeid */ - /* BOLT-offers #12: - * - otherwise (responding to a `send_invoice` offer): - * - MUST set `node_id` to the id of the node to send payment to. - * - MUST set `description` the same as the offer. + * - if the invoice is in response to an `invoice_request`: + * - MUST copy all non-signature fields from the `invoice_request` + * (including unknown fields). */ - sent->inv->node_id = tal(sent->inv, struct point32); + sent->inv = invoice_for_invreq(sent, sent->invreq); - /* This only fails if pubkey is invalid. */ - if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_ctx, - &sent->inv->node_id->pubkey, - NULL, - &local_id.pubkey)) - abort(); - - sent->inv->description - = tal_dup_talarr(sent->inv, char, sent->offer->description); + /* This is how long we'll wait for a reply for. */ + sent->wait_timeout = *timeout; /* BOLT-offers #12: - * - MUST set (or not set) `send_invoice` the same as the offer. + * - if `invreq_amount` is present: + * - MUST set `invoice_amount` to `invreq_amount` + * - otherwise: + * - MUST set `invoice_amount` to the *expected amount*. */ - sent->inv->send_invoice = tal(sent->inv, struct tlv_invoice_send_invoice); + if (!msat) + sent->inv->invoice_amount = tal_dup(sent->inv, u64, + sent->invreq->invreq_amount); + else + sent->inv->invoice_amount = tal_dup(sent->inv, u64, + &msat->millisatoshis); /* Raw: tlv */ /* BOLT-offers #12: - * - MUST set `offer_id` to the id of the offer. + * - MUST set `invoice_created_at` to the number of seconds since Midnight 1 + * January 1970, UTC when the offer was created. + * - MUST set `invoice_amount` to the minimum amount it will accept, in units of + * the minimal lightning-payable unit (e.g. milli-satoshis for bitcoin) for + * `invreq_chain`. */ - sent->inv->offer_id = tal(sent->inv, struct sha256); - merkle_tlv(sent->offer->fields, sent->inv->offer_id); + sent->inv->invoice_created_at = tal(sent->inv, u64); + *sent->inv->invoice_created_at = time_now().ts.tv_sec; - /* BOLT-offers #12: - * - SHOULD not respond to an offer if the current time is after - * `absolute_expiry`. - */ - if (sent->offer->absolute_expiry - && time_now().ts.tv_sec > *sent->offer->absolute_expiry) - return command_fail(cmd, OFFER_EXPIRED, "Offer expired"); + /* FIXME: Support blinded paths, in which case use fake nodeid */ /* BOLT-offers #12: - * - otherwise (responding to a `send_invoice` offer): - *... - * - if the offer had a `quantity_min` or `quantity_max` field: - * - MUST set `quantity` - * - MUST set it within that (inclusive) range. - * - otherwise: - * - MUST NOT set `quantity` + * - MUST set `invoice_payment_hash` to the SHA256 hash of the + * `payment_preimage` that will be given in return for payment. */ - if (sent->offer->quantity_min || sent->offer->quantity_max) { - if (!sent->inv->quantity) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "quantity parameter required"); - if (sent->offer->quantity_min - && *sent->inv->quantity < *sent->offer->quantity_min) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "quantity must be >= %"PRIu64, - *sent->offer->quantity_min); - if (sent->offer->quantity_max - && *sent->inv->quantity > *sent->offer->quantity_max) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "quantity must be <= %"PRIu64, - *sent->offer->quantity_max); - } else { - if (sent->inv->quantity) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "quantity parameter unnecessary"); - } + randombytes_buf(&sent->inv_preimage, sizeof(sent->inv_preimage)); + sent->inv->invoice_payment_hash = tal(sent->inv, struct sha256); + sha256(sent->inv->invoice_payment_hash, + &sent->inv_preimage, sizeof(sent->inv_preimage)); /* BOLT-offers #12: - * - MUST set `created_at` to the number of seconds since Midnight 1 - * January 1970, UTC when the offer was created. + * - if `offer_node_id` is present: + * - MUST set `invoice_node_id` to `offer_node_id`. + * - otherwise: + * - MUST set `invoice_node_id` to a valid public key. */ - sent->inv->created_at = tal(sent->inv, u64); - *sent->inv->created_at = time_now().ts.tv_sec; + /* FIXME: Use transitory id! */ + sent->inv->invoice_node_id = tal(sent->inv, struct pubkey); + sent->inv->invoice_node_id->pubkey = local_id.pubkey; /* BOLT-offers #12: - * - if the expiry for accepting payment is not 7200 seconds after - * `created_at`: - * - MUST set `relative_expiry` `seconds_from_creation` to the number - * of seconds after `created_at` that payment of this invoice should - * not be attempted. + * - if the expiry for accepting payment is not 7200 seconds + * after `invoice_created_at`: + * - MUST set `invoice_relative_expiry`.`seconds_from_creation` + * to the number of seconds after `invoice_created_at` that + * payment of this invoice should not be attempted. */ if (sent->wait_timeout != 7200) { - sent->inv->relative_expiry = tal(sent->inv, u32); - *sent->inv->relative_expiry = sent->wait_timeout; + sent->inv->invoice_relative_expiry = tal(sent->inv, u32); + *sent->inv->invoice_relative_expiry = sent->wait_timeout; } - /* BOLT-offers #12: - * - MUST set `payer_key` to the `node_id` of the offer. - */ - sent->inv->payer_key = sent->offer->node_id; - /* FIXME: recurrence? */ - if (sent->offer->recurrence) + if (sent->inv->offer_recurrence) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "FIXME: handle recurring send_invoice offer!"); - - /* BOLT-offers #12: - * - * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. - * - otherwise: - * - the bitcoin chain is implied as the first and only entry. - */ - if (!streq(chainparams->network_name, "bitcoin")) { - sent->inv->chain = tal_dup(sent->inv, struct bitcoin_blkid, - &chainparams->genesis_blockhash); - } - - sent->inv->features - = plugin_feature_set(cmd->plugin)->bits[BOLT11_FEATURE]; - - randombytes_buf(&sent->inv_preimage, sizeof(sent->inv_preimage)); - sent->inv->payment_hash = tal(sent->inv, struct sha256); - sha256(sent->inv->payment_hash, - &sent->inv_preimage, sizeof(sent->inv_preimage)); + "FIXME: handle recurring invreq?"); - /* BOLT-offers #12: - * - MUST set (or not set) `refund_for` exactly as the offer did. - * - if it sets `refund_for`: - * - MUST set `refund_signature` to the signature of the - * `refunded_payment_hash` using prefix `refund_signature` and - * the `payer_key` from the to-be-refunded invoice. - * - otherwise: - * - MUST NOT set `refund_signature` - */ - if (sent->offer->refund_for) { - sent->inv->refund_for = sent->offer->refund_for; - /* Find original payment invoice */ - req = jsonrpc_request_start(cmd->plugin, cmd, "listsendpays", - &listsendpays_done, - &forward_error, - sent); - json_add_sha256(req->js, "payment_hash", - sent->offer->refund_for); - return send_outreq(cmd->plugin, req); - } + sent->inv->invoice_features + = plugin_feature_set(cmd->plugin)->bits[BOLT12_INVOICE_FEATURE]; return sign_invoice(cmd, sent); } #if DEVELOPER -static struct command_result *param_invreq(struct command *cmd, - const char *name, - const char *buffer, - const jsmntok_t *tok, - struct tlv_invoice_request **invreq) +/* This version doesn't do sanity checks! */ +static struct command_result *param_raw_invreq(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct tlv_invoice_request **invreq) { char *fail; @@ -1743,35 +1557,23 @@ static struct command_result *json_rawrequest(struct command *cmd, { struct sent *sent = tal(cmd, struct sent); u32 *timeout; - struct node_id *node_id; - struct point32 node_id32; - enum nodeid_parity parity; + struct pubkey *node_id; if (!param(cmd, buffer, params, - p_req("invreq", param_invreq, &sent->invreq), - p_req("nodeid", param_node_id, &node_id), + p_req("invreq", param_raw_invreq, &sent->invreq), + p_req("nodeid", param_pubkey, &node_id), p_opt_def("timeout", param_number, &timeout, 60), NULL)) return command_param_failed(); - /* Skip over 02/03 in node_id */ - if (!secp256k1_xonly_pubkey_parse(secp256k1_ctx, - &node_id32.pubkey, - node_id->k + 1)) - return command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "Invalid nodeid"); /* This is how long we'll wait for a reply for. */ sent->wait_timeout = *timeout; sent->cmd = cmd; sent->offer = NULL; - sent->path = path_to_node(sent, cmd->plugin, - &node_id32, - &parity); + sent->path = path_to_node(sent, cmd->plugin, node_id); if (!sent->path) { - /* We *do* know parity: they gave it to us! */ - parity = node_id->k[0]; - return connect_direct(cmd, &node_id32, parity, + return connect_direct(cmd, node_id, sendinvreq_after_connect, sent); } @@ -1790,7 +1592,7 @@ static const struct plugin_command commands[] = { { "sendinvoice", "payment", - "Request remote node for to pay this send_invoice {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required.", + "Request remote node for to pay this {invreq}, with {label}, optional {amount_msat}, and {timeout} (default 90 seconds).", NULL, json_sendinvoice, }, @@ -1826,7 +1628,7 @@ static const char *init(struct plugin *p, const char *buf UNUSED, static const struct plugin_hook hooks[] = { { - "onion_message_ourpath", + "onion_message_recv_secret", recv_modern_onion_message }, { diff --git a/plugins/grpc-plugin/Cargo.toml b/plugins/grpc-plugin/Cargo.toml index 634248ef2a49..435908d11d8c 100644 --- a/plugins/grpc-plugin/Cargo.toml +++ b/plugins/grpc-plugin/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "cln-grpc-plugin" -version = "0.1.0" +version = "0.1.1" [[bin]] name = "cln-grpc" @@ -10,8 +10,8 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" log = "0.4" -prost = "0.8" -rcgen = { version = "0.8", features = ["pem", "x509-parser"] } +prost = "0.11" +rcgen = { version = "0.10", features = ["pem", "x509-parser"] } [dependencies.cln-grpc] path = "../../cln-grpc" @@ -28,4 +28,4 @@ version = "1" [dependencies.tonic] features = ["tls", "transport"] -version = "^0.5" +version = "0.8" diff --git a/plugins/grpc-plugin/README.md b/plugins/grpc-plugin/README.md new file mode 100644 index 000000000000..0894c49e17b4 --- /dev/null +++ b/plugins/grpc-plugin/README.md @@ -0,0 +1,144 @@ +# GRPC plugin for Core Lightning + +This plugin exposes the JSON-RPC interface through grpc over the +network. It listens on a configurable port, authenticates clients +using mTLS certificates, and will forward any request to the JSON-RPC +interface, performing translations from protobuf to JSON and back. + + +## Getting started + +The plugin only runs when `lightningd` is configured with the option +`--grpc-port`. Upon starting the plugin generates a number of files, +if they don't already exist: + + - `ca.pem` and `ca-key.pem`: These are the certificate and private + key for your own certificate authority. The plugin will only accept + incoming connections using certificates that are signed by theis + CA. + - `server.pem` and `server-key.pem`: this is the identity + (certificate and private key) used by the plugin to authenticate + itself. It is signed by the CA, and the client will verify its + identity. + - `client.pem` and `client-key.pem`: this is an example identity that + can be used by a client to connect to the plugin, and issue + requests. It is also signed by the CA. + +These files are generated with sane defaults, however you can generate +custom certificates should you require some changes (see below for +details). + +## Connecting + +The client needs a valid mTLS identity in order to connect to the +plugin, so copy over the `ca.pem`, `client.pem` and `client-key.pem` +files from the node. The RPC interface is described in the [protobuf +file][proto], and we'll first need to generate language specific +bindings. + +In this example we walk through the steps for python, however they are +mostly the same for other languages. + +We start by downloading the dependencies and `protoc` compiler: + +```bash +pip install grpcio-tools +``` + +Next we generate the bindings in the current directory: + +```bash +python -m grpc_tools.protoc \ + -I path/to/cln-grpc/proto \ + path/to/cln-grpc/proto/node.proto \ + --python_out=. \ + --grpc_python_out=. \ + --experimental_allow_proto3_optional +``` + +This will generate two files in the current directory: + + - `node_pb2.py`: the description of the protobuf messages we'll be + exchanging with the server. + - `node_pb2_grpc.py`: the service and method stubs representing the + server-side methods as local objects and associated methods. + +And finally we can use the generated stubs and mTLS identity to +connect to the node: + +```python +from pathlib import Path +from node_pb2_grpc import NodeStub +import node_pb2 + +p = Path(".") +cert_path = p / "client.pem" +key_path = p / "client-key.pem" +ca_cert_path = p / "ca.pem" + +creds = grpc.ssl_channel_credentials( + root_certificates=ca_cert_path.open('rb').read(), + private_key=key_path.open('rb').read(), + certificate_chain=cert_path.open('rb').read() +) + +channel = grpc.secure_channel( + f"localhost:{grpc_port}", + creds, + options=(('grpc.ssl_target_name_override', 'cln'),) +) +stub = NodeStub(channel) + +print(stub.Getinfo(node_pb2.GetinfoRequest())) +``` + +In this example we first local the client identity, as well as the CA +certificate so we can verify the server's identity against it. We then +create a `creds` instance using those details. Next we open a secure +channel, i.e., a channel over TLS with verification of identities. + +Notice that we override the expected SSL name with `cln`. This is +required because the plugin does not know the domain under which it +will be reachable, and will therefore use `cln` as a standin. See +custom certificate generation for how this could be changed. + +We then use the channel to instantiate the `NodeStub` representing the +service and its methods, so we can finally call the `Getinfo` method +with default arguments. + +## Generating custom certificates + +The automatically generated mTLS certificate will not know about +potential domains that it'll be served under, and will chose a number +of other parameters by default. If you'd like to generate a server +certificate with a custom domain you can use the following: + + +```bash +openssl genrsa -out server-key.pem 2048 +``` + +This generates the private key. Next we create a Certificate Signature Request (CSR) that we can then process using our CA identity: + +```bash +openssl req -key server-key.pem -new -out server.csr +``` + +You will be asked a number of questions, the most important of which +is the _Common Name_, which you should set to the domain name you'll +be serving the interface under. Next we can generate the actual +certificate by processing the request with the CA identity: + +```bash +openssl x509 -req -CA ca.pem -CAkey ca-key.pem \ + -in server.csr \ + -out server.pem \ + -days 365 -CAcreateserial +``` + +This will finally create the `server.pem` file, signed by the CA, +allowing you to access the node through its real domain name. You can +now move `server.pem` and `server-key.pem` into the lightning +directory, and they should be picked up during the start. + +[proto]: https://github.com/ElementsProject/lightning/blob/master/cln-grpc/proto/node.proto diff --git a/plugins/grpc-plugin/src/tls.rs b/plugins/grpc-plugin/src/tls.rs index 28a2972f7737..7f2446b40ec7 100644 --- a/plugins/grpc-plugin/src/tls.rs +++ b/plugins/grpc-plugin/src/tls.rs @@ -84,7 +84,7 @@ fn generate_or_load_identity( if parent.is_none() { params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); } else { - params.is_ca = rcgen::IsCa::SelfSignedOnly; + params.is_ca = rcgen::IsCa::NoCa; } params .distinguished_name diff --git a/plugins/keysend.c b/plugins/keysend.c index ea806c3545ca..c7ab8f74be10 100644 --- a/plugins/keysend.c +++ b/plugins/keysend.c @@ -174,6 +174,8 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf, p->destination = tal_steal(p, destination); p->payment_secret = NULL; p->payment_metadata = NULL; + p->blindedpath = NULL; + p->blindedpay = NULL; p->amount = *msat; p->routes = tal_steal(p, hints); // 22 is the Rust-Lightning default and the highest minimum we know of. diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index 86aa7b0e47ba..f579c504dc3d 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -1,6 +1,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -91,7 +92,7 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd, p->features = parent->features; p->id = parent->id; p->local_id = parent->local_id; - p->local_offer_id = parent->local_offer_id; + p->local_invreq_id = parent->local_invreq_id; p->groupid = parent->groupid; p->invstring = parent->invstring; p->description = parent->description; @@ -106,7 +107,7 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd, p->description = NULL; /* Caller must set this. */ p->local_id = NULL; - p->local_offer_id = NULL; + p->local_invreq_id = NULL; p->groupid = 0; } @@ -1222,9 +1223,7 @@ handle_final_failure(struct command *cmd, case WIRE_PERMANENT_NODE_FAILURE: case WIRE_TEMPORARY_NODE_FAILURE: case WIRE_REQUIRED_NODE_FEATURE_MISSING: -#if EXPERIMENTAL_FEATURES case WIRE_INVALID_ONION_BLINDING: -#endif case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: case WIRE_MPP_TIMEOUT: goto error; @@ -1325,9 +1324,7 @@ handle_intermediate_failure(struct command *cmd, case WIRE_REQUIRED_NODE_FEATURE_MISSING: case WIRE_INVALID_ONION_PAYLOAD: case WIRE_INVALID_REALM: -#if EXPERIMENTAL_FEATURES case WIRE_INVALID_ONION_BLINDING: -#endif tal_arr_expand(&root->excluded_nodes, *errnode); goto error; @@ -1602,8 +1599,8 @@ static struct command_result *payment_createonion_success(struct command *cmd, if (p->destination) json_add_node_id(req->js, "destination", p->destination); - if (p->local_offer_id) - json_add_sha256(req->js, "localofferid", p->local_offer_id); + if (p->local_invreq_id) + json_add_sha256(req->js, "localinvreqid", p->local_invreq_id); send_outreq(p->plugin, req); return command_still_pending(cmd); @@ -1666,6 +1663,33 @@ static void payment_add_hop_onion_payload(struct payment *p, } } +static void payment_add_blindedpath(const tal_t *ctx, + struct createonion_hop *hops, + const struct blinded_path *bpath, + struct amount_msat final_amt, + u32 final_cltv) +{ + /* It's a bit of a weird API for us, so we convert it back to + * the struct tlv_tlv_payload */ + u8 **tlvs = blinded_onion_hops(tmpctx, final_amt, final_cltv, bpath); + + for (size_t i = 0; i < tal_count(tlvs); i++) { + const u8 *cursor = tlvs[i]; + size_t max = tal_bytelen(tlvs[i]); + /* First one has to use real node_id */ + if (i == 0) + node_id_from_pubkey(&hops[i].pubkey, + &bpath->first_node_id); + else + node_id_from_pubkey(&hops[i].pubkey, + &bpath->path[i]->blinded_node_id); + + /* Length is prepended, discard that first! */ + fromwire_bigsize(&cursor, &max); + hops[i].tlv_payload = fromwire_tlv_tlv_payload(ctx, &cursor, &max); + } +} + static void payment_compute_onion_payloads(struct payment *p) { struct createonion_request *cr; @@ -1694,7 +1718,9 @@ static void payment_compute_onion_payloads(struct payment *p) cr->assocdata = tal_arr(cr, u8, 0); towire_sha256(&cr->assocdata, p->payment_hash); cr->session_key = NULL; - cr->hops = tal_arr(cr, struct createonion_hop, tal_count(p->route)); + cr->hops = tal_arr(cr, struct createonion_hop, + tal_count(p->route) + + (root->blindedpath ? tal_count(root->blindedpath->path) - 1: 0)); /* Non-final hops */ for (size_t i = 0; i < hopcount - 1; i++) { @@ -1708,14 +1734,27 @@ static void payment_compute_onion_payloads(struct payment *p) &p->route[i].scid)); } - /* Final hop */ - payment_add_hop_onion_payload( - p, &cr->hops[hopcount - 1], &p->route[hopcount - 1], - &p->route[hopcount - 1], true, - root->payment_secret, root->payment_metadata); - tal_append_fmt(&routetxt, "%s", - type_to_string(tmpctx, struct short_channel_id, - &p->route[hopcount - 1].scid)); + /* If we're headed to a blinded path, connect that now. */ + if (root->blindedpath) { + payment_add_blindedpath(cr->hops, cr->hops + hopcount - 1, + root->blindedpath, + root->blindedfinalamount, + root->blindedfinalcltv); + tal_append_fmt(&routetxt, "%s -> blinded path (%zu hops)", + type_to_string(tmpctx, struct short_channel_id, + &p->route[hopcount-1].scid), + tal_count(root->blindedpath->path)); + } else { + /* Final hop */ + payment_add_hop_onion_payload( + p, &cr->hops[hopcount - 1], &p->route[hopcount - 1], + &p->route[hopcount - 1], true, + root->payment_secret, + root->payment_metadata); + tal_append_fmt(&routetxt, "%s", + type_to_string(tmpctx, struct short_channel_id, + &p->route[hopcount - 1].scid)); + } paymod_log(p, LOG_DBG, "Created outgoing onion for route: %s", routetxt); @@ -2239,9 +2278,7 @@ static bool payment_can_retry(struct payment *p) case WIRE_PERMANENT_CHANNEL_FAILURE: case WIRE_REQUIRED_CHANNEL_FEATURE_MISSING: case WIRE_TEMPORARY_CHANNEL_FAILURE: -#if EXPERIMENTAL_FEATURES case WIRE_INVALID_ONION_BLINDING: -#endif return true; } diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index 8ec8ec07a439..94444d0a41b9 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -188,6 +188,12 @@ struct payment { /* Payment metadata, from the invoice if any. */ u8 *payment_metadata; + /* Blinded path (for bolt12) */ + struct blinded_path *blindedpath; + struct blinded_payinfo *blindedpay; + struct amount_msat blindedfinalamount; + u32 blindedfinalcltv; + u64 groupid; u32 partid; u32 next_partid; @@ -271,9 +277,9 @@ struct payment { /* Description, usually set if bolt11 has only description_hash */ const char *description; - /* If this is paying a local offer, this is the one (sendpay ensures we - * don't pay twice for single-use offers) */ - struct sha256 *local_offer_id; + /* If this is paying a local invoice_request, this is the one (sendpay + * ensures we don't pay twice for single-use invoice requests) */ + struct sha256 *local_invreq_id; /* Textual explanation of why this payment was attempted. */ const char *why; diff --git a/plugins/libplugin.c b/plugins/libplugin.c index ef68530eb629..ee813acc6246 100644 --- a/plugins/libplugin.c +++ b/plugins/libplugin.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,11 @@ struct command_result *command_done(void) return &complete; } +struct json_filter **command_filter_ptr(struct command *cmd) +{ + return &cmd->filter; +} + static void ld_send(struct plugin *plugin, struct json_stream *stream) { struct jstream *jstr = tal(plugin, struct jstream); @@ -255,6 +261,8 @@ struct json_stream *jsonrpc_stream_success(struct command *cmd) struct json_stream *js = jsonrpc_stream_start(cmd); json_object_start(js, "result"); + if (cmd->filter) + json_stream_attach_filter(js, cmd->filter); return js; } @@ -267,6 +275,7 @@ struct json_stream *jsonrpc_stream_fail(struct command *cmd, json_object_start(js, "error"); json_add_primitive_fmt(js, "code", "%d", code); json_add_string(js, "message", err); + cmd->filter = tal_free(cmd->filter); return js; } @@ -296,6 +305,14 @@ static struct command_result *command_complete(struct command *cmd, struct command_result *command_finished(struct command *cmd, struct json_stream *response) { + /* Detach filter before it complains about closing object it never saw */ + if (cmd->filter) { + const char *err = json_stream_detach_filter(tmpctx, response); + if (err) + json_add_string(response, "warning_parameter_filter", + err); + } + /* "result" or "error" object */ json_object_end(response); @@ -704,6 +721,82 @@ struct command_result *jsonrpc_set_datastore_(struct plugin *plugin, return send_outreq(plugin, req); } +struct get_ds_info { + struct command_result *(*string_cb)(struct command *command, + const char *val, + void *arg); + struct command_result *(*binary_cb)(struct command *command, + const u8 *val, + void *arg); + void *arg; +}; + +static struct command_result *listdatastore_done(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct get_ds_info *dsi) +{ + const jsmntok_t *ds = json_get_member(buf, result, "datastore"); + void *val; + + if (ds->size == 0) + val = NULL; + else { + /* First element in array is object */ + ds = ds + 1; + if (dsi->string_cb) { + const jsmntok_t *s; + s = json_get_member(buf, ds, "string"); + if (!s) { + /* Complain loudly, since they + * expected string! */ + plugin_log(cmd->plugin, LOG_BROKEN, + "Datastore gave nonstring result %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + val = NULL; + } else { + val = json_strdup(cmd, buf, s); + } + } else { + const jsmntok_t *hex; + hex = json_get_member(buf, ds, "hex"); + val = json_tok_bin_from_hex(cmd, buf, hex); + } + } + + if (dsi->string_cb) + return dsi->string_cb(cmd, val, dsi->arg); + return dsi->binary_cb(cmd, val, dsi->arg); +} + +struct command_result *jsonrpc_get_datastore_(struct plugin *plugin, + struct command *cmd, + const char *path, + struct command_result *(*string_cb)(struct command *command, + const char *val, + void *arg), + struct command_result *(*binary_cb)(struct command *command, + const u8 *val, + void *arg), + void *arg) +{ + struct out_req *req; + struct get_ds_info *dsi = tal(NULL, struct get_ds_info); + + dsi->string_cb = string_cb; + dsi->binary_cb = binary_cb; + dsi->arg = arg; + + /* listdatastore doesn't fail (except API misuse) */ + req = jsonrpc_request_start(plugin, cmd, "listdatastore", + listdatastore_done, datastore_fail, dsi); + tal_steal(req, dsi); + + json_add_keypath(req->js->jout, "key", path); + return send_outreq(plugin, req); +} + static void handle_rpc_reply(struct plugin *plugin, const jsmntok_t *toks) { const jsmntok_t *idtok, *contenttok; @@ -871,6 +964,7 @@ handle_getmanifest(struct command *getmanifest_cmd, } json_add_bool(params, "dynamic", p->restartability == PLUGIN_RESTARTABLE); + json_add_bool(params, "nonnumericids", true); json_array_start(params, "notifications"); for (size_t i = 0; p->notif_topics && i < p->num_notif_topics; i++) { @@ -1429,11 +1523,12 @@ void plugin_set_memleak_handler(struct plugin *plugin, static void ld_command_handle(struct plugin *plugin, const jsmntok_t *toks) { - const jsmntok_t *methtok, *paramstok; + const jsmntok_t *methtok, *paramstok, *filtertok; struct command *cmd; methtok = json_get_member(plugin->buffer, toks, "method"); paramstok = json_get_member(plugin->buffer, toks, "params"); + filtertok = json_get_member(plugin->buffer, toks, "filter"); if (!methtok || !paramstok) plugin_err(plugin, "Malformed JSON-RPC notification missing " @@ -1444,6 +1539,7 @@ static void ld_command_handle(struct plugin *plugin, cmd = tal(plugin, struct command); cmd->plugin = plugin; cmd->usage_only = false; + cmd->filter = NULL; cmd->methodname = json_strdup(cmd, plugin->buffer, methtok); cmd->id = json_get_id(cmd, plugin->buffer, toks); @@ -1504,6 +1600,13 @@ static void ld_command_handle(struct plugin *plugin, } } + if (filtertok) { + /* On error, this fails cmd */ + if (parse_filter(cmd, "filter", plugin->buffer, filtertok) + != NULL) + return; + } + for (size_t i = 0; i < plugin->num_commands; i++) { if (streq(cmd->methodname, plugin->commands[i].name)) { plugin->commands[i].handle(cmd, diff --git a/plugins/libplugin.h b/plugins/libplugin.h index 3e9b0f29584c..b5d2ae5e4639 100644 --- a/plugins/libplugin.h +++ b/plugins/libplugin.h @@ -55,6 +55,8 @@ struct command { const char *methodname; bool usage_only; struct plugin *plugin; + /* Optional output field filter. */ + struct json_filter *filter; }; /* Create an array of these, one for each command you support. */ @@ -163,7 +165,7 @@ struct json_stream *jsonrpc_stream_fail_data(struct command *cmd, /* Helper to jsonrpc_request_start() and send_outreq() to update datastore. * NULL cb means ignore, NULL errcb means plugin_error. -*/ + */ struct command_result *jsonrpc_set_datastore_(struct plugin *plugin, struct command *cmd, const char *path, @@ -208,6 +210,41 @@ struct command_result *jsonrpc_set_datastore_(struct plugin *plugin, const jsmntok_t *result), \ (arg)) +/* Helper to jsonrpc_request_start() and send_outreq() to read datastore. + * If the value not found, cb gets NULL @val. + */ +struct command_result *jsonrpc_get_datastore_(struct plugin *plugin, + struct command *cmd, + const char *path, + struct command_result *(*string_cb)(struct command *command, + const char *val, + void *arg), + struct command_result *(*binary_cb)(struct command *command, + const u8 *val, + void *arg), + void *arg); + +#define jsonrpc_get_datastore_string(plugin, cmd, path, cb, arg) \ + jsonrpc_get_datastore_((plugin), (cmd), (path), \ + typesafe_cb_preargs(struct command_result *, \ + void *, \ + (cb), (arg), \ + struct command *command, \ + const char *val), \ + NULL, \ + (arg)) + +#define jsonrpc_get_datastore_binary(plugin, cmd, path, cb, arg) \ + jsonrpc_get_datastore_((plugin), (cmd), (path), \ + NULL, \ + typesafe_cb_preargs(struct command_result *, \ + void *, \ + (cb), (arg), \ + struct command *command, \ + const u8 *val), \ + (arg)) + + /* This command is finished, here's the response (the content of the * "result" or "error" field) */ WARN_UNUSED_RESULT diff --git a/plugins/offers.c b/plugins/offers.c index cfe3309cf9e0..a34e0f07a7d2 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -17,9 +18,11 @@ #include #include -struct point32 id; +struct pubkey id; +u32 blockheight; u16 cltv_final; bool offers_enabled; +struct secret invoicesecret_base; static struct command_result *finished(struct command *cmd, const char *buf, @@ -45,8 +48,8 @@ static struct command_result *sendonionmessage_error(struct command *cmd, struct command_result * send_onion_reply(struct command *cmd, - struct tlv_onionmsg_payload_reply_path *reply_path, - struct tlv_onionmsg_payload *payload) + struct blinded_path *reply_path, + struct tlv_onionmsg_tlv *payload) { struct out_req *req; size_t nhops; @@ -60,22 +63,22 @@ send_onion_reply(struct command *cmd, nhops = tal_count(reply_path->path); for (size_t i = 0; i < nhops; i++) { - struct tlv_onionmsg_payload *omp; + struct tlv_onionmsg_tlv *omp; u8 *tlv; json_object_start(req->js, NULL); - json_add_pubkey(req->js, "id", &reply_path->path[i]->node_id); + json_add_pubkey(req->js, "id", &reply_path->path[i]->blinded_node_id); /* Put payload in last hop. */ if (i == nhops - 1) omp = payload; else - omp = tlv_onionmsg_payload_new(tmpctx); + omp = tlv_onionmsg_tlv_new(tmpctx); - omp->encrypted_data_tlv = reply_path->path[i]->encrypted_recipient_data; + omp->encrypted_recipient_data = reply_path->path[i]->encrypted_recipient_data; tlv = tal_arr(tmpctx, u8, 0); - towire_tlv_onionmsg_payload(&tlv, omp); + towire_tlv_onionmsg_tlv(&tlv, omp); json_add_hex_talarr(req->js, "tlv", tlv); json_object_end(req->js); } @@ -88,7 +91,7 @@ static struct command_result *onion_message_modern_call(struct command *cmd, const jsmntok_t *params) { const jsmntok_t *om, *replytok, *invreqtok, *invtok; - struct tlv_onionmsg_payload_reply_path *reply_path = NULL; + struct blinded_path *reply_path = NULL; if (!offers_enabled) return command_hook_success(cmd); @@ -96,7 +99,7 @@ static struct command_result *onion_message_modern_call(struct command *cmd, om = json_get_member(buf, params, "onion_message"); replytok = json_get_member(buf, om, "reply_blindedpath"); if (replytok) { - reply_path = json_to_reply_path(cmd, buf, replytok); + reply_path = json_to_blinded_path(cmd, buf, replytok); if (!reply_path) plugin_err(cmd->plugin, "Invalid reply path %.*s?", json_tok_full_len(replytok), @@ -127,11 +130,28 @@ static struct command_result *onion_message_modern_call(struct command *cmd, static const struct plugin_hook hooks[] = { { - "onion_message_blinded", + "onion_message_recv", onion_message_modern_call }, }; +static struct command_result *block_added_notify(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + json_scan(cmd, buf, params, "{block:{height:%}}", + JSON_SCAN(json_to_u32, &blockheight)); + return notification_handled(cmd); +} + +static const struct plugin_notification notifications[] = { + { + "block_added", + block_added_notify, + }, +}; + + struct decodable { const char *type; struct bolt11 *b11; @@ -218,9 +238,10 @@ static struct command_result *param_decodable(struct command *cmd, } static void json_add_chains(struct json_stream *js, + const char *fieldname, const struct bitcoin_blkid *chains) { - json_array_start(js, "chains"); + json_array_start(js, fieldname); for (size_t i = 0; i < tal_count(chains); i++) json_add_sha256(js, NULL, &chains[i].shad.sha); json_array_end(js); @@ -228,14 +249,15 @@ static void json_add_chains(struct json_stream *js, static void json_add_onionmsg_path(struct json_stream *js, const char *fieldname, - const struct onionmsg_path *path, + const struct onionmsg_hop *hop, const struct blinded_payinfo *payinfo) { json_object_start(js, fieldname); - json_add_pubkey(js, "node_id", &path->node_id); - json_add_hex_talarr(js, "encrypted_recipient_data", path->encrypted_recipient_data); + json_add_pubkey(js, "blinded_node_id", &hop->blinded_node_id); + json_add_hex_talarr(js, "encrypted_recipient_data", hop->encrypted_recipient_data); if (payinfo) { - json_add_u32(js, "fee_base_msat", payinfo->fee_base_msat); + json_add_amount_msat_only(js, "fee_base_msat", + amount_msat(payinfo->fee_base_msat)); json_add_u32(js, "fee_proportional_millionths", payinfo->fee_proportional_millionths); json_add_u32(js, "cltv_expiry_delta", @@ -247,13 +269,15 @@ static void json_add_onionmsg_path(struct json_stream *js, /* Returns true if valid */ static bool json_add_blinded_paths(struct json_stream *js, + const char *fieldname, struct blinded_path **paths, struct blinded_payinfo **blindedpay) { size_t n = 0; - json_array_start(js, "paths"); + json_array_start(js, fieldname); for (size_t i = 0; i < tal_count(paths); i++) { json_object_start(js, NULL); + json_add_pubkey(js, "first_node_id", &paths[i]->first_node_id); json_add_pubkey(js, "blinding", &paths[i]->blinding); json_array_start(js, "path"); for (size_t j = 0; j < tal_count(paths[i]->path); j++) { @@ -268,12 +292,11 @@ static bool json_add_blinded_paths(struct json_stream *js, json_array_end(js); /* BOLT-offers #12: - * - MUST reject the invoice if `blinded_payinfo` does not contain - * exactly as many `payinfo` as total `onionmsg_path` in - * `blinded_path`. + * - MUST reject the invoice if `invoice_blindedpay` does not contain + * exactly one `blinded_payinfo` per `invoice_paths`.`blinded_path`. */ if (blindedpay && n != tal_count(blindedpay)) { - json_add_string(js, "warning_invoice_invalid_blinded_payinfo", + json_add_string(js, "warning_invalid_invoice_blindedpay", "invoice does not have correct number of blinded_payinfo"); return false; } @@ -299,108 +322,235 @@ static const char *recurrence_time_unit_name(u8 time_unit) return NULL; } -static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer) +static bool json_add_utf8(struct json_stream *js, + const char *fieldname, + const char *utf8str) +{ + if (utf8_check(utf8str, tal_bytelen(utf8str))) { + json_add_stringn(js, fieldname, utf8str, tal_bytelen(utf8str)); + return true; + } + json_add_string(js, tal_fmt(tmpctx, "warning_invalid_%s", fieldname), + "invalid UTF8"); + return false; +} + +static bool json_add_offer_fields(struct json_stream *js, + const struct bitcoin_blkid *offer_chains, + const u8 *offer_metadata, + const char *offer_currency, + const u64 *offer_amount, + const char *offer_description, + const u8 *offer_features, + const u64 *offer_absolute_expiry, + struct blinded_path **offer_paths, + const char *offer_issuer, + const u64 *offer_quantity_max, + const struct pubkey *offer_node_id, + const struct recurrence *offer_recurrence, + const struct recurrence_paywindow *offer_recurrence_paywindow, + const u32 *offer_recurrence_limit, + const struct recurrence_base *offer_recurrence_base) { - struct sha256 offer_id; bool valid = true; - merkle_tlv(offer->fields, &offer_id); - json_add_sha256(js, "offer_id", &offer_id); - if (offer->chains) - json_add_chains(js, offer->chains); - if (offer->currency) { + if (offer_chains) + json_add_chains(js, "offer_chains", offer_chains); + if (offer_metadata) + json_add_hex_talarr(js, "offer_metadata", offer_metadata); + if (offer_currency) { const struct iso4217_name_and_divisor *iso4217; - json_add_stringn(js, "currency", - offer->currency, tal_bytelen(offer->currency)); - if (offer->amount) - json_add_u64(js, "amount", *offer->amount); - iso4217 = find_iso4217(offer->currency, - tal_bytelen(offer->currency)); + valid &= json_add_utf8(js, "offer_currency", offer_currency); + if (offer_amount) + json_add_u64(js, "offer_amount", *offer_amount); + iso4217 = find_iso4217(offer_currency, + tal_bytelen(offer_currency)); if (iso4217) - json_add_num(js, "minor_unit", iso4217->minor_unit); + json_add_num(js, "currency_minor_unit", iso4217->minor_unit); else - json_add_string(js, "warning_offer_unknown_currency", + json_add_string(js, "warning_unknown_offer_currency", "unknown currency code"); - } else if (offer->amount) - json_add_amount_msat_only(js, "amount_msat", - amount_msat(*offer->amount)); - if (offer->send_invoice) - json_add_bool(js, "send_invoice", true); - if (offer->refund_for) - json_add_sha256(js, "refund_for", offer->refund_for); + } else if (offer_amount) + json_add_amount_msat_only(js, "offer_amount_msat", + amount_msat(*offer_amount)); /* BOLT-offers #12: * A reader of an offer: *... - * - if `node_id` or `description` is not set: - * - MUST NOT respond to the offer. + * - if `offer_description` is not set: + * - MUST NOT respond to the offer. */ - if (offer->description) - json_add_stringn(js, "description", - offer->description, - tal_bytelen(offer->description)); + if (offer_description) + valid &= json_add_utf8(js, "offer_description", + offer_description); else { - json_add_string(js, "warning_offer_missing_description", + json_add_string(js, "warning_missing_offer_description", "offers without a description are invalid"); valid = false; } - if (offer->issuer) - json_add_stringn(js, "issuer", offer->issuer, - tal_bytelen(offer->issuer)); - if (offer->features) - json_add_hex_talarr(js, "features", offer->features); - if (offer->absolute_expiry) - json_add_u64(js, "absolute_expiry", - *offer->absolute_expiry); - if (offer->paths) - valid &= json_add_blinded_paths(js, offer->paths, NULL); - - if (offer->quantity_min) - json_add_u64(js, "quantity_min", *offer->quantity_min); - if (offer->quantity_max) - json_add_u64(js, "quantity_max", *offer->quantity_max); - if (offer->recurrence) { + if (offer_issuer) + valid &= json_add_utf8(js, "offer_issuer", offer_issuer); + if (offer_features) + json_add_hex_talarr(js, "offer_features", offer_features); + if (offer_absolute_expiry) + json_add_u64(js, "offer_absolute_expiry", + *offer_absolute_expiry); + if (offer_paths) + valid &= json_add_blinded_paths(js, "offer_paths", + offer_paths, NULL); + + if (offer_quantity_max) + json_add_u64(js, "offer_quantity_max", *offer_quantity_max); + + if (offer_recurrence) { const char *name; - json_object_start(js, "recurrence"); - json_add_num(js, "time_unit", offer->recurrence->time_unit); - name = recurrence_time_unit_name(offer->recurrence->time_unit); + json_object_start(js, "offer_recurrence"); + json_add_num(js, "time_unit", offer_recurrence->time_unit); + name = recurrence_time_unit_name(offer_recurrence->time_unit); if (name) json_add_string(js, "time_unit_name", name); - json_add_num(js, "period", offer->recurrence->period); - if (offer->recurrence_base) { + json_add_num(js, "period", offer_recurrence->period); + if (offer_recurrence_base) { json_add_u64(js, "basetime", - offer->recurrence_base->basetime); - if (offer->recurrence_base->start_any_period) + offer_recurrence_base->basetime); + if (offer_recurrence_base->start_any_period) json_add_bool(js, "start_any_period", true); } - if (offer->recurrence_limit) - json_add_u32(js, "limit", *offer->recurrence_limit); - if (offer->recurrence_paywindow) { + if (offer_recurrence_limit) + json_add_u32(js, "limit", *offer_recurrence_limit); + if (offer_recurrence_paywindow) { json_object_start(js, "paywindow"); json_add_u32(js, "seconds_before", - offer->recurrence_paywindow->seconds_before); + offer_recurrence_paywindow->seconds_before); json_add_u32(js, "seconds_after", - offer->recurrence_paywindow->seconds_after); - if (offer->recurrence_paywindow->proportional_amount) + offer_recurrence_paywindow->seconds_after); + if (offer_recurrence_paywindow->proportional_amount) json_add_bool(js, "proportional_amount", true); json_object_end(js); } json_object_end(js); } - if (offer->node_id) - json_add_point32(js, "node_id", offer->node_id); - else - valid = false; + /* Required for offers, *not* for others! */ + if (offer_node_id) + json_add_pubkey(js, "offer_node_id", offer_node_id); + + return valid; +} + +static void json_add_extra_fields(struct json_stream *js, + const char *fieldname, + const struct tlv_field *fields) +{ + bool have_extra = false; + + for (size_t i = 0; i < tal_count(fields); i++) { + if (fields[i].meta) + continue; + if (!have_extra) { + json_array_start(js, fieldname); + have_extra = true; + } + json_object_start(js, NULL); + json_add_u64(js, "type", fields[i].numtype); + json_add_u64(js, "length", fields[i].length); + json_add_hex(js, "value", + fields[i].value, fields[i].length); + } + if (have_extra) + json_array_end(js); +} - /* If it's present, offer_decode checked it was valid */ - if (offer->signature) - json_add_bip340sig(js, "signature", offer->signature); +static void json_add_offer(struct json_stream *js, const struct tlv_offer *offer) +{ + struct sha256 offer_id; + bool valid = true; + + offer_offer_id(offer, &offer_id); + json_add_sha256(js, "offer_id", &offer_id); + valid &= json_add_offer_fields(js, + offer->offer_chains, + offer->offer_metadata, + offer->offer_currency, + offer->offer_amount, + offer->offer_description, + offer->offer_features, + offer->offer_absolute_expiry, + offer->offer_paths, + offer->offer_issuer, + offer->offer_quantity_max, + offer->offer_node_id, + offer->offer_recurrence, + offer->offer_recurrence_paywindow, + offer->offer_recurrence_limit, + offer->offer_recurrence_base); + /* BOLT-offers #12: + * - if `offer_node_id` is not set: + * - MUST NOT respond to the offer. + */ + if (!offer->offer_node_id) { + json_add_string(js, "warning_missing_offer_node_id", + "offers without a node_id are invalid"); + valid = false; + } + json_add_extra_fields(js, "unknown_offer_tlvs", offer->fields); json_add_bool(js, "valid", valid); } +static bool json_add_invreq_fields(struct json_stream *js, + const u8 *invreq_metadata, + const struct bitcoin_blkid *invreq_chain, + const u64 *invreq_amount, + const u8 *invreq_features, + const u64 *invreq_quantity, + const struct pubkey *invreq_payer_id, + const utf8 *invreq_payer_note, + const u32 *invreq_recurrence_counter, + const u32 *invreq_recurrence_start) +{ + bool valid = true; + + /* BOLT-offers #12: + * - MUST fail the request if `invreq_payer_id` or `invreq_metadata` are not present. + */ + if (invreq_metadata) + json_add_hex_talarr(js, "invreq_metadata", + invreq_metadata); + else { + json_add_string(js, "warning_missing_invreq_metadata", + "invreq_metadata required"); + valid = false; + } + + /* This can be missing for an invoice though! */ + if (invreq_payer_id) + json_add_pubkey(js, "invreq_payer_id", invreq_payer_id); + + if (invreq_chain) + json_add_sha256(js, "invreq_chain", &invreq_chain->shad.sha); + + if (invreq_amount) + json_add_amount_msat_only(js, "invreq_amount_msat", + amount_msat(*invreq_amount)); + if (invreq_features) + json_add_hex_talarr(js, "invreq_features", invreq_features); + if (invreq_quantity) + json_add_u64(js, "invreq_quantity", *invreq_quantity); + if (invreq_payer_note) + valid &= json_add_utf8(js, "invreq_payer_note", invreq_payer_note); + if (invreq_recurrence_counter) { + json_add_u32(js, "invreq_recurrence_counter", + *invreq_recurrence_counter); + if (invreq_recurrence_start) + json_add_u32(js, "invreq_recurrence_start", + *invreq_recurrence_start); + } + + return valid; +} + /* Returns true if valid */ static bool json_add_fallback_address(struct json_stream *js, const struct chainparams *chain, @@ -415,7 +565,7 @@ static bool json_add_fallback_address(struct json_stream *js, return true; } json_add_string(js, - "warning_invoice_fallbacks_address_invalid", + "warning_invalid_invoice_fallbacks_address", "invalid fallback address for this version"); return false; } @@ -434,7 +584,7 @@ static bool json_add_fallbacks(struct json_stream *js, else chain = chainparams_for_network("bitcoin"); - json_array_start(js, "fallbacks"); + json_array_start(js, "invoice_fallbacks"); for (size_t i = 0; i < tal_count(fallbacks); i++) { size_t addrlen = tal_bytelen(fallbacks[i]->address); @@ -443,7 +593,7 @@ static bool json_add_fallbacks(struct json_stream *js, json_add_hex_talarr(js, "hex", fallbacks[i]->address); /* BOLT-offers #12: - * - for the bitcoin chain, if the invoice specifies `fallbacks`: + * - for the bitcoin chain, if the invoice specifies `invoice_fallbacks`: * - MUST ignore any `fallback_address` for which `version` is * greater than 16. * - MUST ignore any `fallback_address` for which `address` is @@ -453,12 +603,12 @@ static bool json_add_fallbacks(struct json_stream *js, */ if (fallbacks[i]->version > 16) { json_add_string(js, - "warning_invoice_fallbacks_version_invalid", + "warning_invalid_invoice_fallbacks_version", "invoice fallback version > 16"); valid = false; } else if (addrlen < 2 || addrlen > 40) { json_add_string(js, - "warning_invoice_fallbacks_address_invalid", + "warning_invalid_invoice_fallbacks_address", "invoice fallback address bad length"); valid = false; } else if (chain) { @@ -473,252 +623,223 @@ static bool json_add_fallbacks(struct json_stream *js, return valid; } -static void json_add_b12_invoice(struct json_stream *js, - const struct tlv_invoice *invoice) +static void json_add_invoice_request(struct json_stream *js, + const struct tlv_invoice_request *invreq) { bool valid = true; - if (invoice->chain) - json_add_sha256(js, "chain", &invoice->chain->shad.sha); - if (invoice->offer_id) - json_add_sha256(js, "offer_id", invoice->offer_id); + /* If there's an offer_node_id, then there's an offer. */ + if (invreq->offer_node_id) { + struct sha256 offer_id; + + invreq_offer_id(invreq, &offer_id); + json_add_sha256(js, "offer_id", &offer_id); + } + + valid &= json_add_offer_fields(js, + invreq->offer_chains, + invreq->offer_metadata, + invreq->offer_currency, + invreq->offer_amount, + invreq->offer_description, + invreq->offer_features, + invreq->offer_absolute_expiry, + invreq->offer_paths, + invreq->offer_issuer, + invreq->offer_quantity_max, + invreq->offer_node_id, + invreq->offer_recurrence, + invreq->offer_recurrence_paywindow, + invreq->offer_recurrence_limit, + invreq->offer_recurrence_base); + valid &= json_add_invreq_fields(js, + invreq->invreq_metadata, + invreq->invreq_chain, + invreq->invreq_amount, + invreq->invreq_features, + invreq->invreq_quantity, + invreq->invreq_payer_id, + invreq->invreq_payer_note, + invreq->invreq_recurrence_counter, + invreq->invreq_recurrence_start); /* BOLT-offers #12: - * - MUST reject the invoice if `msat` is not present. + * - MUST fail the request if `invreq_payer_id` or `invreq_metadata` are not present. */ - if (invoice->amount) - json_add_amount_msat_only(js, "amount_msat", - amount_msat(*invoice->amount)); - else { - json_add_string(js, "warning_invoice_missing_amount", - "invoices without an amount are invalid"); + if (!invreq->invreq_payer_id) { + json_add_string(js, "warning_missing_invreq_payer_id", + "invreq_payer_id required"); valid = false; } /* BOLT-offers #12: - * - MUST reject the invoice if `description` is not present. + * - MUST fail the request if `signature` is not correct as detailed + * in [Signature Calculation](#signature-calculation) using the + * `invreq_payer_id`. */ - if (invoice->description) - json_add_stringn(js, "description", invoice->description, - tal_bytelen(invoice->description)); - else { - json_add_string(js, "warning_invoice_missing_description", - "invoices without a description are invalid"); + if (invreq->signature) { + if (invreq->invreq_payer_id + && !bolt12_check_signature(invreq->fields, + "invoice_request", + "signature", + invreq->invreq_payer_id, + invreq->signature)) { + json_add_string(js, "warning_invalid_invoice_request_signature", + "Bad signature"); + valid = false; + } else { + json_add_bip340sig(js, "signature", invreq->signature); + } + } else { + json_add_string(js, "warning_missing_invoice_request_signature", + "Missing signature"); valid = false; } - if (invoice->issuer) - json_add_stringn(js, "issuer", invoice->issuer, - tal_bytelen(invoice->issuer)); - if (invoice->features) - json_add_hex_talarr(js, "features", invoice->features); - if (invoice->paths) { - /* BOLT-offers #12: - * - if `blinded_path` is present: - * - MUST reject the invoice if `blinded_payinfo` is not - * present. - * - MUST reject the invoice if `blinded_payinfo` does not - * contain exactly as many `payinfo` as total `onionmsg_path` - * in `blinded_path`. - */ - if (!invoice->blindedpay) { - json_add_string(js, "warning_invoice_missing_blinded_payinfo", - "invoices with blinded_path without blinded_payinfo are invalid"); - valid = false; - } - valid &= json_add_blinded_paths(js, invoice->paths, invoice->blindedpay); - } - if (invoice->quantity) - json_add_u64(js, "quantity", *invoice->quantity); - if (invoice->send_invoice) - json_add_bool(js, "send_invoice", true); - if (invoice->refund_for) - json_add_sha256(js, "refund_for", invoice->refund_for); - if (invoice->recurrence_counter) { - json_add_u32(js, "recurrence_counter", - *invoice->recurrence_counter); - if (invoice->recurrence_start) - json_add_u32(js, "recurrence_start", - *invoice->recurrence_start); - /* BOLT-offers-recurrence #12: - * - if the offer contained `recurrence`: - * - MUST reject the invoice if `recurrence_basetime` is not - * set. - */ - if (invoice->recurrence_basetime) - json_add_u64(js, "recurrence_basetime", - *invoice->recurrence_basetime); - else { - json_add_string(js, "warning_invoice_missing_recurrence_basetime", - "recurring invoices without a recurrence_basetime are invalid"); - valid = false; - } + json_add_extra_fields(js, "unknown_invoice_request_tlvs", invreq->fields); + json_add_bool(js, "valid", valid); +} + +static void json_add_b12_invoice(struct json_stream *js, + const struct tlv_invoice *invoice) +{ + bool valid = true; + + /* If there's an offer_node_id, then there's an offer. */ + if (invoice->offer_node_id) { + struct sha256 offer_id; + + invoice_offer_id(invoice, &offer_id); + json_add_sha256(js, "offer_id", &offer_id); } - if (invoice->payer_key) - json_add_point32(js, "payer_key", invoice->payer_key); - if (invoice->payer_info) - json_add_hex_talarr(js, "payer_info", invoice->payer_info); - if (invoice->payer_note) - json_add_stringn(js, "payer_note", invoice->payer_note, - tal_bytelen(invoice->payer_note)); + valid &= json_add_offer_fields(js, + invoice->offer_chains, + invoice->offer_metadata, + invoice->offer_currency, + invoice->offer_amount, + invoice->offer_description, + invoice->offer_features, + invoice->offer_absolute_expiry, + invoice->offer_paths, + invoice->offer_issuer, + invoice->offer_quantity_max, + invoice->offer_node_id, + invoice->offer_recurrence, + invoice->offer_recurrence_paywindow, + invoice->offer_recurrence_limit, + invoice->offer_recurrence_base); + valid &= json_add_invreq_fields(js, + invoice->invreq_metadata, + invoice->invreq_chain, + invoice->invreq_amount, + invoice->invreq_features, + invoice->invreq_quantity, + invoice->invreq_payer_id, + invoice->invreq_payer_note, + invoice->invreq_recurrence_counter, + invoice->invreq_recurrence_start); /* BOLT-offers #12: - * - MUST reject the invoice if `created_at` is not present. + * - MUST reject the invoice if `invoice_paths` is not present + * or is empty. + * - MUST reject the invoice if `invoice_blindedpay` is not present. + * - MUST reject the invoice if `invoice_blindedpay` does not contain + * exactly one `blinded_payinfo` per `invoice_paths`.`blinded_path`. */ - if (invoice->created_at) { - json_add_u64(js, "created_at", *invoice->created_at); + if (invoice->invoice_paths) { + if (!invoice->invoice_blindedpay) { + json_add_string(js, "warning_missing_invoice_blindedpay", + "invoices with paths without blindedpay are invalid"); + valid = false; + } + valid &= json_add_blinded_paths(js, "invoice_paths", + invoice->invoice_paths, + invoice->invoice_blindedpay); } else { - json_add_string(js, "warning_invoice_missing_created_at", - "invoices without created_at are invalid"); + json_add_string(js, "warning_missing_invoice_paths", + "invoices without a invoice_paths are invalid"); valid = false; } - /* BOLT-offers #12: - * - MUST reject the invoice if `payment_hash` is not present. - */ - if (invoice->payment_hash) - json_add_sha256(js, "payment_hash", invoice->payment_hash); - else { - json_add_string(js, "warning_invoice_missing_payment_hash", - "invoices without a payment_hash are invalid"); + if (invoice->invoice_created_at) { + json_add_u64(js, "invoice_created_at", *invoice->invoice_created_at); + } else { + json_add_string(js, "warning_missing_invoice_created_at", + "invoices without created_at are invalid"); valid = false; } /* BOLT-offers #12: * - * - if the expiry for accepting payment is not 7200 seconds after - * `created_at`: - * - MUST set `relative_expiry` - */ - if (invoice->relative_expiry) - json_add_u32(js, "relative_expiry", *invoice->relative_expiry); - else - json_add_u32(js, "relative_expiry", 7200); - - /* BOLT-offers #12: - * - if the `min_final_cltv_expiry` for the last HTLC in the route is - * not 18: - * - MUST set `min_final_cltv_expiry`. + * - if `invoice_relative_expiry` is present: + * - MUST reject the invoice if the current time since 1970-01-01 UTC + * is greater than `invoice_created_at` plus `seconds_from_creation`. + * - otherwise: + * - MUST reject the invoice if the current time since 1970-01-01 UTC + * is greater than `invoice_created_at` plus 7200. */ - if (invoice->cltv) - json_add_u32(js, "min_final_cltv_expiry", *invoice->cltv); + if (invoice->invoice_relative_expiry) + json_add_u32(js, "invoice_relative_expiry", *invoice->invoice_relative_expiry); else - json_add_u32(js, "min_final_cltv_expiry", 18); - - if (invoice->fallbacks) - valid &= json_add_fallbacks(js, - invoice->chain, - invoice->fallbacks); + json_add_u32(js, "invoice_relative_expiry", BOLT12_DEFAULT_REL_EXPIRY); - /* BOLT-offers #12: - * - if the offer contained `refund_for`: - * - MUST reject the invoice if `payer_key` does not match the invoice - * whose `payment_hash` is equal to `refund_for` - * `refunded_payment_hash` - * - MUST reject the invoice if `refund_signature` is not set. - * - MUST reject the invoice if `refund_signature` is not a valid - * signature using `payer_key` as described in - * [Signature Calculation](#signature-calculation). - */ - if (invoice->refund_signature) { - json_add_bip340sig(js, "refund_signature", - invoice->refund_signature); - if (!invoice->payer_key) { - json_add_string(js, "warning_invoice_refund_signature_missing_payer_key", - "Can't have refund_signature without payer key"); - valid = false; - } else if (!bolt12_check_signature(invoice->fields, - "invoice", - "refund_signature", - invoice->payer_key, - invoice->refund_signature)) { - json_add_string(js, "warning_invoice_refund_signature_invalid", - "refund_signature does not match"); - valid = false; - } - } else if (invoice->refund_for) { - json_add_string(js, "warning_invoice_refund_missing_signature", - "refund_for requires refund_signature"); + if (invoice->invoice_payment_hash) + json_add_sha256(js, "invoice_payment_hash", invoice->invoice_payment_hash); + else { + json_add_string(js, "warning_missing_invoice_payment_hash", + "invoices without a payment_hash are invalid"); valid = false; } - /* invoice_decode checked these */ - json_add_point32(js, "node_id", invoice->node_id); - json_add_bip340sig(js, "signature", invoice->signature); - - json_add_bool(js, "valid", valid); -} - -static void json_add_invoice_request(struct json_stream *js, - const struct tlv_invoice_request *invreq) -{ - bool valid = true; - - if (invreq->chain) - json_add_sha256(js, "chain", &invreq->chain->shad.sha); - /* BOLT-offers #12: - * - MUST fail the request if `payer_key` is not present. - *... - * - MUST fail the request if `features` contains unknown even bits. - * - MUST fail the request if `offer_id` is not present. + * - MUST reject the invoice if `invoice_amount` is not present. */ - if (invreq->offer_id) - json_add_sha256(js, "offer_id", invreq->offer_id); + if (invoice->invoice_amount) + json_add_amount_msat_only(js, "invoice_amount_msat", + amount_msat(*invoice->invoice_amount)); else { - json_add_string(js, "warning_invoice_request_missing_offer_id", - "invoice_request requires offer_id"); + json_add_string(js, "warning_missing_invoice_amount", + "invoices without an amount are invalid"); valid = false; } - if (invreq->amount) - json_add_amount_msat_only(js, "amount_msat", - amount_msat(*invreq->amount)); - if (invreq->features) - json_add_hex_talarr(js, "features", invreq->features); - if (invreq->quantity) - json_add_u64(js, "quantity", *invreq->quantity); - - if (invreq->recurrence_counter) - json_add_u32(js, "recurrence_counter", - *invreq->recurrence_counter); - if (invreq->recurrence_start) - json_add_u32(js, "recurrence_start", - *invreq->recurrence_start); - if (invreq->payer_key) - json_add_point32(js, "payer_key", invreq->payer_key); + + if (invoice->invoice_fallbacks) + valid &= json_add_fallbacks(js, + invoice->invreq_chain, + invoice->invoice_fallbacks); + + if (invoice->invoice_features) + json_add_hex_talarr(js, "features", invoice->invoice_features); + + if (invoice->invoice_node_id) + json_add_pubkey(js, "invoice_node_id", invoice->invoice_node_id); else { - json_add_string(js, "warning_invoice_request_missing_payer_key", - "invoice_request requires payer_key"); + json_add_string(js, "warning_missing_invoice_node_id", + "invoices without an invoice_node_id are invalid"); valid = false; } - if (invreq->payer_info) - json_add_hex_talarr(js, "payer_info", invreq->payer_info); - if (invreq->payer_note) - json_add_stringn(js, "payer_note", invreq->payer_note, - tal_bytelen(invreq->payer_note)); - /* BOLT-offers #12: - * - MUST fail the request if there is no `signature` field. - * - MUST fail the request if `signature` is not correct. + /* BOLT-offers-recurrence #12: + * - if the offer contained `recurrence`: + * - MUST reject the invoice if `recurrence_basetime` is not + * set. */ - if (invreq->signature) { - if (invreq->payer_key - && !bolt12_check_signature(invreq->fields, - "invoice_request", - "signature", - invreq->payer_key, - invreq->signature)) { - json_add_string(js, "warning_invoice_request_invalid_signature", - "Bad signature"); + if (invoice->offer_recurrence) { + if (invoice->invoice_recurrence_basetime) + json_add_u64(js, "invoice_recurrence_basetime", + *invoice->invoice_recurrence_basetime); + else { + json_add_string(js, "warning_missing_invoice_recurrence_basetime", + "recurring invoices without a recurrence_basetime are invalid"); valid = false; } - } else { - json_add_string(js, "warning_invoice_request_missing_signature", - "Missing signature"); - valid = false; } + /* invoice_decode checked this */ + json_add_bip340sig(js, "signature", invoice->signature); + + json_add_extra_fields(js, "unknown_invoice_tlvs", invoice->fields); json_add_bool(js, "valid", valid); } @@ -921,14 +1042,10 @@ static const char *init(struct plugin *p, const char *buf UNUSED, const jsmntok_t *config UNUSED) { - struct pubkey k; - rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)), - "{id:%}", JSON_SCAN(json_to_pubkey, &k)); - if (secp256k1_xonly_pubkey_from_pubkey(secp256k1_ctx, &id.pubkey, - NULL, &k.pubkey) != 1) - abort(); + "{id:%}", JSON_SCAN(json_to_pubkey, &id), + "{blockheight:%}", JSON_SCAN(json_to_u32, &blockheight)); rpc_scan(p, "listconfigs", take(json_out_obj(NULL, NULL, NULL)), @@ -936,6 +1053,11 @@ static const char *init(struct plugin *p, JSON_SCAN(json_to_u16, &cltv_final), JSON_SCAN(json_to_bool, &offers_enabled)); + rpc_scan(p, "makesecret", + take(json_out_obj(NULL, "string", INVOICE_PATH_BASE_STRING)), + "{secret:%}", + JSON_SCAN(json_to_secret, &invoicesecret_base)); + return NULL; } @@ -948,11 +1070,11 @@ static const struct plugin_command commands[] = { json_offer }, { - "offerout", + "invoicerequest", "payment", - "Create an offer to send money", - "Create an offer to pay invoices of {amount} with {description}, optional {issuer}, internal {label}, {absolute_expiry} and {refund_for}", - json_offerout + "Create an invoice_request to send money", + "Create an invoice_request to pay invoices of {amount} with {description}, optional {issuer}, internal {label}, and {absolute_expiry}", + json_invoicerequest }, { "decode", @@ -969,7 +1091,9 @@ int main(int argc, char *argv[]) /* We deal in UTC; mktime() uses local time */ setenv("TZ", "", 1); - plugin_main(argv, init, PLUGIN_RESTARTABLE, true, NULL, commands, - ARRAY_SIZE(commands), NULL, 0, hooks, ARRAY_SIZE(hooks), + plugin_main(argv, init, PLUGIN_RESTARTABLE, true, NULL, + commands, ARRAY_SIZE(commands), + notifications, ARRAY_SIZE(notifications), + hooks, ARRAY_SIZE(hooks), NULL, 0, NULL); } diff --git a/plugins/offers.h b/plugins/offers.h index d283da351ad9..dccdaff21029 100644 --- a/plugins/offers.h +++ b/plugins/offers.h @@ -8,6 +8,6 @@ struct command; /* Helper to send a reply */ struct command_result *WARN_UNUSED_RESULT send_onion_reply(struct command *cmd, - struct tlv_onionmsg_payload_reply_path *reply_path, - struct tlv_onionmsg_payload *payload); + struct blinded_path *reply_path, + struct tlv_onionmsg_tlv *payload); #endif /* LIGHTNING_PLUGINS_OFFERS_H */ diff --git a/plugins/offers_inv_hook.c b/plugins/offers_inv_hook.c index 680307bee12a..ebe43977ac1f 100644 --- a/plugins/offers_inv_hook.c +++ b/plugins/offers_inv_hook.c @@ -11,12 +11,13 @@ /* We need to keep the reply path around so we can reply if error */ struct inv { struct tlv_invoice *inv; + struct sha256 invreq_id; /* May be NULL */ - struct tlv_onionmsg_payload_reply_path *reply_path; + struct blinded_path *reply_path; - /* The offer, once we've looked it up. */ - struct tlv_offer *offer; + /* The invreq, once we've looked it up. */ + struct tlv_invoice_request *invreq; }; static struct command_result *WARN_UNUSED_RESULT @@ -26,17 +27,13 @@ fail_inv_level(struct command *cmd, const char *fmt, va_list ap) { char *full_fmt, *msg; - struct tlv_onionmsg_payload *payload; + struct tlv_onionmsg_tlv *payload; struct tlv_invoice_error *err; full_fmt = tal_fmt(tmpctx, "Failed invoice"); if (inv->inv) { tal_append_fmt(&full_fmt, " %s", invoice_encode(tmpctx, inv->inv)); - if (inv->inv->offer_id) - tal_append_fmt(&full_fmt, " for offer %s", - type_to_string(tmpctx, struct sha256, - inv->inv->offer_id)); } tal_append_fmt(&full_fmt, ": %s", fmt); @@ -56,7 +53,7 @@ fail_inv_level(struct command *cmd, err->error = tal_dup_arr(err, char, msg, strlen(msg), 0); /* FIXME: Add suggested_value / erroneous_field! */ - payload = tlv_onionmsg_payload_new(tmpctx); + payload = tlv_onionmsg_tlv_new(tmpctx); payload->invoice_error = tal_arr(payload, u8, 0); towire_tlv_invoice_error(&payload->invoice_error, err); return send_onion_reply(cmd, inv->reply_path, payload); @@ -92,52 +89,17 @@ fail_internalerr(struct command *cmd, return ret; } -#define inv_must_have(cmd_, i_, fld_) \ - test_field(cmd_, i_, i_->inv->fld_ != NULL, #fld_, "missing") -#define inv_must_not_have(cmd_, i_, fld_) \ - test_field(cmd_, i_, i_->inv->fld_ == NULL, #fld_, "unexpected") -#define inv_must_equal_offer(cmd_, i_, fld_) \ - test_field_eq(cmd_, i_, i_->inv->fld_, i_->offer->fld_, #fld_) - -static struct command_result * -test_field(struct command *cmd, - const struct inv *inv, - bool test, const char *fieldname, const char *what) -{ - if (!test) - return fail_inv(cmd, inv, "%s %s", what, fieldname); - return NULL; -} - -static struct command_result * -test_field_eq(struct command *cmd, - const struct inv *inv, - const tal_t *invfield, - const tal_t *offerfield, - const char *fieldname) -{ - if (invfield && !offerfield) - return fail_inv(cmd, inv, "Unexpected %s", fieldname); - if (!invfield && offerfield) - return fail_inv(cmd, inv, "Expected %s", fieldname); - if (!memeq(invfield, tal_bytelen(invfield), - offerfield, tal_bytelen(offerfield))) - return fail_inv(cmd, inv, "Different %s", fieldname); - return NULL; -} - static struct command_result *pay_done(struct command *cmd, const char *buf, const jsmntok_t *result, struct inv *inv) { - struct amount_msat msat = amount_msat(*inv->inv->amount); + struct amount_msat msat = amount_msat(*inv->inv->invoice_amount); plugin_log(cmd->plugin, LOG_INFORM, - "Payed out %s for offer %s%s: %.*s", + "Payed out %s for invreq %s: %.*s", type_to_string(tmpctx, struct amount_msat, &msat), - type_to_string(tmpctx, struct sha256, inv->inv->offer_id), - inv->offer->refund_for ? " (refund)": "", + type_to_string(tmpctx, struct sha256, &inv->invreq_id), json_tok_full_len(result), json_tok_full(buf, result)); return command_hook_success(cmd); @@ -155,178 +117,105 @@ static struct command_result *pay_error(struct command *cmd, json_tok_full(buf, msgtok)); } -static struct command_result *listoffers_done(struct command *cmd, - const char *buf, - const jsmntok_t *result, - struct inv *inv) +static struct command_result *listinvreqs_done(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct inv *inv) { - const jsmntok_t *arr = json_get_member(buf, result, "offers"); - const jsmntok_t *offertok, *activetok, *b12tok; + const jsmntok_t *arr = json_get_member(buf, result, "invoicerequests"); + const jsmntok_t *activetok; bool active; struct amount_msat amt; - char *fail; struct out_req *req; - struct command_result *err; + struct sha256 merkle, sighash; /* BOLT-offers #12: - * - otherwise if `offer_id` is set: - * - MUST reject the invoice if the `offer_id` does not refer an - * unexpired offer with `send_invoice` + * A reader of an invoice: + *... + * - if the invoice is a response to an `invoice_request`: + * - MUST reject the invoice if all fields less than type 160 do not exactly match the `invoice_request`. + * - if `offer_node_id` is present (invoice_request for an offer): + * - MUST reject the invoice if `invoice_node_id` is not equal to `offer_node_id`. + * - otherwise (invoice_request without an offer): + * - MAY reject the invoice if it cannot confirm that `invoice_node_id` is correct, out-of-band. + * + * - otherwise: (a invoice presented without being requested, eg. scanned by user): */ - if (arr->size == 0) - return fail_inv(cmd, inv, "Unknown offer"); - plugin_log(cmd->plugin, LOG_INFORM, - "Attempting payment of offer %.*s", - json_tok_full_len(result), - json_tok_full(buf, result)); + /* Since the invreq_id hashes all fields < 160, we know it matches */ + if (arr->size == 0) + return fail_inv(cmd, inv, "Unknown invoice_request %s", + type_to_string(tmpctx, struct sha256, &inv->invreq_id)); - offertok = arr + 1; - activetok = json_get_member(buf, offertok, "active"); + activetok = json_get_member(buf, arr + 1, "active"); if (!activetok) { return fail_internalerr(cmd, inv, "Missing active: %.*s", - json_tok_full_len(offertok), - json_tok_full(buf, offertok)); + json_tok_full_len(arr), + json_tok_full(buf, arr)); } json_to_bool(buf, activetok, &active); if (!active) - return fail_inv(cmd, inv, "Offer no longer available"); + return fail_inv(cmd, inv, "invoice_request no longer available"); - b12tok = json_get_member(buf, offertok, "bolt12"); - if (!b12tok) { - return fail_internalerr(cmd, inv, - "Missing bolt12: %.*s", - json_tok_full_len(offertok), - json_tok_full(buf, offertok)); - } - inv->offer = offer_decode(inv, - buf + b12tok->start, - b12tok->end - b12tok->start, - plugin_feature_set(cmd->plugin), - chainparams, &fail); - if (!inv->offer) { - return fail_internalerr(cmd, inv, - "Invalid offer: %s (%.*s)", - fail, - json_tok_full_len(offertok), - json_tok_full(buf, offertok)); - } - - if (inv->offer->absolute_expiry - && time_now().ts.tv_sec >= *inv->offer->absolute_expiry) { - /* FIXME: do deloffer to disable it */ - return fail_inv(cmd, inv, "Offer expired"); - } - - if (!inv->offer->send_invoice) { - return fail_inv(cmd, inv, "Offer did not expect invoice"); - } + /* We only save ones without offers to the db! */ + assert(!inv->inv->offer_node_id); /* BOLT-offers #12: - * - MUST reject the invoice unless the following fields are equal - * or unset exactly as they are in the `offer`: - * - `refund_for` - * - `description` + * - MUST reject the invoice if `signature` is not a valid signature + * using `invoice_node_id` as described in [Signature + * Calculation](#signature-calculation). */ - err = inv_must_equal_offer(cmd, inv, refund_for); - if (err) - return err; - err = inv_must_equal_offer(cmd, inv, description); - if (err) - return err; + if (!inv->inv->signature) + return fail_inv(cmd, inv, "invoice missing signature"); - /* BOLT-offers #12: - * - if the offer had a `quantity_min` or `quantity_max` field: - * - MUST fail the request if there is no `quantity` field. - * - MUST fail the request if there is `quantity` is not within - * that (inclusive) range. - * - otherwise: - * - MUST fail the request if there is a `quantity` field. - */ - if (inv->offer->quantity_min || inv->offer->quantity_max) { - err = inv_must_have(cmd, inv, quantity); - if (err) - return err; - - if (inv->offer->quantity_min && - *inv->inv->quantity < *inv->offer->quantity_min) { - return fail_inv(cmd, inv, - "quantity %"PRIu64 " < %"PRIu64, - *inv->inv->quantity, - *inv->offer->quantity_min); - } - - if (inv->offer->quantity_max && - *inv->inv->quantity > *inv->offer->quantity_max) { - return fail_inv(cmd, inv, - "quantity %"PRIu64" > %"PRIu64, - *inv->inv->quantity, - *inv->offer->quantity_max); - } - } else { - err = inv_must_not_have(cmd, inv, quantity); - if (err) - return err; - } + merkle_tlv(inv->inv->fields, &merkle); + sighash_from_merkle("invoice", "signature", &merkle, &sighash); + if (!check_schnorr_sig(&sighash, &inv->inv->invoice_node_id->pubkey, inv->inv->signature)) + return fail_inv(cmd, inv, "invalid invoice signature"); /* BOLT-offers #12: - * - MUST reject the invoice if `msat` is not present. + * - SHOULD confirm authorization if `invoice_amount`.`msat` is not + * within the amount range authorized. */ - err = inv_must_have(cmd, inv, amount); - if (err) - return err; - - /* FIXME: Handle alternate currency conversion here! */ - if (inv->offer->currency) - return fail_inv(cmd, inv, "FIXME: support currency"); - - amt = amount_msat(*inv->inv->amount); - /* If you send an offer without an amount, you want to give away - * unlimited money. Err, ok? */ - if (inv->offer->amount) { - struct amount_msat expected = amount_msat(*inv->offer->amount); - - /* We could allow invoices for less, I suppose. */ - if (!amount_msat_eq(expected, amt)) - return fail_inv(cmd, inv, "Expected invoice for %s", - fmt_amount_msat(tmpctx, expected)); - } + /* Because there's no offer, we had to set invreq_amount */ + if (*inv->inv->invoice_amount > *inv->inv->invreq_amount) + return fail_inv(cmd, inv, "invoice amount is too large"); + /* FIXME: Create a hook for validating the invoice_node_id! */ + amt = amount_msat(*inv->inv->invoice_amount); plugin_log(cmd->plugin, LOG_INFORM, - "Attempting payment of %s for offer %s%s", + "Attempting payment of %s for invoice_request %s", type_to_string(tmpctx, struct amount_msat, &amt), - type_to_string(tmpctx, struct sha256, inv->inv->offer_id), - inv->offer->refund_for ? " (refund)": ""); + type_to_string(tmpctx, struct sha256, &inv->invreq_id)); req = jsonrpc_request_start(cmd->plugin, cmd, "pay", pay_done, pay_error, inv); json_add_string(req->js, "bolt11", invoice_encode(tmpctx, inv->inv)); - json_add_sha256(req->js, "localofferid", inv->inv->offer_id); + json_add_sha256(req->js, "localinvreqid", &inv->invreq_id); return send_outreq(cmd->plugin, req); } -static struct command_result *listoffers_error(struct command *cmd, - const char *buf, - const jsmntok_t *err, - struct inv *inv) +static struct command_result *listinvreqs_error(struct command *cmd, + const char *buf, + const jsmntok_t *err, + struct inv *inv) { return fail_internalerr(cmd, inv, - "listoffers gave JSON error: %.*s", + "listinvoicerequests gave JSON error: %.*s", json_tok_full_len(err), json_tok_full(buf, err)); } struct command_result *handle_invoice(struct command *cmd, const u8 *invbin, - struct tlv_onionmsg_payload_reply_path *reply_path STEALS) + struct blinded_path *reply_path STEALS) { size_t len = tal_count(invbin); struct inv *inv = tal(cmd, struct inv); struct out_req *req; - struct command_result *err; int bad_feature; - struct sha256 m, shash; + u64 invexpiry; inv->reply_path = tal_steal(inv, reply_path); @@ -336,69 +225,102 @@ struct command_result *handle_invoice(struct command *cmd, "Invalid invoice %s", tal_hex(tmpctx, invbin)); } + invoice_invreq_id(inv->inv, &inv->invreq_id); /* BOLT-offers #12: - * - * The reader of an invoice_request: + * A reader of an invoice: + * - MUST reject the invoice if `invoice_amount` is not present. + * - MUST reject the invoice if `invoice_created_at` is not present. + * - MUST reject the invoice if `invoice_payment_hash` is not present. + * - MUST reject the invoice if `invoice_node_id` is not present. + */ + if (!inv->inv->invoice_amount) + return fail_inv(cmd, inv, "Missing invoice_amount"); + if (!inv->inv->invoice_created_at) + return fail_inv(cmd, inv, "Missing invoice_created_at"); + if (!inv->inv->invoice_payment_hash) + return fail_inv(cmd, inv, "Missing invoice_payment_hash"); + if (!inv->inv->invoice_node_id) + return fail_inv(cmd, inv, "Missing invoice_node_id"); + + /* BOLT-offers #12: + * A reader of an invoice: *... - * - MUST fail the request if `features` contains unknown even bits. + * - if `invoice_features` contains unknown _odd_ bits that are non-zero: + * - MUST ignore the bit. + * - if `invoice_features` contains unknown _even_ bits that are non-zero: + * - MUST reject the invoice. */ bad_feature = features_unsupported(plugin_feature_set(cmd->plugin), - inv->inv->features, - BOLT11_FEATURE); + inv->inv->invoice_features, + BOLT12_INVOICE_FEATURE); if (bad_feature != -1) { return fail_inv(cmd, inv, - "Unsupported inv feature %i", + "Unsupported invoice feature %i", bad_feature); } /* BOLT-offers #12: - * - * The reader of an invoice_request: + * A reader of an invoice: *... - * - if `chain` is not present: - * - MUST fail the request if bitcoin is not a supported chain. - * - otherwise: - * - MUST fail the request if `chain` is not a supported chain. + * - if `invoice_relative_expiry` is present: + * - MUST reject the invoice if the current time since 1970-01-01 UTC is greater than `invoice_created_at` plus `seconds_from_creation`. + * - otherwise: + * - MUST reject the invoice if the current time since 1970-01-01 UTC is greater than `invoice_created_at` plus 7200. */ - if (!bolt12_chain_matches(inv->inv->chain, chainparams)) { + if (inv->inv->invoice_relative_expiry) + invexpiry = *inv->inv->invoice_created_at + *inv->inv->invoice_relative_expiry; + else + invexpiry = *inv->inv->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY; + if (time_now().ts.tv_sec > invexpiry) + return fail_inv(cmd, inv, "Expired invoice"); + + /* BOLT-offers #12: + * A reader of an invoice: + *... + * - MUST reject the invoice if `invoice_paths` is not present or is empty. + * - MUST reject the invoice if `invoice_blindedpay` is not present. + * - MUST reject the invoice if `invoice_blindedpay` does not contain exactly one `blinded_payinfo` per `invoice_paths`.`blinded_path`. + */ + if (!inv->inv->invoice_paths) + return fail_inv(cmd, inv, "Missing invoice_paths"); + if (!inv->inv->invoice_blindedpay) + return fail_inv(cmd, inv, "Missing invoice_blindedpay"); + if (tal_count(inv->inv->invoice_blindedpay) + != tal_count(inv->inv->invoice_paths)) return fail_inv(cmd, inv, - "Wrong chain %s", - tal_hex(tmpctx, inv->inv->chain)); - } + "Mismatch between invoice_blindedpay and invoice_paths"); /* BOLT-offers #12: - * - MUST reject the invoice if `signature` is not a valid signature - * using `node_id` as described in - * [Signature Calculation](#signature-calculation). + * A reader of an invoice: + *... + * - For each `invoice_blindedpay`.`payinfo`: + * - MUST NOT use the corresponding `invoice_paths`.`path` if + * `payinfo`.`features` has any unknown even bits set. + * - MUST reject the invoice if this leaves no usable paths. */ - err = inv_must_have(cmd, inv, node_id); - if (err) - return err; - - err = inv_must_have(cmd, inv, signature); - if (err) - return err; - - merkle_tlv(inv->inv->fields, &m); - sighash_from_merkle("invoice", "signature", &m, &shash); - if (secp256k1_schnorrsig_verify(secp256k1_ctx, - inv->inv->signature->u8, - shash.u.u8, - sizeof(shash.u.u8), - &inv->inv->node_id->pubkey) != 1) { - return fail_inv(cmd, inv, "Bad signature"); + for (size_t i = 0; i < tal_count(inv->inv->invoice_blindedpay); i++) { + bad_feature = features_unsupported(plugin_feature_set(cmd->plugin), + inv->inv->invoice_blindedpay[i]->features, + /* FIXME: Technically a different feature set? */ + BOLT12_INVOICE_FEATURE); + if (bad_feature == -1) + continue; + + tal_arr_remove(&inv->inv->invoice_paths, i); + tal_arr_remove(&inv->inv->invoice_blindedpay, i); + i--; + } + if (tal_count(inv->inv->invoice_paths) == 0) { + return fail_inv(cmd, inv, + "Unsupported feature for all paths (%i)", + bad_feature); } - /* We don't pay random invoices off the internet, sorry. */ - err = inv_must_have(cmd, inv, offer_id); - if (err) - return err; - - /* Now find the offer. */ - req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers", - listoffers_done, listoffers_error, inv); - json_add_sha256(req->js, "offer_id", inv->inv->offer_id); + /* Now find the invoice_request. */ + req = jsonrpc_request_start(cmd->plugin, cmd, "listinvoicerequests", + listinvreqs_done, listinvreqs_error, inv); + json_add_sha256(req->js, "invreq_id", &inv->invreq_id); return send_outreq(cmd->plugin, req); } diff --git a/plugins/offers_inv_hook.h b/plugins/offers_inv_hook.h index fbc12ace6815..164b853880be 100644 --- a/plugins/offers_inv_hook.h +++ b/plugins/offers_inv_hook.h @@ -6,5 +6,6 @@ /* We got an onionmessage with an invoice! reply_path could be NULL. */ struct command_result *handle_invoice(struct command *cmd, const u8 *invbin, - struct tlv_onionmsg_payload_reply_path *reply_path STEALS); + struct blinded_path *reply_path STEALS); + #endif /* LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H */ diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 17eb2c688b97..9d79895685b8 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -16,10 +18,10 @@ /* We need to keep the reply path around so we can reply with invoice */ struct invreq { struct tlv_invoice_request *invreq; - struct tlv_onionmsg_payload_reply_path *reply_path; + struct blinded_path *reply_path; - /* The offer, once we've looked it up. */ - struct tlv_offer *offer; + /* The offer id */ + struct sha256 offer_id; /* The invoice we're preparing (can require additional lookups) */ struct tlv_invoice *inv; @@ -35,17 +37,13 @@ fail_invreq_level(struct command *cmd, const char *fmt, va_list ap) { char *full_fmt, *msg; - struct tlv_onionmsg_payload *payload; + struct tlv_onionmsg_tlv *payload; struct tlv_invoice_error *err; - full_fmt = tal_fmt(tmpctx, "Failed invoice_request"); + full_fmt = tal_fmt(tmpctx, "Failed invreq"); if (invreq->invreq) { tal_append_fmt(&full_fmt, " %s", invrequest_encode(tmpctx, invreq->invreq)); - if (invreq->invreq->offer_id) - tal_append_fmt(&full_fmt, " for offer %s", - type_to_string(tmpctx, struct sha256, - invreq->invreq->offer_id)); } tal_append_fmt(&full_fmt, ": %s", fmt); @@ -61,7 +59,7 @@ fail_invreq_level(struct command *cmd, err->error = tal_dup_arr(err, char, msg, strlen(msg), 0); /* FIXME: Add suggested_value / erroneous_field! */ - payload = tlv_onionmsg_payload_new(tmpctx); + payload = tlv_onionmsg_tlv_new(tmpctx); payload->invoice_error = tal_arr(payload, u8, 0); towire_tlv_invoice_error(&payload->invoice_error, err); return send_onion_reply(cmd, invreq->reply_path, payload); @@ -122,13 +120,13 @@ test_field(struct command *cmd, */ static void set_recurring_inv_expiry(struct tlv_invoice *inv, u64 last_pay) { - inv->relative_expiry = tal(inv, u32); + inv->invoice_relative_expiry = tal(inv, u32); /* Don't give them a 0 second invoice, even if it's true. */ - if (last_pay <= *inv->created_at) - *inv->relative_expiry = 1; + if (last_pay <= *inv->invoice_created_at) + *inv->invoice_relative_expiry = 1; else - *inv->relative_expiry = last_pay - *inv->created_at; + *inv->invoice_relative_expiry = last_pay - *inv->invoice_created_at; /* FIXME: Shorten expiry if we're doing currency conversion! */ } @@ -136,15 +134,14 @@ static void set_recurring_inv_expiry(struct tlv_invoice *inv, u64 last_pay) /* We rely on label forms for uniqueness. */ static void json_add_label(struct json_stream *js, const struct sha256 *offer_id, - const struct point32 *payer_key, + const struct pubkey *payer_key, const u32 counter) { char *label; label = tal_fmt(tmpctx, "%s-%s-%u", type_to_string(tmpctx, struct sha256, offer_id), - type_to_string(tmpctx, struct point32, - payer_key), + type_to_string(tmpctx, struct pubkey, payer_key), counter); json_add_string(js, "label", label); } @@ -171,7 +168,7 @@ static struct command_result *createinvoice_done(struct command *cmd, { char *hrp; u8 *rawinv; - struct tlv_onionmsg_payload *payload; + struct tlv_onionmsg_tlv *payload; const jsmntok_t *t; /* We have a signed invoice, use it as a reply. */ @@ -184,7 +181,7 @@ static struct command_result *createinvoice_done(struct command *cmd, json_tok_full(buf, t)); } - payload = tlv_onionmsg_payload_new(tmpctx); + payload = tlv_onionmsg_tlv_new(tmpctx); payload->invoice = rawinv; return send_onion_reply(cmd, ir->reply_path, payload); } @@ -211,15 +208,221 @@ static struct command_result *create_invoicereq(struct command *cmd, { struct out_req *req; + /* FIXME: We should add a real blinded path, and we *need to* + * if we don't have public channels! */ + /* Now, write invoice to db (returns the signed version) */ req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoice", createinvoice_done, createinvoice_error, ir); json_add_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv)); json_add_preimage(req->js, "preimage", &ir->preimage); - json_add_label(req->js, ir->inv->offer_id, ir->inv->payer_key, - ir->inv->recurrence_counter - ? *ir->inv->recurrence_counter : 0); + json_add_label(req->js, &ir->offer_id, ir->inv->invreq_payer_id, + ir->inv->invreq_recurrence_counter + ? *ir->inv->invreq_recurrence_counter : 0); + return send_outreq(cmd->plugin, req); +} + +/* Create and encode an enctlv */ +static u8 *create_enctlv(const tal_t *ctx, + /* in and out */ + struct privkey *blinding, + const struct pubkey *node_id, + struct short_channel_id *next_scid, + struct tlv_encrypted_data_tlv_payment_relay *payment_relay, + struct tlv_encrypted_data_tlv_payment_constraints *payment_constraints, + u8 *path_secret, + struct pubkey *node_alias) +{ + struct tlv_encrypted_data_tlv *tlv = tlv_encrypted_data_tlv_new(tmpctx); + + tlv->short_channel_id = next_scid; + tlv->path_id = path_secret; + tlv->payment_relay = payment_relay; + tlv->payment_constraints = payment_constraints; + /* FIXME: Add padding! */ + + return encrypt_tlv_encrypted_data(ctx, blinding, node_id, tlv, + blinding, node_alias); +} + +/* If we only have private channels, we need to add a blinded path to the + * invoice. We need to choose a peer who supports blinded payments, too. */ +struct chaninfo { + struct pubkey id; + struct short_channel_id scid; + struct amount_msat capacity, htlc_min, htlc_max; + u32 feebase, feeppm, cltv; + bool public; +}; + +/* FIXME: This is naive: + * - Only creates if we have no public channels. + * - Always creates a path from direct neighbor. + * - Doesn't append dummy hops. + * - Doesn't pad to length. + */ +/* (We only create if we have to, because our code doesn't handle + * making a payment if the blinded path starts with ourselves!) */ +static struct command_result *listincoming_done(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct invreq *ir) +{ + const jsmntok_t *arr, *t; + size_t i; + struct chaninfo *best = NULL; + bool any_public = false; + + arr = json_get_member(buf, result, "incoming"); + json_for_each_arr(i, t, arr) { + struct chaninfo ci; + const jsmntok_t *pftok; + u8 *features; + const char *err; + + err = json_scan(tmpctx, buf, t, + "{id:%," + "incoming_capacity_msat:%," + "htlc_min_msat:%," + "htlc_max_msat:%," + "fee_base_msat:%," + "fee_proportional_millionths:%," + "cltv_expiry_delta:%," + "short_channel_id:%," + "public:%}", + JSON_SCAN(json_to_pubkey, &ci.id), + JSON_SCAN(json_to_msat, &ci.capacity), + JSON_SCAN(json_to_msat, &ci.htlc_min), + JSON_SCAN(json_to_msat, &ci.htlc_max), + JSON_SCAN(json_to_u32, &ci.feebase), + JSON_SCAN(json_to_u32, &ci.feeppm), + JSON_SCAN(json_to_u32, &ci.cltv), + JSON_SCAN(json_to_short_channel_id, &ci.scid), + JSON_SCAN(json_to_bool, &ci.public)); + if (err) { + plugin_log(cmd->plugin, LOG_BROKEN, + "Could not parse listincoming: %s", + err); + continue; + } + + any_public |= ci.public; + + /* Not presented if there's no channel_announcement for peer: + * we could use listpeers, but if it's private we probably + * don't want to blinded route through it! */ + pftok = json_get_member(buf, t, "peer_features"); + if (!pftok) + continue; + features = json_tok_bin_from_hex(tmpctx, buf, pftok); + if (!feature_offered(features, OPT_ROUTE_BLINDING)) + continue; + + if (amount_msat_less(ci.htlc_max, + amount_msat(*ir->inv->invoice_amount))) + continue; + + /* Only pick a private one if no public candidates. */ + if (!best || (!best->public && ci.public)) + best = tal_dup(tmpctx, struct chaninfo, &ci); + } + + /* If there are any public channels, don't add. */ + if (any_public) + goto done; + + /* BOLT-offers #12: + * - MUST include `invoice_paths` containing one or more paths to the node. + * - MUST specify `invoice_paths` in order of most-preferred to + * least-preferred if it has a preference. + * - MUST include `invoice_blindedpay` with exactly one `blinded_payinfo` + * for each `blinded_path` in `paths`, in order. + */ + if (!best) { + /* Note: since we don't make one, createinvoice adds a dummy. */ + plugin_log(cmd->plugin, LOG_UNUSUAL, + "No incoming channel for %s, so no blinded path", + fmt_amount_msat(tmpctx, + amount_msat(*ir->inv->invoice_amount))); + } else { + struct privkey blinding; + struct tlv_encrypted_data_tlv_payment_relay relay; + struct tlv_encrypted_data_tlv_payment_constraints constraints; + struct onionmsg_hop **hops; + u32 base; + + relay.cltv_expiry_delta = best->cltv; + relay.fee_base_msat = best->feebase; + relay.fee_proportional_millionths = best->feeppm; + + /* BOLT-offers #12: + * - if the expiry for accepting payment is not 7200 seconds + * after `invoice_created_at`: + * - MUST set `invoice_relative_expiry` + */ + /* Give them 6 blocks, plus one per 10 minutes until expiry. */ + if (ir->inv->invoice_relative_expiry) + base = blockheight + 6 + *ir->inv->invoice_relative_expiry / 600; + else + base = blockheight + 6 + 7200 / 600; + constraints.max_cltv_expiry = base + best->cltv + cltv_final; + constraints.htlc_minimum_msat = best->htlc_min.millisatoshis; /* Raw: tlv */ + + randombytes_buf(&blinding, sizeof(blinding)); + + ir->inv->invoice_paths = tal_arr(ir->inv, struct blinded_path *, 1); + ir->inv->invoice_paths[0] = tal(ir->inv->invoice_paths, struct blinded_path); + ir->inv->invoice_paths[0]->first_node_id = best->id; + if (!pubkey_from_privkey(&blinding, + &ir->inv->invoice_paths[0]->blinding)) + abort(); + hops = tal_arr(ir->inv->invoice_paths[0], struct onionmsg_hop *, 2); + ir->inv->invoice_paths[0]->path = hops; + + /* First hop is the peer */ + hops[0] = tal(hops, struct onionmsg_hop); + hops[0]->encrypted_recipient_data + = create_enctlv(hops[0], + &blinding, + &best->id, + &best->scid, + &relay, &constraints, + NULL, + &hops[0]->blinded_node_id); + /* Second hops is us (so we can identify correct use of path) */ + hops[1] = tal(hops, struct onionmsg_hop); + hops[1]->encrypted_recipient_data + = create_enctlv(hops[1], + &blinding, + &id, + NULL, NULL, NULL, + invoice_path_id(tmpctx, &invoicesecret_base, + ir->inv->invoice_payment_hash), + &hops[1]->blinded_node_id); + + /* FIXME: This should be a "normal" feerate and range. */ + ir->inv->invoice_blindedpay = tal_arr(ir->inv, struct blinded_payinfo *, 1); + ir->inv->invoice_blindedpay[0] = tal(ir->inv->invoice_blindedpay, struct blinded_payinfo); + ir->inv->invoice_blindedpay[0]->fee_base_msat = best->feebase; + ir->inv->invoice_blindedpay[0]->fee_proportional_millionths = best->feeppm; + ir->inv->invoice_blindedpay[0]->cltv_expiry_delta = best->cltv; + ir->inv->invoice_blindedpay[0]->htlc_minimum_msat = best->htlc_min; + ir->inv->invoice_blindedpay[0]->htlc_maximum_msat = best->htlc_max; + ir->inv->invoice_blindedpay[0]->features = NULL; + } + +done: + return create_invoicereq(cmd, ir); +} + +static struct command_result *add_blindedpaths(struct command *cmd, + struct invreq *ir) +{ + struct out_req *req; + + req = jsonrpc_request_start(cmd->plugin, cmd, "listincoming", + listincoming_done, listincoming_done, ir); return send_outreq(cmd->plugin, req); } @@ -232,17 +435,17 @@ static struct command_result *check_period(struct command *cmd, struct command_result *err; /* If we have a recurrence base, that overrides. */ - if (ir->offer->recurrence_base) - basetime = ir->offer->recurrence_base->basetime; + if (ir->invreq->offer_recurrence_base) + basetime = ir->invreq->offer_recurrence_base->basetime; /* BOLT-offers-recurrence #12: * - if the invoice corresponds to an offer with `recurrence`: * - MUST set `recurrence_basetime` to the start of period #0 as * calculated by [Period Calculation](#offer-period-calculation). */ - ir->inv->recurrence_basetime = tal_dup(ir->inv, u64, &basetime); + ir->inv->invoice_recurrence_basetime = tal_dup(ir->inv, u64, &basetime); - period_idx = *ir->invreq->recurrence_counter; + period_idx = *ir->invreq->invreq_recurrence_counter; /* BOLT-offers-recurrence #12: * - if the offer had `recurrence_base` and `start_any_period` @@ -253,19 +456,19 @@ static struct command_result *check_period(struct command *cmd, * `recurrence_start` field plus the `recurrence_counter` * `counter` field. */ - if (ir->offer->recurrence_base - && ir->offer->recurrence_base->start_any_period) { - err = invreq_must_have(cmd, ir, recurrence_start); + if (ir->invreq->offer_recurrence_base + && ir->invreq->offer_recurrence_base->start_any_period) { + err = invreq_must_have(cmd, ir, invreq_recurrence_start); if (err) return err; - period_idx += *ir->invreq->recurrence_start; + period_idx += *ir->invreq->invreq_recurrence_start; /* BOLT-offers-recurrence #12: * - MUST set (or not set) `recurrence_start` exactly as the - * invoice_request did. + * invreq did. */ - ir->inv->recurrence_start - = tal_dup(ir->inv, u32, ir->invreq->recurrence_start); + ir->inv->invreq_recurrence_start + = tal_dup(ir->inv, u32, ir->invreq->invreq_recurrence_start); } else { /* BOLT-offers-recurrence #12: * @@ -275,7 +478,7 @@ static struct command_result *check_period(struct command *cmd, * - MUST consider the period index for this request to be the * `recurrence_counter` `counter` field. */ - err = invreq_must_not_have(cmd, ir, recurrence_start); + err = invreq_must_not_have(cmd, ir, invreq_recurrence_start); if (err) return err; } @@ -285,26 +488,26 @@ static struct command_result *check_period(struct command *cmd, * - MUST fail the request if the period index is greater than * `max_period`. */ - if (ir->offer->recurrence_limit - && period_idx > *ir->offer->recurrence_limit) { + if (ir->invreq->offer_recurrence_limit + && period_idx > *ir->invreq->offer_recurrence_limit) { return fail_invreq(cmd, ir, "period_index %"PRIu64" too great", period_idx); } - offer_period_paywindow(ir->offer->recurrence, - ir->offer->recurrence_paywindow, - ir->offer->recurrence_base, + offer_period_paywindow(ir->invreq->offer_recurrence, + ir->invreq->offer_recurrence_paywindow, + ir->invreq->offer_recurrence_base, basetime, period_idx, &paywindow_start, &paywindow_end); - if (*ir->inv->created_at < paywindow_start) { + if (*ir->inv->invoice_created_at < paywindow_start) { return fail_invreq(cmd, ir, "period_index %"PRIu64 " too early (start %"PRIu64")", period_idx, paywindow_start); } - if (*ir->inv->created_at > paywindow_end) { + if (*ir->inv->invoice_created_at > paywindow_end) { return fail_invreq(cmd, ir, "period_index %"PRIu64 " too late (ended %"PRIu64")", @@ -324,25 +527,25 @@ static struct command_result *check_period(struct command *cmd, * - MUST adjust the *base invoice amount* proportional to time * remaining in the period. */ - if (*ir->invreq->recurrence_counter != 0 - && ir->offer->recurrence_paywindow - && ir->offer->recurrence_paywindow->proportional_amount == 1) { + if (*ir->invreq->invreq_recurrence_counter != 0 + && ir->invreq->offer_recurrence_paywindow + && ir->invreq->offer_recurrence_paywindow->proportional_amount == 1) { u64 start = offer_period_start(basetime, period_idx, - ir->offer->recurrence); + ir->invreq->offer_recurrence); u64 end = offer_period_start(basetime, period_idx + 1, - ir->offer->recurrence); + ir->invreq->offer_recurrence); - if (*ir->inv->created_at > start) { - *ir->inv->amount - *= (double)((*ir->inv->created_at - start) + if (*ir->inv->invoice_created_at > start) { + *ir->inv->invoice_amount + *= (double)((*ir->inv->invoice_created_at - start) / (end - start)); /* Round up to make it non-zero if necessary. */ - if (*ir->inv->amount == 0) - *ir->inv->amount = 1; + if (*ir->inv->invoice_amount == 0) + *ir->inv->invoice_amount = 1; } } - return create_invoicereq(cmd, ir); + return add_blindedpaths(cmd, ir); } static struct command_result *prev_invoice_done(struct command *cmd, @@ -359,7 +562,7 @@ static struct command_result *prev_invoice_done(struct command *cmd, if (arr->size == 0) { return fail_invreq(cmd, ir, "No previous invoice #%u", - *ir->inv->recurrence_counter - 1); + *ir->inv->invreq_recurrence_counter - 1); } /* Was it paid? */ @@ -367,7 +570,7 @@ static struct command_result *prev_invoice_done(struct command *cmd, if (!json_tok_streq(buf, status, "paid")) { return fail_invreq(cmd, ir, "Previous invoice #%u status %.*s", - *ir->inv->recurrence_counter - 1, + *ir->inv->invreq_recurrence_counter - 1, json_tok_full_len(status), json_tok_full(buf, status)); } @@ -377,7 +580,7 @@ static struct command_result *prev_invoice_done(struct command *cmd, if (!b12) { return fail_internalerr(cmd, ir, "Previous invoice #%u no bolt12 (%.*s)", - *ir->inv->recurrence_counter - 1, + *ir->inv->invreq_recurrence_counter - 1, json_tok_full_len(arr + 1), json_tok_full(buf, arr + 1)); } @@ -390,12 +593,12 @@ static struct command_result *prev_invoice_done(struct command *cmd, json_tok_full_len(b12), json_tok_full(buf, b12)); } - if (!previnv->recurrence_basetime) { + if (!previnv->invoice_recurrence_basetime) { return fail_internalerr(cmd, ir, "Previous invoice %.*s no recurrence_basetime?", json_tok_full_len(b12), json_tok_full(buf, b12)); } - return check_period(cmd, ir, *previnv->recurrence_basetime); + return check_period(cmd, ir, *previnv->invoice_recurrence_basetime); } /* Now, we need to check the previous invoice was paid, and maybe get timebase */ @@ -405,8 +608,8 @@ static struct command_result *check_previous_invoice(struct command *cmd, struct out_req *req; /* No previous? Just pass through */ - if (*ir->invreq->recurrence_counter == 0) - return check_period(cmd, ir, *ir->inv->created_at); + if (*ir->invreq->invreq_recurrence_counter == 0) + return check_period(cmd, ir, *ir->inv->invoice_created_at); req = jsonrpc_request_start(cmd->plugin, cmd, "listinvoices", @@ -414,53 +617,55 @@ static struct command_result *check_previous_invoice(struct command *cmd, error, ir); json_add_label(req->js, - ir->invreq->offer_id, - ir->invreq->payer_key, - *ir->invreq->recurrence_counter - 1); + &ir->offer_id, + ir->invreq->invreq_payer_id, + *ir->invreq->invreq_recurrence_counter - 1); return send_outreq(cmd->plugin, req); } /* BOLT-offers #12: - * - MUST fail the request if `signature` is not correct. + + * - MUST fail the request if `signature` is not correct as detailed in + * [Signature Calculation](#signature-calculation) using the + * `invreq_payer_id`. + *... + * - MUST reject the invoice if `signature` is not a valid signature using + * `invoice_node_id` as described in [Signature Calculation](#signature-calculation). */ static bool check_payer_sig(struct command *cmd, const struct tlv_invoice_request *invreq, - const struct point32 *payer_key, + const struct pubkey *payer_key, const struct bip340sig *sig) { struct sha256 merkle, sighash; merkle_tlv(invreq->fields, &merkle); sighash_from_merkle("invoice_request", "signature", &merkle, &sighash); - return secp256k1_schnorrsig_verify(secp256k1_ctx, - sig->u8, - sighash.u.u8, - sizeof(sighash.u.u8), - &payer_key->pubkey) == 1; + return check_schnorr_sig(&sighash, &payer_key->pubkey, sig); } static struct command_result *invreq_amount_by_quantity(struct command *cmd, const struct invreq *ir, u64 *raw_amt) { - assert(ir->offer->amount); + assert(ir->invreq->offer_amount); /* BOLT-offers #12: - * - MUST calculate the *base invoice amount* using the offer `amount`: + * - MUST calculate the *expected amount* using the `offer_amount`: */ - *raw_amt = *ir->offer->amount; + *raw_amt = *ir->invreq->offer_amount; /* BOLT-offers #12: - * - if request contains `quantity`, multiply by `quantity`. + * - if `invreq_quantity` is present, multiply by `invreq_quantity`.`quantity`. */ - if (ir->invreq->quantity) { - if (mul_overflows_u64(*ir->invreq->quantity, *raw_amt)) { + if (ir->invreq->invreq_quantity) { + if (mul_overflows_u64(*ir->invreq->invreq_quantity, *raw_amt)) { return fail_invreq(cmd, ir, "quantity %"PRIu64 " causes overflow", - *ir->invreq->quantity); + *ir->invreq->invreq_quantity); } - *raw_amt *= *ir->invreq->quantity; + *raw_amt *= *ir->invreq->invreq_quantity; } return NULL; @@ -473,28 +678,28 @@ static struct command_result *invreq_base_amount_simple(struct command *cmd, { struct command_result *err; - if (ir->offer->amount) { + if (ir->invreq->offer_amount) { u64 raw_amount; - assert(!ir->offer->currency); + assert(!ir->invreq->offer_currency); err = invreq_amount_by_quantity(cmd, ir, &raw_amount); if (err) return err; *amt = amount_msat(raw_amount); } else { - /* BOLT-offers-recurrence #12: + /* BOLT-offers #12: * - * - otherwise: - * - MUST fail the request if it does not contain `amount`. - * - MUST use the request `amount` as the *base invoice amount*. - * (Note: invoice amount can be further modified by recurrence - * below) + * The reader: + *... + * - otherwise (no `offer_amount`): + * - MUST fail the request if it does not contain + * `invreq_amount`. */ - err = invreq_must_have(cmd, ir, amount); + err = invreq_must_have(cmd, ir, invreq_amount); if (err) return err; - *amt = amount_msat(*ir->invreq->amount); + *amt = amount_msat(*ir->invreq->invreq_amount); } return NULL; } @@ -504,56 +709,50 @@ static struct command_result *handle_amount_and_recurrence(struct command *cmd, struct amount_msat base_inv_amount) { /* BOLT-offers #12: - * - if the offer included `amount`: - *... - * - if the request contains `amount`: - * - MUST fail the request if its `amount` is less than the - * *base invoice amount*. + * - if `invreq_amount` is present: + * - MUST fail the request if `invreq_amount`.`msat` is less than the + * *expected amount*. */ - if (ir->offer->amount && ir->invreq->amount) { - if (amount_msat_less(amount_msat(*ir->invreq->amount), base_inv_amount)) { + if (ir->invreq->offer_amount && ir->invreq->invreq_amount) { + if (amount_msat_less(amount_msat(*ir->invreq->invreq_amount), base_inv_amount)) { return fail_invreq(cmd, ir, "Amount must be at least %s", type_to_string(tmpctx, struct amount_msat, &base_inv_amount)); } /* BOLT-offers #12: - * - MAY fail the request if its `amount` is much greater than - * the *base invoice amount*. + * - MAY fail the request if `invreq_amount`.`msat` greatly exceeds + * the *expected amount*. */ /* Much == 5? Easier to divide and compare, than multiply. */ - if (amount_msat_greater(amount_msat_div(amount_msat(*ir->invreq->amount), 5), + if (amount_msat_greater(amount_msat_div(amount_msat(*ir->invreq->invreq_amount), 5), base_inv_amount)) { return fail_invreq(cmd, ir, "Amount vastly exceeds %s", type_to_string(tmpctx, struct amount_msat, &base_inv_amount)); } - /* BOLT-offers #12: - * - MUST use the request's `amount` as the *base invoice - * amount*. - */ - base_inv_amount = amount_msat(*ir->invreq->amount); + base_inv_amount = amount_msat(*ir->invreq->invreq_amount); } + /* BOLT-offers #12: + * - if `invreq_amount` is present: + * - MUST set `invoice_amount` to `invreq_amount` + * - otherwise: + * - MUST set `invoice_amount` to the *expected amount*. + */ /* This may be adjusted by recurrence if proportional_amount set */ - ir->inv->amount = tal_dup(ir->inv, u64, - &base_inv_amount.millisatoshis); /* Raw: wire protocol */ + ir->inv->invoice_amount = tal_dup(ir->inv, u64, + &base_inv_amount.millisatoshis); /* Raw: wire protocol */ /* Last of all, we handle recurrence details, which often requires * further lookups. */ - - /* BOLT-offers-recurrence #12: - * - MUST set (or not set) `recurrence_counter` exactly as the - * invoice_request did. - */ - if (ir->invreq->recurrence_counter) { - ir->inv->recurrence_counter = ir->invreq->recurrence_counter; + if (ir->inv->invreq_recurrence_counter) { return check_previous_invoice(cmd, ir); } /* We're happy with 2 hours timeout (default): they can always * request another. */ /* FIXME: Fallbacks? */ - return create_invoicereq(cmd, ir); + return add_blindedpaths(cmd, ir); } static struct command_result *currency_done(struct command *cmd, @@ -568,16 +767,16 @@ static struct command_result *currency_done(struct command *cmd, if (!msat) return fail_internalerr(cmd, ir, "Cannot convert currency %.*s: %.*s", - (int)tal_bytelen(ir->offer->currency), - (const char *)ir->offer->currency, + (int)tal_bytelen(ir->invreq->offer_currency), + (const char *)ir->invreq->offer_currency, json_tok_full_len(result), json_tok_full(buf, result)); if (!json_to_msat(buf, msat, &amount)) return fail_internalerr(cmd, ir, "Bad convert for currency %.*s: %.*s", - (int)tal_bytelen(ir->offer->currency), - (const char *)ir->offer->currency, + (int)tal_bytelen(ir->invreq->offer_currency), + (const char *)ir->invreq->offer_currency, json_tok_full_len(msat), json_tok_full(buf, msat)); @@ -593,7 +792,7 @@ static struct command_result *convert_currency(struct command *cmd, struct command_result *err; const struct iso4217_name_and_divisor *iso4217; - assert(ir->offer->currency); + assert(ir->invreq->offer_currency); /* Multiply by quantity *first*, for best precision */ err = invreq_amount_by_quantity(cmd, ir, &raw_amount); @@ -601,19 +800,18 @@ static struct command_result *convert_currency(struct command *cmd, return err; /* BOLT-offers #12: - * - MUST calculate the *base invoice amount* using the offer - * `amount`: - * - if offer `currency` is not the invoice currency, convert - * to the invoice currency. + * - MUST calculate the *expected amount* using the `offer_amount`: + * - if `offer_currency` is not the `invreq_chain` currency, convert to the + * `invreq_chain` currency. */ - iso4217 = find_iso4217(ir->offer->currency, - tal_bytelen(ir->offer->currency)); + iso4217 = find_iso4217(ir->invreq->offer_currency, + tal_bytelen(ir->invreq->offer_currency)); /* We should not create offer with unknown currency! */ if (!iso4217) return fail_internalerr(cmd, ir, "Unknown offer currency %.*s", - (int)tal_bytelen(ir->offer->currency), - ir->offer->currency); + (int)tal_bytelen(ir->invreq->offer_currency), + ir->invreq->offer_currency); double_amount = (double)raw_amount; for (size_t i = 0; i < iso4217->minor_unit; i++) double_amount /= 10; @@ -621,8 +819,8 @@ static struct command_result *convert_currency(struct command *cmd, req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert", currency_done, error, ir); json_add_stringn(req->js, "currency", - (const char *)ir->offer->currency, - tal_bytelen(ir->offer->currency)); + (const char *)ir->invreq->offer_currency, + tal_bytelen(ir->invreq->offer_currency)); json_add_primitive_fmt(req->js, "amount", "%f", double_amount); return send_outreq(cmd->plugin, req); } @@ -635,14 +833,13 @@ static struct command_result *listoffers_done(struct command *cmd, const jsmntok_t *arr = json_get_member(buf, result, "offers"); const jsmntok_t *offertok, *activetok, *b12tok; bool active; - char *fail; struct command_result *err; struct amount_msat amt; /* BOLT-offers #12: * - * - MUST fail the request if the `offer_id` does not refer to an - * unexpired offer. + * - MUST fail the request if the offer fields do not exactly match a + * valid, unexpired offer. */ if (arr->size == 0) return fail_invreq(cmd, ir, "Unknown offer"); @@ -660,6 +857,8 @@ static struct command_result *listoffers_done(struct command *cmd, if (!active) return fail_invreq(cmd, ir, "Offer no longer available"); + /* Now, since we looked up by hash, we know that the entire offer + * is faithfully mirrored in this invreq. */ b12tok = json_get_member(buf, offertok, "bolt12"); if (!b12tok) { return fail_internalerr(cmd, ir, @@ -667,76 +866,66 @@ static struct command_result *listoffers_done(struct command *cmd, json_tok_full_len(offertok), json_tok_full(buf, offertok)); } - ir->offer = offer_decode(ir, - buf + b12tok->start, - b12tok->end - b12tok->start, - plugin_feature_set(cmd->plugin), - chainparams, &fail); - if (!ir->offer) { - return fail_internalerr(cmd, ir, - "Invalid offer: %s (%.*s)", - fail, - json_tok_full_len(offertok), - json_tok_full(buf, offertok)); - } - if (ir->offer->absolute_expiry - && time_now().ts.tv_sec >= *ir->offer->absolute_expiry) { + if (ir->invreq->offer_absolute_expiry + && time_now().ts.tv_sec >= *ir->invreq->offer_absolute_expiry) { /* FIXME: do deloffer to disable it */ return fail_invreq(cmd, ir, "Offer expired"); } /* BOLT-offers #12: - * - if the offer had a `quantity_min` or `quantity_max` field: - * - MUST fail the request if there is no `quantity` field. - * - MUST fail the request if there is `quantity` is not within - * that (inclusive) range. + * - if `offer_quantity_max` is present: + * - MUST fail the request if there is no `invreq_quantity` field. + * - if `offer_quantity_max` is non-zero: + * - MUST fail the request if `invreq_quantity` is zero, OR greater than + * `offer_quantity_max`. * - otherwise: - * - MUST fail the request if there is a `quantity` field. + * - MUST fail the request if there is an `invreq_quantity` field. */ - if (ir->offer->quantity_min || ir->offer->quantity_max) { - err = invreq_must_have(cmd, ir, quantity); + if (ir->invreq->offer_quantity_max) { + err = invreq_must_have(cmd, ir, invreq_quantity); if (err) return err; - if (ir->offer->quantity_min && - *ir->invreq->quantity < *ir->offer->quantity_min) { + if (*ir->invreq->invreq_quantity == 0) return fail_invreq(cmd, ir, - "quantity %"PRIu64 " < %"PRIu64, - *ir->invreq->quantity, - *ir->offer->quantity_min); - } + "quantity zero invalid"); - if (ir->offer->quantity_max && - *ir->invreq->quantity > *ir->offer->quantity_max) { + if (*ir->invreq->offer_quantity_max && + *ir->invreq->invreq_quantity > *ir->invreq->offer_quantity_max) { return fail_invreq(cmd, ir, "quantity %"PRIu64" > %"PRIu64, - *ir->invreq->quantity, - *ir->offer->quantity_max); + *ir->invreq->invreq_quantity, + *ir->invreq->offer_quantity_max); } } else { - err = invreq_must_not_have(cmd, ir, quantity); + err = invreq_must_not_have(cmd, ir, invreq_quantity); if (err) return err; } + /* BOLT-offers #12: + * - MUST fail the request if `signature` is not correct as + * detailed in [Signature Calculation](#signature-calculation) using + * the `invreq_payer_id`. + */ err = invreq_must_have(cmd, ir, signature); if (err) return err; if (!check_payer_sig(cmd, ir->invreq, - ir->invreq->payer_key, + ir->invreq->invreq_payer_id, ir->invreq->signature)) { return fail_invreq(cmd, ir, "bad signature"); } - if (ir->offer->recurrence) { + if (ir->invreq->offer_recurrence) { /* BOLT-offers-recurrence #12: * * - if the offer had a `recurrence`: * - MUST fail the request if there is no `recurrence_counter` * field. */ - err = invreq_must_have(cmd, ir, recurrence_counter); + err = invreq_must_have(cmd, ir, invreq_recurrence_counter); if (err) return err; } else { @@ -747,78 +936,56 @@ static struct command_result *listoffers_done(struct command *cmd, * - MUST fail the request if there is a `recurrence_start` * field. */ - err = invreq_must_not_have(cmd, ir, recurrence_counter); + err = invreq_must_not_have(cmd, ir, invreq_recurrence_counter); if (err) return err; - err = invreq_must_not_have(cmd, ir, recurrence_start); + err = invreq_must_not_have(cmd, ir, invreq_recurrence_start); if (err) return err; } - ir->inv = tlv_invoice_new(cmd); /* BOLT-offers #12: - * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. + * A writer of an invoice: + *... + * - if the invoice is in response to an `invoice_request`: + * - MUST copy all non-signature fields from the `invoice_request` (including + * unknown fields). */ - if (!streq(chainparams->network_name, "bitcoin")) { - ir->inv->chain = tal_dup(ir->inv, struct bitcoin_blkid, - &chainparams->genesis_blockhash); - } + ir->inv = invoice_for_invreq(cmd, ir->invreq); + assert(ir->inv->invreq_payer_id); /* BOLT-offers #12: - * - MUST set `offer_id` to the id of the offer. - */ - /* Which is the same as the invreq */ - ir->inv->offer_id = tal_dup(ir->inv, struct sha256, - ir->invreq->offer_id); - ir->inv->description = tal_dup_talarr(ir->inv, char, - ir->offer->description); - ir->inv->features = tal_dup_talarr(ir->inv, u8, - plugin_feature_set(cmd->plugin) - ->bits[BOLT11_FEATURE]); - /* FIXME: Insert paths and payinfo */ - - ir->inv->issuer = tal_dup_talarr(ir->inv, char, ir->offer->issuer); - ir->inv->node_id = tal_dup(ir->inv, struct point32, ir->offer->node_id); - /* BOLT-offers #12: - * - MUST set (or not set) `quantity` exactly as the invoice_request - * did. + * - if `offer_node_id` is present: + * - MUST set `invoice_node_id` to `offer_node_id`. */ - if (ir->offer->quantity_min || ir->offer->quantity_max) - ir->inv->quantity = tal_dup(ir->inv, u64, ir->invreq->quantity); + /* We always provide an offer_node_id! */ + ir->inv->invoice_node_id = ir->inv->offer_node_id; /* BOLT-offers #12: - * - MUST set `payer_key` exactly as the invoice_request did. + * - MUST set `invoice_created_at` to the number of seconds since + * Midnight 1 January 1970, UTC when the offer was created. */ - ir->inv->payer_key = tal_dup(ir->inv, struct point32, - ir->invreq->payer_key); + ir->inv->invoice_created_at = tal(ir->inv, u64); + *ir->inv->invoice_created_at = time_now().ts.tv_sec; /* BOLT-offers #12: - * - MUST set (or not set) `payer_info` exactly as the invoice_request - * did. + * - MUST set `invoice_payment_hash` to the SHA256 hash of the + * `payment_preimage` that will be given in return for payment. */ - ir->inv->payer_info - = tal_dup_talarr(ir->inv, u8, ir->invreq->payer_info); + randombytes_buf(&ir->preimage, sizeof(ir->preimage)); + ir->inv->invoice_payment_hash = tal(ir->inv, struct sha256); + sha256(ir->inv->invoice_payment_hash, + &ir->preimage, sizeof(ir->preimage)); /* BOLT-offers #12: - * - MUST set (or not set) `payer_note` exactly as the invoice_request - * did, or MUST not set it. + * - or if it allows multiple parts to pay the invoice: + * - MUST set `invoice_features`.`features` bit `MPP/optional` */ - /* i.e. we don't have to do anything, but we do. */ - ir->inv->payer_note - = tal_dup_talarr(ir->inv, char, ir->invreq->payer_note); - - randombytes_buf(&ir->preimage, sizeof(ir->preimage)); - ir->inv->payment_hash = tal(ir->inv, struct sha256); - sha256(ir->inv->payment_hash, &ir->preimage, sizeof(ir->preimage)); - - ir->inv->cltv = tal_dup(ir->inv, u16, &cltv_final); - - ir->inv->created_at = tal(ir->inv, u64); - *ir->inv->created_at = time_now().ts.tv_sec; + ir->inv->invoice_features + = plugin_feature_set(cmd->plugin)->bits[BOLT12_INVOICE_FEATURE]; /* We may require currency lookup; if so, do it now. */ - if (ir->offer->amount && ir->offer->currency) + if (ir->invreq->offer_amount && ir->invreq->offer_currency) return convert_currency(cmd, ir); err = invreq_base_amount_simple(cmd, ir, &amt); @@ -827,25 +994,19 @@ static struct command_result *listoffers_done(struct command *cmd, return handle_amount_and_recurrence(cmd, ir, amt); } -static struct command_result *handle_offerless_request(struct command *cmd, - struct invreq *ir) -{ - /* FIXME: shut up and take their money! */ - return fail_internalerr(cmd, ir, "FIXME: handle offerless req!"); -} - struct command_result *handle_invoice_request(struct command *cmd, const u8 *invreqbin, - struct tlv_onionmsg_payload_reply_path *reply_path) + struct blinded_path *reply_path) { size_t len = tal_count(invreqbin); + const u8 *cursor = invreqbin; struct invreq *ir = tal(cmd, struct invreq); struct out_req *req; int bad_feature; ir->reply_path = tal_steal(ir, reply_path); - ir->invreq = fromwire_tlv_invoice_request(cmd, &invreqbin, &len); + ir->invreq = fromwire_tlv_invoice_request(cmd, &cursor, &len); if (!ir->invreq) { return fail_invreq(cmd, ir, "Invalid invreq %s", @@ -854,13 +1015,39 @@ struct command_result *handle_invoice_request(struct command *cmd, /* BOLT-offers #12: * - * The reader of an invoice_request: + * The reader: + * - MUST fail the request if `invreq_payer_id` or `invreq_metadata` + * are not present. + */ + if (!ir->invreq->invreq_payer_id) + return fail_invreq(cmd, ir, "Missing invreq_payer_id"); + if (!ir->invreq->invreq_metadata) + return fail_invreq(cmd, ir, "Missing invreq_metadata"); + + /* BOLT-offers #12: + * The reader: + * ... + * - MUST fail the request if any non-signature TLV fields greater or + * equal to 160. + */ + /* BOLT-offers #12: + * Each form is signed using one or more *signature TLV elements*: + * TLV types 240 through 1000 (inclusive) + */ + if (tlv_span(invreqbin, 0, 159, NULL) + + tlv_span(invreqbin, 240, 1000, NULL) != tal_bytelen(invreqbin)) + return fail_invreq(cmd, ir, "Fields beyond 160"); + + /* BOLT-offers #12: + * + * The reader: *... - * - MUST fail the request if `features` contains unknown even bits. + * - if `invreq_features` contains unknown _even_ bits that are non-zero: + * - MUST fail the request. */ bad_feature = features_unsupported(plugin_feature_set(cmd->plugin), - ir->invreq->features, - BOLT11_FEATURE); + ir->invreq->invreq_features, + BOLT12_INVREQ_FEATURE); if (bad_feature != -1) { return fail_invreq(cmd, ir, "Unsupported invreq feature %i", @@ -869,34 +1056,41 @@ struct command_result *handle_invoice_request(struct command *cmd, /* BOLT-offers #12: * - * The reader of an invoice_request: + * The reader: *... - * - if `chain` is not present: - * - MUST fail the request if bitcoin is not a supported chain. - * - otherwise: - * - MUST fail the request if `chain` is not a supported chain. + * - if `invreq_chain` is not present: + * - MUST fail the request if bitcoin is not a supported chain. + * - otherwise: + * - MUST fail the request if `invreq_chain`.`chain` is not a + * supported chain. */ - if (!bolt12_chain_matches(ir->invreq->chain, chainparams)) { + if (!bolt12_chain_matches(ir->invreq->invreq_chain, chainparams)) { return fail_invreq(cmd, ir, "Wrong chain %s", - tal_hex(tmpctx, ir->invreq->chain)); + tal_hex(tmpctx, ir->invreq->invreq_chain)); } /* BOLT-offers #12: * - * The reader of an invoice_request: - * - MUST fail the request if `payer_key` is not present. + * - otherwise (no `offer_node_id`, not a response to our offer): */ - if (!ir->invreq->payer_key) - return fail_invreq(cmd, ir, "Missing payer key"); + /* FIXME-OFFERS: handle this! */ + if (!ir->invreq->offer_node_id) { + return fail_invreq(cmd, ir, "Not based on an offer"); + } - if (!ir->invreq->offer_id) - return handle_offerless_request(cmd, ir); + /* BOLT-offers #12: + * + * - if `offer_node_id` is present (response to an offer): + * - MUST fail the request if the offer fields do not exactly match a + * valid, unexpired offer. + */ + invreq_offer_id(ir->invreq, &ir->offer_id); /* Now, look up offer */ req = jsonrpc_request_start(cmd->plugin, cmd, "listoffers", listoffers_done, error, ir); - json_add_sha256(req->js, "offer_id", ir->invreq->offer_id); + json_add_sha256(req->js, "offer_id", &ir->offer_id); return send_outreq(cmd->plugin, req); } diff --git a/plugins/offers_invreq_hook.h b/plugins/offers_invreq_hook.h index c9dc5f37c64d..2259fe9b2a04 100644 --- a/plugins/offers_invreq_hook.h +++ b/plugins/offers_invreq_hook.h @@ -4,9 +4,12 @@ #include extern u16 cltv_final; +extern u32 blockheight; +extern struct secret invoicesecret_base; +extern struct pubkey id; /* We got an onionmessage with an invreq! */ struct command_result *handle_invoice_request(struct command *cmd, const u8 *invreqbin, - struct tlv_onionmsg_payload_reply_path *reply_path STEALS); + struct blinded_path *reply_path STEALS); #endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */ diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c index 520513daddfe..e2ba3d81d91f 100644 --- a/plugins/offers_offer.c +++ b/plugins/offers_offer.c @@ -8,6 +8,7 @@ #include #include #include +#include static bool msat_or_any(const char *buffer, const jsmntok_t *tok, @@ -21,23 +22,11 @@ static bool msat_or_any(const char *buffer, buffer + tok->start, tok->end - tok->start)) return false; - offer->amount = tal_dup(offer, u64, + offer->offer_amount = tal_dup(offer, u64, &msat.millisatoshis); /* Raw: other currencies */ return true; } -static struct command_result *param_msat_or_any(struct command *cmd, - const char *name, - const char *buffer, - const jsmntok_t *tok, - struct tlv_offer *offer) -{ - if (msat_or_any(buffer, tok, offer)) - return NULL; - return command_fail_badparam(cmd, name, buffer, tok, - "should be 'any' or msatoshis"); -} - static struct command_result *param_amount(struct command *cmd, const char *name, const char *buffer, @@ -51,12 +40,12 @@ static struct command_result *param_amount(struct command *cmd, if (msat_or_any(buffer, tok, offer)) return NULL; - offer->amount = tal(offer, u64); + offer->offer_amount = tal(offer, u64); /* BOLT-offers #12: * - * - MUST specify `iso4217` as an ISO 4712 three-letter code. - * - MUST specify `amount` in the currency unit adjusted by the ISO 4712 + * - MUST specify `offer_currency` `iso4217` as an ISO 4712 three-letter code. + * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4712 * exponent (e.g. USD cents). */ if (tok->end - tok->start < ISO4217_NAMELEN) @@ -70,7 +59,7 @@ static struct command_result *param_amount(struct command *cmd, ISO4217_NAMELEN, buffer + tok->end - ISO4217_NAMELEN); - offer->currency + offer->offer_currency = tal_dup_arr(offer, utf8, isocode->name, ISO4217_NAMELEN, 0); number = *tok; @@ -89,19 +78,19 @@ static struct command_result *param_amount(struct command *cmd, "Bad minor units"); } - if (!json_to_u64(buffer, &whole, offer->amount)) + if (!json_to_u64(buffer, &whole, offer->offer_amount)) return command_fail_badparam(cmd, name, buffer, tok, "should be 'any', msatoshis or [.]"); for (size_t i = 0; i < isocode->minor_unit; i++) { - if (mul_overflows_u64(*offer->amount, 10)) + if (mul_overflows_u64(*offer->offer_amount, 10)) return command_fail_badparam(cmd, name, buffer, &whole, "excessively large value"); - *offer->amount *= 10; + *offer->offer_amount *= 10; } - *offer->amount += cents; + *offer->offer_amount += cents; return NULL; } @@ -151,8 +140,7 @@ static struct command_result *param_recurrence(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, - struct tlv_offer_recurrence - **recurrence) + struct recurrence **recurrence) { u32 mul; const struct time_string *ts; @@ -162,7 +150,7 @@ static struct command_result *param_recurrence(struct command *cmd, return command_fail_badparam(cmd, name, buffer, tok, "not a valid time"); - *recurrence = tal(cmd, struct tlv_offer_recurrence); + *recurrence = tal(cmd, struct recurrence); (*recurrence)->time_unit = ts->unit; (*recurrence)->period = ts->mul * mul; return NULL; @@ -172,12 +160,12 @@ static struct command_result *param_recurrence_base(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, - struct tlv_offer_recurrence_base **base) + struct recurrence_base **base) { /* Make copy so we can manipulate it */ jsmntok_t t = *tok; - *base = tal(cmd, struct tlv_offer_recurrence_base); + *base = tal(cmd, struct recurrence_base); if (json_tok_startswith(buffer, &t, "@")) { t.start++; (*base)->start_any_period = false; @@ -195,12 +183,12 @@ static struct command_result *param_recurrence_paywindow(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, - struct tlv_offer_recurrence_paywindow + struct recurrence_paywindow **paywindow) { jsmntok_t t, before, after; - *paywindow = tal(cmd, struct tlv_offer_recurrence_paywindow); + *paywindow = tal(cmd, struct recurrence_paywindow); t = *tok; if (json_tok_endswith(buffer, &t, "%")) { (*paywindow)->proportional_amount = true; @@ -225,32 +213,6 @@ static struct command_result *param_recurrence_paywindow(struct command *cmd, return NULL; } -static struct command_result *param_invoice_payment_hash(struct command *cmd, - const char *name, - const char *buffer, - const jsmntok_t *tok, - struct sha256 **hash) -{ - struct tlv_invoice *inv; - char *fail; - - inv = invoice_decode(tmpctx, buffer + tok->start, tok->end - tok->start, - plugin_feature_set(cmd->plugin), chainparams, - &fail); - if (!inv) - return command_fail_badparam(cmd, name, buffer, tok, - tal_fmt(cmd, - "Unparsable invoice: %s", - fail)); - - if (!inv->payment_hash) - return command_fail_badparam(cmd, name, buffer, tok, - "invoice missing payment_hash"); - - *hash = tal_steal(cmd, inv->payment_hash); - return NULL; -} - struct offer_info { const struct tlv_offer *offer; const char *label; @@ -269,14 +231,14 @@ static struct command_result *check_result(struct command *cmd, &active)) { return command_fail(cmd, LIGHTNINGD, - "Bad creaoffer status reply %.*s", + "Bad createoffer/createinvoicerequest status reply %.*s", json_tok_full_len(result), json_tok_full(buf, result)); } if (!active) return command_fail(cmd, OFFER_ALREADY_EXISTS, - "Offer already exists, but isn't active"); + "Already exists, but isn't active"); /* Otherwise, push through the result. */ return forward_result(cmd, buf, result, arg); @@ -327,19 +289,18 @@ struct command_result *json_offer(struct command *cmd, p_req("description", param_escaped_string, &desc), p_opt("issuer", param_escaped_string, &issuer), p_opt("label", param_escaped_string, &offinfo->label), - p_opt("quantity_min", param_u64, &offer->quantity_min), - p_opt("quantity_max", param_u64, &offer->quantity_max), - p_opt("absolute_expiry", param_u64, &offer->absolute_expiry), - p_opt("recurrence", param_recurrence, &offer->recurrence), + p_opt("quantity_max", param_u64, &offer->offer_quantity_max), + p_opt("absolute_expiry", param_u64, &offer->offer_absolute_expiry), + p_opt("recurrence", param_recurrence, &offer->offer_recurrence), p_opt("recurrence_base", param_recurrence_base, - &offer->recurrence_base), + &offer->offer_recurrence_base), p_opt("recurrence_paywindow", param_recurrence_paywindow, - &offer->recurrence_paywindow), + &offer->offer_recurrence_paywindow), p_opt("recurrence_limit", param_number, - &offer->recurrence_limit), + &offer->offer_recurrence_limit), p_opt_def("single_use", param_bool, &offinfo->single_use, false), /* FIXME: hints support! */ @@ -350,66 +311,66 @@ struct command_result *json_offer(struct command *cmd, return command_fail(cmd, LIGHTNINGD, "experimental-offers not enabled"); - /* BOLT-offers #12: - * - MUST NOT set `quantity_min` or `quantity_max` less than 1. - */ - if (offer->quantity_min && *offer->quantity_min < 1) - return command_fail_badparam(cmd, "quantity_min", - buffer, params, - "must be >= 1"); - if (offer->quantity_max && *offer->quantity_max < 1) + /* Doesn't make sense to have max quantity 1. */ + if (offer->offer_quantity_max && *offer->offer_quantity_max == 1) return command_fail_badparam(cmd, "quantity_max", buffer, params, - "must be >= 1"); - /* BOLT-offers #12: - * - if both: - * - MUST set `quantity_min` less than or equal to `quantity_max`. - */ - if (offer->quantity_min && offer->quantity_max) { - if (*offer->quantity_min > *offer->quantity_max) - return command_fail_badparam(cmd, "quantity_min", - buffer, params, - "must be <= quantity_max"); - } - + "must be 0 or > 1"); /* BOLT-offers #12: * * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. + * - MUST specify `offer_chains` the offer is valid for. * - otherwise: - * - the bitcoin chain is implied as the first and only entry. + * - MAY omit `offer_chains`, implying that bitcoin is only chain. */ if (!streq(chainparams->network_name, "bitcoin")) { - offer->chains = tal_arr(offer, struct bitcoin_blkid, 1); - offer->chains[0] = chainparams->genesis_blockhash; + offer->offer_chains = tal_arr(offer, struct bitcoin_blkid, 1); + offer->offer_chains[0] = chainparams->genesis_blockhash; } - if (!offer->recurrence) { - if (offer->recurrence_limit) + if (!offer->offer_recurrence) { + if (offer->offer_recurrence_limit) return command_fail_badparam(cmd, "recurrence_limit", buffer, params, "needs recurrence"); - if (offer->recurrence_base) + if (offer->offer_recurrence_base) return command_fail_badparam(cmd, "recurrence_base", buffer, params, "needs recurrence"); - if (offer->recurrence_paywindow) + if (offer->offer_recurrence_paywindow) return command_fail_badparam(cmd, "recurrence_paywindow", buffer, params, "needs recurrence"); } - offer->description = tal_dup_arr(offer, char, desc, strlen(desc), 0); + /* BOLT-offers #12: + * - MUST set `offer_description` to a complete description of the + * purpose of the payment. + */ + offer->offer_description + = tal_dup_arr(offer, char, desc, strlen(desc), 0); + + /* BOLT-offers #12: + * - if it sets `offer_issuer`: + * - SHOULD set it to identify the issuer of the invoice clearly. + * - if it includes a domain name: + * - SHOULD begin it with either user@domain or domain + * - MAY follow with a space and more text + */ if (issuer) { - offer->issuer + offer->offer_issuer = tal_dup_arr(offer, char, issuer, strlen(issuer), 0); } - offer->node_id = tal_dup(offer, struct point32, &id); + /* BOLT-offers #12: + * - MUST set `offer_node_id` to the node's public key to request the + * invoice from. + */ + offer->offer_node_id = tal_dup(offer, struct pubkey, &id); /* If they specify a different currency, warn if we can't * convert it! */ - if (offer->currency) { + if (offer->offer_currency) { struct out_req *req; req = jsonrpc_request_start(cmd->plugin, cmd, "currencyconvert", @@ -417,32 +378,34 @@ struct command_result *json_offer(struct command *cmd, offinfo); json_add_u32(req->js, "amount", 1); json_add_stringn(req->js, "currency", - (const char *)offer->currency, - tal_bytelen(offer->currency)); + (const char *)offer->offer_currency, + tal_bytelen(offer->offer_currency)); return send_outreq(cmd->plugin, req); } return create_offer(cmd, offinfo); } -struct command_result *json_offerout(struct command *cmd, - const char *buffer, - const jsmntok_t *params) +struct command_result *json_invoicerequest(struct command *cmd, + const char *buffer, + const jsmntok_t *params) { const char *desc, *issuer, *label; - struct tlv_offer *offer; + struct tlv_invoice_request *invreq; struct out_req *req; + struct amount_msat *msat; + bool *single_use; - offer = tlv_offer_new(cmd); + invreq = tlv_invoice_request_new(cmd); if (!param(cmd, buffer, params, - p_req("amount", param_msat_or_any, offer), + p_req("amount", param_msat, &msat), p_req("description", param_escaped_string, &desc), p_opt("issuer", param_escaped_string, &issuer), p_opt("label", param_escaped_string, &label), - p_opt("absolute_expiry", param_u64, &offer->absolute_expiry), - p_opt("refund_for", param_invoice_payment_hash, &offer->refund_for), - /* FIXME: hints support! */ + p_opt("absolute_expiry", param_u64, + &invreq->offer_absolute_expiry), + p_opt_def("single_use", param_bool, &single_use, true), NULL)) return command_param_failed(); @@ -450,35 +413,61 @@ struct command_result *json_offerout(struct command *cmd, return command_fail(cmd, LIGHTNINGD, "experimental-offers not enabled"); - offer->send_invoice = tal(offer, struct tlv_offer_send_invoice); - /* BOLT-offers #12: - * - * - if the chain for the invoice is not solely bitcoin: - * - MUST specify `chains` the offer is valid for. - * - otherwise: - * - the bitcoin chain is implied as the first and only entry. + * - otherwise (not responding to an offer): + * - MUST set (or not set) `offer_metadata`, `offer_description`, `offer_absolute_expiry`, `offer_paths` and `offer_issuer` as it would for an offer. + * - MUST set `invreq_payer_id` as it would set `offer_node_id` for an offer. + * - MUST NOT include `signature`, `offer_chains`, `offer_amount`, `offer_currency`, `offer_features`, `offer_quantity_max` or `offer_node_id` + * - if the chain for the invoice is not solely bitcoin: + * - MUST specify `invreq_chain` the offer is valid for. + * - MUST set `invreq_amount`. */ + invreq->offer_description + = tal_dup_arr(invreq, char, desc, strlen(desc), 0); + if (issuer) { + invreq->offer_issuer + = tal_dup_arr(invreq, char, issuer, strlen(issuer), 0); + } + if (!streq(chainparams->network_name, "bitcoin")) { - offer->chains = tal_arr(offer, struct bitcoin_blkid, 1); - offer->chains[0] = chainparams->genesis_blockhash; + invreq->invreq_chain + = tal_dup(invreq, struct bitcoin_blkid, + &chainparams->genesis_blockhash); } + /* BOLT-offers #12: + * - if it sets `invreq_amount`: + * - MUST set `msat` in multiples of the minimum lightning-payable unit + * (e.g. milli-satoshis for bitcoin) for `invreq_chain` (or for bitcoin, if there is no `invreq_chain`). + */ + invreq->invreq_amount + = tal_dup(invreq, u64, &msat->millisatoshis); /* Raw: wire */ - offer->description = tal_dup_arr(offer, char, desc, strlen(desc), 0); - if (issuer) - offer->issuer = tal_dup_arr(offer, char, - issuer, strlen(issuer), 0); + /* FIXME: enable blinded paths! */ - offer->node_id = tal_dup(offer, struct point32, &id); + /* BOLT-offers #12: + * - MUST set `invreq_metadata` to an unpredictable series of bytes. + */ + /* BOLT-offers #12: + * - otherwise (not responding to an offer): + *... + * - MUST set `invreq_payer_id` as it would set `offer_node_id` for an offer. + */ + /* createinvoicerequest sets these! */ - req = jsonrpc_request_start(cmd->plugin, cmd, "createoffer", + /* BOLT-offers #12: + * - if it supports bolt12 invoice request features: + * - MUST set `invreq_features`.`features` to the bitmap of features. + */ + req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoicerequest", check_result, forward_error, - offer); - json_add_string(req->js, "bolt12", offer_encode(tmpctx, offer)); + invreq); + json_add_string(req->js, "bolt12", invrequest_encode(tmpctx, invreq)); + json_add_bool(req->js, "savetodb", true); + /* FIXME: Allow invoicerequests using aliases! */ + json_add_bool(req->js, "exposeid", true); + json_add_bool(req->js, "single_use", *single_use); if (label) json_add_string(req->js, "label", label); - json_add_bool(req->js, "single_use", true); - return send_outreq(cmd->plugin, req); } diff --git a/plugins/offers_offer.h b/plugins/offers_offer.h index a0e436ac2993..b7b25013b595 100644 --- a/plugins/offers_offer.h +++ b/plugins/offers_offer.h @@ -3,14 +3,14 @@ #include "config.h" #include -extern struct point32 id; +extern struct pubkey id; extern bool offers_enabled; struct command_result *json_offer(struct command *cmd, const char *buffer, const jsmntok_t *params); -struct command_result *json_offerout(struct command *cmd, - const char *buffer, - const jsmntok_t *params); +struct command_result *json_invoicerequest(struct command *cmd, + const char *buffer, + const jsmntok_t *params); #endif /* LIGHTNING_PLUGINS_OFFERS_OFFER_H */ diff --git a/plugins/pay.c b/plugins/pay.c index 2f99e702c181..faa456df8ecf 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -987,7 +987,7 @@ static struct command_result *json_pay(struct command *cmd, struct shadow_route_data *shadow_route; struct amount_msat *invmsat; u64 invexpiry; - struct sha256 *local_offer_id; + struct sha256 *local_invreq_id; const struct tlv_invoice *b12; struct out_req *req; struct route_exclusion **exclusions; @@ -1011,7 +1011,7 @@ static struct command_result *json_pay(struct command *cmd, p_opt_def("maxdelay", param_number, &maxdelay, maxdelay_default), p_opt("exemptfee", param_msat, &exemptfee), - p_opt("localofferid", param_sha256, &local_offer_id), + p_opt("localinvreqid", param_sha256, &local_invreq_id), p_opt("exclude", param_route_exclusion_array, &exclusions), p_opt("maxfee", param_msat, &maxfee), p_opt("description", param_string, &description), @@ -1024,6 +1024,9 @@ static struct command_result *json_pay(struct command *cmd, p = payment_new(cmd, cmd, NULL /* No parent */, paymod_mods); p->invstring = tal_steal(p, b11str); p->description = tal_steal(p, description); + /* Overridded by bolt12 if present */ + p->blindedpath = NULL; + p->blindedpay = NULL; if (!bolt12_has_prefix(b11str)) { b11 = @@ -1063,7 +1066,7 @@ static struct command_result *json_pay(struct command *cmd, * - MUST check that the SHA2 256-bit hash in the `h` field * exactly matches the hashed description. */ - if (!b11->description && !deprecated_apis) { + if (!b11->description) { if (!b11->description_hash) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, @@ -1085,64 +1088,78 @@ static struct command_result *json_pay(struct command *cmd, return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "experimental-offers disabled"); - p->features = tal_steal(p, b12->features); + /* FIXME: We disable MPP for now */ + /* p->features = tal_steal(p, b12->features); */ + p->features = NULL; - if (!b12->node_id) + if (!b12->invoice_node_id) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing node_id"); - if (!b12->payment_hash) + if (!b12->invoice_payment_hash) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing payment_hash"); - if (!b12->created_at) + if (!b12->invoice_created_at) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "invoice missing created_at"); - if (b12->amount) { - invmsat = tal(cmd, struct amount_msat); - *invmsat = amount_msat(*b12->amount); - } else - invmsat = NULL; + if (!b12->invoice_amount) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "invoice missing invoice_amount"); + invmsat = tal(cmd, struct amount_msat); + *invmsat = amount_msat(*b12->invoice_amount); - /* FIXME: gossmap should store as point32 */ p->destination = tal(p, struct node_id); - gossmap_guess_node_id(get_gossmap(cmd->plugin), b12->node_id, - p->destination); - p->payment_hash = tal_dup(p, struct sha256, b12->payment_hash); - if (b12->recurrence_counter && !label) + node_id_from_pubkey(p->destination, b12->invoice_node_id); + p->payment_hash = tal_dup(p, struct sha256, + b12->invoice_payment_hash); + if (b12->invreq_recurrence_counter && !label) return command_fail( cmd, JSONRPC2_INVALID_PARAMS, "recurring invoice requires a label"); - /* FIXME payment_secret should be signature! */ - { - struct sha256 merkle; - - p->payment_secret = tal(p, struct secret); - merkle_tlv(b12->fields, &merkle); - memcpy(p->payment_secret, &merkle, sizeof(merkle)); - BUILD_ASSERT(sizeof(*p->payment_secret) == - sizeof(merkle)); + + /* BOLT-offers #12: + * - MUST reject the invoice if `invoice_paths` is not present + * or is empty. + */ + if (tal_count(b12->invoice_paths) == 0) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "invoice missing invoice_paths"); + + /* BOLT-offers #12: + * - MUST reject the invoice if `invoice_blindedpay` does not + * contain exactly one `blinded_payinfo` per + * `invoice_paths`.`blinded_path`. */ + if (tal_count(b12->invoice_paths) + != tal_count(b12->invoice_blindedpay)) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Wrong blinding info: %zu paths, %zu payinfo", + tal_count(b12->invoice_paths), + tal_count(b12->invoice_blindedpay)); } + + /* FIXME: do MPP across these! We choose first one. */ + p->blindedpath = tal_steal(p, b12->invoice_paths[0]); + p->blindedpay = tal_steal(p, b12->invoice_blindedpay[0]); + p->min_final_cltv_expiry = p->blindedpay->cltv_expiry_delta; + + /* Set destination to introduction point */ + node_id_from_pubkey(p->destination, &p->blindedpath->first_node_id); p->payment_metadata = NULL; p->routes = NULL; - /* FIXME: paths! */ - if (b12->cltv) - p->min_final_cltv_expiry = *b12->cltv; - else - p->min_final_cltv_expiry = 18; /* BOLT-offers #12: - * - if `relative_expiry` is present: + * - if `invoice_relative_expiry` is present: * - MUST reject the invoice if the current time since - * 1970-01-01 UTC is greater than `created_at` plus + * 1970-01-01 UTC is greater than `invoice_created_at` plus * `seconds_from_creation`. * - otherwise: * - MUST reject the invoice if the current time since - * 1970-01-01 UTC is greater than `created_at` plus - * 7200. + * 1970-01-01 UTC is greater than `invoice_created_at` plus + * 7200. */ - if (b12->relative_expiry) - invexpiry = *b12->created_at + *b12->relative_expiry; + if (b12->invoice_relative_expiry) + invexpiry = *b12->invoice_created_at + *b12->invoice_relative_expiry; else - invexpiry = *b12->created_at + BOLT12_DEFAULT_REL_EXPIRY; - p->local_offer_id = tal_steal(p, local_offer_id); + invexpiry = *b12->invoice_created_at + BOLT12_DEFAULT_REL_EXPIRY; + p->local_invreq_id = tal_steal(p, local_invreq_id); } if (time_now().ts.tv_sec > invexpiry) @@ -1163,6 +1180,19 @@ static struct command_result *json_pay(struct command *cmd, p->amount = *msat; } + /* We replace real final values if we're using a blinded path */ + if (p->blindedpath) { + p->blindedfinalcltv = p->min_final_cltv_expiry; + p->blindedfinalamount = p->amount; + + p->min_final_cltv_expiry += p->blindedpay->cltv_expiry_delta; + if (!amount_msat_add_fee(&p->amount, + p->blindedpay->fee_base_msat, + p->blindedpay->fee_proportional_millionths)) + return command_fail(cmd, PAY_ROUTE_TOO_EXPENSIVE, + "This payment blinded path fee overflows!"); + } + if (node_id_eq(&my_id, p->destination)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "This payment is destined for ourselves. " diff --git a/plugins/src/codec.rs b/plugins/src/codec.rs index e3d1a5fefd78..b6037c9c914d 100644 --- a/plugins/src/codec.rs +++ b/plugins/src/codec.rs @@ -12,7 +12,7 @@ use std::{io, str}; use tokio_util::codec::{Decoder, Encoder}; use crate::messages::{Notification, Request}; -pub use crate::messages::JsonRpc; +use crate::messages::JsonRpc; /// A simple codec that parses messages separated by two successive /// `\n` newlines. diff --git a/plugins/src/lib.rs b/plugins/src/lib.rs index 8c034a4a6343..5e0779065e97 100644 --- a/plugins/src/lib.rs +++ b/plugins/src/lib.rs @@ -1,10 +1,12 @@ use crate::codec::{JsonCodec, JsonRpcCodec}; -pub use anyhow::{anyhow, Context}; +pub use anyhow::anyhow; +use anyhow::Context; use futures::sink::SinkExt; use tokio::io::{AsyncReadExt, AsyncWriteExt}; extern crate log; use log::trace; use messages::Configuration; +use options::ConfigOption; use std::collections::HashMap; use std::future::Future; use std::pin::Pin; @@ -14,18 +16,16 @@ use tokio::sync::Mutex; use tokio_stream::StreamExt; use tokio_util::codec::FramedRead; use tokio_util::codec::FramedWrite; -use options::ConfigOption; -pub mod codec; -pub mod logging; -mod messages; +mod codec; +mod logging; +pub mod messages; #[macro_use] extern crate serde_json; pub mod options; - /// Need to tell us about something that went wrong? Use this error /// type to do that. Use this alias to be safe from future changes in /// our internal error handling, since we'll implement any necessary @@ -47,6 +47,7 @@ where rpcmethods: HashMap>, subscriptions: HashMap>, dynamic: bool, + nonnumericids: bool, } /// A plugin that has registered with the lightning daemon, and gotten @@ -116,6 +117,7 @@ where options: vec![], rpcmethods: HashMap::new(), dynamic: false, + nonnumericids: true, } } @@ -319,6 +321,7 @@ where hooks: self.hooks.keys().map(|s| s.clone()).collect(), rpcmethods, dynamic: self.dynamic, + nonnumericids: true, } } @@ -329,16 +332,25 @@ where // Match up the ConfigOptions and fill in their values if we // have a matching entry. for opt in self.options.iter_mut() { - if let Some(val) = call.options.get(opt.name()) { - opt.value = Some(match (opt.default(), &val) { - (OValue::String(_), JValue::String(s)) => OValue::String(s.clone()), - (OValue::Integer(_), JValue::Number(n)) => OValue::Integer(n.as_i64().unwrap()), - (OValue::Boolean(_), JValue::Bool(n)) => OValue::Boolean(*n), - - // It's ok to panic, if we get here Core Lightning - // has not enforced the option type. - (_, _) => panic!("Mismatching types in options: {:?} != {:?}", opt, val), - }); + let val = call.options.get(opt.name()); + opt.value = match (&opt, &opt.default(), &val) { + (_, OValue::String(_), Some(JValue::String(s))) => Some(OValue::String(s.clone())), + (_, OValue::OptString, Some(JValue::String(s))) => Some(OValue::String(s.clone())), + (_, OValue::OptString, None) => None, + + (_, OValue::Integer(_), Some(JValue::Number(s))) => { + Some(OValue::Integer(s.as_i64().unwrap())) + } + (_, OValue::OptInteger, Some(JValue::Number(s))) => { + Some(OValue::Integer(s.as_i64().unwrap())) + } + (_, OValue::OptInteger, None) => None, + + (_, OValue::Boolean(_), Some(JValue::Bool(s))) => Some(OValue::Boolean(*s)), + (_, OValue::OptBoolean, Some(JValue::Bool(s))) => Some(OValue::Boolean(*s)), + (_, OValue::OptBoolean, None) => None, + + (o, _, _) => panic!("Type mismatch for option {:?}", o), } } @@ -484,6 +496,12 @@ where .next() .map(|co| co.value.clone().unwrap_or(co.default().clone())) } + + /// return the cln configuration send to the + /// plugin after the initialization. + pub fn configuration(&self) -> Configuration { + self.configuration.clone() + } } impl PluginDriver diff --git a/plugins/src/messages.rs b/plugins/src/messages.rs index 89722b997b1d..0a7a8e71b692 100644 --- a/plugins/src/messages.rs +++ b/plugins/src/messages.rs @@ -58,12 +58,12 @@ pub(crate) enum Notification { } #[derive(Deserialize, Debug)] -pub struct GetManifestCall {} +pub(crate) struct GetManifestCall {} #[derive(Deserialize, Debug)] pub(crate) struct InitCall { pub(crate) options: HashMap, - pub(crate) configuration: Configuration, + pub configuration: Configuration, } #[derive(Clone, Deserialize, Debug)] @@ -93,7 +93,7 @@ pub struct ProxyInfo { } #[derive(Debug)] -pub enum JsonRpc { +pub(crate) enum JsonRpc { Request(serde_json::Value, R), Notification(N), CustomRequest(serde_json::Value, Value), @@ -157,6 +157,7 @@ pub(crate) struct GetManifestResponse { pub(crate) subscriptions: Vec, pub(crate) hooks: Vec, pub(crate) dynamic: bool, + pub(crate) nonnumericids: bool, } #[derive(Serialize, Default, Debug)] diff --git a/plugins/src/options.rs b/plugins/src/options.rs index 587686c241df..909ccd7ff320 100644 --- a/plugins/src/options.rs +++ b/plugins/src/options.rs @@ -1,11 +1,70 @@ use serde::ser::{SerializeStruct, Serializer}; -use serde::{Serialize}; +use serde::Serialize; #[derive(Clone, Debug)] pub enum Value { String(String), Integer(i64), Boolean(bool), + OptString, + OptInteger, + OptBoolean, +} + +impl Value { + /// Returns true if the `Value` is a String. Returns false otherwise. + /// + /// For any Value on which `is_string` returns true, `as_str` is guaranteed + /// to return the string slice. + pub fn is_string(&self) -> bool { + self.as_str().is_some() + } + + /// If the `Value` is a String, returns the associated str. Returns None + /// otherwise. + pub fn as_str(&self) -> Option<&str> { + match self { + Value::String(s) => Some(s), + _ => None, + } + } + + /// Returns true if the `Value` is an integer between `i64::MIN` and + /// `i64::MAX`. + /// + /// For any Value on which `is_i64` returns true, `as_i64` is guaranteed to + /// return the integer value. + pub fn is_i64(&self) -> bool { + self.as_i64().is_some() + + + } + + /// If the `Value` is an integer, represent it as i64. Returns + /// None otherwise. + pub fn as_i64(&self) -> Option { + match *self { + Value::Integer(n) => Some(n), + _ => None, + } + } + + /// Returns true if the `Value` is a Boolean. Returns false otherwise. + /// + /// For any Value on which `is_boolean` returns true, `as_bool` is + /// guaranteed to return the boolean value. + pub fn is_boolean(&self) -> bool { + self.as_bool().is_some() + } + + /// If the `Value` is a Boolean, returns the associated bool. Returns None + /// otherwise. + pub fn as_bool(&self) -> Option { + match *self { + Value::Boolean(b) => Some(b), + _ => None, + } + } } /// An stringly typed option that is passed to @@ -45,11 +104,19 @@ impl Serialize for ConfigOption { s.serialize_field("type", "int")?; s.serialize_field("default", i)?; } - Value::Boolean(b) => { s.serialize_field("type", "bool")?; s.serialize_field("default", b)?; } + Value::OptString => { + s.serialize_field("type", "string")?; + } + Value::OptInteger => { + s.serialize_field("type", "int")?; + } + Value::OptBoolean => { + s.serialize_field("type", "bool")?; + } } s.serialize_field("description", &self.description)?; diff --git a/plugins/test/run-route-overlong.c b/plugins/test/run-route-overlong.c index 2283e89e98b2..207ee852da65 100644 --- a/plugins/test/run-route-overlong.c +++ b/plugins/test/run-route-overlong.c @@ -8,6 +8,12 @@ #include /* AUTOGENERATED MOCKS START */ +/* Generated stub for blinded_onion_hops */ +u8 **blinded_onion_hops(const tal_t *ctx UNNEEDED, + struct amount_msat final_amount UNNEEDED, + u32 final_cltv UNNEEDED, + const struct blinded_path *path UNNEEDED) +{ fprintf(stderr, "blinded_onion_hops called!\n"); abort(); } /* Generated stub for command_finished */ struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response UNNEEDED) { fprintf(stderr, "command_finished called!\n"); abort(); } diff --git a/plugins/topology.c b/plugins/topology.c index 691920920ca1..628ec42c0d70 100644 --- a/plugins/topology.c +++ b/plugins/topology.c @@ -305,6 +305,7 @@ static struct node_map *local_connected(const tal_t *ctx, struct node_map *connected = tal(ctx, struct node_map); node_map_init(connected); + tal_add_destructor(connected, node_map_clear); json_for_each_arr(i, t, peers) { const jsmntok_t *chans, *c; @@ -580,6 +581,7 @@ static struct command_result *json_listincoming(struct command *cmd, struct gossmap_chan *ourchan; struct gossmap_node *peer; struct short_channel_id scid; + const u8 *peer_features; ourchan = gossmap_nth_chan(gossmap, me, i, &dir); /* If its half is disabled, ignore. */ @@ -596,6 +598,9 @@ static struct command_result *json_listincoming(struct command *cmd, json_add_amount_msat_only(js, "fee_base_msat", amount_msat(ourchan->half[!dir] .base_fee)); + json_add_amount_msat_only(js, "htlc_min_msat", + amount_msat(fp16_to_u64(ourchan->half[!dir] + .htlc_min))); json_add_amount_msat_only(js, "htlc_max_msat", amount_msat(fp16_to_u64(ourchan->half[!dir] .htlc_max))); @@ -605,6 +610,10 @@ static struct command_result *json_listincoming(struct command *cmd, json_add_amount_msat_only(js, "incoming_capacity_msat", peer_capacity(gossmap, me, peer, ourchan)); + json_add_bool(js, "public", !ourchan->private); + peer_features = gossmap_node_get_features(tmpctx, gossmap, peer); + if (peer_features) + json_add_hex_talarr(js, "peer_features", peer_features); json_object_end(js); } done: diff --git a/tests/fuzz/Makefile b/tests/fuzz/Makefile index cf980ea1c536..e536321378ca 100644 --- a/tests/fuzz/Makefile +++ b/tests/fuzz/Makefile @@ -33,8 +33,6 @@ FUZZ_COMMON_OBJS := \ common/permute_tx.o \ common/initial_channel.o \ common/initial_commit_tx.o \ - common/json_parse_simple.o \ - common/json_stream.o \ common/key_derive.o \ common/keyset.o \ common/msg_queue.o \ diff --git a/tests/plugins/test_libplugin.c b/tests/plugins/test_libplugin.c index 00f0c3196f06..21722ab0f8e8 100644 --- a/tests/plugins/test_libplugin.c +++ b/tests/plugins/test_libplugin.c @@ -6,11 +6,31 @@ #include #include - -const char *name_option; +static const char *somearg; static bool self_disable = false; static bool dont_shutdown = false; +static struct command_result *get_ds_done(struct command *cmd, + const char *val, + char *arg) +{ + if (!val) + val = "NOT FOUND"; + return command_success(cmd, json_out_obj(cmd, arg, val)); +} + +static struct command_result *get_ds_bin_done(struct command *cmd, + const u8 *val, + char *arg) +{ + plugin_log(cmd->plugin, LOG_INFORM, "get_ds_bin_done: %s", + val ? tal_hex(tmpctx, val) : "NOT FOUND"); + + return jsonrpc_get_datastore_string(cmd->plugin, cmd, + "test_libplugin/name", + get_ds_done, arg); +} + static struct command_result *json_helloworld(struct command *cmd, const char *buf, const jsmntok_t *params) @@ -23,8 +43,12 @@ static struct command_result *json_helloworld(struct command *cmd, return command_param_failed(); plugin_notify_message(cmd, LOG_INFORM, "Notification from %s", "json_helloworld"); + if (!name) - name = name_option ? name_option : tal_strdup(tmpctx, "world"); + return jsonrpc_get_datastore_binary(cmd->plugin, cmd, + "test_libplugin/name", + get_ds_bin_done, + "hello"); return command_success(cmd, json_out_obj(cmd, "hello", name)); } @@ -103,28 +127,37 @@ static struct command_result *json_testrpc(struct command *cmd, return send_outreq(cmd->plugin, req); } -#if DEVELOPER -static void memleak_mark(struct plugin *p, struct htable *memtable) -{ - /* name_option is not a leak! */ - memleak_ptr(memtable, name_option); -} -#endif /* DEVELOPER */ - static const char *init(struct plugin *p, const char *buf UNUSED, const jsmntok_t *config UNUSED) { + const char *name; + const u8 *binname; + plugin_log(p, LOG_DBG, "test_libplugin initialised!"); + if (somearg) + plugin_log(p, LOG_DBG, "somearg = %s", somearg); + somearg = tal_free(somearg); if (self_disable) return "Disabled via selfdisable option"; -#if DEVELOPER - plugin_set_memleak_handler(p, memleak_mark); -#endif - - return NULL; + /* Test rpc_scan_datastore funcs */ + if (!rpc_scan_datastore_str(p, "test_libplugin/name", + JSON_SCAN_TAL(tmpctx, json_strdup, + &name))) + name = NULL; + if (!rpc_scan_datastore_hex(p, "test_libplugin/name", + JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, + &binname))) + binname = NULL; + + plugin_log(p, LOG_INFORM, "String name from datastore: %s", + name ? name : "NOT FOUND"); + plugin_log(p, LOG_INFORM, "Hex name from datastore: %s", + binname ? tal_hex(tmpctx, binname) : "NOT FOUND"); + + return NULL; } static const struct plugin_command commands[] = { { @@ -180,14 +213,14 @@ int main(int argc, char *argv[]) commands, ARRAY_SIZE(commands), notifs, ARRAY_SIZE(notifs), hooks, ARRAY_SIZE(hooks), NULL, 0, /* Notification topics we publish */ - plugin_option("name", + plugin_option("somearg", "string", - "Who to say hello to.", - charp_option, &name_option), - plugin_option_deprecated("name-deprecated", + "Argument to print at init.", + charp_option, &somearg), + plugin_option_deprecated("somearg-deprecated", "string", - "Who to say hello to.", - charp_option, &name_option), + "Deprecated arg for init.", + charp_option, &somearg), plugin_option("selfdisable", "flag", "Whether to disable.", diff --git a/tests/test_cln_rs.py b/tests/test_cln_rs.py index 6d408d9cb846..9d6e619a6579 100644 --- a/tests/test_cln_rs.py +++ b/tests/test_cln_rs.py @@ -72,6 +72,20 @@ def test_plugin_start(node_factory): l1.daemon.wait_for_log(r'Got a connect notification') +def test_plugin_optional_opts(node_factory): + """Start a minimal plugin and ensure it is well-behaved + """ + bin_path = Path.cwd() / "target" / "debug" / "examples" / "cln-plugin-startup" + l1 = node_factory.get_node(options={"plugin": str(bin_path), 'opt-option': 31337}) + opts = l1.rpc.testoptions() + print(opts) + + # Do not set any value, should be None now + l1 = node_factory.get_node(options={"plugin": str(bin_path)}) + opts = l1.rpc.testoptions() + print(opts) + + def test_grpc_connect(node_factory): """Attempts to connect to the grpc interface and call getinfo""" # These only exist if we have rust! diff --git a/tests/test_closing.py b/tests/test_closing.py index cd6e5242b951..7cc49cfc605b 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -26,7 +26,7 @@ def test_closing_simple(node_factory, bitcoind, chainparams): l1, l2 = node_factory.line_graph(2, opts={'plugin': coin_mvt_plugin}) chan = l1.get_channel_scid(l2) channel_id = first_channel_id(l1, l2) - fee = closing_fee(3750, 2) if not chainparams['elements'] else 4263 + fee = closing_fee(3750, 2) if not chainparams['elements'] else 4278 l1.pay(l2, 200000000) @@ -1668,7 +1668,9 @@ def test_penalty_rbf_burn(node_factory, bitcoind, executor, chainparams): may_fail=True, allow_broken_log=True) l2 = node_factory.get_node(options={'dev-disable-commit-after': 1, 'watchtime-blocks': to_self_delay, - 'plugin': coin_mvt_plugin}) + 'plugin': coin_mvt_plugin}, + # Exporbitant feerates mean we don't have cap on RBF! + feerates=(15000000, 11000, 7500, 3750)) l1.rpc.connect(l2.info['id'], 'localhost', l2.port) l1.fundchannel(l2, 10**7) @@ -3389,7 +3391,7 @@ def test_closing_higherfee(node_factory, bitcoind, executor): l1.rpc.connect(l2.info['id'], 'localhost', l2.port) # This causes us to *exceed* previous requirements! - l1.daemon.wait_for_log(r'deriving max fee from rate 30000 -> 16440sat \(not 1000000sat\)') + l1.daemon.wait_for_log(r'deriving max fee from rate 30000 -> 16560sat \(not 1000000sat\)') # This will fail because l1 restarted! with pytest.raises(RpcError, match=r'Connection to RPC server lost.'): @@ -3578,12 +3580,12 @@ def save_notifications(message, progress, request, **kwargs): l1.rpc.close(l2.info['id'], feerange=['253perkw', 'normal']) if not chainparams['elements']: - l1_range = [138, 4110] - l2_range = [1027, 1000000] + l1_range = [139, 4140] + l2_range = [1035, 1000000] else: # That fee output is a little chunky. - l1_range = [220, 6547] - l2_range = [1636, 1000000] + l1_range = [221, 6577] + l2_range = [1644, 1000000] l1.daemon.wait_for_log('Negotiating closing fee between {}sat and {}sat satoshi'.format(l1_range[0], l1_range[1])) l2.daemon.wait_for_log('Negotiating closing fee between {}sat and {}sat satoshi'.format(l2_range[0], l2_range[1])) @@ -3635,19 +3637,20 @@ def test_close_weight_estimate(node_factory, bitcoind): # This is the actual weight: in theory this could use their # actual sig, and thus vary, but we don't do that. log = l1.daemon.wait_for_log('Their actual closing tx fee is') - actual_weight = int(re.match('.*: weight is ([0-9]*).*', log).group(1)) + final_estimate = int(re.match('.*: weight is ([0-9]*).*', log).group(1)) - assert actual_weight == expected_weight + assert final_estimate == expected_weight log = l1.daemon.wait_for_log('sendrawtransaction: ') tx = re.match('.*sendrawtransaction: ([0-9a-f]*).*', log).group(1) - # This could actually be a bit shorter: 1 in 256 chance we get - # lucky with a sig and it's shorter. We have 2 sigs, so that's - # 1 in 128. Unlikely to do better than 2 bytes off though! + # To match the signer's estimate we use the pessimistic estimate + # of 73bytes / signature. We will always end up with at most 71 + # bytes since we grind the signatures, and sometimes we get lucky + # and get a 70 byte signature, hence the below ranges. signed_weight = int(bitcoind.rpc.decoderawtransaction(tx)['weight']) - assert signed_weight <= actual_weight - assert signed_weight >= actual_weight - 2 + assert signed_weight + 4 <= final_estimate # 71byte signature + assert signed_weight + 6 >= final_estimate # 70byte signature @pytest.mark.developer("needs dev_disconnect") diff --git a/tests/test_connection.py b/tests/test_connection.py index a0860386eaaa..c46fb6635bfa 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -3465,9 +3465,29 @@ def test_wumbo_channels(node_factory, bitcoind): wait_for(lambda: 'CHANNELD_NORMAL' in [c['state'] for c in only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['channels']]) # Exact amount depends on fees, but it will be wumbo! - amount = [c['funding']['local_funds_msat'] for c in only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['channels'] if c['state'] == 'CHANNELD_NORMAL'][0] + chan = only_one([c for c in only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['channels'] if c['state'] == 'CHANNELD_NORMAL']) + amount = chan['funding']['local_funds_msat'] assert amount > Millisatoshi(str((1 << 24) - 1) + "sat") + # We should know we can spend that much! + spendable = chan['spendable_msat'] + assert spendable > Millisatoshi(str((1 << 24) - 1) + "sat") + + # So should peer. + chan = only_one([c for c in only_one(l2.rpc.listpeers(l1.info['id'])['peers'])['channels'] if c['state'] == 'CHANNELD_NORMAL']) + assert chan['receivable_msat'] == spendable + + # And we can wumbo pay, right? + inv = l2.rpc.invoice(str(1 << 24) + "sat", "test_wumbo_channels", "wumbo payment") + # We actually do warn about capacity: l2 sees that *l1* doesn't have + # enough incoming to pay (not knowing that l1 is the intended payer). + assert 'warning_capacity' in inv + assert 'warning_mpp' not in inv + + l1.rpc.pay(inv['bolt11']) + # Done in a single shot! + assert len(l1.rpc.listsendpays()['payments']) == 1 + @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @@ -3510,7 +3530,7 @@ def test_nonstatic_channel(node_factory, bitcoind): opts=[{}, # needs at least 15 to connect # (and 9 is a dependent) - {'dev-force-features': '9,15/////'}]) + {'dev-force-features': '9,15////////'}]) chan = only_one(only_one(l1.rpc.listpeers()['peers'])['channels']) assert 'option_static_remotekey' not in chan['features'] assert 'option_anchor_outputs' not in chan['features'] diff --git a/tests/test_db.py b/tests/test_db.py index 8b1ac2969d4b..eccb492add99 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -508,6 +508,9 @@ def test_db_forward_migrate(bitcoind, node_factory): assert l1.rpc.getinfo()['fees_collected_msat'] == 4 assert len(l1.rpc.listforwards()['forwards']) == 4 + # The two null in_htlc_id are replaced with bogus entries! + assert sum([f['in_htlc_id'] > 0xFFFFFFFFFFFF for f in l1.rpc.listforwards()['forwards']]) == 2 + # Make sure autoclean can handle these! l1.stop() l1.daemon.opts['autoclean-succeededforwards-age'] = 2 diff --git a/tests/test_gossip.py b/tests/test_gossip.py index f2e0a34cafbe..d12351c146da 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -117,7 +117,8 @@ def test_announce_address(node_factory, bitcoind): """Make sure our announcements are well formed.""" # We do not allow announcement of duplicates. - opts = {'disable-dns': None, 'announce-addr': + opts = {'announce-addr-dns': True, + 'announce-addr': ['4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion', '1.2.3.4:1234', 'example.com:1236', @@ -158,6 +159,31 @@ def test_announce_address(node_factory, bitcoind): assert addresses_dns[0]['port'] == 1236 +def test_announce_dns_suppressed(node_factory, bitcoind): + """By default announce DNS names as IPs""" + opts = {'announce-addr': 'example.com:1236', + 'start': False} + l1, l2 = node_factory.get_nodes(2, opts=[opts, {}]) + # Remove unwanted disable-dns option! + del l1.daemon.opts['disable-dns'] + l1.start() + + # Need a channel so l1 will announce itself. + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + scid, _ = l1.fundchannel(l2, 10**6) + bitcoind.generate_block(5) + + # Wait for l2 to see l1, with addresses. + wait_for(lambda: l2.rpc.listnodes(l1.info['id'])['nodes'] != []) + wait_for(lambda: 'addresses' in only_one(l2.rpc.listnodes(l1.info['id'])['nodes'])) + + addresses = only_one(l2.rpc.listnodes(l1.info['id'])['nodes'])['addresses'] + assert len(addresses) == 1 + assert addresses[0]['type'] == 'ipv4' + assert addresses[0]['address'] != 'example.com' + assert addresses[0]['port'] == 1236 + + @pytest.mark.developer("gossip without DEVELOPER=1 is slow") def test_announce_and_connect_via_dns(node_factory, bitcoind): """ Test that DNS annoucements propagate and can be used when connecting. @@ -176,6 +202,7 @@ def test_announce_and_connect_via_dns(node_factory, bitcoind): - 'dev-allow-localhost' must not be set, so it does not resolve localhost anyway. """ opts1 = {'disable-dns': None, + 'announce-addr-dns': True, 'announce-addr': ['localhost.localdomain:12345'], # announce dns 'bind-addr': ['127.0.0.1:12345', '[::1]:12345']} # and bind local IPs opts3 = {'may_reconnect': True} @@ -225,7 +252,8 @@ def test_announce_and_connect_via_dns(node_factory, bitcoind): def test_only_announce_one_dns(node_factory, bitcoind): # and test that we can't announce more than one DNS address l1 = node_factory.get_node(expect_fail=True, start=False, - options={'announce-addr': ['localhost.localdomain:12345', 'example.com:12345']}) + options={'announce-addr-dns': True, + 'announce-addr': ['localhost.localdomain:12345', 'example.com:12345']}) l1.daemon.start(wait_for_initialized=False, stderr_redir=True) wait_for(lambda: l1.daemon.is_in_stderr("Only one DNS can be announced")) @@ -234,7 +262,7 @@ def test_announce_dns_without_port(node_factory, bitcoind): """ Checks that the port of a DNS announcement is set to the corresponding network port. In this case regtest 19846 """ - opts = {'announce-addr': ['example.com']} + opts = {'announce-addr-dns': True, 'announce-addr': ['example.com']} l1 = node_factory.get_node(options=opts) # 'address': [{'type': 'dns', 'address': 'example.com', 'port': 0}] @@ -2165,3 +2193,33 @@ def test_close_12_block_delay(node_factory, bitcoind): # One more block, it's forgotten too. bitcoind.generate_block(1) wait_for(lambda: l4.rpc.listchannels(source=l2.info['id'])['channels'] == []) + + +@pytest.mark.developer("needs --dev-fast-gossip") +def test_gossip_private_updates(node_factory, bitcoind): + """Check that private channel updates are properly added and deleted from + the gossip store. + + """ + l1, l2 = node_factory.get_nodes(2) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + scid, _ = l1.fundchannel(l2, 10**6, None, False) + bitcoind.generate_block(5) + + l1.wait_channel_active(scid) + l2.wait_channel_active(scid) + + l2.rpc.setchannel(l1.info['id'], feebase=11) + wait_for(lambda: sum([c['base_fee_millisatoshi'] for c in l1.rpc.listchannels()['channels']]) == 12) + l2.rpc.setchannel(l1.info['id'], feebase=12) + wait_for(lambda: sum([c['base_fee_millisatoshi'] for c in l1.rpc.listchannels()['channels']]) == 13) + l2.rpc.setchannel(l1.info['id'], feebase=13) + wait_for(lambda: sum([c['base_fee_millisatoshi'] for c in l1.rpc.listchannels()['channels']]) == 14) + l2.rpc.setchannel(l1.info['id'], feebase=14) + wait_for(lambda: sum([c['base_fee_millisatoshi'] for c in l1.rpc.listchannels()['channels']]) == 15) + l2.rpc.setchannel(l1.info['id'], feebase=15) + wait_for(lambda: sum([c['base_fee_millisatoshi'] for c in l1.rpc.listchannels()['channels']]) == 16) + l1.restart() + + wait_for(lambda: l1.daemon.is_in_log(r'gossip_store_compact_offline: 5 deleted, 3 copied')) diff --git a/tests/test_misc.py b/tests/test_misc.py index 9fff772fa7eb..8dfe56c4622c 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -927,6 +927,16 @@ def test_cli(node_factory): j, _ = json.JSONDecoder().raw_decode(out) assert 'help [command]' in j['help'][0]['verbose'] + # Test filtering + out = subprocess.check_output(['cli/lightning-cli', + '--network={}'.format(TEST_NETWORK), + '--lightning-dir={}' + .format(l1.daemon.lightning_dir), + '-J', '--filter={"help":[{"command":true}]}', + 'help', 'help']).decode('utf-8') + j, _ = json.JSONDecoder().raw_decode(out) + assert j == {'help': [{'command': 'help [command]'}]} + # Test missing parameters. try: # This will error due to missing parameters. @@ -2812,6 +2822,54 @@ def test_torv2_in_db(node_factory): l1.start() +def test_field_filter(node_factory, chainparams): + l1, l2 = node_factory.get_nodes(2) + + addr1 = l1.rpc.newaddr('bech32')['bech32'] + addr2 = l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'] + inv = l1.rpc.invoice(123000, 'label', 'description', 3700, [addr1, addr2]) + + # Simple case: single field + dec = l1.rpc.call('decodepay', {'bolt11': inv['bolt11']}, filter={"currency": True}) + assert dec == {"currency": chainparams['bip173_prefix']} + + # Use context manager: + with l1.rpc.reply_filter({"currency": True}): + dec = l1.rpc.decodepay(bolt11=inv['bolt11']) + assert dec == {"currency": chainparams['bip173_prefix']} + + # Two fields + dec = l1.rpc.call('decodepay', {'bolt11': inv['bolt11']}, filter={"currency": True, "payment_hash": True}) + assert dec == {"currency": chainparams['bip173_prefix'], + "payment_hash": inv['payment_hash']} + + # Nested fields + dec = l1.rpc.call('decodepay', {'bolt11': inv['bolt11']}, + filter={"currency": True, + "payment_hash": True, + "fallbacks": [{"type": True}]}) + assert dec == {"currency": chainparams['bip173_prefix'], + "payment_hash": inv['payment_hash'], + "fallbacks": [{"type": 'P2WPKH'}, {"type": 'P2SH'}]} + + # Nonexistent fields. + dec = l1.rpc.call('decodepay', {'bolt11': inv['bolt11']}, + filter={"foobar": True}) + assert dec == {} + + # Bad filters + dec = l1.rpc.call('decodepay', {'bolt11': inv['bolt11']}, + filter={"currency": True, + "payment_hash": True, + "fallbacks": {'type': True}}) + assert dec['warning_parameter_filter'] == '.fallbacks is an array' + + # C plugins implement filters! + res = l1.rpc.call('decode', {'string': inv['bolt11']}, + filter={"currency": True}) + assert res == {"currency": chainparams['bip173_prefix']} + + def test_checkmessage_pubkey_not_found(node_factory): l1 = node_factory.get_node() diff --git a/tests/test_mkfunding.py b/tests/test_mkfunding.py new file mode 100644 index 000000000000..ffae3e29dcac --- /dev/null +++ b/tests/test_mkfunding.py @@ -0,0 +1,152 @@ +# Blackbox tests for myfunding devtool +# +# Devtool usage: mkfunding +# +# +# +# +# To run tests in this file only, enter this: +# $ pytest tests/test_mkfunding.py +# + +import subprocess +import sys +import traceback + +# good command-line values used in test cases +TIMEOUT = 10 +EXECUTABLE = 'devtools/mkfunding' +INPUT_TXID = '16835ac8c154b616baac524163f41fb0c4f82c7b972ad35d4d6f18d854f6856b' +INPUT_TXOUTPUT = '1' +INPUT_AMOUNT = '0.01btc' +FEERATE_PER_KW = '253' +INPUT_PRIVKEY = '76edf0c303b9e692da9cb491abedef46ca5b81d32f102eb4648461b239cb0f99' +LOCAL_FUNDING_PRIVKEY = '0000000000000000000000000000000000000000000000000000000000000010' +REMOTE_FUNDING_PRIVKEY = '0000000000000000000000000000000000000000000000000000000000000020' + + +def subprocess_run(args): + try: + response = subprocess.run( + args, + timeout=TIMEOUT, + capture_output=True, + encoding='utf-8') + print("*** returncode ***") + print(response.returncode) + print("*** stderr ***") + print(response.stderr) + print("*** stdout ***") + print(response.stdout.strip()) + return response + except Exception: + # Get current system exception + ex_type, ex_value, ex_traceback = sys.exc_info() + + # Extract unformatter stack traces as tuples + trace_back = traceback.extract_tb(ex_traceback) + + # Format stacktrace + stack_trace = list() + + for trace in trace_back: + stack_trace.append( + "File : %s , Line : %d, Func.Name : %s, Message : %s" % + (trace[0], trace[1], trace[2], trace[3])) + + print("Exception type : %s" % ex_type.__name__) + print("Exception message : %s" % ex_value) + print("Stack trace : %s" % stack_trace) + + +def test_mkfunding_bad_usage(): + response = subprocess_run([EXECUTABLE]) + assert response.returncode == 1 + assert 'Usage:' in response.stderr + + +def test_mkfunding_bad_input_txid(): + response = subprocess_run( + [EXECUTABLE, + 'alpha', 'beta', 'gamma', 'delta', 'epsilon', + 'zeta', 'eta']) + assert response.returncode == 1 + assert 'Bad input-txid' in response.stderr + + +def test_mkfunding_bad_input_amount(): + response = subprocess_run( + [EXECUTABLE, + INPUT_TXID, INPUT_TXOUTPUT, + 'gamma', 'delta', 'epsilon', 'zeta', 'eta']) + assert response.returncode == 1 + assert 'Bad input-amount' in response.stderr + + +def test_mkfunding_bad_input_privkey(): + response = subprocess_run( + [EXECUTABLE, + INPUT_TXID, INPUT_TXOUTPUT, INPUT_AMOUNT, + FEERATE_PER_KW, + 'epsilon', 'zeta', 'eta']) + assert response.returncode == 1 + assert 'Parsing input-privkey' in response.stderr + + +def test_mkfunding_bad_local_funding_privkey(): + response = subprocess_run( + [EXECUTABLE, + INPUT_TXID, INPUT_TXOUTPUT, INPUT_AMOUNT, + FEERATE_PER_KW, INPUT_PRIVKEY, + 'zeta', 'eta']) + assert response.returncode == 1 + assert 'Parsing local-funding-privkey' in response.stderr + + +def test_mkfunding_bad_remote_funding_privkey(): + response = subprocess_run( + [EXECUTABLE, + INPUT_TXID, INPUT_TXOUTPUT, INPUT_AMOUNT, + FEERATE_PER_KW, INPUT_PRIVKEY, + LOCAL_FUNDING_PRIVKEY, + 'eta']) + assert response.returncode == 1 + assert 'Parsing remote-funding-privkey' in response.stderr + + +def test_mkfunding_bad_privkeys(): + bad_privkey = ('0' * 64) + response = subprocess_run( + [EXECUTABLE, + INPUT_TXID, INPUT_TXOUTPUT, INPUT_AMOUNT, + FEERATE_PER_KW, + bad_privkey, bad_privkey, bad_privkey]) + assert response.returncode == 1 + assert 'Bad privkeys' in response.stderr + + +def test_mkfunding_bad_cantaffordfee(): + input_amount_less_than_fee = '0.00000122btc' + response = subprocess_run( + [EXECUTABLE, + INPUT_TXID, INPUT_TXOUTPUT, + input_amount_less_than_fee, + FEERATE_PER_KW, INPUT_PRIVKEY, + LOCAL_FUNDING_PRIVKEY, REMOTE_FUNDING_PRIVKEY]) + assert response.returncode == 1 + assert 'can\'t afford fee' in response.stderr + + +def test_mkfunding_good_noabort(): + response = subprocess_run( + [EXECUTABLE, + INPUT_TXID, INPUT_TXOUTPUT, INPUT_AMOUNT, + FEERATE_PER_KW, INPUT_PRIVKEY, + LOCAL_FUNDING_PRIVKEY, REMOTE_FUNDING_PRIVKEY]) + # prior to bug fix for issue #5363, + # subprocess_run had a return code of -6 (abort) + assert response.returncode == 0 + assert 'funding sig' in response.stdout + assert 'funding witnesses' in response.stdout + assert 'funding amount' in response.stdout + assert 'funding txid' in response.stdout diff --git a/tests/test_opening.py b/tests/test_opening.py index fa02825714ac..a3a69586c0fe 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -1907,3 +1907,44 @@ def test_zeroreserve_alldust(node_factory): # Now try with just a bit more l1.connect(l2) l1.rpc.fundchannel(l2.info['id'], minfunding + 1) + + +def test_coinbase_unspendable(node_factory, bitcoind): + """ A node should not be able to spend a coinbase output + before it's mature """ + + [l1] = node_factory.get_nodes(1) + + addr = l1.rpc.newaddr()["bech32"] + bitcoind.rpc.generatetoaddress(1, addr) + + addr2 = l1.rpc.newaddr()["bech32"] + + # Wait til money in wallet + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1) + out = only_one(l1.rpc.listfunds()['outputs']) + assert out['status'] == 'immature' + + with pytest.raises(RpcError, match='Could not afford all using all 0 available UTXOs'): + l1.rpc.withdraw(addr2, "all") + + # Nothing sent to the mempool! + assert len(bitcoind.rpc.getrawmempool()) == 0 + + # Mine 98 blocks + bitcoind.rpc.generatetoaddress(98, l1.rpc.newaddr()['bech32']) + assert len([out for out in l1.rpc.listfunds()['outputs'] if out['status'] == 'confirmed']) == 0 + with pytest.raises(RpcError, match='Could not afford all using all 0 available UTXOs'): + l1.rpc.withdraw(addr2, "all") + + # One more and the first coinbase unlocks + bitcoind.rpc.generatetoaddress(1, l1.rpc.newaddr()['bech32']) + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 100) + assert len([out for out in l1.rpc.listfunds()['outputs'] if out['status'] == 'confirmed']) == 1 + l1.rpc.withdraw(addr2, "all") + # One tx in the mempool now! + assert len(bitcoind.rpc.getrawmempool()) == 1 + + # Mine one block, assert one more is spendable + bitcoind.rpc.generatetoaddress(1, l1.rpc.newaddr()['bech32']) + assert len([out for out in l1.rpc.listfunds()['outputs'] if out['status'] == 'confirmed']) == 1 diff --git a/tests/test_pay.py b/tests/test_pay.py index 49c778507208..aa5d71848147 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3627,7 +3627,8 @@ def test_keysend_strip_tlvs(node_factory): ksinfo = """💕 ₿"' More info """ - l1.rpc.keysend(l2.info['id'], amt, extratlvs={133773310: bytes(ksinfo, encoding='utf8').hex()}) + # Since we're at it, use this to test string-keyed TLVs + l1.rpc.keysend(l2.info['id'], amt, extratlvs={"133773310": bytes(ksinfo, encoding='utf8').hex()}) inv = only_one(l2.rpc.listinvoices()['invoices']) assert inv['description'] == 'keysend: ' + ksinfo l2.daemon.wait_for_log('Keysend payment uses illegal even field 133773310: stripping') @@ -4379,13 +4380,13 @@ def test_offer_needs_option(node_factory): with pytest.raises(RpcError, match='experimental-offers not enabled'): l1.rpc.call('offer', {'amount': '1msat', 'description': 'test'}) with pytest.raises(RpcError, match='experimental-offers not enabled'): - l1.rpc.call('offerout', {'amount': '2msat', - 'description': 'simple test'}) + l1.rpc.call('invoicerequest', {'amount': '2msat', + 'description': 'simple test'}) with pytest.raises(RpcError, match='Unknown command'): l1.rpc.call('fetchinvoice', {'offer': 'aaaa'}) # Decode still works though - assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyys5qq7ypnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq633uzqaxlsxzxergsrav494jjrpuy9hcldjeglha57lxvz20fhha6hjwhv69nnzwzjsajntyf0c4z8h9e70dfdlfq8jdvc9rdht8vr955udtg')['valid'] + assert l1.rpc.decode('lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqyqs5pr5v4ehg93pqfnwgkvdr57yzh6h92zg3qctvrm7w38djg67kzcm4yeg8vc4cq63s')['valid'] def test_offer(node_factory, bitcoind): @@ -4404,7 +4405,6 @@ def test_offer(node_factory, bitcoind): offer = only_one(l1.rpc.call('listoffers', [ret['offer_id']])['offers']) assert offer['bolt12'] == ret['bolt12'] - assert offer['bolt12_unsigned'] == ret['bolt12_unsigned'] assert offer['offer_id'] == ret['offer_id'] output = subprocess.check_output([bolt12tool, 'decode', @@ -4413,12 +4413,6 @@ def test_offer(node_factory, bitcoind): assert 'amount' not in output else: assert 'amount' in output - output = subprocess.check_output([bolt12tool, 'decode', - offer['bolt12_unsigned']]).decode('ASCII') - if amount == 'any': - assert 'amount' not in output - else: - assert 'amount' in output # Try wrong amount precision: with pytest.raises(RpcError, match='Currency AUD requires 2 minor units'): @@ -4458,14 +4452,14 @@ def test_offer(node_factory, bitcoind): offer['bolt12']]).decode('UTF-8') assert 'issuer: ' + weird_issuer in output - # Test quantity min/max + # Test quantity ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_min test', - 'quantity_min': 1}) + 'description': 'quantity_max existence test', + 'quantity_max': 0}) offer = only_one(l1.rpc.call('listoffers', [ret['offer_id']])['offers']) output = subprocess.check_output([bolt12tool, 'decode', offer['bolt12']]).decode('UTF-8') - assert 'quantity_min: 1' in output + assert 'quantity_max: 0' in output ret = l1.rpc.call('offer', {'amount': '100000sat', 'description': 'quantity_max test', @@ -4475,26 +4469,6 @@ def test_offer(node_factory, bitcoind): offer['bolt12']]).decode('UTF-8') assert 'quantity_max: 2' in output - # BOLT-offers #12: - # * - MUST NOT set `quantity_min` or `quantity_max` less than 1. - with pytest.raises(RpcError, match='quantity_min: must be >= 1'): - ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_min test', - 'quantity_min': 0}) - - with pytest.raises(RpcError, match='quantity_max: must be >= 1'): - ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_max test', - 'quantity_max': 0}) - # BOLT-offers #12: - # - if both: - # - MUST set `quantity_min` greater or equal to `quantity_max`. - with pytest.raises(RpcError, match='quantity_min: must be <= quantity_max'): - ret = l1.rpc.call('offer', {'amount': '100000sat', - 'description': 'quantity_max test', - 'quantity_min': 10, - 'quantity_max': 9}) - # Test absolute_expiry exp = int(time.time() + 2) ret = l1.rpc.call('offer', {'amount': '100000sat', @@ -4607,7 +4581,7 @@ def test_fetchinvoice(node_factory, bitcoind): assert offer1['created'] is True inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) - inv2 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12_unsigned'], + inv2 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'payer_note': 'Thanks for the fish!'}) assert inv1 != inv2 assert 'next_period' not in inv1 @@ -4621,15 +4595,15 @@ def test_fetchinvoice(node_factory, bitcoind): # listinvoices will show these on l3 assert [x['local_offer_id'] for x in l3.rpc.listinvoices()['invoices']] == [offer1['offer_id'], offer1['offer_id']] - assert 'payer_note' not in only_one(l3.rpc.call('listinvoices', {'invstring': inv1['invoice']})['invoices']) - assert only_one(l3.rpc.call('listinvoices', {'invstring': inv2['invoice']})['invoices'])['payer_note'] == 'Thanks for the fish!' + assert 'invreq_payer_note' not in only_one(l3.rpc.call('listinvoices', {'invstring': inv1['invoice']})['invoices']) + assert only_one(l3.rpc.call('listinvoices', {'invstring': inv2['invoice']})['invoices'])['invreq_payer_note'] == 'Thanks for the fish!' # BTW, test listinvoices-by-offer_id: assert len(l3.rpc.listinvoices(offer_id=offer1['offer_id'])['invoices']) == 2 # We can also set the amount explicitly, to tip. inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'amount_msat': 3}) - assert l1.rpc.call('decode', [inv1['invoice']])['amount_msat'] == 3 + assert l1.rpc.call('decode', [inv1['invoice']])['invoice_amount_msat'] == 3 l1.rpc.pay(inv1['invoice']) # More than ~5x expected is rejected as absurd (it's actually a divide test, @@ -4664,7 +4638,8 @@ def test_fetchinvoice(node_factory, bitcoind): l1.rpc.pay(inv1['invoice']) # We can't pay the other one now. - with pytest.raises(RpcError, match="INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS.*'erring_node': '{}'".format(l3.info['id'])): + # FIXME: Even dummy blinded paths always return WIRE_INVALID_ONION_BLINDING! + with pytest.raises(RpcError, match="INVALID_ONION_BLINDING.*'erring_node': '{}'".format(l3.info['id'])): l1.rpc.pay(inv2['invoice']) # We can't reuse the offer, either. @@ -4758,7 +4733,7 @@ def test_fetchinvoice(node_factory, bitcoind): 'description': 'simple test'}) assert offer1['created'] is False l3.rpc.call('disableoffer', {'offer_id': offer1['offer_id']}) - with pytest.raises(RpcError, match="1000.*Offer already exists, but isn't active"): + with pytest.raises(RpcError, match="1000.*Already exists, but isn't active"): l3.rpc.call('offer', {'amount': '2msat', 'description': 'simple test'}) @@ -4828,13 +4803,13 @@ def test_fetchinvoice_autoconnect(node_factory, bitcoind): l3.rpc.call('fetchinvoice', {'offer': offer['bolt12']}) assert l3.rpc.listpeers(l2.info['id'])['peers'] != [] - # Similarly for send-invoice offer. + # Similarly for an invoice_request. l3.rpc.disconnect(l2.info['id']) - offer = l2.rpc.call('offerout', {'amount': '2msat', - 'description': 'simple test'}) + invreq = l2.rpc.call('invoicerequest', {'amount': '2msat', + 'description': 'simple test'}) # Ofc l2 can't actually pay it! with pytest.raises(RpcError, match='pay attempt failed: "Ran out of routes to try'): - l3.rpc.call('sendinvoice', {'offer': offer['bolt12'], 'label': 'payme!'}) + l3.rpc.call('sendinvoice', {'invreq': invreq['bolt12'], 'label': 'payme!'}) assert l3.rpc.listpeers(l2.info['id'])['peers'] != [] @@ -4848,7 +4823,7 @@ def test_fetchinvoice_autoconnect(node_factory, bitcoind): wait_for(lambda: only_one(only_one(l2.rpc.listpeers(l1.info['id'])['peers'])['channels'])['spendable_msat'] != Millisatoshi(0)) l3.rpc.disconnect(l2.info['id']) - l3.rpc.call('sendinvoice', {'offer': offer['bolt12'], 'label': 'payme for real!'}) + l3.rpc.call('sendinvoice', {'invreq': invreq['bolt12'], 'label': 'payme for real!'}) # It will have autoconnected, to send invoice (since l1 says it doesn't do onion messages!) assert l3.rpc.listpeers(l2.info['id'])['peers'] != [] @@ -4895,18 +4870,22 @@ def test_sendinvoice(node_factory, bitcoind): l2opts]) # Simple offer to send money (balances channel a little) - offer = l1.rpc.call('offerout', {'amount': '100000sat', - 'description': 'simple test'}) + invreq = l1.rpc.call('invoicerequest', {'amount': '100000sat', + 'description': 'simple test'}) + + # Fetchinvoice will refuse, since it's not an offer. + with pytest.raises(RpcError, match='unexpected prefix lnr'): + l2.rpc.call('fetchinvoice', {'offer': invreq['bolt12']}) - # Fetchinvoice will refuse, since you're supposed to send an invoice. - with pytest.raises(RpcError, match='Offer wants an invoice, not invoice_request'): - l2.rpc.call('fetchinvoice', {'offer': offer['bolt12']}) + # Pay will refuse, since it's not an invoice. + with pytest.raises(RpcError, match='unexpected prefix lnr'): + l2.rpc.call('fetchinvoice', {'offer': invreq['bolt12']}) # used will be false - assert only_one(l1.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is False + assert only_one(l1.rpc.call('listinvoicerequests', [invreq['invreq_id']])['invoicerequests'])['used'] is False # sendinvoice should work. - out = l2.rpc.call('sendinvoice', {'offer': offer['bolt12_unsigned'], + out = l2.rpc.call('sendinvoice', {'invreq': invreq['bolt12'], 'label': 'test sendinvoice 1'}) assert out['label'] == 'test sendinvoice 1' assert out['description'] == 'simple test' @@ -4922,37 +4901,19 @@ def test_sendinvoice(node_factory, bitcoind): # Note, if we're slow, this fails with "Offer no longer available", # *but* if it hasn't heard about payment success yet, l2 will fail # simply because payments are already pending. - with pytest.raises(RpcError, match='Offer no longer available|pay attempt failed'): - l2.rpc.call('sendinvoice', {'offer': offer['bolt12'], + with pytest.raises(RpcError, match='no longer available|pay attempt failed'): + l2.rpc.call('sendinvoice', {'invreq': invreq['bolt12'], 'label': 'test sendinvoice 2'}) # Technically, l1 may not have gotten payment success, so we need to wait. - wait_for(lambda: only_one(l1.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is True) - - # Now try a refund. - offer = l2.rpc.call('offer', {'amount': '100msat', - 'description': 'simple test'}) - assert only_one(l2.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is False - - inv = l1.rpc.call('fetchinvoice', {'offer': offer['bolt12']}) - l1.rpc.pay(inv['invoice']) - assert only_one(l2.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is True - - refund = l2.rpc.call('offerout', {'amount': '100msat', - 'description': 'refund test', - 'refund_for': inv['invoice']}) - assert only_one(l2.rpc.call('listoffers', [refund['offer_id']])['offers'])['used'] is False - - l1.rpc.call('sendinvoice', {'offer': refund['bolt12'], - 'label': 'test sendinvoice refund'}) - wait_for(lambda: only_one(l2.rpc.call('listoffers', [refund['offer_id']])['offers'])['used'] is True) + wait_for(lambda: only_one(l1.rpc.call('listinvoicerequests', [invreq['invreq_id']])['invoicerequests'])['used'] is True) - # Offer with issuer: we must not copy issuer into our invoice! - offer = l1.rpc.call('offerout', {'amount': '10000sat', - 'description': 'simple test', - 'issuer': "clightning test suite"}) + # Offer with issuer: we must copy issuer into our invoice! + invreq = l1.rpc.call('invoicerequest', {'amount': '10000sat', + 'description': 'simple test', + 'issuer': "clightning test suite"}) - out = l2.rpc.call('sendinvoice', {'offer': offer['bolt12'], + out = l2.rpc.call('sendinvoice', {'invreq': invreq['bolt12'], 'label': 'test sendinvoice 3'}) assert out['label'] == 'test sendinvoice 3' assert out['description'] == 'simple test' @@ -5329,14 +5290,14 @@ def test_payerkey(node_factory): """payerkey calculation should not change across releases!""" nodes = node_factory.get_nodes(7) - expected_keys = ["ed648d8c53c73eb4ef97f3e9586ecfd86e2628037dd91e96ecdc469467dcc1b2", - "ee90e2adcf0e12c5dd1d802af792a4f4b18fd3926a9cc325ffe181bab1c48661", - "17b9ecb1870b5d3896e88247fcb592833fbee8abb5e89673d16560b0ed38f5c6", - "d37f723b611c15b7af394984aea84837d85371ba9eee95364b3c9f89a086f7bf", - "b33482c9753af9deb6df365cf834eccaab7afb24d080caaf87a57010f78f5817", - "f1d699068e3d276eddf9fc4caa0955604a34ee9b9b6529a1ec2eacebb82eb11e", - "4ef73851fe22604e9b7034f548bcb79583ec503983879c56963b9a40fc854758"] + expected_keys = ["02294ec1cd3f100947fe859d71a42cb87932e36e7771abf2d50b02a7a92be8e4d5", + "026a4a3b6b0c694da6f14629ca5140713fc703591a6d8aae5c79ba9b5556fc5723", + "03defd2b1f3004b0145351f469f34512c6fa4d02fe891a977bafdb34fe7b73ea48", + "03eccb00c0a3c760465bb69a6297d7cfa5bcbd989d5a88e435bd8d6e4c723013cd", + "021b4bfa652f0df7498d734b0ca888b4e3b07f59e1a974ec7d4a9d6046e8e5ab92", + "03fc91d60b061e517f9182e3e40ea14c27df520c51db204f1409ff50e5cf9a5e4d", + "03a3bbda0137722ba62207b9d3e5e6cc2a11e58480f801892093e01383aacb7fb2"] for n, k in zip(nodes, expected_keys): - b12 = n.rpc.createinvoicerequest('lnr1qvsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcyyrjjthf4rh99n7equvlrzrlalcacxj4y9hgzxc79yrntrth6mp3nkvssy5mac4pkfq2m3gq4ttajwh097s')['bolt12'] - assert n.rpc.decode(b12)['payer_key'] == k + b12 = n.rpc.createinvoicerequest('lnr1qqgz2d7u2smys9dc5q2447e8thjlgq3qqc3xu3s3rg94nj40zfsy866mhu5vxne6tcej5878k2mneuvgjy8ssqvepgz5zsjrg3z3vggzvkm2khkgvrxj27r96c00pwl4kveecdktm29jdd6w0uwu5jgtv5v9qgqxyfhyvyg6pdvu4tcjvpp7kkal9rp57wj7xv4pl3ajku70rzy3pu', False)['bolt12'] + assert n.rpc.decode(b12)['invreq_payer_id'] == k diff --git a/tests/test_plugin.py b/tests/test_plugin.py index f75b4fcc1484..aeb54cef5b65 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -416,7 +416,7 @@ def test_pay_plugin(node_factory): # Make sure usage messages are present. msg = 'pay bolt11 [amount_msat] [label] [riskfactor] [maxfeepercent] '\ - '[retry_for] [maxdelay] [exemptfee] [localofferid] [exclude] '\ + '[retry_for] [maxdelay] [exemptfee] [localinvreqid] [exclude] '\ '[maxfee] [description]' if DEVELOPER: msg += ' [use_shadow]' @@ -1042,7 +1042,7 @@ def test_channel_state_change_history(node_factory, bitcoind): assert(history[3]['message'] == "Closing complete") -@pytest.mark.developer("without DEVELOPER=1, gossip v slow") +@pytest.mark.developer("Gossip slow, and we test --dev-onion-reply-length") def test_htlc_accepted_hook_fail(node_factory): """Send payments from l1 to l2, but l2 just declines everything. @@ -1053,7 +1053,8 @@ def test_htlc_accepted_hook_fail(node_factory): """ l1, l2, l3 = node_factory.line_graph(3, opts=[ {}, - {'plugin': os.path.join(os.getcwd(), 'tests/plugins/fail_htlcs.py')}, + {'dev-onion-reply-length': 1111, + 'plugin': os.path.join(os.getcwd(), 'tests/plugins/fail_htlcs.py')}, {} ], wait_for_announce=True) @@ -1489,28 +1490,49 @@ def test_libplugin(node_factory): plugin = os.path.join(os.getcwd(), "tests/plugins/test_libplugin") l1 = node_factory.get_node(options={"plugin": plugin, 'allow-deprecated-apis': False, - 'log-level': 'io'}) + 'log-level': 'io'}, + allow_broken_log=True) # Test startup assert l1.daemon.is_in_log("test_libplugin initialised!") + assert l1.daemon.is_in_log("String name from datastore: NOT FOUND") + assert l1.daemon.is_in_log("Hex name from datastore: NOT FOUND") + + # This will look on datastore for default, won't find it. + assert l1.rpc.call("helloworld") == {"hello": "NOT FOUND"} + l1.daemon.wait_for_log("get_ds_bin_done: NOT FOUND") + # Test dynamic startup l1.rpc.plugin_stop(plugin) + # Non-string datastore value: + l1.rpc.datastore(["test_libplugin", "name"], hex="00010203") l1.rpc.plugin_start(plugin) l1.rpc.check("helloworld") myname = os.path.splitext(os.path.basename(sys.argv[0]))[0] - # Side note: getmanifest will trace back to plugin_start - l1.daemon.wait_for_log(r": {}:plugin#[0-9]*/cln:getmanifest#[0-9]*\[OUT\]".format(myname)) + # Note: getmanifest always uses numeric ids, since it doesn't know + # yet whether strings are allowed: + l1.daemon.wait_for_log(r"test_libplugin: [0-9]*\[OUT\]") + + l1.daemon.wait_for_log("String name from datastore: NOT FOUND") + l1.daemon.wait_for_log("Hex name from datastore: 00010203") # Test commands - assert l1.rpc.call("helloworld") == {"hello": "world"} + assert l1.rpc.call("helloworld") == {"hello": "NOT FOUND"} + l1.daemon.wait_for_log("get_ds_bin_done: 00010203") + l1.daemon.wait_for_log("BROKEN.* Datastore gave nonstring result.*00010203") assert l1.rpc.call("helloworld", {"name": "test"}) == {"hello": "test"} l1.stop() l1.daemon.opts["plugin"] = plugin - l1.daemon.opts["name"] = "test_opt" + l1.daemon.opts["somearg"] = "test_opt" l1.start() - assert l1.rpc.call("helloworld") == {"hello": "test_opt"} + assert l1.daemon.is_in_log("somearg = test_opt") + l1.rpc.datastore(["test_libplugin", "name"], "foobar", mode="must-replace") + + assert l1.rpc.call("helloworld") == {"hello": "foobar"} + l1.daemon.wait_for_log("get_ds_bin_done: 666f6f626172") + # But param takes over! assert l1.rpc.call("helloworld", {"name": "test"}) == {"hello": "test"} @@ -1534,17 +1556,17 @@ def test_libplugin(node_factory): with pytest.raises(RpcError, match=r"Deprecated command.*testrpc-deprecated"): l1.rpc.help('testrpc-deprecated') - assert 'name-deprecated' not in str(l1.rpc.listconfigs()) + assert 'somearg-deprecated' not in str(l1.rpc.listconfigs()) l1.stop() - l1.daemon.opts["name-deprecated"] = "test_opt" + l1.daemon.opts["somearg-deprecated"] = "test_opt" l1.daemon.start(wait_for_initialized=False, stderr_redir=True) # Will exit with failure code. assert l1.daemon.wait() == 1 - assert l1.daemon.is_in_stderr(r"name-deprecated: deprecated option") + assert l1.daemon.is_in_stderr(r"somearg-deprecated: deprecated option") - del l1.daemon.opts["name-deprecated"] + del l1.daemon.opts["somearg-deprecated"] l1.start() @@ -1552,10 +1574,10 @@ def test_libplugin_deprecated(node_factory): """Sanity checks for plugins made with libplugin using deprecated args""" plugin = os.path.join(os.getcwd(), "tests/plugins/test_libplugin") l1 = node_factory.get_node(options={"plugin": plugin, - 'name-deprecated': 'test_opt depr', + 'somearg-deprecated': 'test_opt depr', 'allow-deprecated-apis': True}) - assert l1.rpc.call("helloworld") == {"hello": "test_opt depr"} + assert l1.daemon.is_in_log("somearg = test_opt depr") l1.rpc.help('testrpc-deprecated') assert l1.rpc.call("testrpc-deprecated") == l1.rpc.getinfo() @@ -1940,6 +1962,8 @@ def test_plugin_fail(node_factory): time.sleep(2) # It should clean up! assert 'failcmd' not in [h['command'] for h in l1.rpc.help()['help']] + # Can happen *before* the 'Server started with public key' + l1.daemon.logsearch_start = 0 l1.daemon.wait_for_log(r': exited during normal operation') l1.rpc.plugin_start(plugin) diff --git a/tests/utils.py b/tests/utils.py index c1380d02c14c..b6dc4729f993 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -427,7 +427,7 @@ def basic_fee(feerate): def closing_fee(feerate, num_outputs): assert num_outputs == 1 or num_outputs == 2 - weight = 424 + 124 * num_outputs + weight = 428 + 124 * num_outputs return (weight * feerate) // 1000 diff --git a/tools/fromschema.py b/tools/fromschema.py index e6c4b9c6d70d..5fc76d4d9f8f 100755 --- a/tools/fromschema.py +++ b/tools/fromschema.py @@ -4,6 +4,12 @@ # https://creativecommons.org/publicdomain/zero/1.0/ from argparse import ArgumentParser import json +import re + + +def esc_underscores(s): + """Backslash-escape underscores outside of backtick-enclosed spans""" + return ''.join(['\\_' if x == '_' else x for x in re.findall(r'[^`_\\]+|`(?:[^`\\]|\\.)*`|\\.|_', s)]) def json_value(obj): @@ -13,7 +19,7 @@ def json_value(obj): return '*true*' return '*false*' if type(obj) is str: - return '"' + obj + '"' + return '"' + esc_underscores(obj) + '"' if obj is None: return '*null*' assert False @@ -30,9 +36,9 @@ def output(line): def output_type(properties, is_optional): - typename = properties['type'].replace('_', '\\_') + typename = esc_underscores(properties['type']) if typename == 'array': - typename += ' of {}s'.format(properties['items']['type'].replace('_', '\\_')) + typename += ' of {}s'.format(esc_underscores(properties['items']['type'])) if is_optional: typename += ", optional" output(" ({})".format(typename)) @@ -67,7 +73,7 @@ def output_range(properties): def fmt_propname(propname): """Pretty-print format a property name""" - return '**{}**'.format(propname.replace('_', '\\_')) + return '**{}**'.format(esc_underscores(propname)) def output_member(propname, properties, is_optional, indent, print_type=True, prefix=None): @@ -84,7 +90,7 @@ def output_member(propname, properties, is_optional, indent, print_type=True, pr output_type(properties, is_optional) if 'description' in properties: - output(": {}".format(properties['description'])) + output(": {}".format(esc_underscores(properties['description']))) output_range(properties) @@ -103,10 +109,10 @@ def output_array(items, indent): if items['type'] == 'object': output_members(items, indent) elif items['type'] == 'array': - output(indent + '- {}:\n'.format(items['description'])) + output(indent + '- {}:\n'.format(esc_underscores(items['description']))) output_array(items['items'], indent + ' ') else: - output(indent + '- {}'.format(items['description'])) + output(indent + '- {}'.format(esc_underscores(items['description']))) output_range(items) output('\n') diff --git a/tools/gen/impl_template b/tools/gen/impl_template index cb3d1f77b206..ec6f9d196494 100644 --- a/tools/gen/impl_template +++ b/tools/gen/impl_template @@ -90,7 +90,7 @@ fromwire_${type_}_array(cursor, plen, ${fieldname}, ${f.size('*plen')}); ${fieldname} = ${f.size('*plen')} ? tal_arr(${ctx}, ${typename}, 0) : NULL; % endif % if f.is_implicit_len(): - for (size_t i = 0; *plen != 0; i++) { + while (*plen != 0) { % else: for (size_t i = 0; i < ${f.size()}; i++) { % endif diff --git a/tools/generate-wire.py b/tools/generate-wire.py index 24a2a1370801..8b7b148d740f 100755 --- a/tools/generate-wire.py +++ b/tools/generate-wire.py @@ -237,7 +237,8 @@ class Type(FieldSet): 'height_states', 'onionreply', 'feature_set', - 'onionmsg_path', + 'onionmsg_hop', + 'blinded_path', 'route_hop', 'tx_parts', 'wally_psbt', diff --git a/tools/reckless b/tools/reckless new file mode 100755 index 000000000000..913511dbcb85 --- /dev/null +++ b/tools/reckless @@ -0,0 +1,743 @@ +#!/usr/bin/env python3 + +from subprocess import Popen, PIPE +import sys +import json +import os +import argparse +from pathlib import Path, PosixPath +import shutil +import tempfile +from typing import Union +from urllib.parse import urlparse +from urllib.request import urlopen +import logging + + +logging.basicConfig( + level=logging.DEBUG, + format='[%(asctime)s] %(levelname)s: %(message)s', + handlers=[logging.StreamHandler(stream=sys.stdout)], +) + + +repos = ['https://github.com/lightningd/plugins'] + + +def py_entry_guesses(name): + return [name, f'{name}.py', '__init__.py'] + + +def unsupported_entry(name): + return [f'{name}.go', f'{name}.sh'] + + +class InstInfo: + def __init__(self, name, url, git_url): + self.name = name + self.repo = url # Used for 'git clone' + self.git_url = git_url # API access for github repos + self.entry = None + self.deps = None + self.subdir = None + self.commit = None + + def __repr__(self): + return (f'InstInfo({self.name}, {self.repo}, {self.git_url}' + f'{self.entry}, {self.deps})') + + def get_inst_details(self): + """ + Populate installation details from a github repo url. + Return True if all data is found. + """ + r = urlopen(self.git_url, timeout=5) + if r.status != 200: + return False + if 'git/tree' in self.git_url: + tree = json.loads(r.read().decode())['tree'] + else: + tree = json.loads(r.read().decode()) + entry_guesses = py_entry_guesses(self.name) + for g in entry_guesses: + for f in tree: + if f['path'] == g: + self.entry = g + break + if self.entry is not None: + break + if self.entry is None: + for g in unsupported_entry(self.name): + for f in tree: + if f['path'] == g: + # FIXME: This should be easier to implement + print(f'entrypoint {g} is not yet supported') + return False + dependency_info = ['requirements.txt', 'pyproject.toml'] + for d in dependency_info: + for f in tree: + if f['path'] == d: + self.deps = d + break + if self.deps is not None: + break + if not self.entry: + return False + if not self.deps: + return False + return True + + +def create_dir(r: int, directory: PosixPath) -> bool: + """Creation of a directory at path `d` with a maximum new dir depth `r`""" + if directory.exists(): + return True + if r <= 0: + return False + if create_dir(r-1, directory.parent): + os.mkdir(directory, 0o777) + print(f'created directory {directory}') + assert directory.exists() + return True + + +def remove_dir(target: str) -> bool: + try: + shutil.rmtree(target) + return True + except NotADirectoryError: + print(f"Tried to remove directory {target} that does not exist.") + except PermissionError: + print(f"Permission denied removing dir: {target}") + return False + + +class Config(): + """A generic class for procuring, reading and editing config files""" + def obtain_config(self, + config_path: str, + default_text: str, + warn: bool = False) -> str: + """Return a config file from the desired location. Create one with + default_text if it cannot be found.""" + if isinstance(config_path, type(None)): + raise Exception("Generic config must be passed a config_path.") + assert isinstance(config_path, str) + # FIXME: warn if reckless dir exists, but conf not found + if Path(config_path).exists(): + with open(config_path, 'r+') as f: + config_content = f.readlines() + return config_content + print(f'config file not found: {config_path}') + if warn: + confirm = input('press [Y] to create one now.\n').upper() == 'Y' + else: + confirm = True + if not confirm: + sys.exit(1) + parent_path = Path(config_path).parent + # Create up to one parent in the directory tree. + if create_dir(1, parent_path): + with open(self.conf_fp, 'w') as f: + f.write(default_text) + # FIXME: Handle write failure + return default_text + else: + logging.debug(f'could not create the parent directory {parent_path}') + raise FileNotFoundError('invalid parent directory') + + def editConfigFile(self, addline: str, removeline: str): + remove_these_lines = [] + with open(self.conf_fp, 'r') as reckless_conf: + original = reckless_conf.readlines() + empty_lines = [] + for n, l in enumerate(original): + if l.strip() == removeline: + remove_these_lines.append(n) + continue + if l.strip() == '': + empty_lines.append(n) + if n-1 in empty_lines: + # The white space is getting excessive. + remove_these_lines.append(n) + continue + with open(self.conf_fp, 'w') as conf_write: + # no need to write if passed 'None' + line_exists = not bool(addline) + for n, l in enumerate(original): + if n not in remove_these_lines: + if n > 0: + conf_write.write(f'\n{l.strip()}') + else: + conf_write.write(l.strip()) + if addline.strip() == l.strip(): + # addline is idempotent + line_exists = True + if not line_exists: + conf_write.write(f'\n{addline}') + + def __init__(self, path: Union[str, None] = None, + default_text: Union[str, None] = None, + warn: bool = False): + assert path is not None + assert default_text is not None + self.conf_fp = path + self.content = self.obtain_config(self.conf_fp, default_text, + warn=warn) + + +class RecklessConfig(Config): + """Reckless config (by default, specific to the bitcoin network only.) + This is inherited by the lightningd config and contains all reckless + maintained plugins.""" + + def enable_plugin(self, plugin_path: str): + """Handle persistent plugin loading via config""" + self.editConfigFile(f'plugin={plugin_path}', + f'disable-plugin={plugin_path}') + + def disable_plugin(self, plugin_path: str): + """Handle persistent plugin disabling via config""" + self.editConfigFile(f'disable-plugin={plugin_path}', + f'plugin={plugin_path}') + + def __init__(self, path: Union[str, None] = None, + default_text: Union[str, None] = None): + if path is None: + path = Path(LIGHTNING_DIR) / 'reckless' / 'bitcoin-reckless.conf' + if default_text is None: + default_text = ( + '# This configuration file is managed by reckless to activate ' + 'and disable\n# reckless-installed plugins\n\n' + ) + Config.__init__(self, path=str(path), default_text=default_text) + self.reckless_dir = Path(path).parent + + +class LightningBitcoinConfig(Config): + """lightningd config specific to the bitcoin network. This is inherited by + the main lightningd config and in turn, inherits bitcoin-reckless.conf.""" + + def __init__(self, path: Union[str, None] = None, + default_text: Union[str, None] = None, + warn: bool = True): + if path is None: + path = Path(LIGHTNING_DIR).joinpath('bitcoin', 'config') + if default_text is None: + default_text = "# This config was autopopulated by reckless\n\n" + Config.__init__(self, path=str(path), + default_text=default_text, warn=warn) + + +class InferInstall(): + """Once a plugin is installed, we may need its directory and entrypoint""" + def __init__(self, name: str): + reck_contents = os.listdir(RECKLESS_CONFIG.reckless_dir) + if name[-3:] == '.py': + name = name[:-3] + if name in reck_contents: + self.dir = Path(RECKLESS_CONFIG.reckless_dir).joinpath(name) + else: + raise Exception(f"Could not find a reckless directory for {name}") + plug_dir = Path(RECKLESS_CONFIG.reckless_dir).joinpath(name) + for guess in py_entry_guesses(name): + for content in plug_dir.iterdir(): + if content.name == guess: + self.entry = str(content) + self.name = guess + return + raise Exception(f'plugin entrypoint not found in {self.dir}') + + +def help_alias(targets: list): + if len(targets) == 0: + parser.print_help(sys.stdout) + else: + print('try "reckless {} -h"'.format(' '.join(targets))) + sys.exit(1) + + +def _search_repo(name: str, url: str) -> InstInfo: + """look in given repo and, if found, populate InstInfo""" + # Remove api subdomain, subdirectories, etc. + repo = url.split('/') + while '' in repo: + repo.remove('') + repo_name = None + parsed_url = urlparse(url) + if 'github.com' not in parsed_url.netloc: + # FIXME: Handle non-github repos. + return False + if len(parsed_url.path.split('/')) < 2: + return False + start = 1 + # Maybe we were passed an api.github.com/repo/ url + if 'api' in parsed_url.netloc: + start += 1 + repo_user = parsed_url.path.split('/')[start] + repo_name = parsed_url.path.split('/')[start + 1] + + # Get details from the github API. + api_url = f'https://api.github.com/repos/{repo_user}/{repo_name}/contents/' + plugins_cont = api_url + r = urlopen(plugins_cont, timeout=5) + if r.status != 200: + print("Plugin repository unavailable") + return False + # Repo is for this plugin + if repo_name == name: + MyPlugin = InstInfo(name, + f'https://github.com/{repo_user}/{repo_name}', + api_url) + if not MyPlugin.get_inst_details(): + return False + return MyPlugin + # Repo contains multiple plugins? + for x in json.loads(r.read().decode()): + if x["name"] == name: + # Look for the rest of the install details + # These are in lightningd/plugins directly + if 'lightningd/plugins/' in x['html_url']: + MyPlugin = InstInfo(name, + 'https://github.com/lightningd/plugins', + x['git_url']) + MyPlugin.subdir = x['name'] + # submodules from another github repo + else: + MyPlugin = InstInfo(name, x['html_url'], x['git_url']) + # Submodule URLs are appended with /tree/ + if MyPlugin.repo.split('/')[-2] == 'tree': + MyPlugin.commit = MyPlugin.repo.split('/')[-1] + MyPlugin.repo = MyPlugin.repo.split('/tree/')[0] + logging.debug(f'repo using commit: {MyPlugin.commit}') + if not MyPlugin.get_inst_details(): + return False + return MyPlugin + return False + + +def _install_plugin(src: InstInfo) -> bool: + """make sure the repo exists and clone it.""" + logging.debug(f'Install requested from {src}.') + if RECKLESS_CONFIG is None: + print('error: reckless install directory unavailable') + sys.exit(2) + + # FIXME: This request seems rather pointless + req = urlopen(src.repo, timeout=10) + if not req.status == 200: + print('plugin source repository unavailable') + sys.exit(1) + # Use a unique directory for each cloned repo. + clone_path = 'reckless-{}'.format(str(hash(os.times()))[-9:]) + clone_path = Path(tempfile.gettempdir()) / clone_path + inst_path = Path(RECKLESS_CONFIG.reckless_dir) / src.name + if Path(clone_path).exists(): + logging.debug(f'{clone_path} already exists - deleting') + shutil.rmtree(clone_path) + # clone git repository to /tmp/reckless-... + if ('http' in src.repo[:4]) or ('github.com' in src.repo): + # Ugly, but interactively handling stderr gets hairy. + if logging.root.level < logging.WARNING: + git = Popen(['git', 'clone', src.repo, str(clone_path)], + stdout=PIPE) + else: + git = Popen(['git', 'clone', src.repo, str(clone_path)], + stdout=PIPE, stderr=PIPE) + git.wait() + if git.returncode != 0: + if git.stderr: + print(git.stderr.read().decode()) + if Path(clone_path).exists(): + remove_dir(clone_path) + print('Error: Failed to clone repo') + return False + plugin_path = clone_path + if src.subdir is not None: + plugin_path = Path(clone_path) / src.subdir + if src.commit: + logging.debug(f"Checking out commit {src.commit}") + checkout = Popen(['git', 'checkout', src.commit], + cwd=plugin_path, stdout=PIPE, stderr=PIPE) + checkout.wait() + if checkout.returncode != 0: + print(f'failed to checkout referenced commit {src.commit}') + return False + + # Install dependencies via requirements.txt or pyproject.toml + mypip = 'pip3' if shutil.which('pip3') else 'pip' + if not shutil.which(mypip): + raise Exception(f'{mypip} not found in PATH') + install_methods = { + 'requirements.txt': [mypip, 'install', '-r', 'requirements.txt'], + 'pyproject.toml': [mypip, 'install', '-e', '.'] + } + + if src.deps is not None: + logging.debug(f'installing dependencies using {src.deps}') + procedure = install_methods[src.deps] + # Verbose output requested. + if logging.root.level < logging.WARNING: + pip = Popen(procedure, cwd=plugin_path) + else: + pip = Popen(procedure, cwd=plugin_path, stdout=PIPE, stderr=PIPE) + pip.wait() + if pip.returncode == 0: + print('dependencies installed successfully') + else: + print('error encountered installing dependencies') + logging.debug(pip.stdout.read()) + return False + test = Popen([Path(plugin_path).joinpath(src.entry)], cwd=plugin_path, + stdout=PIPE, stderr=PIPE, universal_newlines=True) + test_log = [] + with test.stderr: + for line in test.stderr: + test_log.append(line.strip('\n')) + test.wait() + # FIXME: add noexec test/warning. Maybe try chmod entrypoint. + if test.returncode != 0: + logging.debug("plugin testing error:") + for line in test_log: + logging.debug(f' {line}') + print('plugin testing failed') + return False + + # Find this cute little plugin a forever home + shutil.copytree(plugin_path, inst_path) + print(f'plugin installed: {inst_path}') + remove_dir(clone_path) + return True + + +def install(plugin_name: str): + """downloads plugin from source repos, installs and activates plugin""" + assert isinstance(plugin_name, str) + src = search(plugin_name) + if src: + logging.debug(f'Retrieving {plugin_name} from {src.repo}') + if not _install_plugin(src): + print('installation aborted') + sys.exit(1) + inst_path = Path(RECKLESS_CONFIG.reckless_dir) / src.name / src.entry + RECKLESS_CONFIG.enable_plugin(inst_path) + enable(plugin_name) + + +def uninstall(plugin_name: str): + """disables plugin and deletes the plugin's reckless dir""" + assert isinstance(plugin_name, str) + logging.debug(f'Uninstalling plugin {plugin_name}') + disable(plugin_name) + plugin_dir = Path(RECKLESS_CONFIG.reckless_dir) / plugin_name + logging.debug(f'looking for {plugin_dir}') + if remove_dir(plugin_dir): + print(f"{plugin_name} uninstalled successfully.") + + +def search(plugin_name: str) -> InstInfo: + """searches plugin index for plugin""" + ordered_repos = RECKLESS_SOURCES + for r in RECKLESS_SOURCES: + # Search repos named after the plugin first + if r.split('/')[-1].lower() == plugin_name.lower(): + ordered_repos.remove(r) + ordered_repos.insert(0, r) + for r in ordered_repos: + p = _search_repo(plugin_name, r) + if p: + print(f"found {p.name} in repo: {p.repo}") + logging.debug(f"entry: {p.entry}") + if p.subdir: + logging.debug(f'sub-directory: {p.subdir}') + return p + print(f'Unable to locate source for plugin {plugin_name}') + + +class RPCError(Exception): + """lightning-cli fails to connect to lightningd RPC""" + def __init__(self, err): + self.err = err + + def __str__(self): + return 'RPCError({self.err})' + + +class CLIError(Exception): + """lightningd error response""" + def __init__(self, code, message): + self.code = code + self.message = message + + def __str__(self): + return f'CLIError({self.code} {self.message})' + + +def lightning_cli(*args, timeout=15) -> dict: + # CLI commands will be added to any necessary options + cmd = LIGHTNING_CLI_CALL.copy() + cmd.extend(args) + clncli = Popen(cmd, stdout=PIPE, stderr=PIPE) + clncli.wait(timeout=timeout) + out = clncli.stdout.read().decode() + if len(out) > 0 and out[0] == '{': + # If all goes well, a json object is typically returned + out = json.loads(out.replace('\n', '')) + else: + # help, -V, etc. may not return json, so stash it here. + out = {'content': out} + if clncli.returncode == 0: + return out + if clncli.returncode == 1: + # RPC doesn't like our input + # output contains 'code' and 'message' + raise CLIError(out['code'], out['message']) + if clncli.returncode == 2: + # RPC not available - lightningd not running or using alternate config + err = clncli.stderr.read().decode() + raise RPCError(err) + + +def enable(plugin_name: str): + """dynamically activates plugin and adds to config (persistent)""" + assert isinstance(plugin_name, str) + inst = InferInstall(plugin_name) + path = inst.entry + if not Path(path).exists(): + print(f'cannot find installed plugin at expected path {path}') + sys.exit(1) + logging.debug(f'activating {plugin_name}') + try: + lightning_cli('plugin', 'start', path) + except CLIError as err: + if 'already registered' in err.message: + logging.debug(f'{inst.name} is already running') + else: + print(f'reckless: {inst.name} failed to start!') + raise err + except RPCError: + logging.debug('lightningd rpc unavailable. Skipping dynamic activation.') + RECKLESS_CONFIG.enable_plugin(path) + print(f'{plugin_name} enabled') + + +def disable(plugin_name: str): + """reckless disable + deactivates an installed plugin""" + assert isinstance(plugin_name, str) + inst = InferInstall(plugin_name) + path = inst.entry + if not Path(path).exists(): + sys.stderr.write(f'Could not find plugin at {path}\n') + sys.exit(1) + logging.debug(f'deactivating {plugin_name}') + try: + lightning_cli('plugin', 'stop', path) + except CLIError as err: + if err.code == -32602: + logging.debug('plugin not currently running') + else: + print('lightning-cli plugin stop failed') + raise err + except RPCError: + logging.debug('lightningd rpc unavailable. Skipping dynamic deactivation.') + RECKLESS_CONFIG.disable_plugin(path) + print(f'{plugin_name} disabled') + + +def load_config(reckless_dir: Union[str, None] = None, + network: str = 'bitcoin') -> Config: + """Initial directory discovery and config file creation.""" + net_conf = None + # Does the lightning-cli already reference an explicit config? + try: + active_config = lightning_cli('listconfigs', timeout=3) + if 'conf' in active_config: + net_conf = LightningBitcoinConfig(path=active_config['conf']) + except RPCError: + pass + if reckless_dir is None: + reckless_dir = Path(LIGHTNING_DIR) / 'reckless' + else: + if not os.path.isabs(reckless_dir): + reckless_dir = Path.cwd() / reckless_dir + if LIGHTNING_CONFIG: + network_path = LIGHTNING_CONFIG + else: + network_path = Path(LIGHTNING_DIR) / network / 'config' + reck_conf_path = Path(reckless_dir) / f'{network}-reckless.conf' + if net_conf: + if str(network_path) != net_conf.conf_fp: + print('error: reckless configuration does not match lightningd:\n' + f'reckless network config path: {network_path}\n' + f'lightningd active config: {net_conf.conf_fp}') + sys.exit(1) + else: + # The network-specific config file (bitcoin by default) + net_conf = LightningBitcoinConfig(path=network_path) + # Reckless manages plugins here. + try: + reckless_conf = RecklessConfig(path=reck_conf_path) + except FileNotFoundError: + print('Error: reckless config file could not be written: ', + str(reck_conf_path)) + sys.exit(1) + if not net_conf: + print('Error: could not load or create the network specific lightningd' + ' config (default .lightning/bitcoin)') + sys.exit(1) + net_conf.editConfigFile(f'include {reckless_conf.conf_fp}', None) + return reckless_conf + + +def get_sources_file() -> str: + return Path(RECKLESS_DIR) / '.sources' + + +def sources_from_file() -> list: + sources_file = get_sources_file() + read_sources = [] + with open(sources_file, 'r') as f: + for src in f.readlines(): + if len(src.strip()) > 0: + read_sources.append(src.strip()) + return read_sources + + +def loadSources() -> list: + """Look for the repo sources file.""" + sources_file = get_sources_file() + # This would have been created if possible + if not Path(sources_file).exists(): + logging.debug('Warning: Reckless requires write access') + Config(path=str(sources_file), + default_text='https://github.com/lightningd/plugins') + return ['https://github.com/lightningd/plugins'] + return sources_from_file() + + +def add_source(src: str): + """Additional git repositories, directories, etc. are passed here.""" + assert isinstance(src, str) + # Is it a file? + maybe_path = os.path.realpath(src) + if Path(maybe_path).exists(): + # FIXME: This should handle either a directory or a git repo + if os.path.isdir(maybe_path): + print(f'Plugin source directory found: {maybe_path}') + elif 'github.com' in src: + my_file = Config(path=str(get_sources_file()), + default_text='https://github.com/lightningd/plugins') + my_file.editConfigFile(src, None) + + +def remove_source(src: str): + """Remove a source from the sources file.""" + assert isinstance(src, str) + if src in sources_from_file(): + my_file = Config(path=get_sources_file(), + default_text='https://github.com/lightningd/plugins') + my_file.editConfigFile(None, src) + print('plugin source removed') + else: + print(f'source not found: {src}') + + +def list_source(): + """Provide the user with all stored source repositories.""" + for src in sources_from_file(): + print(src) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + # This default depends on the .lightning directory + parser.add_argument('-d', '--reckless-dir', + help='specify a data directory for reckless to use', + type=str, default=None) + parser.add_argument('-l', '--lightning', + help='lightning data directory (default:~/.lightning)', + type=str, + default=Path.home().joinpath('.lightning')) + parser.add_argument('-c', '--conf', + help=' config file used by lightningd', + type=str, + default=None) + parser.add_argument('-r', '--regtest', action='store_true') + # parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('-v', '--verbose', action="store_const", + dest="loglevel", const=logging.DEBUG, default=logging.WARNING) + cmd1 = parser.add_subparsers(dest='cmd1', help='command', + required=True) + + install_cmd = cmd1.add_parser('install', help='search for and install a ' + 'plugin, then test and activate') + install_cmd.add_argument('targets', type=str, nargs='*') + install_cmd.set_defaults(func=install) + + uninstall_cmd = cmd1.add_parser('uninstall', help='deactivate a plugin ' + 'and remove it from the directory') + uninstall_cmd.add_argument('targets', type=str, nargs='*') + uninstall_cmd.set_defaults(func=uninstall) + + search_cmd = cmd1.add_parser('search', help='search for a plugin from ' + 'the available source repositories') + search_cmd.add_argument('targets', type=str, nargs='*') + search_cmd.set_defaults(func=search) + + enable_cmd = cmd1.add_parser('enable', help='dynamically enable a plugin ' + 'and update config') + enable_cmd.add_argument('targets', type=str, nargs='*') + enable_cmd.set_defaults(func=enable) + disable_cmd = cmd1.add_parser('disable', help='disable a plugin') + disable_cmd.add_argument('targets', type=str, nargs='*') + disable_cmd.set_defaults(func=disable) + source_parser = cmd1.add_parser('source', help='manage plugin search ' + 'sources') + source_subs = source_parser.add_subparsers(dest='source_subs', + required=True) + list_parse = source_subs.add_parser('list', help='list available plugin ' + 'sources (repositories)') + list_parse.set_defaults(func=list_source) + source_add = source_subs.add_parser('add', help='add a source repository') + source_add.add_argument('targets', type=str, nargs='*') + source_add.set_defaults(func=add_source) + source_rem = source_subs.add_parser('remove', aliases=['rem', 'rm'], + help='remove a plugin source ' + 'repository') + source_rem.add_argument('targets', type=str, nargs='*') + source_rem.set_defaults(func=remove_source) + + help_cmd = cmd1.add_parser('help', help='for contextual help, use ' + '"reckless -h"') + help_cmd.add_argument('targets', type=str, nargs='*') + help_cmd.set_defaults(func=help_alias) + + args = parser.parse_args() + + NETWORK = 'regtest' if args.regtest else 'bitcoin' + LIGHTNING_DIR = Path(args.lightning) + LIGHTNING_CLI_CALL = ['lightning-cli'] + if NETWORK != 'bitcoin': + LIGHTNING_CLI_CALL.append(f'--network={NETWORK}') + if LIGHTNING_DIR != Path.home().joinpath('.lightning'): + LIGHTNING_CLI_CALL.append(f'--lightning-dir={LIGHTNING_DIR}') + if args.reckless_dir: + RECKLESS_DIR = args.reckless_dir + else: + RECKLESS_DIR = Path(LIGHTNING_DIR) / 'reckless' + LIGHTNING_CONFIG = args.conf + RECKLESS_CONFIG = load_config(reckless_dir=RECKLESS_DIR, + network=NETWORK) + RECKLESS_SOURCES = loadSources() + logging.root.setLevel(args.loglevel) + + if 'targets' in args: + # FIXME: Catch missing argument + if args.func.__name__ == 'help_alias': + args.func(args.targets) + sys.exit(0) + for target in args.targets: + args.func(target) + else: + args.func() diff --git a/tools/rel.sh b/tools/rel.sh index e27b7467cc8a..8d1fd4262509 100755 --- a/tools/rel.sh +++ b/tools/rel.sh @@ -3,5 +3,5 @@ from=${1} to=${2} common=$(printf '%s\n%s' "${from}" "${to}" | sed 'N;s/\(.*\).*\n\1.*$/\1/' | sed 's@/[^/]*$@/@') -prefix=$(printf '%s\n' "${from#$common}" | sed 's@[^/][^/]*@..@g') -printf '%s\n' "$prefix/${to#$common}" +prefix=$(printf '%s\n' "${from#"$common"}" | sed 's@[^/][^/]*@..@g') +printf '%s\n' "$prefix/${to#"$common"}" diff --git a/wallet/db.c b/wallet/db.c index a18e4f0820dc..537a12a1c39c 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -912,7 +912,10 @@ static struct migration dbmigrations[] = { ", PRIMARY KEY(in_channel_scid, in_htlc_id))"), NULL}, {SQL("INSERT INTO forwards SELECT" " in_channel_scid" - ", (SELECT channel_htlc_id FROM channel_htlcs WHERE id = forwarded_payments.in_htlc_id)" + ", COALESCE(" + " (SELECT channel_htlc_id FROM channel_htlcs WHERE id = forwarded_payments.in_htlc_id)," + " -_ROWID_" + " )" ", out_channel_scid" ", (SELECT channel_htlc_id FROM channel_htlcs WHERE id = forwarded_payments.out_htlc_id)" ", in_msatoshi" @@ -929,6 +932,17 @@ static struct migration dbmigrations[] = { /* Adds scid column, then moves short_channel_id across to it */ {SQL("ALTER TABLE channels ADD scid BIGINT;"), migrate_channels_scids_as_integers}, {SQL("ALTER TABLE payments ADD failscid BIGINT;"), migrate_payments_scids_as_integers}, + {SQL("ALTER TABLE outputs ADD is_in_coinbase INTEGER DEFAULT 0;"), NULL}, + {SQL("CREATE TABLE invoicerequests (" + " invreq_id BLOB" + ", bolt12 TEXT" + ", label TEXT" + ", status INTEGER" + ", PRIMARY KEY (invreq_id)" + ");"), NULL}, + /* A reference into our own invoicerequests table, if it was made from one */ + {SQL("ALTER TABLE payments ADD COLUMN local_invreq_id BLOB DEFAULT NULL REFERENCES invoicerequests(invreq_id);"), NULL}, + /* FIXME: Remove payments local_offer_id column! */ /* Splicing requires us to store HTLC sigs for inflight splices and allows us to discard old sigs after splice confirmation. */ {SQL("ALTER TABLE htlc_sigs ADD inflight_id BLOB REFERENCES channel_funding_inflights(funding_tx_id)"), NULL}, {SQL("ALTER TABLE channel_funding_inflights ADD starting_htlc_id INTEGER DEFAULT 0"), NULL}, @@ -1476,6 +1490,7 @@ static void migrate_channels_scids_as_integers(struct lightningd *ld, { struct db_stmt *stmt; char **scids = tal_arr(tmpctx, char *, 0); + size_t changes; stmt = db_prepare_v2(db, SQL("SELECT short_channel_id FROM channels")); db_query_prepared(stmt); @@ -1487,6 +1502,7 @@ static void migrate_channels_scids_as_integers(struct lightningd *ld, } tal_free(stmt); + changes = 0; for (size_t i = 0; i < tal_count(scids); i++) { struct short_channel_id scid; if (!short_channel_id_from_str(scids[i], strlen(scids[i]), &scid)) @@ -1499,12 +1515,21 @@ static void migrate_channels_scids_as_integers(struct lightningd *ld, db_bind_scid(stmt, 0, &scid); db_bind_text(stmt, 1, scids[i]); db_exec_prepared_v2(stmt); + + /* This was reported to happen with an (old, closed) channel: that we'd have + * more than one change here! That's weird, but just log about it. */ if (db_count_changes(stmt) != 1) - db_fatal("Converting channels.short_channel_id '%s' gave %zu changes != 1?", - scids[i], db_count_changes(stmt)); + log_broken(ld->log, + "migrate_channels_scids_as_integers: converting channels.short_channel_id '%s' gave %zu changes != 1!", + scids[i], db_count_changes(stmt)); + changes += db_count_changes(stmt); tal_free(stmt); } + if (changes != tal_count(scids)) + fatal("migrate_channels_scids_as_integers: only converted %zu of %zu scids!", + changes, tal_count(scids)); + /* FIXME: We cannot use ->delete_columns to remove * short_channel_id, as other tables reference the channels * (and sqlite3 has them referencing a now-deleted table!). diff --git a/wallet/reservation.c b/wallet/reservation.c index 41ba958f93c7..de9b5ed377cc 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -434,29 +434,6 @@ static inline u32 minconf_to_maxheight(u32 minconf, struct lightningd *ld) return ld->topology->tip->height - minconf + 1; } -static struct command_result *param_reserve_num(struct command *cmd, - const char *name, - const char *buffer, - const jsmntok_t *tok, - unsigned int **num) -{ - bool flag; - - if (deprecated_apis) { - /* "reserve=true" means 6 hours */ - if (json_to_bool(buffer, tok, &flag)) { - *num = tal(cmd, unsigned int); - if (flag) - **num = RESERVATION_DEFAULT; - else - **num = 0; - return NULL; - } - } - - return param_number(cmd, name, buffer, tok, num); -} - static struct command_result *json_fundpsbt(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -474,7 +451,7 @@ static struct command_result *json_fundpsbt(struct command *cmd, p_req("feerate", param_feerate, &feerate_per_kw), p_req("startweight", param_number, &weight), p_opt_def("minconf", param_number, &minconf, 1), - p_opt_def("reserve", param_reserve_num, &reserve, + p_opt_def("reserve", param_number, &reserve, RESERVATION_DEFAULT), p_opt("locktime", param_number, &locktime), p_opt_def("min_witness_weight", param_number, @@ -655,7 +632,7 @@ static struct command_result *json_utxopsbt(struct command *cmd, p_req("feerate", param_feerate, &feerate_per_kw), p_req("startweight", param_number, &weight), p_req("utxos", param_txout, &utxos), - p_opt_def("reserve", param_reserve_num, &reserve, + p_opt_def("reserve", param_number, &reserve, RESERVATION_DEFAULT), p_opt_def("reservedok", param_bool, &reserved_ok, false), p_opt("locktime", param_number, &locktime), diff --git a/wallet/test/Makefile b/wallet/test/Makefile index c6c26ac914d2..c1df830636d8 100644 --- a/wallet/test/Makefile +++ b/wallet/test/Makefile @@ -26,6 +26,7 @@ WALLET_TEST_COMMON_OBJS := \ common/setup.o \ common/timeout.o \ common/utils.o \ + common/utxo.o \ common/wireaddr.o \ common/version.o \ wallet/db_sqlite3_sqlgen.o \ diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index bbed0e2063e9..af1ccd1ebe48 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -347,6 +347,11 @@ void json_add_node_id(struct json_stream *response UNNEEDED, void json_add_num(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, unsigned int value UNNEEDED) { fprintf(stderr, "json_add_num called!\n"); abort(); } +/* Generated stub for json_add_pubkey */ +void json_add_pubkey(struct json_stream *response UNNEEDED, + const char *fieldname UNNEEDED, + const struct pubkey *key UNNEEDED) +{ fprintf(stderr, "json_add_pubkey called!\n"); abort(); } /* Generated stub for json_add_s32 */ void json_add_s32(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, int32_t value UNNEEDED) @@ -548,10 +553,12 @@ enum watch_result onchaind_funding_spent(struct channel *channel UNNEEDED, { fprintf(stderr, "onchaind_funding_spent called!\n"); abort(); } /* Generated stub for onion_decode */ struct onion_payload *onion_decode(const tal_t *ctx UNNEEDED, + bool blinding_support UNNEEDED, const struct route_step *rs UNNEEDED, const struct pubkey *blinding UNNEEDED, - const struct secret *blinding_ss UNNEEDED, const u64 *accepted_extra_tlvs UNNEEDED, + struct amount_msat amount_in UNNEEDED, + u32 cltv_expiry UNNEEDED, u64 *failtlvtype UNNEEDED, size_t *failtlvpos UNNEEDED) { fprintf(stderr, "onion_decode called!\n"); abort(); } @@ -804,6 +811,9 @@ u8 *towire_incorrect_cltv_expiry(const tal_t *ctx UNNEEDED, u32 cltv_expiry UNNE /* Generated stub for towire_incorrect_or_unknown_payment_details */ u8 *towire_incorrect_or_unknown_payment_details(const tal_t *ctx UNNEEDED, struct amount_msat htlc_msat UNNEEDED, u32 height UNNEEDED) { fprintf(stderr, "towire_incorrect_or_unknown_payment_details called!\n"); abort(); } +/* Generated stub for towire_invalid_onion_blinding */ +u8 *towire_invalid_onion_blinding(const tal_t *ctx UNNEEDED, const struct sha256 *sha256_of_onion UNNEEDED) +{ fprintf(stderr, "towire_invalid_onion_blinding called!\n"); abort(); } /* Generated stub for towire_invalid_onion_payload */ u8 *towire_invalid_onion_payload(const tal_t *ctx UNNEEDED, bigsize type UNNEEDED, u16 offset UNNEEDED) { fprintf(stderr, "towire_invalid_onion_payload called!\n"); abort(); } diff --git a/wallet/wallet.c b/wallet/wallet.c index bb07fbd558c9..6737364439ea 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -148,7 +148,8 @@ static bool wallet_add_utxo(struct wallet *w, struct utxo *utxo, ", confirmation_height" ", spend_height" ", scriptpubkey" - ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); + ", is_in_coinbase" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); db_bind_txid(stmt, 0, &utxo->outpoint.txid); db_bind_int(stmt, 1, utxo->outpoint.n); db_bind_amount_sat(stmt, 2, &utxo->amount); @@ -183,6 +184,7 @@ static bool wallet_add_utxo(struct wallet *w, struct utxo *utxo, db_bind_blob(stmt, 12, utxo->scriptPubkey, tal_bytelen(utxo->scriptPubkey)); + db_bind_int(stmt, 13, utxo->is_in_coinbase); db_exec_prepared_v2(take(stmt)); return true; } @@ -200,6 +202,9 @@ static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt) utxo->is_p2sh = db_col_int(stmt, "type") == p2sh_wpkh; utxo->status = db_col_int(stmt, "status"); utxo->keyindex = db_col_int(stmt, "keyindex"); + + utxo->is_in_coinbase = db_col_int(stmt, "is_in_coinbase") == 1; + if (!db_col_is_null(stmt, "channel_id")) { utxo->close_info = tal(utxo, struct unilateral_close_info); utxo->close_info->channel_id = db_col_u64(stmt, "channel_id"); @@ -277,7 +282,6 @@ bool wallet_update_output_status(struct wallet *w, struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w, const enum output_status state) { struct utxo **results; - int i; struct db_stmt *stmt; if (state == OUTPUT_STATE_ANY) { @@ -297,6 +301,7 @@ struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w, const enum ou ", scriptpubkey " ", reserved_til " ", csv_lock " + ", is_in_coinbase " "FROM outputs")); } else { stmt = db_prepare_v2(w->db, SQL("SELECT" @@ -315,6 +320,7 @@ struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w, const enum ou ", scriptpubkey " ", reserved_til " ", csv_lock " + ", is_in_coinbase " "FROM outputs " "WHERE status= ? ")); db_bind_int(stmt, 0, output_status_in_db(state)); @@ -322,7 +328,7 @@ struct utxo **wallet_get_utxos(const tal_t *ctx, struct wallet *w, const enum ou db_query_prepared(stmt); results = tal_arr(ctx, struct utxo*, 0); - for (i=0; db_step(stmt); i++) { + while (db_step(stmt)) { struct utxo *u = wallet_stmt2output(results, stmt); tal_arr_expand(&results, u); } @@ -336,7 +342,6 @@ struct utxo **wallet_get_unconfirmed_closeinfo_utxos(const tal_t *ctx, { struct db_stmt *stmt; struct utxo **results; - int i; stmt = db_prepare_v2(w->db, SQL("SELECT" " prev_out_tx" @@ -354,13 +359,14 @@ struct utxo **wallet_get_unconfirmed_closeinfo_utxos(const tal_t *ctx, ", scriptpubkey" ", reserved_til" ", csv_lock" + ", is_in_coinbase" " FROM outputs" " WHERE channel_id IS NOT NULL AND " "confirmation_height IS NULL")); db_query_prepared(stmt); results = tal_arr(ctx, struct utxo *, 0); - for (i = 0; db_step(stmt); i++) { + while (db_step(stmt)) { struct utxo *u = wallet_stmt2output(results, stmt); tal_arr_expand(&results, u); } @@ -391,6 +397,7 @@ struct utxo *wallet_utxo_get(const tal_t *ctx, struct wallet *w, ", scriptpubkey" ", reserved_til" ", csv_lock" + ", is_in_coinbase" " FROM outputs" " WHERE prev_out_tx = ?" " AND prev_out_index = ?")); @@ -501,6 +508,11 @@ static bool deep_enough(u32 maxheight, const struct utxo *utxo, if (csv_free > current_blockheight) return false; } + + bool immature = utxo_is_immature(utxo, current_blockheight); + if (immature) + return false; + /* If we require confirmations check that we have a * confirmation height and that it is below the required * maxheight (current_height - minconf) */ @@ -539,6 +551,7 @@ struct utxo *wallet_find_utxo(const tal_t *ctx, struct wallet *w, ", scriptpubkey " ", reserved_til" ", csv_lock" + ", is_in_coinbase" " FROM outputs" " WHERE status = ?" " OR (status = ? AND reserved_til <= ?)" @@ -2296,6 +2309,7 @@ void wallet_confirm_tx(struct wallet *w, } int wallet_extract_owned_outputs(struct wallet *w, const struct wally_tx *wtx, + bool is_coinbase, const u32 *blockheight, struct amount_sat *total) { @@ -2330,19 +2344,21 @@ int wallet_extract_owned_outputs(struct wallet *w, const struct wally_tx *wtx, wally_txid(wtx, &utxo->outpoint.txid); utxo->outpoint.n = output; utxo->close_info = NULL; + utxo->is_in_coinbase = is_coinbase; utxo->blockheight = blockheight ? blockheight : NULL; utxo->spendheight = NULL; utxo->scriptPubkey = tal_dup_talarr(utxo, u8, script); - log_debug(w->log, "Owning output %zu %s (%s) txid %s%s", + log_debug(w->log, "Owning output %zu %s (%s) txid %s%s%s", output, type_to_string(tmpctx, struct amount_sat, &utxo->amount), is_p2sh ? "P2SH" : "SEGWIT", type_to_string(tmpctx, struct bitcoin_txid, &utxo->outpoint.txid), - blockheight ? " CONFIRMED" : ""); + blockheight ? " CONFIRMED" : "", + is_coinbase ? " COINBASE" : ""); /* We only record final ledger movements */ if (blockheight) { @@ -3076,7 +3092,7 @@ void wallet_payment_store(struct wallet *wallet, " bolt11," " total_msat," " partid," - " local_offer_id," + " local_invreq_id," " groupid," " paydescription" ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); @@ -3121,8 +3137,8 @@ void wallet_payment_store(struct wallet *wallet, db_bind_amount_msat(stmt, 11, &payment->total_msat); db_bind_u64(stmt, 12, payment->partid); - if (payment->local_offer_id != NULL) - db_bind_sha256(stmt, 13, payment->local_offer_id); + if (payment->local_invreq_id != NULL) + db_bind_sha256(stmt, 13, payment->local_invreq_id); else db_bind_null(stmt, 13); @@ -3267,11 +3283,11 @@ static struct wallet_payment *wallet_stmt2payment(const tal_t *ctx, else payment->partid = 0; - if (!db_col_is_null(stmt, "local_offer_id")) { - payment->local_offer_id = tal(payment, struct sha256); - db_col_sha256(stmt, "local_offer_id", payment->local_offer_id); + if (!db_col_is_null(stmt, "local_invreq_id")) { + payment->local_invreq_id = tal(payment, struct sha256); + db_col_sha256(stmt, "local_invreq_id", payment->local_invreq_id); } else - payment->local_offer_id = NULL; + payment->local_invreq_id = NULL; if (!db_col_is_null(stmt, "completed_at")) { payment->completed_at = tal(payment, u32); @@ -3315,7 +3331,7 @@ wallet_payment_by_hash(const tal_t *ctx, struct wallet *wallet, ", failonionreply" ", total_msat" ", partid" - ", local_offer_id" + ", local_invreq_id" ", groupid" ", completed_at" " FROM payments" @@ -3557,7 +3573,7 @@ wallet_payment_list(const tal_t *ctx, ", failonionreply" ", total_msat" ", partid" - ", local_offer_id" + ", local_invreq_id" ", groupid" ", completed_at" " FROM payments" @@ -3584,7 +3600,7 @@ wallet_payment_list(const tal_t *ctx, ", failonionreply" ", total_msat" ", partid" - ", local_offer_id" + ", local_invreq_id" ", groupid" ", completed_at" " FROM payments" @@ -3610,9 +3626,9 @@ wallet_payment_list(const tal_t *ctx, } const struct wallet_payment ** -wallet_payments_by_offer(const tal_t *ctx, - struct wallet *wallet, - const struct sha256 *local_offer_id) +wallet_payments_by_invoice_request(const tal_t *ctx, + struct wallet *wallet, + const struct sha256 *local_invreq_id) { const struct wallet_payment **payments; struct db_stmt *stmt; @@ -3638,12 +3654,12 @@ wallet_payments_by_offer(const tal_t *ctx, ", failonionreply" ", total_msat" ", partid" - ", local_offer_id" + ", local_invreq_id" ", groupid" ", completed_at" " FROM payments" - " WHERE local_offer_id = ?;")); - db_bind_sha256(stmt, 0, local_offer_id); + " WHERE local_invreq_id = ?;")); + db_bind_sha256(stmt, 0, local_invreq_id); db_query_prepared(stmt); for (i = 0; db_step(stmt); i++) { @@ -3654,7 +3670,7 @@ wallet_payments_by_offer(const tal_t *ctx, /* Now attach payments not yet in db. */ list_for_each(&wallet->unstored_payments, p, list) { - if (!p->local_offer_id || !sha256_eq(p->local_offer_id, local_offer_id)) + if (!p->local_invreq_id || !sha256_eq(p->local_invreq_id, local_invreq_id)) continue; tal_resize(&payments, i+1); payments[i++] = p; @@ -4765,7 +4781,6 @@ bool wallet_forward_delete(struct wallet *w, struct wallet_transaction *wallet_transactions_get(struct wallet *w, const tal_t *ctx) { struct db_stmt *stmt; - size_t count; struct wallet_transaction *cur = NULL, *txs = tal_arr(ctx, struct wallet_transaction, 0); struct bitcoin_txid last; @@ -4793,7 +4808,7 @@ struct wallet_transaction *wallet_transactions_get(struct wallet *w, const tal_t "ORDER BY t.blockheight, t.txindex ASC")); db_query_prepared(stmt); - for (count = 0; db_step(stmt); count++) { + while (db_step(stmt)) { struct bitcoin_txid curtxid; db_col_txid(stmt, "t.id", &curtxid); @@ -5112,6 +5127,172 @@ void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id) } } +bool wallet_invoice_request_create(struct wallet *w, + const struct sha256 *invreq_id, + const char *bolt12, + const struct json_escape *label, + enum offer_status status) +{ + struct db_stmt *stmt; + + assert(offer_status_active(status)); + + /* Test if already exists. */ + stmt = db_prepare_v2(w->db, SQL("SELECT 1" + " FROM invoicerequests" + " WHERE invreq_id = ?;")); + db_bind_sha256(stmt, 0, invreq_id); + db_query_prepared(stmt); + + if (db_step(stmt)) { + db_col_ignore(stmt, "1"); + tal_free(stmt); + return false; + } + tal_free(stmt); + + stmt = db_prepare_v2(w->db, + SQL("INSERT INTO invoicerequests (" + " invreq_id" + ", bolt12" + ", label" + ", status" + ") VALUES (?, ?, ?, ?);")); + + db_bind_sha256(stmt, 0, invreq_id); + db_bind_text(stmt, 1, bolt12); + if (label) + db_bind_json_escape(stmt, 2, label); + else + db_bind_null(stmt, 2); + db_bind_int(stmt, 3, offer_status_in_db(status)); + db_exec_prepared_v2(take(stmt)); + return true; +} + +char *wallet_invoice_request_find(const tal_t *ctx, + struct wallet *w, + const struct sha256 *invreq_id, + const struct json_escape **label, + enum offer_status *status) +{ + struct db_stmt *stmt; + char *bolt12; + + stmt = db_prepare_v2(w->db, SQL("SELECT bolt12, label, status" + " FROM invoicerequests" + " WHERE invreq_id = ?;")); + db_bind_sha256(stmt, 0, invreq_id); + db_query_prepared(stmt); + + if (!db_step(stmt)) { + tal_free(stmt); + return NULL; + } + + bolt12 = db_col_strdup(ctx, stmt, "bolt12"); + if (label) { + if (db_col_is_null(stmt, "label")) + *label = NULL; + else + *label = db_col_json_escape(ctx, stmt, "label"); + } else + db_col_ignore(stmt, "label"); + + if (status) + *status = offer_status_in_db(db_col_int(stmt, "status")); + else + db_col_ignore(stmt, "status"); + + tal_free(stmt); + return bolt12; +} + +struct db_stmt *wallet_invreq_id_first(struct wallet *w, struct sha256 *invreq_id) +{ + struct db_stmt *stmt; + + stmt = db_prepare_v2(w->db, SQL("SELECT invreq_id FROM invoicerequests;")); + db_query_prepared(stmt); + + return wallet_invreq_id_next(w, stmt, invreq_id); +} + +struct db_stmt *wallet_invreq_id_next(struct wallet *w, + struct db_stmt *stmt, + struct sha256 *invreq_id) +{ + if (!db_step(stmt)) + return tal_free(stmt); + + db_col_sha256(stmt, "invreq_id", invreq_id); + return stmt; +} + +/* If we make an invoice_request inactive */ +static void invoice_request_status_update(struct db *db, + const struct sha256 *invreq_id, + enum offer_status oldstatus, + enum offer_status newstatus) +{ + struct db_stmt *stmt; + + stmt = db_prepare_v2(db, SQL("UPDATE invoicerequests" + " SET status=?" + " WHERE invreq_id = ?;")); + db_bind_int(stmt, 0, offer_status_in_db(newstatus)); + db_bind_sha256(stmt, 1, invreq_id); + db_exec_prepared_v2(take(stmt)); +} + +enum offer_status wallet_invoice_request_disable(struct wallet *w, + const struct sha256 *invreq_id, + enum offer_status s) +{ + enum offer_status newstatus; + + assert(offer_status_active(s)); + + newstatus = offer_status_in_db(s & ~OFFER_STATUS_ACTIVE_F); + invoice_request_status_update(w->db, invreq_id, s, newstatus); + + return newstatus; +} + +void wallet_invoice_request_mark_used(struct db *db, const struct sha256 *invreq_id) +{ + struct db_stmt *stmt; + enum offer_status status; + + stmt = db_prepare_v2(db, SQL("SELECT status" + " FROM invoicerequests" + " WHERE invreq_id = ?;")); + db_bind_sha256(stmt, 0, invreq_id); + db_query_prepared(stmt); + if (!db_step(stmt)) + fatal("%s: unknown invreq_id %s", + __func__, + type_to_string(tmpctx, struct sha256, invreq_id)); + + status = offer_status_in_db(db_col_int(stmt, "status")); + tal_free(stmt); + + if (!offer_status_active(status)) + fatal("%s: invreq_id %s not active: status %i", + __func__, + type_to_string(tmpctx, struct sha256, invreq_id), + status); + + if (!offer_status_used(status)) { + enum offer_status newstatus; + + if (offer_status_single(status)) + newstatus = OFFER_SINGLE_USE_USED; + else + newstatus = OFFER_MULTIPLE_USE_USED; + invoice_request_status_update(db, invreq_id, status, newstatus); + } +} /* We join key parts with nuls for now. */ static void db_bind_datastore_key(struct db_stmt *stmt, @@ -5279,7 +5460,8 @@ struct wallet_htlc_iter *wallet_htlcs_first(const tal_t *ctx, ", h.payment_hash" ", h.hstate" " FROM channel_htlcs h" - " WHERE channel_id = ?")); + " WHERE channel_id = ?" + " ORDER BY id ASC")); db_bind_u64(i->stmt, 0, chan->dbid); } else { i->scid.u64 = 0; @@ -5293,7 +5475,8 @@ struct wallet_htlc_iter *wallet_htlcs_first(const tal_t *ctx, ", h.payment_hash" ", h.hstate" " FROM channel_htlcs h" - " JOIN channels ON channels.id = h.channel_id")); + " JOIN channels ON channels.id = h.channel_id" + " ORDER BY h.id ASC")); } /* FIXME: db_prepare should take ctx! */ tal_steal(i, i->stmt); diff --git a/wallet/wallet.h b/wallet/wallet.h index 22bbfbf8fa34..fe84c6ffeae1 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -3,7 +3,7 @@ #include "config.h" #include "db.h" -#include +#include #include #include #include @@ -368,8 +368,8 @@ struct wallet_payment { /* If we could not decode the fail onion, just add it here. */ const u8 *failonion; - /* If we are associated with an internal offer */ - struct sha256 *local_offer_id; + /* If we are associated with an internal invoice_request */ + struct sha256 *local_invreq_id; }; struct outpoint { @@ -685,6 +685,7 @@ void wallet_blocks_heights(struct wallet *w, u32 def, u32 *min, u32 *max); * wallet_extract_owned_outputs - given a tx, extract all of our outputs */ int wallet_extract_owned_outputs(struct wallet *w, const struct wally_tx *tx, + bool is_coinbase, const u32 *blockheight, struct amount_sat *total); @@ -1194,11 +1195,12 @@ const struct wallet_payment **wallet_payment_list(const tal_t *ctx, /** - * wallet_payments_by_offer - Retrieve a list of payments for this local_offer_id + * wallet_payments_by_invoice_request - Retrieve a list of payments for this local_invreq_id */ -const struct wallet_payment **wallet_payments_by_offer(const tal_t *ctx, - struct wallet *wallet, - const struct sha256 *local_offer_id); +const struct wallet_payment ** +wallet_payments_by_invoice_request(const tal_t *ctx, + struct wallet *wallet, + const struct sha256 *local_invreq_id); /** * wallet_htlc_sigs_save - Store the latest HTLC sigs for the channel @@ -1582,6 +1584,85 @@ enum offer_status wallet_offer_disable(struct wallet *w, void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id) NO_NULL_ARGS; +/** + * Store an offer in the database. + * @w: the wallet + * @invreq_id: the hash of the invoice_request. + * @bolt12: invoice_request as text. + * @label: optional label for this invoice_request. + * @status: OFFER_SINGLE_USE or OFFER_MULTIPLE_USE + */ +bool wallet_invoice_request_create(struct wallet *w, + const struct sha256 *invreq_id, + const char *bolt12, + const struct json_escape *label, + enum offer_status status) + NON_NULL_ARGS(1,2,3); + +/** + * Retrieve an invoice_request from the database. + * @ctx: the tal context to allocate return from. + * @w: the wallet + * @invreq_id: the merkle root, as used for signing (must be unique) + * @label: the label of the invoice_request, set to NULL if none (or NULL) + * @status: set if succeeds (or NULL) + * + * If @invreq_id is found, returns the bolt12 text, sets @label and + * @state. Otherwise returns NULL. + */ +char *wallet_invoice_request_find(const tal_t *ctx, + struct wallet *w, + const struct sha256 *invreq_id, + const struct json_escape **label, + enum offer_status *status) + NON_NULL_ARGS(1,2,3); + +/** + * Iterate through all the invoice_requests. + * @w: the wallet + * @invreq_id: the first invoice_request id (if returns non-NULL) + * + * Returns pointer to hand as @stmt to wallet_invreq_id_next(), or NULL. + * If you choose not to call wallet_invreq_id_next() you must free it! + */ +struct db_stmt *wallet_invreq_id_first(struct wallet *w, + struct sha256 *invreq_id); + +/** + * Iterate through all the invoice_requests. + * @w: the wallet + * @stmt: return from wallet_invreq_id_first() or previous wallet_invreq_id_next() + * @invreq_id: the next invoice_request id (if returns non-NULL) + * + * Returns NULL once we're out of invoice_requests. If you choose not to call + * wallet_invreq_id_next() again you must free return. + */ +struct db_stmt *wallet_invreq_id_next(struct wallet *w, + struct db_stmt *stmt, + struct sha256 *invreq_id); + +/** + * Disable an invoice_request in the database. + * @w: the wallet + * @invreq_id: the merkle root, as used for signing (must be unique) + * @s: the current status (must be active). + * + * Must exist. Returns new status. */ +enum offer_status wallet_invoice_request_disable(struct wallet *w, + const struct sha256 *invreq_id, + enum offer_status s) + NO_NULL_ARGS; + +/** + * Mark an invoice_request in the database used. + * @w: the wallet + * @invreq_id: the merkle root, as used for signing (must be unique) + * + * Must exist and be active. + */ +void wallet_invoice_request_mark_used(struct db *db, const struct sha256 *invreq_id) + NO_NULL_ARGS; + /** * Add an new key/value to the datastore (generation 0) * @w: the wallet diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index b65e38f3017a..30b737880b88 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -240,6 +240,7 @@ static void json_add_utxo(struct json_stream *response, { const char *out; bool reserved; + u32 current_height = get_block_height(wallet->ld->topology); json_object_start(response, fieldname); json_add_txid(response, "txid", &utxo->outpoint.txid); @@ -271,13 +272,16 @@ static void json_add_utxo(struct json_stream *response, if (utxo->spendheight) json_add_string(response, "status", "spent"); else if (utxo->blockheight) { - json_add_string(response, "status", "confirmed"); + json_add_string(response, "status", + utxo_is_immature(utxo, current_height) + ? "immature" + : "confirmed"); + json_add_num(response, "blockheight", *utxo->blockheight); } else json_add_string(response, "status", "unconfirmed"); - reserved = utxo_is_reserved(utxo, - get_block_height(wallet->ld->topology)); + reserved = utxo_is_reserved(utxo, current_height); json_add_bool(response, "reserved", reserved); if (reserved) json_add_num(response, "reserved_to_block", @@ -884,7 +888,7 @@ static void sendpsbt_done(struct bitcoind *bitcoind UNUSED, wallet_transaction_add(ld->wallet, sending->wtx, 0, 0); /* Extract the change output and add it to the DB */ - wallet_extract_owned_outputs(ld->wallet, sending->wtx, NULL, &change); + wallet_extract_owned_outputs(ld->wallet, sending->wtx, false, NULL, &change); wally_txid(sending->wtx, &txid); for (size_t i = 0; i < sending->psbt->num_outputs; i++) diff --git a/wire/Makefile b/wire/Makefile index d6f1bf5f593b..bce447bfcea1 100644 --- a/wire/Makefile +++ b/wire/Makefile @@ -15,18 +15,19 @@ WIRE_HEADERS := wire/onion_defs.h \ wire/peer$(EXP)_printgen.h \ wire/onion$(EXP)_printgen.h -# We don't include peer_printgen/onion_printgen here since most don't need it. +# We don't include peer_printgen/onion_printgen or bolt12 here since most don't need it. WIRE_SRC := wire/wire_sync.c \ wire/wire_io.c \ wire/fromwire.c \ wire/peer_wire.c \ wire/tlvstream.c \ wire/towire.c \ - wire/bolt12$(EXP)_wiregen.c \ wire/peer$(EXP)_wiregen.c \ wire/channel_type_wiregen.c \ wire/onion$(EXP)_wiregen.c +WIRE_BOLT12_SRC := wire/bolt12$(EXP)_wiregen.c + WIRE_PRINT_SRC := \ wire/onion$(EXP)_printgen.c \ wire/peer$(EXP)_printgen.c \ @@ -35,7 +36,8 @@ WIRE_PRINT_SRC := \ WIRE_OBJS := $(WIRE_SRC:.c=.o) WIRE_PRINT_OBJS := $(WIRE_PRINT_SRC:.c=.o) -$(WIRE_OBJS) $(WIRE_PRINT_OBJS): $(WIRE_HEADERS) +WIRE_BOLT12_OBJS := $(WIRE_BOLT12_SRC:.c=.o) +$(WIRE_OBJS) $(WIRE_PRINT_OBJS) $(WIRE_BOLT12_OBJS): $(WIRE_HEADERS) # Make sure these depend on everything: in case we're experimental, # include non-experimental ones here so they get rebuilt. @@ -51,7 +53,7 @@ WIRE_NONEXP_HEADERS := wire/peer_wiregen.h \ wire/onion_printgen.h \ wire/channel_type_printgen.h -ALL_C_SOURCES += $(WIRE_SRC) $(WIRE_PRINT_SRC) $(WIRE_NONEXP_SRC) +ALL_C_SOURCES += $(WIRE_SRC) $(WIRE_BOLT12_SRC) $(WIRE_PRINT_SRC) $(WIRE_NONEXP_SRC) ALL_C_HEADERS += $(WIRE_HEADERS) $(WIRE_NONEXP_HEADERS) @@ -135,7 +137,7 @@ wire/peer_exp_printgen.h_args := --include='wire/channel_type_printgen.h' wire/onion_exp_wiregen.h_args := $(wire/onion_wiregen.h_args) wire/onion_exp_wiregen.c_args := $(wire/onion_wiregen.c_args) -wire/bolt12_wiregen.c_args := -s --expose-tlv-type=tlv_blinded_path --expose-tlv-type=tlv_invoice_request +wire/bolt12_wiregen.c_args := -s --expose-tlv-type=tlv_blinded_path --expose-tlv-type=tlv_invoice_request --expose-tlv-type=tlv_invoice wire/bolt12_wiregen.h_args := --include='bitcoin/short_channel_id.h' --include='bitcoin/signature.h' --include='bitcoin/privkey.h' --include='common/bigsize.h' --include='common/amount.h' --include='common/node_id.h' --include='bitcoin/block.h' --include='wire/onion_wire.h' $(wire/bolt12_wiregen.c_args) wire/bolt12_printgen.c_args := --expose-tlv-type=tlv_blinded_path --expose-tlv-type=tlv_invoice_request --include='wire/onion$(EXP)_wiregen.h' --include='wire/onion$(EXP)_printgen.h' @@ -153,7 +155,7 @@ wire/channel_type_wiregen.h_args := -s wire/channel_type_wiregen.c_args := $(wire/channel_type_wiregen.h_args) # All generated wire/ files depend on this Makefile -$(filter %printgen.h %printgen.c %wiregen.h %wiregen.c, $(WIRE_SRC) $(WIRE_PRINT_SRC) $(WIRE_NONEXP_SRC) $(WIRE_HEADERS) $(WIRE_NONEXP_HEADERS)): wire/Makefile +$(filter %printgen.h %printgen.c %wiregen.h %wiregen.c, $(WIRE_SRC) $(WIRE_BOLT12_SRC) $(WIRE_PRINT_SRC) $(WIRE_NONEXP_SRC) $(WIRE_HEADERS) $(WIRE_NONEXP_HEADERS)): wire/Makefile maintainer-clean: wire-maintainer-clean diff --git a/wire/bolt12_exp_wire.csv b/wire/bolt12_exp_wire.csv index 4b0ce8519d04..10ba1faf0b1c 100644 --- a/wire/bolt12_exp_wire.csv +++ b/wire/bolt12_exp_wire.csv @@ -1,128 +1,169 @@ -tlvtype,offer,chains,2 -tlvdata,offer,chains,chains,chain_hash,... -tlvtype,offer,currency,6 -tlvdata,offer,currency,iso4217,utf8,... -tlvtype,offer,amount,8 -tlvdata,offer,amount,amount,tu64, -tlvtype,offer,description,10 -tlvdata,offer,description,description,utf8,... -tlvtype,offer,features,12 -tlvdata,offer,features,features,byte,... -tlvtype,offer,absolute_expiry,14 -tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, -tlvtype,offer,paths,16 -tlvdata,offer,paths,paths,blinded_path,... -tlvtype,offer,issuer,20 -tlvdata,offer,issuer,issuer,utf8,... -tlvtype,offer,quantity_min,22 -tlvdata,offer,quantity_min,min,tu64, -tlvtype,offer,quantity_max,24 -tlvdata,offer,quantity_max,max,tu64, -tlvtype,offer,recurrence,26 -tlvdata,offer,recurrence,time_unit,byte, -tlvdata,offer,recurrence,period,tu32, -tlvtype,offer,recurrence_paywindow,64 -tlvdata,offer,recurrence_paywindow,seconds_before,u32, -tlvdata,offer,recurrence_paywindow,proportional_amount,byte, -tlvdata,offer,recurrence_paywindow,seconds_after,tu32, -tlvtype,offer,recurrence_limit,66 -tlvdata,offer,recurrence_limit,max_period,tu32, -tlvtype,offer,recurrence_base,28 -tlvdata,offer,recurrence_base,start_any_period,byte, -tlvdata,offer,recurrence_base,basetime,tu64, -tlvtype,offer,node_id,30 -tlvdata,offer,node_id,node_id,point32, -tlvtype,offer,send_invoice,54 -tlvtype,offer,refund_for,34 -tlvdata,offer,refund_for,refunded_payment_hash,sha256, -tlvtype,offer,signature,240 -tlvdata,offer,signature,sig,bip340sig, -subtype,blinded_path -subtypedata,blinded_path,first_node_id,point, -subtypedata,blinded_path,blinding,point, -subtypedata,blinded_path,num_hops,byte, -subtypedata,blinded_path,path,onionmsg_path,num_hops -tlvtype,invoice_request,chain,3 -tlvdata,invoice_request,chain,chain,chain_hash, -tlvtype,invoice_request,offer_id,4 -tlvdata,invoice_request,offer_id,offer_id,sha256, -tlvtype,invoice_request,amount,8 -tlvdata,invoice_request,amount,msat,tu64, -tlvtype,invoice_request,features,12 -tlvdata,invoice_request,features,features,byte,... -tlvtype,invoice_request,quantity,32 -tlvdata,invoice_request,quantity,quantity,tu64, -tlvtype,invoice_request,recurrence_counter,36 -tlvdata,invoice_request,recurrence_counter,counter,tu32, -tlvtype,invoice_request,recurrence_start,68 -tlvdata,invoice_request,recurrence_start,period_offset,tu32, -tlvtype,invoice_request,payer_key,38 -tlvdata,invoice_request,payer_key,key,point32, -tlvtype,invoice_request,payer_note,39 -tlvdata,invoice_request,payer_note,note,utf8,... -tlvtype,invoice_request,payer_info,50 -tlvdata,invoice_request,payer_info,blob,byte,... -tlvtype,invoice_request,replace_invoice,56 -tlvdata,invoice_request,replace_invoice,payment_hash,sha256, +tlvtype,offer,offer_chains,2 +tlvdata,offer,offer_chains,chains,chain_hash,... +tlvtype,offer,offer_metadata,4 +tlvdata,offer,offer_metadata,data,byte,... +tlvtype,offer,offer_currency,6 +tlvdata,offer,offer_currency,iso4217,utf8,... +tlvtype,offer,offer_amount,8 +tlvdata,offer,offer_amount,amount,tu64, +tlvtype,offer,offer_description,10 +tlvdata,offer,offer_description,description,utf8,... +tlvtype,offer,offer_features,12 +tlvdata,offer,offer_features,features,byte,... +tlvtype,offer,offer_absolute_expiry,14 +tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,offer,offer_paths,16 +tlvdata,offer,offer_paths,paths,blinded_path,... +tlvtype,offer,offer_issuer,18 +tlvdata,offer,offer_issuer,issuer,utf8,... +tlvtype,offer,offer_quantity_max,20 +tlvdata,offer,offer_quantity_max,max,tu64, +tlvtype,offer,offer_node_id,22 +tlvdata,offer,offer_node_id,node_id,point, +tlvtype,offer,offer_recurrence,26 +tlvdata,offer,offer_recurrence,recurrence,recurrence, +tlvtype,offer,offer_recurrence_paywindow,28 +tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,offer,offer_recurrence_limit,30 +tlvdata,offer,offer_recurrence_limit,max_period,tu32, +tlvtype,offer,offer_recurrence_base,32 +tlvdata,offer,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_metadata,0 +tlvdata,invoice_request,invreq_metadata,blob,byte,... +tlvtype,invoice_request,offer_chains,2 +tlvdata,invoice_request,offer_chains,chains,chain_hash,... +tlvtype,invoice_request,offer_metadata,4 +tlvdata,invoice_request,offer_metadata,data,byte,... +tlvtype,invoice_request,offer_currency,6 +tlvdata,invoice_request,offer_currency,iso4217,utf8,... +tlvtype,invoice_request,offer_amount,8 +tlvdata,invoice_request,offer_amount,amount,tu64, +tlvtype,invoice_request,offer_description,10 +tlvdata,invoice_request,offer_description,description,utf8,... +tlvtype,invoice_request,offer_features,12 +tlvdata,invoice_request,offer_features,features,byte,... +tlvtype,invoice_request,offer_absolute_expiry,14 +tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice_request,offer_paths,16 +tlvdata,invoice_request,offer_paths,paths,blinded_path,... +tlvtype,invoice_request,offer_issuer,18 +tlvdata,invoice_request,offer_issuer,issuer,utf8,... +tlvtype,invoice_request,offer_quantity_max,20 +tlvdata,invoice_request,offer_quantity_max,max,tu64, +tlvtype,invoice_request,offer_node_id,22 +tlvdata,invoice_request,offer_node_id,node_id,point, +tlvtype,invoice_request,offer_recurrence,26 +tlvdata,invoice_request,offer_recurrence,recurrence,recurrence, +tlvtype,invoice_request,offer_recurrence_paywindow,28 +tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice_request,offer_recurrence_limit,30 +tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice_request,offer_recurrence_base,32 +tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_chain,80 +tlvdata,invoice_request,invreq_chain,chain,chain_hash, +tlvtype,invoice_request,invreq_amount,82 +tlvdata,invoice_request,invreq_amount,msat,tu64, +tlvtype,invoice_request,invreq_features,84 +tlvdata,invoice_request,invreq_features,features,byte,... +tlvtype,invoice_request,invreq_quantity,86 +tlvdata,invoice_request,invreq_quantity,quantity,tu64, +tlvtype,invoice_request,invreq_payer_id,88 +tlvdata,invoice_request,invreq_payer_id,key,point, +tlvtype,invoice_request,invreq_payer_note,89 +tlvdata,invoice_request,invreq_payer_note,note,utf8,... +tlvtype,invoice_request,invreq_recurrence_counter,90 +tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice_request,invreq_recurrence_start,92 +tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32, tlvtype,invoice_request,signature,240 tlvdata,invoice_request,signature,sig,bip340sig, -tlvtype,invoice,chain,3 -tlvdata,invoice,chain,chain,chain_hash, -tlvtype,invoice,offer_id,4 -tlvdata,invoice,offer_id,offer_id,sha256, -tlvtype,invoice,amount,8 -tlvdata,invoice,amount,msat,tu64, -tlvtype,invoice,description,10 -tlvdata,invoice,description,description,utf8,... -tlvtype,invoice,features,12 -tlvdata,invoice,features,features,byte,... -tlvtype,invoice,paths,16 -tlvdata,invoice,paths,paths,blinded_path,... -tlvtype,invoice,blindedpay,18 -tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... -tlvtype,invoice,blinded_capacities,19 -tlvdata,invoice,blinded_capacities,incoming_msat,u64,... -tlvtype,invoice,issuer,20 -tlvdata,invoice,issuer,issuer,utf8,... -tlvtype,invoice,node_id,30 -tlvdata,invoice,node_id,node_id,point32, -tlvtype,invoice,quantity,32 -tlvdata,invoice,quantity,quantity,tu64, -tlvtype,invoice,refund_for,34 -tlvdata,invoice,refund_for,refunded_payment_hash,sha256, -tlvtype,invoice,recurrence_counter,36 -tlvdata,invoice,recurrence_counter,counter,tu32, -tlvtype,invoice,send_invoice,54 -tlvtype,invoice,recurrence_start,68 -tlvdata,invoice,recurrence_start,period_offset,tu32, -tlvtype,invoice,recurrence_basetime,64 -tlvdata,invoice,recurrence_basetime,basetime,tu64, -tlvtype,invoice,payer_key,38 -tlvdata,invoice,payer_key,key,point32, -tlvtype,invoice,payer_note,39 -tlvdata,invoice,payer_note,note,utf8,... -tlvtype,invoice,created_at,40 -tlvdata,invoice,created_at,timestamp,tu64, -tlvtype,invoice,payment_hash,42 -tlvdata,invoice,payment_hash,payment_hash,sha256, -tlvtype,invoice,relative_expiry,44 -tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, -tlvtype,invoice,cltv,46 -tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, -tlvtype,invoice,fallbacks,48 -tlvdata,invoice,fallbacks,fallbacks,fallback_address,... -tlvtype,invoice,payer_info,50 -tlvdata,invoice,payer_info,blob,byte,... -tlvtype,invoice,refund_signature,52 -tlvdata,invoice,refund_signature,payer_signature,bip340sig, -tlvtype,invoice,replace_invoice,56 -tlvdata,invoice,replace_invoice,payment_hash,sha256, +tlvtype,invoice,invreq_metadata,0 +tlvdata,invoice,invreq_metadata,blob,byte,... +tlvtype,invoice,offer_chains,2 +tlvdata,invoice,offer_chains,chains,chain_hash,... +tlvtype,invoice,offer_metadata,4 +tlvdata,invoice,offer_metadata,data,byte,... +tlvtype,invoice,offer_currency,6 +tlvdata,invoice,offer_currency,iso4217,utf8,... +tlvtype,invoice,offer_amount,8 +tlvdata,invoice,offer_amount,amount,tu64, +tlvtype,invoice,offer_description,10 +tlvdata,invoice,offer_description,description,utf8,... +tlvtype,invoice,offer_features,12 +tlvdata,invoice,offer_features,features,byte,... +tlvtype,invoice,offer_absolute_expiry,14 +tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice,offer_paths,16 +tlvdata,invoice,offer_paths,paths,blinded_path,... +tlvtype,invoice,offer_issuer,18 +tlvdata,invoice,offer_issuer,issuer,utf8,... +tlvtype,invoice,offer_quantity_max,20 +tlvdata,invoice,offer_quantity_max,max,tu64, +tlvtype,invoice,offer_node_id,22 +tlvdata,invoice,offer_node_id,node_id,point, +tlvtype,invoice,offer_recurrence,26 +tlvdata,invoice,offer_recurrence,recurrence,recurrence, +tlvtype,invoice,offer_recurrence_paywindow,28 +tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice,offer_recurrence_limit,30 +tlvdata,invoice,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice,offer_recurrence_base,32 +tlvdata,invoice,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice,invreq_chain,80 +tlvdata,invoice,invreq_chain,chain,chain_hash, +tlvtype,invoice,invreq_amount,82 +tlvdata,invoice,invreq_amount,msat,tu64, +tlvtype,invoice,invreq_features,84 +tlvdata,invoice,invreq_features,features,byte,... +tlvtype,invoice,invreq_quantity,86 +tlvdata,invoice,invreq_quantity,quantity,tu64, +tlvtype,invoice,invreq_payer_id,88 +tlvdata,invoice,invreq_payer_id,key,point, +tlvtype,invoice,invreq_payer_note,89 +tlvdata,invoice,invreq_payer_note,note,utf8,... +tlvtype,invoice,invreq_recurrence_counter,90 +tlvdata,invoice,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice,invreq_recurrence_start,92 +tlvdata,invoice,invreq_recurrence_start,period_offset,tu32, +tlvtype,invoice,invoice_paths,160 +tlvdata,invoice,invoice_paths,paths,blinded_path,... +tlvtype,invoice,invoice_blindedpay,162 +tlvdata,invoice,invoice_blindedpay,payinfo,blinded_payinfo,... +tlvtype,invoice,invoice_created_at,164 +tlvdata,invoice,invoice_created_at,timestamp,tu64, +tlvtype,invoice,invoice_relative_expiry,166 +tlvdata,invoice,invoice_relative_expiry,seconds_from_creation,tu32, +tlvtype,invoice,invoice_payment_hash,168 +tlvdata,invoice,invoice_payment_hash,payment_hash,sha256, +tlvtype,invoice,invoice_amount,170 +tlvdata,invoice,invoice_amount,msat,tu64, +tlvtype,invoice,invoice_fallbacks,172 +tlvdata,invoice,invoice_fallbacks,fallbacks,fallback_address,... +tlvtype,invoice,invoice_features,174 +tlvdata,invoice,invoice_features,features,byte,... +tlvtype,invoice,invoice_node_id,176 +tlvdata,invoice,invoice_node_id,node_id,point, +tlvtype,invoice,invoice_recurrence_basetime,178 +tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64, tlvtype,invoice,signature,240 tlvdata,invoice,signature,sig,bip340sig, +subtype,recurrence +subtypedata,recurrence,time_unit,byte, +subtypedata,recurrence,period,tu32, +subtype,recurrence_paywindow +subtypedata,recurrence_paywindow,seconds_before,u32, +subtypedata,recurrence_paywindow,proportional_amount,byte, +subtypedata,recurrence_paywindow,seconds_after,tu32, +subtype,recurrence_base +subtypedata,recurrence_base,start_any_period,byte, +subtypedata,recurrence_base,basetime,tu64, subtype,blinded_payinfo subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32, subtypedata,blinded_payinfo,cltv_expiry_delta,u16, +subtypedata,blinded_payinfo,htlc_minimum_msat,u64, +subtypedata,blinded_payinfo,htlc_maximum_msat,u64, subtypedata,blinded_payinfo,flen,u16, subtypedata,blinded_payinfo,features,byte,flen subtype,fallback_address diff --git a/wire/bolt12_wire.csv b/wire/bolt12_wire.csv index 4b0ce8519d04..10ba1faf0b1c 100644 --- a/wire/bolt12_wire.csv +++ b/wire/bolt12_wire.csv @@ -1,128 +1,169 @@ -tlvtype,offer,chains,2 -tlvdata,offer,chains,chains,chain_hash,... -tlvtype,offer,currency,6 -tlvdata,offer,currency,iso4217,utf8,... -tlvtype,offer,amount,8 -tlvdata,offer,amount,amount,tu64, -tlvtype,offer,description,10 -tlvdata,offer,description,description,utf8,... -tlvtype,offer,features,12 -tlvdata,offer,features,features,byte,... -tlvtype,offer,absolute_expiry,14 -tlvdata,offer,absolute_expiry,seconds_from_epoch,tu64, -tlvtype,offer,paths,16 -tlvdata,offer,paths,paths,blinded_path,... -tlvtype,offer,issuer,20 -tlvdata,offer,issuer,issuer,utf8,... -tlvtype,offer,quantity_min,22 -tlvdata,offer,quantity_min,min,tu64, -tlvtype,offer,quantity_max,24 -tlvdata,offer,quantity_max,max,tu64, -tlvtype,offer,recurrence,26 -tlvdata,offer,recurrence,time_unit,byte, -tlvdata,offer,recurrence,period,tu32, -tlvtype,offer,recurrence_paywindow,64 -tlvdata,offer,recurrence_paywindow,seconds_before,u32, -tlvdata,offer,recurrence_paywindow,proportional_amount,byte, -tlvdata,offer,recurrence_paywindow,seconds_after,tu32, -tlvtype,offer,recurrence_limit,66 -tlvdata,offer,recurrence_limit,max_period,tu32, -tlvtype,offer,recurrence_base,28 -tlvdata,offer,recurrence_base,start_any_period,byte, -tlvdata,offer,recurrence_base,basetime,tu64, -tlvtype,offer,node_id,30 -tlvdata,offer,node_id,node_id,point32, -tlvtype,offer,send_invoice,54 -tlvtype,offer,refund_for,34 -tlvdata,offer,refund_for,refunded_payment_hash,sha256, -tlvtype,offer,signature,240 -tlvdata,offer,signature,sig,bip340sig, -subtype,blinded_path -subtypedata,blinded_path,first_node_id,point, -subtypedata,blinded_path,blinding,point, -subtypedata,blinded_path,num_hops,byte, -subtypedata,blinded_path,path,onionmsg_path,num_hops -tlvtype,invoice_request,chain,3 -tlvdata,invoice_request,chain,chain,chain_hash, -tlvtype,invoice_request,offer_id,4 -tlvdata,invoice_request,offer_id,offer_id,sha256, -tlvtype,invoice_request,amount,8 -tlvdata,invoice_request,amount,msat,tu64, -tlvtype,invoice_request,features,12 -tlvdata,invoice_request,features,features,byte,... -tlvtype,invoice_request,quantity,32 -tlvdata,invoice_request,quantity,quantity,tu64, -tlvtype,invoice_request,recurrence_counter,36 -tlvdata,invoice_request,recurrence_counter,counter,tu32, -tlvtype,invoice_request,recurrence_start,68 -tlvdata,invoice_request,recurrence_start,period_offset,tu32, -tlvtype,invoice_request,payer_key,38 -tlvdata,invoice_request,payer_key,key,point32, -tlvtype,invoice_request,payer_note,39 -tlvdata,invoice_request,payer_note,note,utf8,... -tlvtype,invoice_request,payer_info,50 -tlvdata,invoice_request,payer_info,blob,byte,... -tlvtype,invoice_request,replace_invoice,56 -tlvdata,invoice_request,replace_invoice,payment_hash,sha256, +tlvtype,offer,offer_chains,2 +tlvdata,offer,offer_chains,chains,chain_hash,... +tlvtype,offer,offer_metadata,4 +tlvdata,offer,offer_metadata,data,byte,... +tlvtype,offer,offer_currency,6 +tlvdata,offer,offer_currency,iso4217,utf8,... +tlvtype,offer,offer_amount,8 +tlvdata,offer,offer_amount,amount,tu64, +tlvtype,offer,offer_description,10 +tlvdata,offer,offer_description,description,utf8,... +tlvtype,offer,offer_features,12 +tlvdata,offer,offer_features,features,byte,... +tlvtype,offer,offer_absolute_expiry,14 +tlvdata,offer,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,offer,offer_paths,16 +tlvdata,offer,offer_paths,paths,blinded_path,... +tlvtype,offer,offer_issuer,18 +tlvdata,offer,offer_issuer,issuer,utf8,... +tlvtype,offer,offer_quantity_max,20 +tlvdata,offer,offer_quantity_max,max,tu64, +tlvtype,offer,offer_node_id,22 +tlvdata,offer,offer_node_id,node_id,point, +tlvtype,offer,offer_recurrence,26 +tlvdata,offer,offer_recurrence,recurrence,recurrence, +tlvtype,offer,offer_recurrence_paywindow,28 +tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,offer,offer_recurrence_limit,30 +tlvdata,offer,offer_recurrence_limit,max_period,tu32, +tlvtype,offer,offer_recurrence_base,32 +tlvdata,offer,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_metadata,0 +tlvdata,invoice_request,invreq_metadata,blob,byte,... +tlvtype,invoice_request,offer_chains,2 +tlvdata,invoice_request,offer_chains,chains,chain_hash,... +tlvtype,invoice_request,offer_metadata,4 +tlvdata,invoice_request,offer_metadata,data,byte,... +tlvtype,invoice_request,offer_currency,6 +tlvdata,invoice_request,offer_currency,iso4217,utf8,... +tlvtype,invoice_request,offer_amount,8 +tlvdata,invoice_request,offer_amount,amount,tu64, +tlvtype,invoice_request,offer_description,10 +tlvdata,invoice_request,offer_description,description,utf8,... +tlvtype,invoice_request,offer_features,12 +tlvdata,invoice_request,offer_features,features,byte,... +tlvtype,invoice_request,offer_absolute_expiry,14 +tlvdata,invoice_request,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice_request,offer_paths,16 +tlvdata,invoice_request,offer_paths,paths,blinded_path,... +tlvtype,invoice_request,offer_issuer,18 +tlvdata,invoice_request,offer_issuer,issuer,utf8,... +tlvtype,invoice_request,offer_quantity_max,20 +tlvdata,invoice_request,offer_quantity_max,max,tu64, +tlvtype,invoice_request,offer_node_id,22 +tlvdata,invoice_request,offer_node_id,node_id,point, +tlvtype,invoice_request,offer_recurrence,26 +tlvdata,invoice_request,offer_recurrence,recurrence,recurrence, +tlvtype,invoice_request,offer_recurrence_paywindow,28 +tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice_request,offer_recurrence_limit,30 +tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice_request,offer_recurrence_base,32 +tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice_request,invreq_chain,80 +tlvdata,invoice_request,invreq_chain,chain,chain_hash, +tlvtype,invoice_request,invreq_amount,82 +tlvdata,invoice_request,invreq_amount,msat,tu64, +tlvtype,invoice_request,invreq_features,84 +tlvdata,invoice_request,invreq_features,features,byte,... +tlvtype,invoice_request,invreq_quantity,86 +tlvdata,invoice_request,invreq_quantity,quantity,tu64, +tlvtype,invoice_request,invreq_payer_id,88 +tlvdata,invoice_request,invreq_payer_id,key,point, +tlvtype,invoice_request,invreq_payer_note,89 +tlvdata,invoice_request,invreq_payer_note,note,utf8,... +tlvtype,invoice_request,invreq_recurrence_counter,90 +tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice_request,invreq_recurrence_start,92 +tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32, tlvtype,invoice_request,signature,240 tlvdata,invoice_request,signature,sig,bip340sig, -tlvtype,invoice,chain,3 -tlvdata,invoice,chain,chain,chain_hash, -tlvtype,invoice,offer_id,4 -tlvdata,invoice,offer_id,offer_id,sha256, -tlvtype,invoice,amount,8 -tlvdata,invoice,amount,msat,tu64, -tlvtype,invoice,description,10 -tlvdata,invoice,description,description,utf8,... -tlvtype,invoice,features,12 -tlvdata,invoice,features,features,byte,... -tlvtype,invoice,paths,16 -tlvdata,invoice,paths,paths,blinded_path,... -tlvtype,invoice,blindedpay,18 -tlvdata,invoice,blindedpay,payinfo,blinded_payinfo,... -tlvtype,invoice,blinded_capacities,19 -tlvdata,invoice,blinded_capacities,incoming_msat,u64,... -tlvtype,invoice,issuer,20 -tlvdata,invoice,issuer,issuer,utf8,... -tlvtype,invoice,node_id,30 -tlvdata,invoice,node_id,node_id,point32, -tlvtype,invoice,quantity,32 -tlvdata,invoice,quantity,quantity,tu64, -tlvtype,invoice,refund_for,34 -tlvdata,invoice,refund_for,refunded_payment_hash,sha256, -tlvtype,invoice,recurrence_counter,36 -tlvdata,invoice,recurrence_counter,counter,tu32, -tlvtype,invoice,send_invoice,54 -tlvtype,invoice,recurrence_start,68 -tlvdata,invoice,recurrence_start,period_offset,tu32, -tlvtype,invoice,recurrence_basetime,64 -tlvdata,invoice,recurrence_basetime,basetime,tu64, -tlvtype,invoice,payer_key,38 -tlvdata,invoice,payer_key,key,point32, -tlvtype,invoice,payer_note,39 -tlvdata,invoice,payer_note,note,utf8,... -tlvtype,invoice,created_at,40 -tlvdata,invoice,created_at,timestamp,tu64, -tlvtype,invoice,payment_hash,42 -tlvdata,invoice,payment_hash,payment_hash,sha256, -tlvtype,invoice,relative_expiry,44 -tlvdata,invoice,relative_expiry,seconds_from_creation,tu32, -tlvtype,invoice,cltv,46 -tlvdata,invoice,cltv,min_final_cltv_expiry,tu16, -tlvtype,invoice,fallbacks,48 -tlvdata,invoice,fallbacks,fallbacks,fallback_address,... -tlvtype,invoice,payer_info,50 -tlvdata,invoice,payer_info,blob,byte,... -tlvtype,invoice,refund_signature,52 -tlvdata,invoice,refund_signature,payer_signature,bip340sig, -tlvtype,invoice,replace_invoice,56 -tlvdata,invoice,replace_invoice,payment_hash,sha256, +tlvtype,invoice,invreq_metadata,0 +tlvdata,invoice,invreq_metadata,blob,byte,... +tlvtype,invoice,offer_chains,2 +tlvdata,invoice,offer_chains,chains,chain_hash,... +tlvtype,invoice,offer_metadata,4 +tlvdata,invoice,offer_metadata,data,byte,... +tlvtype,invoice,offer_currency,6 +tlvdata,invoice,offer_currency,iso4217,utf8,... +tlvtype,invoice,offer_amount,8 +tlvdata,invoice,offer_amount,amount,tu64, +tlvtype,invoice,offer_description,10 +tlvdata,invoice,offer_description,description,utf8,... +tlvtype,invoice,offer_features,12 +tlvdata,invoice,offer_features,features,byte,... +tlvtype,invoice,offer_absolute_expiry,14 +tlvdata,invoice,offer_absolute_expiry,seconds_from_epoch,tu64, +tlvtype,invoice,offer_paths,16 +tlvdata,invoice,offer_paths,paths,blinded_path,... +tlvtype,invoice,offer_issuer,18 +tlvdata,invoice,offer_issuer,issuer,utf8,... +tlvtype,invoice,offer_quantity_max,20 +tlvdata,invoice,offer_quantity_max,max,tu64, +tlvtype,invoice,offer_node_id,22 +tlvdata,invoice,offer_node_id,node_id,point, +tlvtype,invoice,offer_recurrence,26 +tlvdata,invoice,offer_recurrence,recurrence,recurrence, +tlvtype,invoice,offer_recurrence_paywindow,28 +tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow, +tlvtype,invoice,offer_recurrence_limit,30 +tlvdata,invoice,offer_recurrence_limit,max_period,tu32, +tlvtype,invoice,offer_recurrence_base,32 +tlvdata,invoice,offer_recurrence_base,base,recurrence_base, +tlvtype,invoice,invreq_chain,80 +tlvdata,invoice,invreq_chain,chain,chain_hash, +tlvtype,invoice,invreq_amount,82 +tlvdata,invoice,invreq_amount,msat,tu64, +tlvtype,invoice,invreq_features,84 +tlvdata,invoice,invreq_features,features,byte,... +tlvtype,invoice,invreq_quantity,86 +tlvdata,invoice,invreq_quantity,quantity,tu64, +tlvtype,invoice,invreq_payer_id,88 +tlvdata,invoice,invreq_payer_id,key,point, +tlvtype,invoice,invreq_payer_note,89 +tlvdata,invoice,invreq_payer_note,note,utf8,... +tlvtype,invoice,invreq_recurrence_counter,90 +tlvdata,invoice,invreq_recurrence_counter,counter,tu32, +tlvtype,invoice,invreq_recurrence_start,92 +tlvdata,invoice,invreq_recurrence_start,period_offset,tu32, +tlvtype,invoice,invoice_paths,160 +tlvdata,invoice,invoice_paths,paths,blinded_path,... +tlvtype,invoice,invoice_blindedpay,162 +tlvdata,invoice,invoice_blindedpay,payinfo,blinded_payinfo,... +tlvtype,invoice,invoice_created_at,164 +tlvdata,invoice,invoice_created_at,timestamp,tu64, +tlvtype,invoice,invoice_relative_expiry,166 +tlvdata,invoice,invoice_relative_expiry,seconds_from_creation,tu32, +tlvtype,invoice,invoice_payment_hash,168 +tlvdata,invoice,invoice_payment_hash,payment_hash,sha256, +tlvtype,invoice,invoice_amount,170 +tlvdata,invoice,invoice_amount,msat,tu64, +tlvtype,invoice,invoice_fallbacks,172 +tlvdata,invoice,invoice_fallbacks,fallbacks,fallback_address,... +tlvtype,invoice,invoice_features,174 +tlvdata,invoice,invoice_features,features,byte,... +tlvtype,invoice,invoice_node_id,176 +tlvdata,invoice,invoice_node_id,node_id,point, +tlvtype,invoice,invoice_recurrence_basetime,178 +tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64, tlvtype,invoice,signature,240 tlvdata,invoice,signature,sig,bip340sig, +subtype,recurrence +subtypedata,recurrence,time_unit,byte, +subtypedata,recurrence,period,tu32, +subtype,recurrence_paywindow +subtypedata,recurrence_paywindow,seconds_before,u32, +subtypedata,recurrence_paywindow,proportional_amount,byte, +subtypedata,recurrence_paywindow,seconds_after,tu32, +subtype,recurrence_base +subtypedata,recurrence_base,start_any_period,byte, +subtypedata,recurrence_base,basetime,tu64, subtype,blinded_payinfo subtypedata,blinded_payinfo,fee_base_msat,u32, subtypedata,blinded_payinfo,fee_proportional_millionths,u32, subtypedata,blinded_payinfo,cltv_expiry_delta,u16, +subtypedata,blinded_payinfo,htlc_minimum_msat,u64, +subtypedata,blinded_payinfo,htlc_maximum_msat,u64, subtypedata,blinded_payinfo,flen,u16, subtypedata,blinded_payinfo,features,byte,flen subtype,fallback_address diff --git a/wire/extracted_bolt12_01_recurrence.patch b/wire/extracted_bolt12_01_recurrence.patch index 186841e476dc..bde21b180591 100644 --- a/wire/extracted_bolt12_01_recurrence.patch +++ b/wire/extracted_bolt12_01_recurrence.patch @@ -1,48 +1,87 @@ -diff --git b/wire/bolt12_wire.csv a/wire/bolt12_wire.csv -index 726c3c0a1..a53ca3cdf 100644 ---- b/wire/bolt12_wire.csv -+++ a/wire/bolt12_wire.csv -@@ -18,6 +18,18 @@ tlvtype,offer,quantity_min,22 - tlvdata,offer,quantity_min,min,tu64, - tlvtype,offer,quantity_max,24 - tlvdata,offer,quantity_max,max,tu64, -+tlvtype,offer,recurrence,26 -+tlvdata,offer,recurrence,time_unit,byte, -+tlvdata,offer,recurrence,period,tu32, -+tlvtype,offer,recurrence_paywindow,64 -+tlvdata,offer,recurrence_paywindow,seconds_before,u32, -+tlvdata,offer,recurrence_paywindow,proportional_amount,byte, -+tlvdata,offer,recurrence_paywindow,seconds_after,tu32, -+tlvtype,offer,recurrence_limit,66 -+tlvdata,offer,recurrence_limit,max_period,tu32, -+tlvtype,offer,recurrence_base,28 -+tlvdata,offer,recurrence_base,start_any_period,byte, -+tlvdata,offer,recurrence_base,basetime,tu64, - tlvtype,offer,node_id,30 - tlvdata,offer,node_id,node_id,point32, - tlvtype,offer,send_invoice,54 -@@ -40,6 +54,10 @@ tlvtype,invoice_request,features,12 - tlvdata,invoice_request,features,features,byte,... - tlvtype,invoice_request,quantity,32 - tlvdata,invoice_request,quantity,quantity,tu64, -+tlvtype,invoice_request,recurrence_counter,36 -+tlvdata,invoice_request,recurrence_counter,counter,tu32, -+tlvtype,invoice_request,recurrence_start,68 -+tlvdata,invoice_request,recurrence_start,period_offset,tu32, - tlvtype,invoice_request,payer_key,38 - tlvdata,invoice_request,payer_key,key,point32, - tlvtype,invoice_request,payer_note,39 -@@ -74,6 +94,13 @@ tlvtype,invoice,quantity,32 - tlvdata,invoice,quantity,quantity,tu64, - tlvtype,invoice,refund_for,34 - tlvdata,invoice,refund_for,refunded_payment_hash,sha256, -+tlvtype,invoice,recurrence_counter,36 -+tlvdata,invoice,recurrence_counter,counter,tu32, -+tlvtype,invoice,send_invoice,54 -+tlvtype,invoice,recurrence_start,68 -+tlvdata,invoice,recurrence_start,period_offset,tu32, -+tlvtype,invoice,recurrence_basetime,64 -+tlvdata,invoice,recurrence_basetime,basetime,tu64, - tlvtype,invoice,payer_key,38 - tlvdata,invoice,payer_key,key,point32, - tlvtype,invoice,payer_note,39 +--- wire/bolt12_wire.csv.raw 2022-10-04 13:26:18.105307201 +1030 ++++ wire/bolt12_wire.csv 2022-10-04 13:25:59.617242667 +1030 +@@ -22,6 +22,14 @@ + tlvdata,offer,offer_quantity_max,max,tu64, + tlvtype,offer,offer_node_id,24 + tlvdata,offer,offer_node_id,node_id,point, ++tlvtype,offer,offer_recurrence,26 ++tlvdata,offer,offer_recurrence,recurrence,recurrence, ++tlvtype,offer,offer_recurrence_paywindow,28 ++tlvdata,offer,offer_recurrence_paywindow,paywindow,recurrence_paywindow, ++tlvtype,offer,offer_recurrence_limit,30 ++tlvdata,offer,offer_recurrence_limit,max_period,tu32, ++tlvtype,offer,offer_recurrence_base,32 ++tlvdata,offer,offer_recurrence_base,base,recurrence_base, + tlvtype,invoice_request,invreq_metadata,0 + tlvdata,invoice_request,invreq_metadata,blob,byte,... + tlvtype,invoice_request,offer_chains,2 +@@ -48,6 +60,14 @@ + tlvdata,invoice_request,offer_quantity_max,max,tu64, + tlvtype,invoice_request,offer_node_id,24 + tlvdata,invoice_request,offer_node_id,node_id,point, ++tlvtype,invoice_request,offer_recurrence,26 ++tlvdata,invoice_request,offer_recurrence,recurrence,recurrence, ++tlvtype,invoice_request,offer_recurrence_paywindow,28 ++tlvdata,invoice_request,offer_recurrence_paywindow,paywindow,recurrence_paywindow, ++tlvtype,invoice_request,offer_recurrence_limit,30 ++tlvdata,invoice_request,offer_recurrence_limit,max_period,tu32, ++tlvtype,invoice_request,offer_recurrence_base,32 ++tlvdata,invoice_request,offer_recurrence_base,base,recurrence_base, + tlvtype,invoice_request,invreq_chain,80 + tlvdata,invoice_request,invreq_chain,chain,chain_hash, + tlvtype,invoice_request,invreq_amount,82 +@@ -60,6 +84,10 @@ + tlvdata,invoice_request,invreq_payer_id,key,point, + tlvtype,invoice_request,invreq_payer_note,89 + tlvdata,invoice_request,invreq_payer_note,note,utf8,... ++tlvtype,invoice_request,invreq_recurrence_counter,90 ++tlvdata,invoice_request,invreq_recurrence_counter,counter,tu32, ++tlvtype,invoice_request,invreq_recurrence_start,92 ++tlvdata,invoice_request,invreq_recurrence_start,period_offset,tu32, + tlvtype,invoice_request,signature,240 + tlvdata,invoice_request,signature,sig,bip340sig, + tlvtype,invoice,invreq_metadata,0 +@@ -89,5 +117,13 @@ + tlvtype,invoice,offer_node_id,24 + tlvdata,invoice,offer_node_id,node_id,point, ++tlvtype,invoice,offer_recurrence,26 ++tlvdata,invoice,offer_recurrence,recurrence,recurrence, ++tlvtype,invoice,offer_recurrence_paywindow,28 ++tlvdata,invoice,offer_recurrence_paywindow,paywindow,recurrence_paywindow, ++tlvtype,invoice,offer_recurrence_limit,30 ++tlvdata,invoice,offer_recurrence_limit,max_period,tu32, ++tlvtype,invoice,offer_recurrence_base,32 ++tlvdata,invoice,offer_recurrence_base,base,recurrence_base, + tlvtype,invoice,invreq_chain,80 + tlvdata,invoice,invreq_chain,chain,chain_hash, + tlvtype,invoice,invreq_amount,82 +@@ -101,6 +141,10 @@ + tlvdata,invoice,invreq_payer_id,key,point, + tlvtype,invoice,invreq_payer_note,89 + tlvdata,invoice,invreq_payer_note,note,utf8,... ++tlvtype,invoice,invreq_recurrence_counter,90 ++tlvdata,invoice,invreq_recurrence_counter,counter,tu32, ++tlvtype,invoice,invreq_recurrence_start,92 ++tlvdata,invoice,invreq_recurrence_start,period_offset,tu32, + tlvtype,invoice,invoice_paths,160 + tlvdata,invoice,invoice_paths,paths,blinded_path,... + tlvtype,invoice,invoice_blindedpay,162 +@@ -119,6 +163,18 @@ + tlvdata,invoice,invoice_features,features,byte,... + tlvtype,invoice,invoice_node_id,176 + tlvdata,invoice,invoice_node_id,node_id,point, ++tlvtype,invoice,invoice_recurrence_basetime,178 ++tlvdata,invoice,invoice_recurrence_basetime,basetime,tu64, + tlvtype,invoice,signature,240 + tlvdata,invoice,signature,sig,bip340sig, ++subtype,recurrence ++subtypedata,recurrence,time_unit,byte, ++subtypedata,recurrence,period,tu32, ++subtype,recurrence_paywindow ++subtypedata,recurrence_paywindow,seconds_before,u32, ++subtypedata,recurrence_paywindow,proportional_amount,byte, ++subtypedata,recurrence_paywindow,seconds_after,tu32, ++subtype,recurrence_base ++subtypedata,recurrence_base,start_any_period,byte, ++subtypedata,recurrence_base,basetime,tu64, + subtype,blinded_payinfo diff --git a/wire/extracted_onion_02_modernonion.patch b/wire/extracted_onion_02_modernonion.patch index 4056d2e9bf57..f697284bafc1 100644 --- a/wire/extracted_onion_02_modernonion.patch +++ b/wire/extracted_onion_02_modernonion.patch @@ -1,6 +1,6 @@ --- wire/onion_wire.csv 2021-11-16 15:17:39.446494580 +1030 +++ wire/onion_wire.csv.raw 2021-11-16 15:36:00.046441058 +1030 -@@ -8,6 +8,36 @@ +@@ -8,6 +8,41 @@ tlvdata,tlv_payload,payment_data,total_msat,tu64, tlvtype,tlv_payload,payment_metadata,16 tlvdata,tlv_payload,payment_metadata,payment_metadata,byte,... @@ -8,6 +8,8 @@ +tlvdata,tlv_payload,encrypted_recipient_data,encrypted_data,byte,... +tlvtype,tlv_payload,blinding_point,12 +tlvdata,tlv_payload,blinding_point,blinding,point, ++tlvtype,tlv_payload,total_amount_msat,18 ++tlvdata,tlv_payload,total_amount_msat,total_msat,tu64, +tlvtype,encrypted_data_tlv,padding,1 +tlvdata,encrypted_data_tlv,padding,padding,byte,... +tlvtype,encrypted_data_tlv,short_channel_id,2 @@ -18,22 +20,25 @@ +tlvdata,encrypted_data_tlv,path_id,data,byte,... +tlvtype,encrypted_data_tlv,next_blinding_override,8 +tlvdata,encrypted_data_tlv,next_blinding_override,blinding,point, -+tlvtype,onionmsg_payload,reply_path,2 -+tlvdata,onionmsg_payload,reply_path,first_node_id,point, -+tlvdata,onionmsg_payload,reply_path,blinding,point, -+tlvdata,onionmsg_payload,reply_path,path,onionmsg_path,... -+tlvtype,onionmsg_payload,encrypted_data_tlv,4 -+tlvdata,onionmsg_payload,encrypted_data_tlv,encrypted_data_tlv,byte,... -+tlvtype,onionmsg_payload,invoice_request,64 -+tlvdata,onionmsg_payload,invoice_request,invoice_request,tlv_invoice_request, -+tlvtype,onionmsg_payload,invoice,66 -+tlvdata,onionmsg_payload,invoice,invoice,tlv_invoice, -+tlvtype,onionmsg_payload,invoice_error,68 -+tlvdata,onionmsg_payload,invoice_error,invoice_error,tlv_invoice_error, -+subtype,onionmsg_path -+subtypedata,onionmsg_path,node_id,point, -+subtypedata,onionmsg_path,enclen,u16, -+subtypedata,onionmsg_path,encrypted_recipient_data,byte,enclen ++tlvtype,onionmsg_tlv,reply_path,2 ++tlvdata,onionmsg_tlv,reply_path,path,blinded_path, ++tlvtype,onionmsg_tlv,encrypted_recipient_data,4 ++tlvdata,onionmsg_tlv,encrypted_recipient_data,encrypted_recipient_data,byte,... ++tlvtype,onionmsg_tlv,invoice_request,64 ++tlvdata,onionmsg_tlv,invoice_request,invoice_request,tlv_invoice_request, ++tlvtype,onionmsg_tlv,invoice,66 ++tlvdata,onionmsg_tlv,invoice,invoice,tlv_invoice, ++tlvtype,onionmsg_tlv,invoice_error,68 ++tlvdata,onionmsg_tlv,invoice_error,invoice_error,tlv_invoice_error, ++subtype,blinded_path ++subtypedata,blinded_path,first_node_id,point, ++subtypedata,blinded_path,blinding,point, ++subtypedata,blinded_path,num_hops,byte, ++subtypedata,blinded_path,path,onionmsg_hop,num_hops ++subtype,onionmsg_hop ++subtypedata,onionmsg_hop,blinded_node_id,point, ++subtypedata,onionmsg_hop,enclen,u16, ++subtypedata,onionmsg_hop,encrypted_recipient_data,byte,enclen msgtype,invalid_realm,PERM|1 msgtype,temporary_node_failure,NODE|2 msgtype,permanent_node_failure,PERM|NODE|2 diff --git a/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch b/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch index 41eedfd3fc6c..e56894ddab06 100644 --- a/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch +++ b/wire/extracted_onion_03_onionmsg-payload-as-bytearr.patch @@ -2,18 +2,18 @@ diff --git b/wire/onion_wire.csv a/wire/onion_wire.csv index 5c52fe9a1..2ac0c4cff 100644 --- b/wire/onion_wire.csv +++ a/wire/onion_wire.csv -@@ -51,11 +29,11 @@ tlvdata,onionmsg_payload,reply_path,path,onionmsg_path,... - tlvtype,onionmsg_payload,encrypted_data_tlv,4 - tlvdata,onionmsg_payload,encrypted_data_tlv,encrypted_data_tlv,byte,... - tlvtype,onionmsg_payload,invoice_request,64 --tlvdata,onionmsg_payload,invoice_request,invoice_request,tlv_invoice_request, -+tlvdata,onionmsg_payload,invoice_request,invoice_request,byte,... - tlvtype,onionmsg_payload,invoice,66 --tlvdata,onionmsg_payload,invoice,invoice,tlv_invoice, -+tlvdata,onionmsg_payload,invoice,invoice,byte,... - tlvtype,onionmsg_payload,invoice_error,68 --tlvdata,onionmsg_payload,invoice_error,invoice_error,tlv_invoice_error, -+tlvdata,onionmsg_payload,invoice_error,invoice_error,byte,... - subtype,onionmsg_path - subtypedata,onionmsg_path,node_id,point, - subtypedata,onionmsg_path,enclen,u16, +@@ -51,11 +29,11 @@ tlvdata,onionmsg_tlv,reply_path,path,onionmsg_path,... + tlvtype,onionmsg_tlv,encrypted_data_tlv,4 + tlvdata,onionmsg_tlv,encrypted_data_tlv,encrypted_data_tlv,byte,... + tlvtype,onionmsg_tlv,invoice_request,64 +-tlvdata,onionmsg_tlv,invoice_request,invoice_request,tlv_invoice_request, ++tlvdata,onionmsg_tlv,invoice_request,invoice_request,byte,... + tlvtype,onionmsg_tlv,invoice,66 +-tlvdata,onionmsg_tlv,invoice,invoice,tlv_invoice, ++tlvdata,onionmsg_tlv,invoice,invoice,byte,... + tlvtype,onionmsg_tlv,invoice_error,68 +-tlvdata,onionmsg_tlv,invoice_error,invoice_error,tlv_invoice_error, ++tlvdata,onionmsg_tlv,invoice_error,invoice_error,byte,... + subtype,blinded_path + subtypedata,blinded_path,first_node_id,point, + subtypedata,blinded_path,blinding,point, diff --git a/wire/extracted_onion_04_route-blinding-htlcs.patch b/wire/extracted_onion_04_route-blinding-htlcs.patch new file mode 100644 index 000000000000..2df6cf3baf7a --- /dev/null +++ b/wire/extracted_onion_04_route-blinding-htlcs.patch @@ -0,0 +1,20 @@ +diff --git a/wire/onion_wire.csv b/wire/onion_wire.csv +index 9326f9f8e..d5d074d1f 100644 +--- a/wire/onion_wire.csv ++++ b/wire/onion_wire.csv +@@ -24,6 +24,15 @@ tlvtype,encrypted_data_tlv,path_id,6 + tlvdata,encrypted_data_tlv,path_id,data,byte,... + tlvtype,encrypted_data_tlv,next_blinding_override,8 + tlvdata,encrypted_data_tlv,next_blinding_override,blinding,point, ++tlvtype,encrypted_data_tlv,payment_relay,10 ++tlvdata,encrypted_data_tlv,payment_relay,cltv_expiry_delta,u16, ++tlvdata,encrypted_data_tlv,payment_relay,fee_proportional_millionths,u32, ++tlvdata,encrypted_data_tlv,payment_relay,fee_base_msat,tu32, ++tlvtype,encrypted_data_tlv,payment_constraints,12 ++tlvdata,encrypted_data_tlv,payment_constraints,max_cltv_expiry,u32, ++tlvdata,encrypted_data_tlv,payment_constraints,htlc_minimum_msat,tu64, ++tlvtype,encrypted_data_tlv,allowed_features,14 ++tlvdata,encrypted_data_tlv,allowed_features,features,byte,... + tlvtype,onionmsg_tlv,reply_path,2 + tlvdata,onionmsg_tlv,reply_path,first_node_id,point, + tlvdata,onionmsg_tlv,reply_path,blinding,point, diff --git a/wire/extracted_onion_05_route-blinding_error.patch b/wire/extracted_onion_05_route-blinding_error.patch new file mode 100644 index 000000000000..fba74e70267b --- /dev/null +++ b/wire/extracted_onion_05_route-blinding_error.patch @@ -0,0 +1,8 @@ +--- wire/onion_wire.csv 2022-08-10 16:09:32.851789435 +0930 ++++ wire/onion_wire.csv.raw 2022-08-10 16:18:47.411275132 +0930 +@@ -95,3 +81,5 @@ + msgdata,invalid_onion_payload,type,bigsize, + msgdata,invalid_onion_payload,offset,u16, + msgtype,mpp_timeout,23 ++msgtype,invalid_onion_blinding,BADONION|PERM|24 ++msgdata,invalid_onion_blinding,sha256_of_onion,sha256, diff --git a/wire/extracted_onion_exp_badonion_blinding.patch b/wire/extracted_onion_exp_badonion_blinding.patch deleted file mode 100644 index 057e741a879b..000000000000 --- a/wire/extracted_onion_exp_badonion_blinding.patch +++ /dev/null @@ -1,9 +0,0 @@ -diff --git a/wire/extracted_onion_wire_csv b/wire/extracted_onion_wire_csv -index 58f278f38..253a50012 100644 ---- a/wire/extracted_onion_wire_csv -+++ b/wire/extracted_onion_wire_csv -@@ -71,3 +71,4 @@ msgtype,invalid_onion_payload,PERM|22 - msgdata,invalid_onion_payload,type,bigsize, - msgdata,invalid_onion_payload,offset,u16, - msgtype,mpp_timeout,23 -+msgtype,invalid_onion_blinding,BADONION|PERM|24 diff --git a/wire/extracted_peer_exp_add_htlc-plus-blinding.patch b/wire/extracted_peer_add_htlc-plus-blinding.patch similarity index 100% rename from wire/extracted_peer_exp_add_htlc-plus-blinding.patch rename to wire/extracted_peer_add_htlc-plus-blinding.patch diff --git a/wire/fromwire.c b/wire/fromwire.c index 4c28d49f6ea6..0a78d4cea392 100644 --- a/wire/fromwire.c +++ b/wire/fromwire.c @@ -224,6 +224,8 @@ u8 *fromwire_tal_arrn(const tal_t *ctx, arr = tal_arr(ctx, u8, num); fromwire_u8_array(cursor, max, arr, num); + if (!*cursor) + return tal_free(arr); return arr; } diff --git a/wire/onion_wire.csv b/wire/onion_wire.csv index 9326f9f8eef8..75f5f5e4f31e 100644 --- a/wire/onion_wire.csv +++ b/wire/onion_wire.csv @@ -14,6 +14,8 @@ tlvtype,tlv_payload,encrypted_recipient_data,10 tlvdata,tlv_payload,encrypted_recipient_data,encrypted_data,byte,... tlvtype,tlv_payload,blinding_point,12 tlvdata,tlv_payload,blinding_point,blinding,point, +tlvtype,tlv_payload,total_amount_msat,18 +tlvdata,tlv_payload,total_amount_msat,total_msat,tu64, tlvtype,encrypted_data_tlv,padding,1 tlvdata,encrypted_data_tlv,padding,padding,byte,... tlvtype,encrypted_data_tlv,short_channel_id,2 @@ -24,22 +26,34 @@ tlvtype,encrypted_data_tlv,path_id,6 tlvdata,encrypted_data_tlv,path_id,data,byte,... tlvtype,encrypted_data_tlv,next_blinding_override,8 tlvdata,encrypted_data_tlv,next_blinding_override,blinding,point, -tlvtype,onionmsg_payload,reply_path,2 -tlvdata,onionmsg_payload,reply_path,first_node_id,point, -tlvdata,onionmsg_payload,reply_path,blinding,point, -tlvdata,onionmsg_payload,reply_path,path,onionmsg_path,... -tlvtype,onionmsg_payload,encrypted_data_tlv,4 -tlvdata,onionmsg_payload,encrypted_data_tlv,encrypted_data_tlv,byte,... -tlvtype,onionmsg_payload,invoice_request,64 -tlvdata,onionmsg_payload,invoice_request,invoice_request,byte,... -tlvtype,onionmsg_payload,invoice,66 -tlvdata,onionmsg_payload,invoice,invoice,byte,... -tlvtype,onionmsg_payload,invoice_error,68 -tlvdata,onionmsg_payload,invoice_error,invoice_error,byte,... -subtype,onionmsg_path -subtypedata,onionmsg_path,node_id,point, -subtypedata,onionmsg_path,enclen,u16, -subtypedata,onionmsg_path,encrypted_recipient_data,byte,enclen +tlvtype,encrypted_data_tlv,payment_relay,10 +tlvdata,encrypted_data_tlv,payment_relay,cltv_expiry_delta,u16, +tlvdata,encrypted_data_tlv,payment_relay,fee_proportional_millionths,u32, +tlvdata,encrypted_data_tlv,payment_relay,fee_base_msat,tu32, +tlvtype,encrypted_data_tlv,payment_constraints,12 +tlvdata,encrypted_data_tlv,payment_constraints,max_cltv_expiry,u32, +tlvdata,encrypted_data_tlv,payment_constraints,htlc_minimum_msat,tu64, +tlvtype,encrypted_data_tlv,allowed_features,14 +tlvdata,encrypted_data_tlv,allowed_features,features,byte,... +tlvtype,onionmsg_tlv,reply_path,2 +tlvdata,onionmsg_tlv,reply_path,path,blinded_path, +tlvtype,onionmsg_tlv,encrypted_recipient_data,4 +tlvdata,onionmsg_tlv,encrypted_recipient_data,encrypted_recipient_data,byte,... +tlvtype,onionmsg_tlv,invoice_request,64 +tlvdata,onionmsg_tlv,invoice_request,invoice_request,byte,... +tlvtype,onionmsg_tlv,invoice,66 +tlvdata,onionmsg_tlv,invoice,invoice,byte,... +tlvtype,onionmsg_tlv,invoice_error,68 +tlvdata,onionmsg_tlv,invoice_error,invoice_error,byte,... +subtype,blinded_path +subtypedata,blinded_path,first_node_id,point, +subtypedata,blinded_path,blinding,point, +subtypedata,blinded_path,num_hops,byte, +subtypedata,blinded_path,path,onionmsg_hop,num_hops +subtype,onionmsg_hop +subtypedata,onionmsg_hop,blinded_node_id,point, +subtypedata,onionmsg_hop,enclen,u16, +subtypedata,onionmsg_hop,encrypted_recipient_data,byte,enclen msgtype,invalid_realm,PERM|1 msgtype,temporary_node_failure,NODE|2 msgtype,permanent_node_failure,PERM|NODE|2 @@ -87,3 +101,5 @@ msgtype,invalid_onion_payload,PERM|22 msgdata,invalid_onion_payload,type,bigsize, msgdata,invalid_onion_payload,offset,u16, msgtype,mpp_timeout,23 +msgtype,invalid_onion_blinding,BADONION|PERM|24 +msgdata,invalid_onion_blinding,sha256_of_onion,sha256, diff --git a/wire/peer_wire.csv b/wire/peer_wire.csv index 7a5b14f3b26a..8083f249771e 100644 --- a/wire/peer_wire.csv +++ b/wire/peer_wire.csv @@ -217,6 +217,9 @@ msgdata,update_add_htlc,amount_msat,u64, msgdata,update_add_htlc,payment_hash,sha256, msgdata,update_add_htlc,cltv_expiry,u32, msgdata,update_add_htlc,onion_routing_packet,byte,1366 +msgdata,update_add_htlc,tlvs,update_add_tlvs, +tlvtype,update_add_tlvs,blinding,2 +tlvdata,update_add_tlvs,blinding,blinding,point, msgtype,update_fulfill_htlc,130 msgdata,update_fulfill_htlc,channel_id,channel_id, msgdata,update_fulfill_htlc,id,u64, diff --git a/wire/test/run-peer-wire.c b/wire/test/run-peer-wire.c index 3f7b83f967ec..85f317979056 100644 --- a/wire/test/run-peer-wire.c +++ b/wire/test/run-peer-wire.c @@ -703,37 +703,21 @@ static void *towire_struct_update_add_htlc(const tal_t *ctx, s->amount_msat, &s->payment_hash, s->expiry, - s->onion_routing_packet -#if EXPERIMENTAL_FEATURES - ,NULL -#endif - ); + s->onion_routing_packet, NULL); } static struct msg_update_add_htlc *fromwire_struct_update_add_htlc(const tal_t *ctx, const void *p) { struct msg_update_add_htlc *s = tal(ctx, struct msg_update_add_htlc); - if (fromwire_update_add_htlc -#if EXPERIMENTAL_FEATURES - (s, p, + if (fromwire_update_add_htlc(s, p, &s->channel_id, &s->id, &s->amount_msat, &s->payment_hash, &s->expiry, s->onion_routing_packet, - &s->tlvs -#else - (p, - &s->channel_id, - &s->id, - &s->amount_msat, - &s->payment_hash, - &s->expiry, - s->onion_routing_packet -#endif - )) + &s->tlvs)) return s; return tal_free(s); }